From 5bbf27cabdeac646bb12951813a5b9af18b74ed4 Mon Sep 17 00:00:00 2001 From: Walter Julius Hennecke Date: Sun, 30 Dec 2012 17:37:54 +0100 Subject: [PATCH] Initial code commit ... --- EF2-game.dsw | 37 + EF2-game.opt | Bin 0 -> 55808 bytes EF2-game.sln | 32 + EF2.dsw | 129 + Executable/gamedefs.h | 13 + Executable/win32/win_bounds.cpp | 187 + Shared/qcommon/alias.h | 117 + Shared/qcommon/cm_public.h | 115 + Shared/qcommon/gameplaydatabase.cpp | 1570 ++ Shared/qcommon/gameplaydatabase.h | 296 + Shared/qcommon/gameplayformulamanager.cpp | 499 + Shared/qcommon/gameplayformulamanager.h | 196 + Shared/qcommon/gameplaymanager.cpp | 850 + Shared/qcommon/gameplaymanager.h | 88 + Shared/qcommon/output.cpp | 942 + Shared/qcommon/output.h | 105 + Shared/qcommon/platform.h | 10 + Shared/qcommon/qfiles.h | 1264 + Shared/qcommon/quaternion.h | 776 + Shared/qcommon/stringresource.hpp | 119 + Shared/qcommon/tiki_script.h | 108 + UpgradeLog.htm | Bin 0 -> 18768 bytes dlls/game/CameraPath.cpp | 801 + dlls/game/CameraPath.h | 293 + dlls/game/CinematicArmature.cpp | 2830 +++ dlls/game/CinematicArmature.h | 765 + dlls/game/DamageModification.cpp | 526 + dlls/game/DamageModification.hpp | 381 + dlls/game/FollowPath.cpp | 1389 ++ dlls/game/FollowPath.h | 267 + dlls/game/FollowPathToEntity.cpp | 133 + dlls/game/FollowPathToEntity.h | 67 + dlls/game/FollowPathToPoint.cpp | 76 + dlls/game/FollowPathToPoint.h | 58 + dlls/game/GameplayDatabase.h | 192 + dlls/game/GameplayFormulaManager.h | 193 + dlls/game/GameplayManager.h | 84 + dlls/game/GoDirectlyToPoint.cpp | 142 + dlls/game/GoDirectlyToPoint.h | 73 + dlls/game/Linklist.h | 118 + dlls/game/MoveRandomDirection.cpp | 434 + dlls/game/MoveRandomDirection.hpp | 123 + dlls/game/PlayAnim.cpp | 442 + dlls/game/PlayAnim.hpp | 137 + dlls/game/PlayerStart.cpp | 122 + dlls/game/PlayerStart.h | 74 + dlls/game/RageAI.cpp | 1169 + dlls/game/RageAI.h | 245 + dlls/game/UseData.cpp | 43 + dlls/game/UseData.h | 96 + dlls/game/WeaponDualWield.cpp | 145 + dlls/game/WeaponDualWield.h | 58 + dlls/game/_pch_cpp.cpp | 22 + dlls/game/_pch_cpp.h | 25 + dlls/game/actor.cpp | 19685 ++++++++++++++++ dlls/game/actor.h | 1531 ++ dlls/game/actor_combatsubsystem.cpp | 1002 + dlls/game/actor_combatsubsystem.h | 97 + dlls/game/actor_enemymanager.cpp | 1074 + dlls/game/actor_enemymanager.h | 120 + dlls/game/actor_headwatcher.cpp | 365 + dlls/game/actor_headwatcher.h | 101 + dlls/game/actor_locomotion.cpp | 2178 ++ dlls/game/actor_locomotion.h | 217 + dlls/game/actor_posturecontroller.cpp | 308 + dlls/game/actor_posturecontroller.hpp | 93 + dlls/game/actor_sensoryperception.cpp | 1085 + dlls/game/actor_sensoryperception.h | 131 + dlls/game/actorgamecomponents.cpp | 143 + dlls/game/actorgamecomponents.h | 79 + dlls/game/actorincludes.h | 537 + dlls/game/actorstrategies.cpp | 580 + dlls/game/actorstrategies.h | 128 + dlls/game/actorutil.cpp | 195 + dlls/game/actorutil.h | 102 + dlls/game/ai_chat.cpp | 1276 + dlls/game/ai_chat.h | 45 + dlls/game/ai_cmd.cpp | 1976 ++ dlls/game/ai_cmd.h | 21 + dlls/game/ai_dmnet.cpp | 2605 ++ dlls/game/ai_dmnet.h | 45 + dlls/game/ai_dmq3.cpp | 5673 +++++ dlls/game/ai_dmq3.h | 191 + dlls/game/ai_main.cpp | 1747 ++ dlls/game/ai_main.h | 286 + dlls/game/ai_team.cpp | 2087 ++ dlls/game/ai_team.h | 23 + dlls/game/ai_vcmd.cpp | 532 + dlls/game/ai_vcmd.h | 20 + dlls/game/ammo.cpp | 175 + dlls/game/ammo.h | 89 + dlls/game/animate.cpp | 1010 + dlls/game/animate.h | 330 + dlls/game/archive.cpp | 1027 + dlls/game/archive.h | 489 + dlls/game/armor.cpp | 1077 + dlls/game/armor.h | 317 + dlls/game/be_aas.h | 210 + dlls/game/be_ai_char.h | 32 + dlls/game/be_ai_chat.h | 97 + dlls/game/be_ai_gen.h | 17 + dlls/game/be_ai_goal.h | 102 + dlls/game/be_ai_move.h | 126 + dlls/game/be_ai_weap.h | 90 + dlls/game/be_ea.h | 51 + dlls/game/beam.cpp | 683 + dlls/game/beam.h | 116 + dlls/game/behavior.cpp | 12928 ++++++++++ dlls/game/behavior.h | 3036 +++ dlls/game/behaviors.h | 7 + dlls/game/behaviors_general.cpp | 7581 ++++++ dlls/game/behaviors_general.h | 2302 ++ dlls/game/behaviors_specific.cpp | 1073 + dlls/game/behaviors_specific.h | 247 + dlls/game/bg_local.h | 88 + dlls/game/bg_misc.c | 112 + dlls/game/bg_pmove.c | 2674 +++ dlls/game/bg_public.h | 587 + dlls/game/bit_vector.h | 762 + dlls/game/body.cpp | 98 + dlls/game/body.h | 34 + dlls/game/botlib.h | 506 + dlls/game/botmenudef.h | 287 + dlls/game/bspline.cpp | 939 + dlls/game/bspline.h | 586 + dlls/game/camera.cpp | 3852 +++ dlls/game/camera.h | 535 + dlls/game/changePosture.cpp | 367 + dlls/game/changePosture.hpp | 127 + dlls/game/characterstate.cpp | 1876 ++ dlls/game/characterstate.h | 600 + dlls/game/chars.h | 124 + dlls/game/class.cpp | 890 + dlls/game/class.h | 515 + dlls/game/closeInOnEnemy.cpp | 365 + dlls/game/closeInOnEnemy.hpp | 144 + dlls/game/closeInOnEnemyWhileFiringWeapon.cpp | 520 + dlls/game/closeInOnEnemyWhileFiringWeapon.hpp | 156 + dlls/game/closeInOnPlayer.cpp | 356 + dlls/game/closeInOnPlayer.hpp | 144 + dlls/game/compiler.cpp | 1308 + dlls/game/compiler.h | 199 + dlls/game/container.h | 620 + dlls/game/corridorCombatWithRangedWeapon.cpp | 1449 ++ dlls/game/corridorCombatWithRangedWeapon.hpp | 294 + dlls/game/coverCombatWithRangedWeapon.cpp | 1226 + dlls/game/coverCombatWithRangedWeapon.hpp | 326 + dlls/game/debuglines.cpp | 809 + dlls/game/debuglines.h | 65 + dlls/game/decals.cpp | 69 + dlls/game/decals.h | 53 + dlls/game/dispenser.cpp | 623 + dlls/game/dispenser.hpp | 128 + dlls/game/doAttack.cpp | 329 + dlls/game/doAttack.hpp | 129 + dlls/game/doors.cpp | 2058 ++ dlls/game/doors.h | 290 + dlls/game/earthquake.cpp | 278 + dlls/game/earthquake.h | 73 + dlls/game/entity.cpp | 10226 ++++++++ dlls/game/entity.h | 1132 + dlls/game/equipment.cpp | 658 + dlls/game/equipment.h | 72 + dlls/game/explosion.cpp | 842 + dlls/game/explosion.h | 164 + dlls/game/g_bot.cpp | 1027 + dlls/game/g_local.h | 190 + dlls/game/g_main.cpp | 2389 ++ dlls/game/g_main.h | 95 + dlls/game/g_phys.cpp | 1694 ++ dlls/game/g_phys.h | 119 + dlls/game/g_public.h | 802 + dlls/game/g_spawn.cpp | 451 + dlls/game/g_spawn.h | 64 + dlls/game/g_utils.cpp | 2499 ++ dlls/game/g_utils.h | 314 + dlls/game/game.cpp | 57 + dlls/game/game.def | 2 + dlls/game/game.dsp | 4459 ++++ dlls/game/game.h | 49 + dlls/game/game.plg | 551 + dlls/game/game.vcxproj | 1578 ++ dlls/game/game.vcxproj.filters | 1080 + dlls/game/gamecmds.cpp | 1362 ++ dlls/game/gamecmds.h | 68 + dlls/game/gamecvars.cpp | 315 + dlls/game/gamecvars.h | 163 + dlls/game/gamescript.cpp | 447 + dlls/game/gamescript.h | 193 + dlls/game/generalCombatWithMeleeWeapon.cpp | 957 + dlls/game/generalCombatWithMeleeWeapon.hpp | 198 + dlls/game/generalCombatWithRangedWeapon.cpp | 1691 ++ dlls/game/generalCombatWithRangedWeapon.hpp | 295 + dlls/game/gibs.cpp | 272 + dlls/game/gibs.h | 86 + dlls/game/globalcmd.cpp | 4089 ++++ dlls/game/globalcmd.h | 289 + dlls/game/goo.cpp | 235 + dlls/game/goo.h | 76 + dlls/game/gotoCurrentHelperNode.cpp | 475 + dlls/game/gotoCurrentHelperNode.hpp | 148 + dlls/game/gotoHelperNode.cpp | 418 + dlls/game/gotoHelperNode.hpp | 158 + dlls/game/gotoHelperNodeEX.cpp | 467 + dlls/game/gotoHelperNodeEX.hpp | 157 + dlls/game/gotoHelperNodeNearestEnemy.cpp | 433 + dlls/game/gotoHelperNodeNearestEnemy.hpp | 154 + dlls/game/gravpath.cpp | 699 + dlls/game/gravpath.h | 200 + dlls/game/groupcoordinator.cpp | 944 + dlls/game/groupcoordinator.hpp | 167 + dlls/game/healGroupMember.cpp | 732 + dlls/game/healGroupMember.hpp | 206 + dlls/game/health.cpp | 229 + dlls/game/health.h | 46 + dlls/game/helper_node.cpp | 2992 +++ dlls/game/helper_node.h | 426 + dlls/game/holdPosition.cpp | 359 + dlls/game/holdPosition.hpp | 166 + dlls/game/interpreter.cpp | 1358 ++ dlls/game/interpreter.h | 132 + dlls/game/inv.h | 152 + dlls/game/inventoryitem.cpp | 56 + dlls/game/inventoryitem.h | 35 + dlls/game/ipfilter.cpp | 326 + dlls/game/ipfilter.h | 24 + dlls/game/item.cpp | 1135 + dlls/game/item.h | 228 + dlls/game/level.cpp | 1362 ++ dlls/game/level.h | 189 + dlls/game/lexer.cpp | 694 + dlls/game/lexer.h | 120 + dlls/game/light.cpp | 138 + dlls/game/light.h | 33 + dlls/game/listener.cpp | 3067 +++ dlls/game/listener.h | 1411 ++ dlls/game/match.h | 122 + dlls/game/misc.cpp | 3075 +++ dlls/game/misc.h | 489 + dlls/game/mover.cpp | 281 + dlls/game/mover.h | 70 + dlls/game/mp_awardsystem.cpp | 768 + dlls/game/mp_awardsystem.hpp | 168 + dlls/game/mp_manager.cpp | 3960 ++++ dlls/game/mp_manager.hpp | 337 + dlls/game/mp_modeBase.cpp | 1952 ++ dlls/game/mp_modeBase.hpp | 267 + dlls/game/mp_modeCtf.cpp | 1188 + dlls/game/mp_modeCtf.hpp | 136 + dlls/game/mp_modeDm.cpp | 256 + dlls/game/mp_modeDm.hpp | 52 + dlls/game/mp_modeTeamBase.cpp | 1164 + dlls/game/mp_modeTeamBase.hpp | 119 + dlls/game/mp_modeTeamDm.cpp | 233 + dlls/game/mp_modeTeamDm.hpp | 49 + dlls/game/mp_modifiers.cpp | 4155 ++++ dlls/game/mp_modifiers.hpp | 700 + dlls/game/mp_shared.hpp | 32 + dlls/game/mp_team.cpp | 442 + dlls/game/mp_team.hpp | 131 + dlls/game/mssccprj.scc | 5 + dlls/game/nature.cpp | 146 + dlls/game/nature.h | 83 + dlls/game/navigate.cpp | 2393 ++ dlls/game/navigate.h | 870 + dlls/game/object.cpp | 483 + dlls/game/object.h | 79 + dlls/game/path.cpp | 478 + dlls/game/path.h | 108 + dlls/game/patrol.cpp | 797 + dlls/game/patrol.hpp | 167 + dlls/game/player.cpp | 14157 +++++++++++ dlls/game/player.h | 1687 ++ dlls/game/player_combat.cpp | 518 + dlls/game/player_util.cpp | 556 + dlls/game/playerheuristics.cpp | 592 + dlls/game/playerheuristics.h | 131 + dlls/game/portal.cpp | 150 + dlls/game/portal.h | 40 + dlls/game/powerups.cpp | 1227 + dlls/game/powerups.h | 466 + dlls/game/program.cpp | 1121 + dlls/game/program.h | 440 + dlls/game/puzzleobject.cpp | 759 + dlls/game/puzzleobject.hpp | 144 + dlls/game/q_math.c | 2567 ++ dlls/game/q_mathsys.c | 163 + dlls/game/q_shared.c | 1508 ++ dlls/game/q_shared.h | 1922 ++ dlls/game/queue.h | 268 + dlls/game/rangedCombatWithWeapon.cpp | 712 + dlls/game/rangedCombatWithWeapon.hpp | 191 + dlls/game/rotateToEntity.cpp | 240 + dlls/game/rotateToEntity.hpp | 123 + dlls/game/script.cpp | 1104 + dlls/game/script.h | 165 + dlls/game/scriptmaster.cpp | 3521 +++ dlls/game/scriptmaster.h | 429 + dlls/game/scriptslave.cpp | 2701 +++ dlls/game/scriptslave.h | 394 + dlls/game/scriptvariable.cpp | 1210 + dlls/game/scriptvariable.h | 233 + dlls/game/selectBestWeapon.cpp | 538 + dlls/game/selectBestWeapon.hpp | 161 + dlls/game/sentient.cpp | 5119 ++++ dlls/game/sentient.h | 371 + dlls/game/shrapnelbomb.cpp | 143 + dlls/game/shrapnelbomb.h | 60 + dlls/game/snipeEnemy.cpp | 856 + dlls/game/snipeEnemy.hpp | 193 + dlls/game/soundman.cpp | 1655 ++ dlls/game/soundman.h | 108 + dlls/game/spawners.cpp | 900 + dlls/game/spawners.h | 189 + dlls/game/specialfx.cpp | 880 + dlls/game/specialfx.h | 171 + dlls/game/stack.h | 139 + dlls/game/stationaryFireCombat.cpp | 601 + dlls/game/stationaryFireCombat.hpp | 195 + dlls/game/stationaryFireCombatEX.cpp | 647 + dlls/game/stationaryFireCombatEX.hpp | 199 + dlls/game/stationaryvehicle.cpp | 213 + dlls/game/stationaryvehicle.hpp | 62 + dlls/game/steering.cpp | 264 + dlls/game/steering.h | 142 + dlls/game/str.cpp | 547 + dlls/game/str.h | 801 + dlls/game/suppressionFireCombat.cpp | 651 + dlls/game/suppressionFireCombat.hpp | 205 + dlls/game/surfaceflags.h | 116 + dlls/game/syn.h | 22 + dlls/game/talk.cpp | 433 + dlls/game/talk.hpp | 101 + dlls/game/teammateroster.cpp | 330 + dlls/game/teammateroster.hpp | 117 + dlls/game/teleportToEntity.cpp | 296 + dlls/game/teleportToEntity.hpp | 125 + dlls/game/teleportToPosition.cpp | 218 + dlls/game/teleportToPosition.hpp | 114 + dlls/game/torsoAimAndFireWeapon.cpp | 962 + dlls/game/torsoAimAndFireWeapon.hpp | 199 + dlls/game/trigger.cpp | 4460 ++++ dlls/game/trigger.h | 991 + dlls/game/umap.h | 331 + dlls/game/useAlarm.cpp | 623 + dlls/game/useAlarm.hpp | 145 + dlls/game/vector.h | 1078 + dlls/game/vehicle.cpp | 2578 ++ dlls/game/vehicle.h | 459 + dlls/game/viewthing.cpp | 2604 ++ dlls/game/viewthing.h | 187 + dlls/game/watchEntity.cpp | 382 + dlls/game/watchEntity.hpp | 148 + dlls/game/watchEntityEX.cpp | 392 + dlls/game/watchEntityEX.hpp | 155 + dlls/game/waypoints.cpp | 183 + dlls/game/waypoints.h | 105 + dlls/game/weapon.cpp | 6018 +++++ dlls/game/weapon.h | 919 + dlls/game/weaputils.cpp | 2755 +++ dlls/game/weaputils.h | 351 + dlls/game/work.cpp | 1063 + dlls/game/work.hpp | 177 + dlls/game/worldspawn.cpp | 1751 ++ dlls/game/worldspawn.h | 313 + licenseAgreement.txt | 51 + linux/Makefile | 843 + linux/make_debug.sh | 3 + linux/make_release.sh | 3 + readme.txt | 68 + 370 files changed, 300695 insertions(+) create mode 100644 EF2-game.dsw create mode 100644 EF2-game.opt create mode 100644 EF2-game.sln create mode 100644 EF2.dsw create mode 100644 Executable/gamedefs.h create mode 100644 Executable/win32/win_bounds.cpp create mode 100644 Shared/qcommon/alias.h create mode 100644 Shared/qcommon/cm_public.h create mode 100644 Shared/qcommon/gameplaydatabase.cpp create mode 100644 Shared/qcommon/gameplaydatabase.h create mode 100644 Shared/qcommon/gameplayformulamanager.cpp create mode 100644 Shared/qcommon/gameplayformulamanager.h create mode 100644 Shared/qcommon/gameplaymanager.cpp create mode 100644 Shared/qcommon/gameplaymanager.h create mode 100644 Shared/qcommon/output.cpp create mode 100644 Shared/qcommon/output.h create mode 100644 Shared/qcommon/platform.h create mode 100644 Shared/qcommon/qfiles.h create mode 100644 Shared/qcommon/quaternion.h create mode 100644 Shared/qcommon/stringresource.hpp create mode 100644 Shared/qcommon/tiki_script.h create mode 100644 UpgradeLog.htm create mode 100644 dlls/game/CameraPath.cpp create mode 100644 dlls/game/CameraPath.h create mode 100644 dlls/game/CinematicArmature.cpp create mode 100644 dlls/game/CinematicArmature.h create mode 100644 dlls/game/DamageModification.cpp create mode 100644 dlls/game/DamageModification.hpp create mode 100644 dlls/game/FollowPath.cpp create mode 100644 dlls/game/FollowPath.h create mode 100644 dlls/game/FollowPathToEntity.cpp create mode 100644 dlls/game/FollowPathToEntity.h create mode 100644 dlls/game/FollowPathToPoint.cpp create mode 100644 dlls/game/FollowPathToPoint.h create mode 100644 dlls/game/GameplayDatabase.h create mode 100644 dlls/game/GameplayFormulaManager.h create mode 100644 dlls/game/GameplayManager.h create mode 100644 dlls/game/GoDirectlyToPoint.cpp create mode 100644 dlls/game/GoDirectlyToPoint.h create mode 100644 dlls/game/Linklist.h create mode 100644 dlls/game/MoveRandomDirection.cpp create mode 100644 dlls/game/MoveRandomDirection.hpp create mode 100644 dlls/game/PlayAnim.cpp create mode 100644 dlls/game/PlayAnim.hpp create mode 100644 dlls/game/PlayerStart.cpp create mode 100644 dlls/game/PlayerStart.h create mode 100644 dlls/game/RageAI.cpp create mode 100644 dlls/game/RageAI.h create mode 100644 dlls/game/UseData.cpp create mode 100644 dlls/game/UseData.h create mode 100644 dlls/game/WeaponDualWield.cpp create mode 100644 dlls/game/WeaponDualWield.h create mode 100644 dlls/game/_pch_cpp.cpp create mode 100644 dlls/game/_pch_cpp.h create mode 100644 dlls/game/actor.cpp create mode 100644 dlls/game/actor.h create mode 100644 dlls/game/actor_combatsubsystem.cpp create mode 100644 dlls/game/actor_combatsubsystem.h create mode 100644 dlls/game/actor_enemymanager.cpp create mode 100644 dlls/game/actor_enemymanager.h create mode 100644 dlls/game/actor_headwatcher.cpp create mode 100644 dlls/game/actor_headwatcher.h create mode 100644 dlls/game/actor_locomotion.cpp create mode 100644 dlls/game/actor_locomotion.h create mode 100644 dlls/game/actor_posturecontroller.cpp create mode 100644 dlls/game/actor_posturecontroller.hpp create mode 100644 dlls/game/actor_sensoryperception.cpp create mode 100644 dlls/game/actor_sensoryperception.h create mode 100644 dlls/game/actorgamecomponents.cpp create mode 100644 dlls/game/actorgamecomponents.h create mode 100644 dlls/game/actorincludes.h create mode 100644 dlls/game/actorstrategies.cpp create mode 100644 dlls/game/actorstrategies.h create mode 100644 dlls/game/actorutil.cpp create mode 100644 dlls/game/actorutil.h create mode 100644 dlls/game/ai_chat.cpp create mode 100644 dlls/game/ai_chat.h create mode 100644 dlls/game/ai_cmd.cpp create mode 100644 dlls/game/ai_cmd.h create mode 100644 dlls/game/ai_dmnet.cpp create mode 100644 dlls/game/ai_dmnet.h create mode 100644 dlls/game/ai_dmq3.cpp create mode 100644 dlls/game/ai_dmq3.h create mode 100644 dlls/game/ai_main.cpp create mode 100644 dlls/game/ai_main.h create mode 100644 dlls/game/ai_team.cpp create mode 100644 dlls/game/ai_team.h create mode 100644 dlls/game/ai_vcmd.cpp create mode 100644 dlls/game/ai_vcmd.h create mode 100644 dlls/game/ammo.cpp create mode 100644 dlls/game/ammo.h create mode 100644 dlls/game/animate.cpp create mode 100644 dlls/game/animate.h create mode 100644 dlls/game/archive.cpp create mode 100644 dlls/game/archive.h create mode 100644 dlls/game/armor.cpp create mode 100644 dlls/game/armor.h create mode 100644 dlls/game/be_aas.h create mode 100644 dlls/game/be_ai_char.h create mode 100644 dlls/game/be_ai_chat.h create mode 100644 dlls/game/be_ai_gen.h create mode 100644 dlls/game/be_ai_goal.h create mode 100644 dlls/game/be_ai_move.h create mode 100644 dlls/game/be_ai_weap.h create mode 100644 dlls/game/be_ea.h create mode 100644 dlls/game/beam.cpp create mode 100644 dlls/game/beam.h create mode 100644 dlls/game/behavior.cpp create mode 100644 dlls/game/behavior.h create mode 100644 dlls/game/behaviors.h create mode 100644 dlls/game/behaviors_general.cpp create mode 100644 dlls/game/behaviors_general.h create mode 100644 dlls/game/behaviors_specific.cpp create mode 100644 dlls/game/behaviors_specific.h create mode 100644 dlls/game/bg_local.h create mode 100644 dlls/game/bg_misc.c create mode 100644 dlls/game/bg_pmove.c create mode 100644 dlls/game/bg_public.h create mode 100644 dlls/game/bit_vector.h create mode 100644 dlls/game/body.cpp create mode 100644 dlls/game/body.h create mode 100644 dlls/game/botlib.h create mode 100644 dlls/game/botmenudef.h create mode 100644 dlls/game/bspline.cpp create mode 100644 dlls/game/bspline.h create mode 100644 dlls/game/camera.cpp create mode 100644 dlls/game/camera.h create mode 100644 dlls/game/changePosture.cpp create mode 100644 dlls/game/changePosture.hpp create mode 100644 dlls/game/characterstate.cpp create mode 100644 dlls/game/characterstate.h create mode 100644 dlls/game/chars.h create mode 100644 dlls/game/class.cpp create mode 100644 dlls/game/class.h create mode 100644 dlls/game/closeInOnEnemy.cpp create mode 100644 dlls/game/closeInOnEnemy.hpp create mode 100644 dlls/game/closeInOnEnemyWhileFiringWeapon.cpp create mode 100644 dlls/game/closeInOnEnemyWhileFiringWeapon.hpp create mode 100644 dlls/game/closeInOnPlayer.cpp create mode 100644 dlls/game/closeInOnPlayer.hpp create mode 100644 dlls/game/compiler.cpp create mode 100644 dlls/game/compiler.h create mode 100644 dlls/game/container.h create mode 100644 dlls/game/corridorCombatWithRangedWeapon.cpp create mode 100644 dlls/game/corridorCombatWithRangedWeapon.hpp create mode 100644 dlls/game/coverCombatWithRangedWeapon.cpp create mode 100644 dlls/game/coverCombatWithRangedWeapon.hpp create mode 100644 dlls/game/debuglines.cpp create mode 100644 dlls/game/debuglines.h create mode 100644 dlls/game/decals.cpp create mode 100644 dlls/game/decals.h create mode 100644 dlls/game/dispenser.cpp create mode 100644 dlls/game/dispenser.hpp create mode 100644 dlls/game/doAttack.cpp create mode 100644 dlls/game/doAttack.hpp create mode 100644 dlls/game/doors.cpp create mode 100644 dlls/game/doors.h create mode 100644 dlls/game/earthquake.cpp create mode 100644 dlls/game/earthquake.h create mode 100644 dlls/game/entity.cpp create mode 100644 dlls/game/entity.h create mode 100644 dlls/game/equipment.cpp create mode 100644 dlls/game/equipment.h create mode 100644 dlls/game/explosion.cpp create mode 100644 dlls/game/explosion.h create mode 100644 dlls/game/g_bot.cpp create mode 100644 dlls/game/g_local.h create mode 100644 dlls/game/g_main.cpp create mode 100644 dlls/game/g_main.h create mode 100644 dlls/game/g_phys.cpp create mode 100644 dlls/game/g_phys.h create mode 100644 dlls/game/g_public.h create mode 100644 dlls/game/g_spawn.cpp create mode 100644 dlls/game/g_spawn.h create mode 100644 dlls/game/g_utils.cpp create mode 100644 dlls/game/g_utils.h create mode 100644 dlls/game/game.cpp create mode 100644 dlls/game/game.def create mode 100644 dlls/game/game.dsp create mode 100644 dlls/game/game.h create mode 100644 dlls/game/game.plg create mode 100644 dlls/game/game.vcxproj create mode 100644 dlls/game/game.vcxproj.filters create mode 100644 dlls/game/gamecmds.cpp create mode 100644 dlls/game/gamecmds.h create mode 100644 dlls/game/gamecvars.cpp create mode 100644 dlls/game/gamecvars.h create mode 100644 dlls/game/gamescript.cpp create mode 100644 dlls/game/gamescript.h create mode 100644 dlls/game/generalCombatWithMeleeWeapon.cpp create mode 100644 dlls/game/generalCombatWithMeleeWeapon.hpp create mode 100644 dlls/game/generalCombatWithRangedWeapon.cpp create mode 100644 dlls/game/generalCombatWithRangedWeapon.hpp create mode 100644 dlls/game/gibs.cpp create mode 100644 dlls/game/gibs.h create mode 100644 dlls/game/globalcmd.cpp create mode 100644 dlls/game/globalcmd.h create mode 100644 dlls/game/goo.cpp create mode 100644 dlls/game/goo.h create mode 100644 dlls/game/gotoCurrentHelperNode.cpp create mode 100644 dlls/game/gotoCurrentHelperNode.hpp create mode 100644 dlls/game/gotoHelperNode.cpp create mode 100644 dlls/game/gotoHelperNode.hpp create mode 100644 dlls/game/gotoHelperNodeEX.cpp create mode 100644 dlls/game/gotoHelperNodeEX.hpp create mode 100644 dlls/game/gotoHelperNodeNearestEnemy.cpp create mode 100644 dlls/game/gotoHelperNodeNearestEnemy.hpp create mode 100644 dlls/game/gravpath.cpp create mode 100644 dlls/game/gravpath.h create mode 100644 dlls/game/groupcoordinator.cpp create mode 100644 dlls/game/groupcoordinator.hpp create mode 100644 dlls/game/healGroupMember.cpp create mode 100644 dlls/game/healGroupMember.hpp create mode 100644 dlls/game/health.cpp create mode 100644 dlls/game/health.h create mode 100644 dlls/game/helper_node.cpp create mode 100644 dlls/game/helper_node.h create mode 100644 dlls/game/holdPosition.cpp create mode 100644 dlls/game/holdPosition.hpp create mode 100644 dlls/game/interpreter.cpp create mode 100644 dlls/game/interpreter.h create mode 100644 dlls/game/inv.h create mode 100644 dlls/game/inventoryitem.cpp create mode 100644 dlls/game/inventoryitem.h create mode 100644 dlls/game/ipfilter.cpp create mode 100644 dlls/game/ipfilter.h create mode 100644 dlls/game/item.cpp create mode 100644 dlls/game/item.h create mode 100644 dlls/game/level.cpp create mode 100644 dlls/game/level.h create mode 100644 dlls/game/lexer.cpp create mode 100644 dlls/game/lexer.h create mode 100644 dlls/game/light.cpp create mode 100644 dlls/game/light.h create mode 100644 dlls/game/listener.cpp create mode 100644 dlls/game/listener.h create mode 100644 dlls/game/match.h create mode 100644 dlls/game/misc.cpp create mode 100644 dlls/game/misc.h create mode 100644 dlls/game/mover.cpp create mode 100644 dlls/game/mover.h create mode 100644 dlls/game/mp_awardsystem.cpp create mode 100644 dlls/game/mp_awardsystem.hpp create mode 100644 dlls/game/mp_manager.cpp create mode 100644 dlls/game/mp_manager.hpp create mode 100644 dlls/game/mp_modeBase.cpp create mode 100644 dlls/game/mp_modeBase.hpp create mode 100644 dlls/game/mp_modeCtf.cpp create mode 100644 dlls/game/mp_modeCtf.hpp create mode 100644 dlls/game/mp_modeDm.cpp create mode 100644 dlls/game/mp_modeDm.hpp create mode 100644 dlls/game/mp_modeTeamBase.cpp create mode 100644 dlls/game/mp_modeTeamBase.hpp create mode 100644 dlls/game/mp_modeTeamDm.cpp create mode 100644 dlls/game/mp_modeTeamDm.hpp create mode 100644 dlls/game/mp_modifiers.cpp create mode 100644 dlls/game/mp_modifiers.hpp create mode 100644 dlls/game/mp_shared.hpp create mode 100644 dlls/game/mp_team.cpp create mode 100644 dlls/game/mp_team.hpp create mode 100644 dlls/game/mssccprj.scc create mode 100644 dlls/game/nature.cpp create mode 100644 dlls/game/nature.h create mode 100644 dlls/game/navigate.cpp create mode 100644 dlls/game/navigate.h create mode 100644 dlls/game/object.cpp create mode 100644 dlls/game/object.h create mode 100644 dlls/game/path.cpp create mode 100644 dlls/game/path.h create mode 100644 dlls/game/patrol.cpp create mode 100644 dlls/game/patrol.hpp create mode 100644 dlls/game/player.cpp create mode 100644 dlls/game/player.h create mode 100644 dlls/game/player_combat.cpp create mode 100644 dlls/game/player_util.cpp create mode 100644 dlls/game/playerheuristics.cpp create mode 100644 dlls/game/playerheuristics.h create mode 100644 dlls/game/portal.cpp create mode 100644 dlls/game/portal.h create mode 100644 dlls/game/powerups.cpp create mode 100644 dlls/game/powerups.h create mode 100644 dlls/game/program.cpp create mode 100644 dlls/game/program.h create mode 100644 dlls/game/puzzleobject.cpp create mode 100644 dlls/game/puzzleobject.hpp create mode 100644 dlls/game/q_math.c create mode 100644 dlls/game/q_mathsys.c create mode 100644 dlls/game/q_shared.c create mode 100644 dlls/game/q_shared.h create mode 100644 dlls/game/queue.h create mode 100644 dlls/game/rangedCombatWithWeapon.cpp create mode 100644 dlls/game/rangedCombatWithWeapon.hpp create mode 100644 dlls/game/rotateToEntity.cpp create mode 100644 dlls/game/rotateToEntity.hpp create mode 100644 dlls/game/script.cpp create mode 100644 dlls/game/script.h create mode 100644 dlls/game/scriptmaster.cpp create mode 100644 dlls/game/scriptmaster.h create mode 100644 dlls/game/scriptslave.cpp create mode 100644 dlls/game/scriptslave.h create mode 100644 dlls/game/scriptvariable.cpp create mode 100644 dlls/game/scriptvariable.h create mode 100644 dlls/game/selectBestWeapon.cpp create mode 100644 dlls/game/selectBestWeapon.hpp create mode 100644 dlls/game/sentient.cpp create mode 100644 dlls/game/sentient.h create mode 100644 dlls/game/shrapnelbomb.cpp create mode 100644 dlls/game/shrapnelbomb.h create mode 100644 dlls/game/snipeEnemy.cpp create mode 100644 dlls/game/snipeEnemy.hpp create mode 100644 dlls/game/soundman.cpp create mode 100644 dlls/game/soundman.h create mode 100644 dlls/game/spawners.cpp create mode 100644 dlls/game/spawners.h create mode 100644 dlls/game/specialfx.cpp create mode 100644 dlls/game/specialfx.h create mode 100644 dlls/game/stack.h create mode 100644 dlls/game/stationaryFireCombat.cpp create mode 100644 dlls/game/stationaryFireCombat.hpp create mode 100644 dlls/game/stationaryFireCombatEX.cpp create mode 100644 dlls/game/stationaryFireCombatEX.hpp create mode 100644 dlls/game/stationaryvehicle.cpp create mode 100644 dlls/game/stationaryvehicle.hpp create mode 100644 dlls/game/steering.cpp create mode 100644 dlls/game/steering.h create mode 100644 dlls/game/str.cpp create mode 100644 dlls/game/str.h create mode 100644 dlls/game/suppressionFireCombat.cpp create mode 100644 dlls/game/suppressionFireCombat.hpp create mode 100644 dlls/game/surfaceflags.h create mode 100644 dlls/game/syn.h create mode 100644 dlls/game/talk.cpp create mode 100644 dlls/game/talk.hpp create mode 100644 dlls/game/teammateroster.cpp create mode 100644 dlls/game/teammateroster.hpp create mode 100644 dlls/game/teleportToEntity.cpp create mode 100644 dlls/game/teleportToEntity.hpp create mode 100644 dlls/game/teleportToPosition.cpp create mode 100644 dlls/game/teleportToPosition.hpp create mode 100644 dlls/game/torsoAimAndFireWeapon.cpp create mode 100644 dlls/game/torsoAimAndFireWeapon.hpp create mode 100644 dlls/game/trigger.cpp create mode 100644 dlls/game/trigger.h create mode 100644 dlls/game/umap.h create mode 100644 dlls/game/useAlarm.cpp create mode 100644 dlls/game/useAlarm.hpp create mode 100644 dlls/game/vector.h create mode 100644 dlls/game/vehicle.cpp create mode 100644 dlls/game/vehicle.h create mode 100644 dlls/game/viewthing.cpp create mode 100644 dlls/game/viewthing.h create mode 100644 dlls/game/watchEntity.cpp create mode 100644 dlls/game/watchEntity.hpp create mode 100644 dlls/game/watchEntityEX.cpp create mode 100644 dlls/game/watchEntityEX.hpp create mode 100644 dlls/game/waypoints.cpp create mode 100644 dlls/game/waypoints.h create mode 100644 dlls/game/weapon.cpp create mode 100644 dlls/game/weapon.h create mode 100644 dlls/game/weaputils.cpp create mode 100644 dlls/game/weaputils.h create mode 100644 dlls/game/work.cpp create mode 100644 dlls/game/work.hpp create mode 100644 dlls/game/worldspawn.cpp create mode 100644 dlls/game/worldspawn.h create mode 100644 licenseAgreement.txt create mode 100644 linux/Makefile create mode 100644 linux/make_debug.sh create mode 100644 linux/make_release.sh create mode 100644 readme.txt diff --git a/EF2-game.dsw b/EF2-game.dsw new file mode 100644 index 0000000..a454499 --- /dev/null +++ b/EF2-game.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "game"=".\dlls\game\game.dsp" - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Code/DLLs/game", NEDAAAAA + .\dlls\game + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/EF2/Code", QJDAAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/EF2-game.opt b/EF2-game.opt new file mode 100644 index 0000000000000000000000000000000000000000..1eb0e612f6c7ce97ea352b128ce7f84da34cf493 GIT binary patch literal 55808 zcmeHQTW}m#TJEvztfM%|#&OKr1Q@g-myI>DCC9OiF_C6QiGvhtB_#?li?*ldXr|YF zb9awr?JirjR33PxiU*3~1*&+XVA+_;m4Yg2t0AG%A8UK6#LF0iOzJ@S?ygqLr z{{DRmC%+U1im(4iE$~06YaQ=x3UCx~3_$PvbAaQ32LaQ7hXC}RAI9$^fJXu70>)#2 zlYqworvOg?i0-HG`vt%mz*)dg1D*u@4B%%0KL;SXU%~GT;ERB#0KWiu8t@F@S-^9E zF9C@59DZj34Zs}WJm3Og9sjG6KUyD+ttN1NATg)E1Mjx^bj-1Vq$#sUDN) zDW2$MXT{GFO81RjlrIoE{PlU1Ph*G1%t6aPLB-Hu!PUlFxACmh3O~yA2+F6iN@de^ zkUUNkkuJZ6_ebyTXy0_jO!I0d2>q`KD~>v1XTH;OoTx)F@=v1|1wrKBW8jZ&EitXLPJq07Lxl*5eL*eqpTU5`7q*o_CH71=fS z0^j!H^7HNrGw{o8vn7$Oq3LYd0=>QXOq668+s;TNAl?!;eN%{Uway*3IVYtL<-H=%AtQ2(Pnpv$?pkf%{G22A%bdwHI1f z8nGM9$dPh$7^7D8E!*q)yK1~NeK)X?^&y9|<;cOj9aw{HD+n5Z=^rmEYhZePv4%H3 z428_ka{Nf#@NRl-Pq=#(sI7tRh-I6)lzJOQnl;DVLqDOaS$-JWJwHShyJoy)$Afh$ zt(Q;c-x1^Dd;Ve^o7OFvUti9OPL|y&<}Z=UZG>Q?yuKg%&0!c~oW3HQK!mT+^h%Yb zn6aq#n-z4g3Cu!KoVC?Jn9j?gKMYod+tuwb@SPr-*rs7B^H{}GqiQ|hZP_7a+5{h% zap*gegqm^Dvt4-;%4W-+JNL+;kKr#i{Wd0_SiP=_#iQ6s&}vq^glgyVi99m8AvoCZ^4D0>WvkNpC|HVei;9<5Ncjf@WeFHtlVU&^Qgdy(HLkJ(1#j?(Kyn z!ZmHx$&fam&mBuA?qe#{DJ`_|cAy5o<$Kim9NiL=s@?5xyLMzXtZ9S-7abbGNr@~6 zTN7DP+Asv@;ytV$-K4b>jQxWYOD3ZJ zF^*m&Qeb7r6s-D!OoP_KJl3~G4o6G5s?)MHGQZ9)=l%e~c(jqZ%hXmhFpQ9#ST3|pvoXv*NESJRZFYqXiA>@a{s zJR*iS&D0XrM6P%a+gc3qldimSQ=nV7h3?-Mt9}nNgC#e3GG$M6hkdG)YNnpBOzd{3 zn4XP?@FJbO=VSJicq~xy;P%kGrHV$YC+mc`J+uQC2t{`{z{;PjM9Y{7{N3~!vfIMU zoky?H9!}H;babUgN=B0r+4`> z((Y=!<9AIbnfPAz)oSbPZTh*|`l@U7L-P*SU-i!Vw1Qh`!=CM#$+)4VUB?gP-eKEI zHV=4o5n}P9!vxj9_OLBPx;;BtXzXCecGBns>tOq+`l5rqrAq7A{efyoHNhUBjcEE+ ziBa36J9+5-{iMg`X2bL$Y5vv8R8Y?e@(DC19fIn9Aci4kUE5OayC!qkh|$GVA}sBC zYz6+V2#0~9!`oTM9z7>4OG*)>H!(_x>sjLPwzm_)aP6? zsEPzyMq(@gdJsE^$0`a;V-Ek@cZD^?;_QeKHUTOHTHD3!SXmLE zXX?2_eDRP`JhGLJ8{;BJ4<7qat8Q638%{Wkl(HIgEGbf^2b3aOFEeW6OedfGQDp*Y z;m}f|tvtSr(&P>@<07#^9v59W+!P7R$DC4Z{=id=syg}%7>v+bapH%MN}P-DoCcXFFM59WBwoW|5D~=x&)Q_O|lr3(aEw@!lykk zS%u2+%>T>IR>igfYk>KG%>Sc}8uR~{|3|zx=Ksl~+@#|Z^Z#-ma7I>OnoyYk$NWF) z2h9JI6AJVHnEyw^f%$*T|I70Ri{b{w<5`h8$o#)fwfI7@(MkuIagogb>wMDuzvGbn zU0r!~1-w7z|FQgELAa6me~|p>u>9YF#d%r&k7PHhKdMO{o)YfJ2nwIlBt_*1v?M0? z*=!W0rOArRYvB_ar73?HleqlnFhW#)1S7ou^hLq+gBRWG^O;LV6+dnfY7*;aWXYpM zEdNKX!2Ca!{~NIUAItwqmQ|PK|FpbYUeK?dNf74IIf*)!|6}<-mjCO^IfUi^M2F@7 zSpF|j=&U;W6j_u%S@M5X`qx{~NcNzYOnT9it{LbblbwKLfP?BE|2$=?^^Z4D$`HEG zr1`xKEqq^Vq5Fn|`y0610dD{6S(GmopY@;pDYKa8DX7c80WJO;D5(#PbN4_R=UF}! zI*IZV8wm$}{ud}A8ArgCl&|g%|92YN?1z2+MJh%={}kSw)ct<2Ny<-eKneEPwf4>R z>-oPYNXeltwWCC&A}UbFm!Wu>WiYb^y*B7nSfc(+dwG7A@_6QI{X$)9n3KY_%omCW zB090*521}yr#zyER-w9W2U2?f#4FJ25uu8BqJ0-S3ZCgew;b9eJ>gl<$Wb*qep_bL z7*yUqtc0$WE`TP{gvDyJ{3pwQ7P#Qd|7HH~zWU**dIfRk#wt}*5-|T)84m$RJSVPJ zR(W9lFYA9pDVO!XS^t|10wjmQUCG7DZdw1ER!i3Zmfa$M=A&jI(=M$4o!Ysg zCSm?B^M6_YJ2U9_S>yjM{(-WG`Q*$~b7abKuC=lfNrQ_s=jyMuTZ?p^vG&sof4b9o zh3x3SE=z|j?R7ToTXx&)!-zv&ar8FK9dT~|3?%RIGztfcP*woNe)&~Z?4zY(4_H_^ z*-_Z7+-3eBj2^N5M=~ecsn$c!?4lIE_b~sD`G3s+lly6w|I8W51S^8=KeGKtW!H>8 zWYYJ_yjeoF|9Bu9;B5a9CLOggNVfk-n^bKlnC(9jgQm~+AHlejq9APlQSQPrL;KV} zU}IRB&!-!<|442k*#2Y2&q@Upiig^zQWhIjQ?ELT;sg+aczhwvk<7A<%7MYCR zkMvB5)b*3Pjb`-5=FpQagU%Kb-GJ?5CrPC$LTTwL66%MpH(TpBSIZSr3E=1F_^FMJ zrFAIFKV&pF&9E=x8?csK`TUfo;#mo;vnfbD*5GJMHnQ~3peY_)O16fT(Z`OTYQ4IZ zmzNwv_Am_VlM5Z_bwkv0457-kAbD9y{b*7-C8HTt5~<7{&xv%_4?wP~3UyTy{*;oP zV{%tzJOZCTIVsXH^iw?nPe!MVW-3Oy*Mxit%z}$hH@wR70EDUBX$?-%av73Xsm@Ev zxx$)p)EJB*!X_ss*8<~+9*)6Z-`0ky$7J@9j^MPUr_=S;^o$2QPOSS*867r5|{FS#)Y@DBHDa`pQ1f3@3~HMy0P8>tB< zmuW*~jWM)L@#*qPH)9voN`k^tDk&-#&?F|eY>uK>|C`z*^L3H=c+oAx{$ElBa`yj{ zxUDNlp0fWJ#m!{@FYNz?{l6&RAd{?>(6&hKCMdxEUn=KSm;Jx6|Ca{)e<>b8ikzO)iUrAhahB~;mw=6=zsOU)8NrEo=&CUvt}MCTlAmYnqDVn%wFbi!H82*$SZ z?l?yxlW+Z_5s;&l9ycpv$XRD)1RQc!;_0xqe|GvPSCvvb?Hq|BkB_CI5=WjzTEfCB z=Lk|c_pCs}&F2VKI@=q;R@Xr~0WH!hcc4XF>R`BtmW~?B6_7qet&%gzigZP_qACL7 zMJ5g=tKxI6Caa=H{g_r!jk>0+ppsk7%t>VhMb2GiMXdCir|=2?zuI~O`IBe4K~|M%m;GyfmEpuA)7kzIFTE1j_P+5bEHe@`6nlEGK@|4u%G zS^uB?zbo(UtpCsY|Cz$SW`V|4w?JQmdKdgW3OkXXGTp z{@)#`8~j<*|8K(1s%b>{_Xe!K3Rrxt8;7<3dRY6TU08ta_hEZ>C~d!bMvQhi+;!?lDF3Tegtq^BxYDYr58M9vTxziYN)p=l?@Nj1 zrV}kYy$#3jO2Z69$^2jD|B7nSZsz|o|96Z?H}ij){~NOZuh<&oe9{z!5a_TjMGq)Y z5;)K(=Kn^_|24_c(KOqCW&5u%7U47hH)HrR{}*FIHUi|69PJN{`M=EneLWInkx@28 zV)2f)6q%hnx?}sVZ2y(*zh?F_K5P8nX=6&W0~7C6M{)namWfl#g(NVa1QwFOMV(0U z4)JZJ(*8l&h63>!>8h}aupcSge@IL$RCC~zf;-!PkY+I0{zGbUg6%)Z9>Mk>gb8~Y zaodB-?DUWz?aQnRw=49)!FPJ89u92^s}@1Rv;9}L|4Lj+sk}oSB2gPk!cZ2{L8T&; z-as)(how4@4(Iccsy~ER3O|xU>h6r{%~0GVwd`(x+qEMLOf!T6Qb0yE014G5K1!Vf(La z|21~@HvKi<_F~yJ*#0X93mFj;A=`hIJ%a7O&NUiy?YqJn#%9+MbJ}oYD&NWWUo(Cd z+kcS9oT3u+Ab;Q~MpYesiWK?~R78@b`63qolJ#W=)V{g*PObC3cWNij zzFYeT|J~Y?fBkOlqciW-es}m@?cculUTx*<`?Y({`?cO*zF)ie_y@In;)9y=S0B_? zPJdW?%lfc(^urHpA4z#ga!P*^ko+cfIos%L3~-X|b#7yG=EB*O{%s7N{>J=bd;Z16 zh1Tqa7nfg{y>Ov*es*#B!qV*d^NZ)3SI#eAX|*r?reU1ZnJH<3lP!t~NfN?%<)2-vRtUw?pkyZii{qXD# z(8tm{-a>hQ3ivPGD#oYWD)jh|0?y=q2}jo#M_OgncnSnPc(%`HRg9^X>TwGso|vu)hbKz>uYxBQ%|Cy3Z?B(3x*U>w@*a+5bDs z|4BX~^Z#Jmx?X8MQHlMr{NFU|e@o6G%l}QY{NFIJd_U~jp2_lmDTi<%hM^tBwxw+M zvi>*A|IIW;_=Bwf&HCS&z(bL5_F3cqvHe%2_s{lUt$`VrIK*uKRdo(^?)Ztc|N4J5 C8mMpp literal 0 HcmV?d00001 diff --git a/EF2-game.sln b/EF2-game.sln new file mode 100644 index 0000000..52e9494 --- /dev/null +++ b/EF2-game.sln @@ -0,0 +1,32 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "game", "dlls\game\game.vcxproj", "{AD2B91AF-BE5E-4D64-952C-5F061241380D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Demo Release|Win32 = Demo Release|Win32 + Intel Release|Win32 = Intel Release|Win32 + Release CDROM|Win32 = Release CDROM|Win32 + Release|Win32 = Release|Win32 + VTune|Win32 = VTune|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.Debug|Win32.ActiveCfg = Debug|Win32 + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.Debug|Win32.Build.0 = Debug|Win32 + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.Demo Release|Win32.ActiveCfg = Demo Release|Win32 + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.Demo Release|Win32.Build.0 = Demo Release|Win32 + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.Intel Release|Win32.ActiveCfg = Intel Release|Win32 + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.Intel Release|Win32.Build.0 = Intel Release|Win32 + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.Release CDROM|Win32.ActiveCfg = Release CDROM|Win32 + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.Release CDROM|Win32.Build.0 = Release CDROM|Win32 + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.Release|Win32.ActiveCfg = Release|Win32 + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.Release|Win32.Build.0 = Release|Win32 + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.VTune|Win32.ActiveCfg = VTune|Win32 + {AD2B91AF-BE5E-4D64-952C-5F061241380D}.VTune|Win32.Build.0 = VTune|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/EF2.dsw b/EF2.dsw new file mode 100644 index 0000000..439f4d6 --- /dev/null +++ b/EF2.dsw @@ -0,0 +1,129 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "EF2"=".\Executable\EF2.dsp" - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/EF2/Code/Executable", UJDAAAAA + .\executable + end source code control +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name renderer + End Project Dependency + Begin Project Dependency + Project_Dep_Name uilib + End Project Dependency + Begin Project Dependency + Project_Dep_Name cgame + End Project Dependency + Begin Project Dependency + Project_Dep_Name game + End Project Dependency +}}} + +############################################################################### + +Project: "bspc"=".\Executable\bspc\bspc.dsp" - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/EF2/Code/Executable/bspc", RTEAAAAA + .\Executable\bspc + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "cgame"=".\dlls\cgame\cgame.dsp" - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Code/DLLs/cgame", LEDAAAAA + .\dlls\cgame + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "game"=".\dlls\game\game.dsp" - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Code/DLLs/game", NEDAAAAA + .\dlls\game + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "renderer"=".\Libs\renderer\renderer.dsp" - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Code/Libs/renderer", MEDAAAAA + .\libs\renderer + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "uilib"=".\Libs\uilib\uilib.dsp" - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/Code/Libs/uilib", REDAAAAA + .\libs\uilib + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/EF2/Code", QJDAAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/Executable/gamedefs.h b/Executable/gamedefs.h new file mode 100644 index 0000000..4f1a0cd --- /dev/null +++ b/Executable/gamedefs.h @@ -0,0 +1,13 @@ +//------------------------------------------------ +// gamename.h -- Defines the name of the game. +// +// This file is included by q_shared.h, which happens +// to be in the game dll directory. However, we want +// the name of the game to be unique to each executable. +// Hence, it is part of the Executable directory structure, +// but lives at the executable dsp level. +//------------------------------------------------ + + +#define GAME_NAME "Elite Force II" +#define GAME_EXE_VERSION "1.10" diff --git a/Executable/win32/win_bounds.cpp b/Executable/win32/win_bounds.cpp new file mode 100644 index 0000000..0dc50cc --- /dev/null +++ b/Executable/win32/win_bounds.cpp @@ -0,0 +1,187 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/executable/win32/win_bounds.cpp $ +// $Revision:: 4 $ +// $Date:: 9/29/02 10:46a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Bounds checks all new's and delete's +// + +extern "C" +{ + void *b_malloc ( unsigned int size ); + void b_free ( void * ); +} + +////////////////////////// +// Bounds checking +// +// #define DISABLE_BOUNDS to disable checking a compiler +// warning will happen if checking is enabled +// +// #define BOUNDS_ENDOFPAGE to check for overflowed, don't +// define it to check for underflows +// +// functions: +// void *b_malloc ( unsigned ); +// Does a bounds-malloc, or just a normal one if checking's +// disabled +// void b_free ( void * ); +// Frees a bounds pointer, or just a normal free if no +// checking +// +// void *operator new ( size_t ), operator new[] ( size ) +// For C++ new's and new []'s, bounds checked +// void operator delete ( void * ), operator delete[] ( void * ) +// For C++ delete's and delete []'s, bounds checked +////////////////////////// +#define DISABLE_BOUNDS + +#if defined(GAME_DLL) || defined(CGAME_DLL) +#define DISABLE_BOUNDS +#endif + +//#define BOUNDS_ENDOFPAGE + +///////////////////////// +// If bounds are disabled +///////////////////////// + +#ifdef DISABLE_BOUNDS + +#include + +void *b_malloc ( unsigned int size ) +{ + return malloc ( size ); +} + +void b_free ( void *what ) +{ + free ( what ); +} + +/////////////////////////// +// If bounds are enabled +/////////////////////////// +#else + +#pragma message ("win_bounds.cpp: Warning - Bounds checking is enabled\n" ) + +#include +#include + +typedef struct +{ + unsigned int header; + int size; + void *returned; +} bounds_type_t; + +#define PAGE_SIZE ( 4096 ) + +#define NORMAL_HEADER 0xdeadbeef +#define ARRAY_HEADER 0xdeadbabe + +static unsigned int bounds_numpages ( unsigned int size ) +{ + unsigned int ret; + + ret = size / PAGE_SIZE + 3; + if ( size % PAGE_SIZE ) + ret++; + + return ret; +} + +void *bounds_malloc ( unsigned int size, unsigned head = NORMAL_HEADER ) +{ + bounds_type_t *where; + unsigned int num_pages; + void *mainaddress; + + num_pages = bounds_numpages ( size ); + + mainaddress = VirtualAlloc ( NULL, num_pages * PAGE_SIZE, MEM_RESERVE, PAGE_NOACCESS ); + VirtualAlloc ( mainaddress, PAGE_SIZE, MEM_COMMIT, PAGE_READWRITE ); + VirtualAlloc ( (char *) mainaddress + PAGE_SIZE * 2, ( num_pages - 3 ) * PAGE_SIZE, MEM_COMMIT, PAGE_READWRITE ); + where = (bounds_type_t *) mainaddress; + + where->header = head; + where->size = size; +#ifdef BOUNDS_ENDOFPAGE + where->returned = (char *) mainaddress + ( num_pages - 1 ) * PAGE_SIZE - size; +#else + where->returned = (char *) mainaddress + PAGE_SIZE * 2; +#endif + memset ( where->returned, 0xdc, size ); + + return where->returned; +} + +void bounds_free ( void *address, unsigned head = NORMAL_HEADER ) +{ + bounds_type_t *where; + unsigned int num_pages; + void *mainaddress; + + mainaddress = (char *) address - PAGE_SIZE * 2; +#ifdef BOUNDS_ENDOFPAGE + mainaddress = (char *) mainaddress - ( (unsigned int) mainaddress % 4096 ); +#endif + + where = (bounds_type_t *) mainaddress; + + if ( where->header != head || where->returned != address ) + { + __asm int 3 // Breakpoint + } + num_pages = bounds_numpages ( where->size ); + + // All pages must be in the same state to be MEM_RELEASED + VirtualFree ( mainaddress, PAGE_SIZE, MEM_DECOMMIT ); + VirtualFree ( (char *) mainaddress + PAGE_SIZE * 2, (num_pages - 3 ) * PAGE_SIZE, MEM_DECOMMIT ); + VirtualFree ( mainaddress, 0, MEM_RELEASE ); +} + +void *b_malloc ( unsigned int size ) +{ + return bounds_malloc ( size ); +} + +void b_free ( void *what ) +{ + bounds_free ( what ); +} + +void *operator new[] ( size_t size ) +{ + return bounds_malloc ( size, ARRAY_HEADER ); +} + +void *operator new ( size_t size ) +{ + return bounds_malloc ( size ); +} + +void operator delete [] ( void *what ) +{ + if ( what ) + bounds_free ( what, ARRAY_HEADER ); +} + +void operator delete ( void *what ) +{ + if ( what ) + bounds_free ( what ); +} + +#endif /* !DISABLE_BOUNDS */ diff --git a/Shared/qcommon/alias.h b/Shared/qcommon/alias.h new file mode 100644 index 0000000..a787a51 --- /dev/null +++ b/Shared/qcommon/alias.h @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/Shared/qcommon/alias.h $ +// $Revision:: 5 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Generic alias system for files. +// + +#ifndef __ALIAS_H__ +#define __ALIAS_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +// +// public implementation +// + +const char * Alias_Find( const char * alias ); +qboolean Alias_Add( const char * alias, const char * name, const char * parameters ); +qboolean Alias_Delete( const char * alias ); +const char * Alias_FindRandom( const char * alias ); +void Alias_Dump( void ); +void Alias_Clear( void ); + +// +// private implementation +// +#define MAX_ALIAS_NAME_LENGTH 32 +#define MAX_REAL_NAME_LENGTH 128 +#define MAX_ANIM_NAME_LENGTH 128 +#define MAX_ALIASLIST_NAME_LENGTH MAX_QPATH + +typedef struct AliasActorNode_s + { + int actor_number; + + int number_of_times_played; + byte been_played_this_loop; + int last_time_played; + + struct AliasActorNode_s * next; + } AliasActorNode_t; + +typedef struct AliasListNode_s + { + char alias_name[MAX_ALIAS_NAME_LENGTH]; + char real_name[MAX_REAL_NAME_LENGTH]; + char anim_name[MAX_ANIM_NAME_LENGTH]; + float weight; + qboolean loop_anim; + + // Static alias info + + byte global_flag; + byte stop_flag; + float timeout; + int maximum_use; + + // Global alias info + + int number_of_times_played; + byte been_played_this_loop; + int last_time_played; + + // Actor infos + + AliasActorNode_t *actor_list; + + struct AliasListNode_s * next; + } AliasListNode_t; + +typedef struct AliasList_s + { + char name[ MAX_ALIASLIST_NAME_LENGTH ]; + qboolean dirty; + int num_in_list; + AliasListNode_t ** sorted_list; + AliasListNode_t * data_list; + } AliasList_t; + +void Alias_ListClearActors( const AliasList_t * list ); +AliasList_t * AliasList_New( const char * name ); +const char * Alias_ListFind( AliasList_t * list, const char * alias ); +AliasListNode_t *Alias_ListFindNode( AliasList_t * list, const char * alias ); +qboolean Alias_ListAdd( AliasList_t * list, const char * alias, const char * name, const char * parameters ); +const char * Alias_ListFindRandom( AliasList_t * list, const char * alias ); +void Alias_ListDump( AliasList_t * list ); +void Alias_ListClear( AliasList_t * list ); +void Alias_ListDelete( AliasList_t * list ); +void Alias_ListSort( AliasList_t * list ); +int Alias_IsGlobal( const AliasListNode_t *node, int actor_number ); +AliasActorNode_t *Alias_FindActor( const AliasListNode_t *node, int actor_number ); +void Alias_ListFindRandomRange( AliasList_t * list, const char * alias, int *min_index, int *max_index, float *total_weight ); +const char * Alias_ListFindDialog( AliasList_t * list, const char * alias, int random, int actor_number); +const char* Alias_ListFindSpecificAnim( const AliasList_t *list, const char *name ); +qboolean Alias_ListCheckLoopAnim( const AliasList_t *list, const char *name ); +void Alias_ListUpdateDialog( AliasList_t * list, const char * alias, int number_of_times_played, byte been_played_this_loop, int last_time_played ); +void Alias_ListAddActorDialog( AliasList_t * list, const char * alias, int actor_number, int number_of_times_played, byte been_played_this_loop, int last_time_played ); +float randweight( void ); + +#ifdef __cplusplus + } +#endif + +#endif /* alias.h */ diff --git a/Shared/qcommon/cm_public.h b/Shared/qcommon/cm_public.h new file mode 100644 index 0000000..dfea75e --- /dev/null +++ b/Shared/qcommon/cm_public.h @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/Shared/qcommon/cm_public.h $ +// $Revision:: 16 $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __CM_PUBLIC_H__ +#define __CM_PUBLIC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "qfiles.h" + + +void CM_LoadMap( const char *name, qboolean clientload, int *checksum); +clipHandle_t CM_InlineModel( int index ); // 0 = world, 1 + are bmodels +clipHandle_t CM_TempBoxModel( const vec3_t mins, const vec3_t maxs, int contents ); + +void CM_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); + +int CM_NumClusters (void); +int CM_NumInlineModels( void ); +char *CM_EntityString (void); + +// returns an ORed contents mask +int CM_PointContents( const vec3_t p, clipHandle_t model ); +int CM_PointBrushNum( const vec3_t p, clipHandle_t model ); +int CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); + +void CM_BoxTrace ( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, qboolean cylinder); +void CM_BoxTraceEx ( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, qboolean cylinder, const unsigned int traceExFlags ); +void CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles, qboolean cylinder, qboolean force_rotation ); + +byte *CM_ClusterPVS (int cluster); + +int CM_PointLeafnum( const vec3_t p ); + +void CM_Clear( void ); + +// only returns non-solid leafs +// overflow if return listsize and if *lastLeaf != list[listsize-1] +int CM_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, + int listsize, int *lastLeaf ); + +int CM_LeafCluster (int leafnum); +int CM_LeafArea (int leafnum); + +void CM_AdjustAreaPortalState( int area1, int area2, qboolean open ); +qboolean CM_AreasConnected( int area1, int area2 ); +void CM_ResetAreaPortals( void ); +void CM_WritePortalState( fileHandle_t ); +void CM_ReadPortalState( fileHandle_t ); + +int CM_WriteAreaBits( byte *buffer, int area ); +byte *CM_VisibilityPointer( void ); + +int CM_GetLightingGroup( const char *group_name ); + +// cm_tag.c +void CM_LerpTag( orientation_t *tag, clipHandle_t model, int startFrame, int endFrame, + float frac, const char *tagName ); + + +// cm_marks.c +int CM_MarkFragments( int numPoints, const vec3_t *points, const vec3_t projection, + int maxPoints, vec3_t pointBuffer, int maxFragments, markFragment_t *fragmentBuffer, float max_dist, qboolean test_normal ); + +// cm_patch.c +void CM_DrawDebugSurface( void (*drawPoly)(int color, int numPoints, float *points) ); +void CM_DrawDebugTerrain( void (*drawPoly)(unsigned int color, int numPoints, float *points) ); + +// ToolServer wrapper functions +void ToolServerInit( void ); +void ToolServerShutdown( void ); +void ToolServerProcessCommands( void ); +void* ToolServerGetData( void ); +unsigned int ToolServerGetNumClients(); + +// GameplayManager functions +void CreateGameplayManager( void ); +void ShutdownGameplayManager( void ); + + +// LoadSaveGameManager functions +void CreateLoadSaveGameManager( void ); +void DeleteLoadSaveGameManager( void ); + +//Arena Controller functions +void CreateArenaController( void ); +void DeleteArenaController( void ); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/Shared/qcommon/gameplaydatabase.cpp b/Shared/qcommon/gameplaydatabase.cpp new file mode 100644 index 0000000..ac0e19e --- /dev/null +++ b/Shared/qcommon/gameplaydatabase.cpp @@ -0,0 +1,1570 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/Shared/qcommon/gameplaydatabase.cpp $ +// $Revision:: 17 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// GameplayDatabase.cpp: implementation of the GameplayDatabase class. +// +////////////////////////////////////////////////////////////////////// + +#include "gameplaydatabase.h" + + + +//////////////////////////////////////////////////////////////// +// +// GameplayProperty CLASS +// +//////////////////////////////////////////////////////////////// +CLASS_DECLARATION( Class, GameplayProperty, NULL ) +{ + {NULL,NULL} +}; + +//=============================================================== +// Name: setName +// Class: GameplayProperty +// +// Description: Sets the name for this property +// +// Parameters: const str& -- the name for the property +// +// Returns: None +// +//=============================================================== +void GameplayProperty::setName( const str &name ) +{ + _name = name; +} + + +//=============================================================== +// Name: setFloatValue +// Class: GameplayProperty +// +// Description: Sets the float value for this property. Only +// sets the value if the value has changed. Returns +// a boolean specifying whether or not the value changed. +// +// Parameters: float -- value +// +// Returns: bool -- true if the value actually changed. +// +//=============================================================== +bool GameplayProperty::setFloatValue(float valuefloat) +{ + if ( _valuefloat == valuefloat ) return false ; + + _valuefloat = valuefloat ; + return true ; +} + +//=============================================================== +// Name: setVectorValue +// Class: GameplayProperty +// +// Description: Sets the vector value for this property. Only +// sets the value if the value has changed. Returns +// a boolean specifying whether or not the value changed. +// +// Parameters: const Vector& vector -- the vector +// +// Returns: bool -- true if the value actually changed. +// +//=============================================================== +bool GameplayProperty::setVectorValue(const Vector& vector) +{ + if ( _valuevector == vector ) return false ; + + _valuevector = vector ; + return true ; +} + +//=============================================================== +// Name: setStringValue +// Class: GameplayProperty +// +// Description: Sets the string value for this property. Only +// sets the value if the value has changed. Returns +// a boolean specifying whether or not the value changed. +// +// Parameters: const str& -- value +// +// Returns: bool -- true if the value actually changed. +// +//=============================================================== +bool GameplayProperty::setStringValue(const str& valuestr) +{ + if ( _valuestr == valuestr ) return false ; + + _valuestr = valuestr ; + return true ; +} + + +//-------------------------------------------------------------- +// +// Name: parseProperty +// Class: GameplayProperty +// +// Description: Parses the gameplay property and returns +// +// Parameters: gameplayFile -- Pointer to the script object (file) +// const str& name -- name of the GameplayProperty +// +// Returns: bool -- sucessful parse or not +// +//-------------------------------------------------------------- +bool GameplayProperty::parseProperty(Parser &gameplayFile, const str& type) +{ + const char *token; + float val1, val2, val3; + int readcount; + + if ( type == "vector" || type == "string" || type == "float" ) + { + token = gameplayFile.GetToken(false); + if ( !token ) + return false; + setName(token); + } + else // Old-style file + setName(type); + + token = gameplayFile.GetToken(false); + if ( !token ) + return false; + + readcount = sscanf(token,"%f %f %f",&val1, &val2, &val3); + + if ( readcount == 1 ) // Single Float + { + setFloatValue(val1); + setType(VALUE_FLOAT); + } + else if ( readcount == 3 ) // Vector + { + setVectorValue(Vector(val1, val2, val3)); + setType(VALUE_VECTOR); + } + else // String + { + setStringValue(token); + setType(VALUE_STRING); + } + + return true; +} + + +//-------------------------------------------------------------- +// +// Name: getFloatValueStr +// Class: GameplayProperty +// +// Description: Gets the float value as a string +// +// Parameters: None +// +// Returns: const str -- string version +// +//-------------------------------------------------------------- +const str GameplayProperty::getFloatValueStr() +{ + char tmpstr[16]; + sprintf(tmpstr, "%g", _valuefloat); + return tmpstr; +} + + +#ifdef GAME_DLL +//-------------------------------------------------------------- +// +// Name: Archive +// Class: GameplayProperty +// +// Description: Archive function. +// +// Parameters: Archiver &arc -- Archive reference +// +// Returns: None +// +//-------------------------------------------------------------- +void GameplayProperty::Archive(Archiver &arc) +{ + if ( arc.Loading() ) + { + return ; + } + + if ( arc.Saving() && isModified() ) + { + ArchiveEnum( _type, GameplayValueType ); + arc.ArchiveString( &_name ); + switch ( _type ) + { + case VALUE_FLOAT: + arc.ArchiveFloat( &_valuefloat ); + break ; + case VALUE_STRING: + arc.ArchiveString( &_valuestr ); + break ; + case VALUE_VECTOR: + arc.ArchiveVector( &_valuevector ); + break ; + } + } +} + +#endif // GAME_DLL + + + + + + +//////////////////////////////////////////////////////////////// +// +// GameplayObject CLASS +// +//////////////////////////////////////////////////////////////// + + +CLASS_DECLARATION( Class, GameplayObject, NULL ) +{ + {NULL,NULL} +}; +//-------------------------------------------------------------- +// +// Name: GameplayObject +// Class: GameplayObject +// +// Description: Constructor. +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayObject::GameplayObject() : + _name(""), + _category(""), + _fullScopeName(""), + _depth(0), + _baseObject(0), + _nextHashObject(0), + _modified( false ) +{ + _propertyList.ClearObjectList(); + _subObjectList.ClearObjectList(); +} + +//-------------------------------------------------------------- +// +// Name: GameplayObject +// Class: GameplayObject +// +// Description: Constructor -- Overload with depth +// +// Parameters: int depth +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayObject::GameplayObject(int depth) : + _name(""), + _category(""), + _fullScopeName(""), + _depth(depth), + _baseObject(0), + _nextHashObject(0), + _modified( false ) +{ + _propertyList.ClearObjectList(); + _subObjectList.ClearObjectList(); +} + +//-------------------------------------------------------------- +// +// Name: ~GameplayObject +// Class: GameplayObject +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayObject::~GameplayObject() +{ + int i; + for ( i=1; i<=_propertyList.NumObjects(); i++ ) + { + GameplayProperty *gp = _propertyList.ObjectAt(i); + delete gp; + } + + for ( i=1; i<=_subObjectList.NumObjects(); i++ ) + { + GameplayObject *go = _subObjectList.ObjectAt(i); + delete go; + } + + _baseObject = 0; // Note: This pointer will get deleted by the database. + + _subObjectList.FreeObjectList(); + _propertyList.FreeObjectList(); +} + +//-------------------------------------------------------------- +// +// Name: parseObject +// Class: GameplayObject +// +// Description: Parses the gameplay object and returns +// +// Parameters: gameplayFile -- Pointer to the script object (file) +// const str& name -- name of the GameplayObject +// +// Returns: bool -- sucessful parse or not +// +//-------------------------------------------------------------- +bool GameplayObject::parseObject(Parser &gameplayFile, const str& name) +{ + const char *token; + GameplayProperty *gpproperty; + + if ( name != "OBJECT" ) + return false; + + if ( !gameplayFile.TokenAvailable(false) ) + return false; + + token = gameplayFile.GetToken(false); + setName(token); + _fullScopeName += _name; + + if ( gameplayFile.TokenAvailable(false) ) + { + token = gameplayFile.GetToken(false); + setCategory(token); + } + + // Get the open brace + token = gameplayFile.GetToken(true); + if ( token[0] != '{' ) + assert(0); + + while (gameplayFile.TokenAvailable(true)) + { + token = gameplayFile.GetToken(true); + + // If we have a close brace, we're done. + if ( token[0] == '}' ) + return true; + + // We have a suboject + if ( !strcmp(token, "OBJECT") ) + { + GameplayObject *gpobject; + gpobject = new GameplayObject(_depth+1); + gpobject->setFullScopeName(_fullScopeName + "."); + if ( gpobject->parseObject(gameplayFile, token) ) + { + _subObjectList.AddObject(gpobject); + } + else + { + delete gpobject; + return false; + } + } + else + { + gpproperty = new GameplayProperty(); + if ( gpproperty->parseProperty(gameplayFile, token) ) + _propertyList.AddObject(gpproperty); + else + { + delete gpproperty; + return false; + } + } + } + + // Premature end of file, missing close brace. + return false; +} + +//-------------------------------------------------------------- +// +// Name: getProperty +// Class: GameplayObject +// +// Description: Gets the property by name, if it exists. +// +// Parameters: const str& propname -- Property name to look up +// +// Returns: Pointer to the property or 0 if not found +// +//-------------------------------------------------------------- +GameplayProperty* GameplayObject::getProperty(const str& propname) +{ + int i; + GameplayProperty *gpprop; + + if ( propname == "" ) + return 0; + + for ( i=1; i<=_propertyList.NumObjects(); i++ ) + { + gpprop = _propertyList.ObjectAt(i); + if ( gpprop->getName() == propname ) + return gpprop; + } + + return 0; +} + + +//-------------------------------------------------------------- +// +// Name: getPropertyFloatValue +// Class: GameplayObject +// +// Description: Gets the float value of the property specified +// Also checks the baseObject if the property doesn't exists +// in this object. +// +// Parameters: const str& propname -- Property name to look up +// +// Returns: float -- value or 1.0 +// +//-------------------------------------------------------------- +float GameplayObject::getPropertyFloatValue(const str& propname) +{ + GameplayProperty *gpprop; + + gpprop = getProperty(propname); + if ( !gpprop ) + { + if ( _baseObject ) + { + gpprop = _baseObject->getProperty(propname); + if ( !gpprop ) + return 1.0f; + } + else + return 1.0f; + } + + return gpprop->getFloatValue(); +} + + +//-------------------------------------------------------------- +// +// Name: getPropertyStringValue +// Class: GameplayObject +// +// Description: Gets the string value of the property specified +// Also checks the baseObject if the property doesn't exists +// in this object. +// +// Parameters: const str& propname -- Property name to look up +// +// Returns: const str -- value or "" +// +//-------------------------------------------------------------- +const str GameplayObject::getPropertyStringValue(const str& propname) +{ + GameplayProperty *gpprop; + + gpprop = getProperty(propname); + if ( !gpprop ) + { + if ( _baseObject ) + { + gpprop = _baseObject->getProperty(propname); + if ( !gpprop ) + return ""; + } + else + return ""; + } + + return gpprop->getStringValue(); +} + +//-------------------------------------------------------------- +// +// Name: setFloatValue +// Class: GameplayObject +// +// Description: Sets float value of a property. +// +// Parameters: const str& propname -- Property name +// float value -- value +// bool create -- whether or not to create properties that are not found +// +// Returns: bool -- true if the value was changed or created. +// +//-------------------------------------------------------------- +bool GameplayObject::setFloatValue(const str& propname, float value, bool create) +{ + GameplayProperty *gpprop = getProperty(propname); + if ( !gpprop ) + { + if ( !create ) return false ; + + + gpprop = new GameplayProperty(); + gpprop->setName(propname); + _propertyList.AddObject(gpprop); + } + + bool valueChanged = gpprop->setFloatValue(value); + if ( valueChanged ) + { + gpprop->setModified( true ); + setModified( true ); + } + + return valueChanged ; +} + + +//-------------------------------------------------------------- +// +// Name: setStringValue +// Class: GameplayObject +// +// Description: Sets float value of a property. +// +// Parameters: const str& propname -- Property name +// const str& value -- string value +// bool create -- whether or not to create properties that are not found +// +// Returns: bool -- true if the value was changed or created. +// +//-------------------------------------------------------------- +bool GameplayObject::setStringValue(const str& propname, const str& valuestr, bool create) +{ + GameplayProperty *gpprop = getProperty(propname); + if ( !gpprop ) + { + if ( !create ) return false; + + gpprop = new GameplayProperty(); + gpprop->setName(propname); + _propertyList.AddObject(gpprop); + } + + bool valueChanged = gpprop->setStringValue(valuestr); + if ( valueChanged ) + { + gpprop->setModified( true ); + setModified( true ); + } + + return valueChanged ; +} + +//-------------------------------------------------------------- +// +// Name: setVectorValue +// Class: GameplayObject +// +// Description: Sets vector value of a property. +// +// Parameters: const str& propname -- Property name +// Vector value -- value +// bool create -- whether or not to create properties that are not found +// +// Returns: bool -- true if the value was changed or created. +// +//-------------------------------------------------------------- +bool GameplayObject::setVectorValue(const str& propname, const Vector& value, bool create) +{ + GameplayProperty *gpprop = getProperty(propname); + if ( !gpprop ) + { + if ( !create ) return false ; + + + gpprop = new GameplayProperty(); + gpprop->setName(propname); + _propertyList.AddObject(gpprop); + } + + bool valueChanged = gpprop->setVectorValue(value); + if ( valueChanged ) + { + gpprop->setModified( true ); + setModified( true ); + } + + return valueChanged ; +} + +//-------------------------------------------------------------- +// +// Name: getModified +// Class: GameplayObject +// +// Description: Gets the modified flag on the given property name +// Also checks the baseObject if the property doesn't exists +// in this object. +// +// Parameters: const std::string& propname -- property name +// +// Returns: bool +// +//-------------------------------------------------------------- +bool GameplayObject::getModified(const str& propname) +{ + GameplayProperty* gpprop = 0; + gpprop = getProperty(propname); + if ( !gpprop ) + { + if ( _baseObject ) + { + gpprop = _baseObject->getProperty(propname); + if ( !gpprop ) + return false; + } + else + return false; + } + + return gpprop->getModified(); +} + + +//-------------------------------------------------------------- +// +// Name: getSubObject +// Class: GameplayObject +// +// Description: Gets the subobject by the specified name. +// +// Parameters: const str& subobjname -- Name to find +// +// Returns: GameplayObject * -- Object or 0 if not found. +// +//-------------------------------------------------------------- +GameplayObject* GameplayObject::getSubObject(const str& subobjname) +{ + int i; + for ( i=1; i<=_subObjectList.NumObjects(); i++ ) + { + GameplayObject *go = _subObjectList.ObjectAt(i); + if ( go->getName() == subobjname ) + return go; + } + + return 0; +} + + +//-------------------------------------------------------------- +// +// Name: hasProperty +// Class: GameplayObject +// +// Description: Checks to see if the property exists +// +// Parameters: const str& propname -- Property name to look up +// bool localonly (default false) -- Specifies whether +// or not to check local properties only. +// +// Returns: bool +// +//-------------------------------------------------------------- +bool GameplayObject::hasProperty(const str& propname, bool localonly) +{ + int i; + GameplayProperty *gpprop; + + if ( propname == "" ) + return false; + + for ( i=1; i<=_propertyList.NumObjects(); i++ ) + { + gpprop = _propertyList.ObjectAt(i); + if ( gpprop->getName() == propname ) + return true; + } + + if ( _baseObject && !localonly && _baseObject->hasProperty(propname) ) + return true; + + return false; +} + + +//-------------------------------------------------------------- +// +// Name: removeProperty +// Class: GameplayObject +// +// Description: Removes the property from this object +// +// Parameters: const str& propname -- Property to remove +// +// Returns: bool - success or not +// +//-------------------------------------------------------------- +bool GameplayObject::removeProperty(const str& propname) +{ + int i; + GameplayProperty *gpprop; + + if ( propname == "" ) + return false; + + for ( i=1; i<=_propertyList.NumObjects(); i++ ) + { + gpprop = _propertyList.ObjectAt(i); + if ( gpprop->getName() == propname ) + { + _propertyList.RemoveObjectAt(i); + delete gpprop; + return true; + } + } + + // Not found + return false; +} + + +//=============================================================== +// Name: getNumberOfModifiedProperties +// Class: GameplayObject +// +// Description: Retrieves the number of properties that have been +// modified from their initial value loaded from disk. +// This includes properties created at runtime. +// +// Parameters: None +// +// Returns: int -- number of properties that have been modified. +// +//=============================================================== +int GameplayObject::getNumberOfModifiedProperties +( + void +) +{ + int numModifiedProperties = 0 ; + int numProperties = _propertyList.NumObjects(); + + for ( int propertyIdx = 1; propertyIdx <= numProperties; ++propertyIdx ) + { + GameplayProperty *property = _propertyList.ObjectAt( propertyIdx ); + if ( property->isModified() ) + { + numModifiedProperties++ ; + } + } + + return numModifiedProperties ; +} + + + +#ifdef GAME_DLL +//-------------------------------------------------------------- +// +// Name: Archive +// Class: GameplayObject +// +// Description: Archive function. We only save out the properties +// that have been modified. +// +// Parameters: Archiver &arc -- Archive reference +// +// Returns: None +// +//-------------------------------------------------------------- +void GameplayObject::Archive(Archiver &arc) +{ + if ( arc.Loading() ) + { + return ; + } + + if ( arc.Saving() && isModified() ) + { + // Get the number of modified properties and total properties + int numModifiedProperties = getNumberOfModifiedProperties(); + int numProperties = _propertyList.NumObjects(); + + // Save our name and number of modified properties + arc.ArchiveString( &_fullScopeName ); + arc.ArchiveInteger( &numModifiedProperties ); + for ( int modifiedIdx = 1; modifiedIdx <= numProperties; ++modifiedIdx ) + { + GameplayProperty *property = _propertyList.ObjectAt( modifiedIdx ); + if ( property->isModified() ) + { + property->Archive( arc ); + } + } + } +} + +#endif // GAME_DLL + +//////////////////////////////////////////////////////////////// +// +// GameplayDatabase CLASS +// +//////////////////////////////////////////////////////////////// + +CLASS_DECLARATION( Listener, GameplayDatabase, NULL ) +{ + {NULL,NULL} +}; +//-------------------------------------------------------------- +// +// Name: GameplayDatabase +// Class: GameplayDatabase +// +// Description: Constructor. +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayDatabase::GameplayDatabase() : + _lastObj(0), + _lastObjName("") +{ + for ( int i=0; iparseObject(gameplayFile, token) ) + { + _objectList.AddObject(gpobject); + } + else + { + delete gpobject; + return false; + } + } + + // Build hash table and link subobjects to their base + _buildHashTable(); + _linkSubObjectsToBase(); + + return true; +} + + +//-------------------------------------------------------------- +// +// Name: getRootObject +// Class: GameplayDatabase +// +// Description: Does not support the scope operation. +// This function requests an object at the root level +// of the database only. +// Use this function ONLY if you want strictly rool level +// objects, otherwise, use getObject. +// +// Parameters: const str& objname -- Object to look. +// +// Returns: Point to the object or 0 if not found +// +//-------------------------------------------------------------- +GameplayObject* GameplayDatabase::getRootObject(const str& objname) +{ + GameplayObject *gpobject; + unsigned int loc = GenerateHashForName(objname.c_str(), false, HASH_SIZE); + gpobject = _hashTable[loc]; + while ( gpobject && gpobject->getName() != objname ) + gpobject = gpobject->getNextObject(); + + return gpobject; +} + + +//-------------------------------------------------------------- +// +// Name: getObject +// Class: GameplayDatabase +// +// Description: Gets the object by the specified name. Also +// supports the . scope operation. +// +// Parameters: const str& objname -- Object to look. +// +// Returns: Point to the object or 0 if not found +// +//-------------------------------------------------------------- +GameplayObject* GameplayDatabase::getObject(const str& objname) +{ + // For speed reasons, we save off the last object we asked for + // if subsequent requests come in for the same object, we don't + // have to look through the hash table again. + if ( objname == _lastObjName ) + return _lastObj; + + GameplayObject *gpobject; + unsigned int loc = GenerateHashForName(objname.c_str(), false, HASH_SIZE); + gpobject = _hashTable[loc]; + while ( gpobject && gpobject->getFullScopeName() != objname ) + gpobject = gpobject->getNextObject(); + + _lastObj = gpobject; + _lastObjName = objname; + + return gpobject; +} + + +//-------------------------------------------------------------- +// +// Name: getSubObject +// Class: GameplayDatabase +// +// Description: Gets the subobject by the specified name +// +// Parameters: const str& objname -- Root Object name +// const str& subobjname -- Subobject name +// +// Returns: Point to the object or 0 if not found +// +//-------------------------------------------------------------- +GameplayObject* GameplayDatabase::getSubObject(const str& objname, const str& subobjname) +{ + int i; + GameplayObject *gpobject; + + for ( i=1; i<=_objectList.NumObjects(); i++ ) + { + gpobject = _objectList.ObjectAt(i); + if ( gpobject->getName() == objname ) + return gpobject->getSubObject(subobjname); + } + + return 0; +} + + +//-------------------------------------------------------------- +// +// Name: getFloatValue +// Class: GameplayDatabase +// +// Description: Gets the float value associated with the property +// of the object. +// +// Parameters: const str& objname -- Object name to look up. +// const str& propnme -- Property name to look up +// +// Returns: The value, or 1.0 +// +//-------------------------------------------------------------- +float GameplayDatabase::getFloatValue(const str& objname, const str& propname) +{ + GameplayObject *gpobject = 0; + gpobject = getObject(objname); + if ( !gpobject ) + return 1.0f; + + return gpobject->getPropertyFloatValue(propname); +} + +//-------------------------------------------------------------- +// +// Name: getStringValue +// Class: GameplayDatabase +// +// Description: Gets the float value associated with the property +// of the object. +// +// Parameters: const str& objname -- Object name to look up. +// const str& propnme -- Property name to look up +// +// Returns: The value, or "" +// +//-------------------------------------------------------------- +const str GameplayDatabase::getStringValue(const str& objname, const str& propname) +{ + GameplayObject *gpobject = 0; + gpobject = getObject(objname); + if ( !gpobject ) + return ""; + + return gpobject->getPropertyStringValue(propname); +} + +//-------------------------------------------------------------- +// +// Name: setFloatValue +// Class: GameplayDatabase +// +// Description: Sets float value of a property. +// +// Parameters: const str& objname -- Object name +// const str& propname -- Property name +// float value -- value +// bool create -- whether or not to create properties that are not found +// +// Returns: bool -- true if the value changed or created. +// +//-------------------------------------------------------------- +bool GameplayDatabase::setFloatValue(const str& objname, const str& propname, float value, bool create) +{ + GameplayObject *gpobject = getObject(objname); + if ( !gpobject ) + { + if ( !create ) return false; + + gpobject = _createFromScope(objname); + gpobject->setCategory("Misc"); // Category doesn't really matter. + return true ; + } + + return gpobject->setFloatValue(propname, value, create); +} + +//-------------------------------------------------------------- +// +// Name: setStringValue +// Class: GameplayDatabase +// +// Description: Sets float value of a property. +// +// Parameters: const str& objname -- Object name +// const str& propname -- Property name +// const str& value -- string value +// bool create -- whether or not to create properties that are not found +// +// Returns: bool -- true if the value was changed or created. +// +//-------------------------------------------------------------- +bool GameplayDatabase::setStringValue(const str& objname, const str& propname, const str& valuestr, bool create) +{ + GameplayObject *gpobject = getObject(objname); + if ( !gpobject ) + { + if ( !create ) return false; + + gpobject = _createFromScope(objname); + gpobject->setCategory("Misc"); // Category doesn't really matter. + return true ; + } + + return gpobject->setStringValue(propname, valuestr, create); +} + +//-------------------------------------------------------------- +// +// Name: setVectorValue +// Class: GameplayDatabase +// +// Description: Sets vector value of a property. +// +// Parameters: const str& objname -- Object name +// const str& propname -- Property name +// Vector value -- value +// bool create -- whether or not to create properties that are not found +// +// Returns: bool -- true if the value changed or created. +// +//-------------------------------------------------------------- +bool GameplayDatabase::setVectorValue(const str& objname, const str& propname, const Vector& value, bool create) +{ + GameplayObject *gpobject = getObject(objname); + if ( !gpobject ) + { + if ( !create ) return false; + + gpobject = _createFromScope(objname); + gpobject->setCategory("Misc"); // Category doesn't really matter. + return true ; + } + + return gpobject->setVectorValue(propname, value, create); +} + + +//-------------------------------------------------------------- +// +// Name: _linkSubObjectsToBase +// Class: GameplayDatabase +// +// Description: Iterates through all the objects in the database +// and calls _linkToBase to link each one's subobjects +// up. +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +void GameplayDatabase::_linkSubObjectsToBase() +{ + int i; + for ( i=1; i<=_objectList.NumObjects(); i++ ) + { + GameplayObject *go = _objectList.ObjectAt(i); + _linkToBase(go); + } +} + +//-------------------------------------------------------------- +// +// Name: _linkToBase +// Class: GameplayDatabase +// +// Description: Links the subojects in each object to the parent +// (inherited) object. +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +void GameplayDatabase::_linkToBase(GameplayObject *object) +{ + int j; + + // Go through all the sub objects + const Container& subObjectList = object->getSubObjectList(); + for ( j=1; j<=subObjectList.NumObjects(); j++ ) + { + GameplayObject *subobj = subObjectList.ObjectAt(j); + GameplayObject *baseObj = getObject(subobj->getName()); + if ( !baseObj ) + assert(0); // Suboject found with no base + else + subobj->setBaseObject(baseObj); + + _linkToBase(subobj); + } +} + +//-------------------------------------------------------------- +// +// Name: hasObject +// Class: GameplayDatabase +// +// Description: Checks to see if the object exists. Includes +// scope support. +// +// Parameters: const str& objname -- Object to look for +// +// Returns: bool +// +//-------------------------------------------------------------- +bool GameplayDatabase::hasObject(const str& objname) +{ + if ( getObject(objname) ) + return true; + + return false; +} + + +//-------------------------------------------------------------- +// +// Name: _createFromScope +// Class: GameplayDatabase +// +// Description: Creates an object given the scope... it will return +// the created object, even if it had to create +// other objects to get to the scope level required on the +// way. +// +// Parameters: const str& scope -- Scope of the object to create +// +// Returns: GameplayObject * -- The new object +// +//-------------------------------------------------------------- +GameplayObject* GameplayDatabase::_createFromScope(const str& scope) +{ + str scopename; + char tmpstr[255], *sptr; + //GameplayObject *newObj = 0; + strcpy(tmpstr, scope.c_str()); + sptr = strtok(tmpstr,".\n"); + scopename += sptr; + GameplayObject *gpobject = 0; + gpobject = getRootObject(sptr); + if ( !gpobject ) + { + gpobject = new GameplayObject; + gpobject->setName(sptr); + gpobject->setFullScopeName(scopename); + _addToHashTable(gpobject); + _objectList.AddObject(gpobject); + } + + sptr = strtok(NULL,".\n"); + while ( sptr ) + { + scopename += "."; + scopename += sptr; + gpobject = gpobject->getSubObject(sptr); + if ( !gpobject ) + { + gpobject = new GameplayObject; + gpobject->setName(sptr); + gpobject->setFullScopeName(scopename); + _addToHashTable(gpobject); + _objectList.AddObject(gpobject); + } + + sptr = strtok(NULL,".\n"); + } + + //if ( newObj ) + // return newObj; + //else + return gpobject; +} + + +//-------------------------------------------------------------- +// +// Name: _addToHashTable +// Class: GameplayDatabase +// +// Description: Adds the object to the hash table +// +// Parameters: GameplayObject *object -- Object to add +// +// Returns: bool -- Collision flag. The object or one if it's +// children collided while being put in the hash table +// +//-------------------------------------------------------------- +bool GameplayDatabase::_addToHashTable(GameplayObject *object) +{ + if ( !object ) + false; + + bool collisionFlag = false; + + // Go through and hash all the object's children (since order doesn't matter + // to the hash table). + const Container& subObjectList = object->getSubObjectList(); + for ( int j=1; j<=subObjectList.NumObjects(); j++ ) + { + GameplayObject *subobj = subObjectList.ObjectAt(j); + if ( _addToHashTable(subobj) ) + collisionFlag = true; + } + + unsigned int loc = GenerateHashForName(object->getFullScopeName().c_str(), false, HASH_SIZE); + GameplayObject *obj = 0; + obj = _hashTable[loc]; + if ( !obj ) + { + _hashTable[loc] = object; + return collisionFlag; + } + + // There's a collision because there's already + // an object in the slot. Get to the last object + // in the list. + GameplayObject *prev = 0; + collisionFlag = true; + while ( obj ) + { + prev = obj; + obj = obj->getNextObject(); + } + + // Attach the object onto the end of the list + prev->setNextObject(object); + + return collisionFlag; +} + + +//-------------------------------------------------------------- +// +// Name: _buildHashTable +// Class: GameplayDatabase +// +// Description: Builds the hash table. +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +void GameplayDatabase::_buildHashTable() +{ + int i; + for ( i=1; i<=_objectList.NumObjects(); i++ ) + { + GameplayObject *go = _objectList.ObjectAt(i); + _addToHashTable(go); + } +} + + +//-------------------------------------------------------------- +// +// Name: setBase +// Class: GameplayDatabase +// +// Description: Sets the base object of objname to be baseobj. +// +// Parameters: const str& objname -- Object on which to set the base +// const str& baseobj -- Base object to reference +// +// Returns: bool +// +//-------------------------------------------------------------- +bool GameplayDatabase::setBase(const str& objname, const str& baseobj) +{ + GameplayObject *obj = 0; + obj = getObject(objname); + if ( !obj ) + return false; + + GameplayObject *base = 0; + base = getRootObject(baseobj); + obj->setBaseObject(base); + + return true; +} + + +//-------------------------------------------------------------- +// +// Name: clearPropertyOverrides +// Class: GameplayDatabase +// +// Description: Clears all properties in this object that override +// properties in the base object. +// +// Parameters: const str& objname -- Object to clear the properties for +// +// Returns: bool - successful or not +// +//-------------------------------------------------------------- +bool GameplayDatabase::clearPropertyOverrides(const str& objname) +{ + int i = 0; + + // Get the object + GameplayObject *obj = 0; + obj = getObject(objname); + if ( !obj ) + return false; + + // Get the base object + GameplayObject *baseobj = 0; + baseobj = obj->getBaseObject(); + if ( !baseobj ) + return true; // No base, return successful + + const Container& proplist = baseobj->getPropertyList(); + for ( i=1; i<=proplist.NumObjects(); i++ ) + { + GameplayProperty *gp = proplist.ObjectAt(i); + + // Check for local properties only in the object. + if ( obj->hasProperty(gp->getName(), true) ) + obj->removeProperty(gp->getName()); + } + + return true; +} + + +//=============================================================== +// Name: getNumberOfModifiedObjects +// Class: GameplayDatabase +// +// Description: Retrieves the number of objects in the database +// that have their modified flag set. This is used +// to determine how many objects in the database +// we're going to have to save out. +// +// Parameters: None +// +// Returns: int -- the number of modified objects. +// +//=============================================================== +int GameplayDatabase::getNumberOfModifiedObjects( void ) +{ + int numberOfModifiedObjects = 0 ; + int numObjects = _objectList.NumObjects(); + + for ( int objectIdx = 1; objectIdx <= numObjects; ++objectIdx ) + { + GameplayObject *object = _objectList.ObjectAt( objectIdx ); + if ( object->isModified() ) + { + numberOfModifiedObjects++ ; + } + } + + return numberOfModifiedObjects ; +} + + +#ifdef GAME_DLL +//-------------------------------------------------------------- +// +// Name: Archive +// Class: GameplayDatabase +// +// Description: Archive function. +// +// Parameters: Archiver &arc -- Archive reference +// +// Returns: None +// +//-------------------------------------------------------------- +void GameplayDatabase::Archive(Archiver &arc) +{ + GameplayObject *object = 0 ; + int numModifiedObjects = getNumberOfModifiedObjects(); + int numObjects = _objectList.NumObjects(); + + arc.ArchiveInteger( &numModifiedObjects ); + + if ( arc.Saving() ) + { + for ( int objectIdx = 1; objectIdx <= numObjects; ++objectIdx ) + { + object = _objectList.ObjectAt( objectIdx ); + object->Archive( arc ); + } + } + else + { + for ( int objectIdx = 1; objectIdx <= numModifiedObjects; ++objectIdx ) + { + str scopedObjectName ; + int numProperties ; + + arc.ArchiveString( &scopedObjectName ); + arc.ArchiveInteger( &numProperties ); + for ( int propertyIdx = 1; propertyIdx <= numProperties; ++propertyIdx ) + { + GameplayValueType propertyType = VALUE_FLOAT ; + Vector vectorValue ; + float floatValue = 0.0f ; + str propertyName ; + str stringValue ; + + ArchiveEnum( propertyType, GameplayValueType ); + arc.ArchiveString( &propertyName ); + switch( propertyType ) + { + case VALUE_FLOAT: + arc.ArchiveFloat( &floatValue ); + setFloatValue( scopedObjectName, propertyName, floatValue, true ); + _pendingDeltaList.AddObject( PendingDelta( scopedObjectName, propertyName, floatValue ) ); + break ; + case VALUE_STRING: + arc.ArchiveString( &stringValue ); + setStringValue( scopedObjectName, propertyName, stringValue, true ); + _pendingDeltaList.AddObject( PendingDelta( scopedObjectName, propertyName, stringValue ) ); + break ; + case VALUE_VECTOR: + arc.ArchiveVector( &vectorValue ); +// setVectorValue( scopedObjectName, propertyName, vectorValue, true ); + _pendingDeltaList.AddObject( PendingDelta( scopedObjectName, propertyName, vectorValue ) ); + break ; + } + } + } + } +} + +#endif // GAME_DLL diff --git a/Shared/qcommon/gameplaydatabase.h b/Shared/qcommon/gameplaydatabase.h new file mode 100644 index 0000000..1ae1665 --- /dev/null +++ b/Shared/qcommon/gameplaydatabase.h @@ -0,0 +1,296 @@ +// GameplayDatabase.h: interface for the GameplayDatabase class. +// +////////////////////////////////////////////////////////////////////// + +class GameplayDatabase; +class GameplayObject; +class GameplayProperty; + +#ifndef __GAMEPLAYDATABASE_H__ +#define __GAMEPLAYDATABASE_H__ + +#ifdef GAME_DLL + +#include +#include + +#endif // GAME_DLL + + +// Select a parser to use. We wouldn't have to do this part +// if the system didn't have 10000 parsers. +#ifdef GAME_DLL +#define Parser Script +#else +#define Parser TikiScript +#endif // GAME_DLL + +#define HASH_SIZE 256 + +#include +#include +#include +#include +#include + +typedef enum GameplayValueType +{ + VALUE_FLOAT, + VALUE_STRING, + VALUE_VECTOR, + GAMEPLAY_VALUE_UNSPECIFIED +}; + +//------------------------ CLASS ------------------------------- +// +// Name: PendingDelta +// Base Class: None +// +// Description: This class stores a database delta. When +// a game is saved, all the changes to the database +// are stored in the save game file. When this +// file is loaded, these changes are made back to +// the gameplay database. +// +// However, the changes are made before the client +// has had a chance to initialize. We store off +// all these changes until then. When the server +// learns the player is fully initialized, the +// GameplayManager will ask for this list of +// pending deltas. It will then send them all +// to the client. +//------------------------------------------------------------- +class PendingDelta +{ +private: + str _objName ; + str _propName ; + str _stringValue ; + float _floatValue ; + Vector _vectorValue ; + GameplayValueType _type ; + +public: + PendingDelta() : _type( GAMEPLAY_VALUE_UNSPECIFIED ) { } + PendingDelta( const str &objName, const str &propName, float floatValue ) + : _objName( objName ), _propName( propName ), _floatValue( floatValue ), _type( VALUE_FLOAT ) { } + PendingDelta( const str &objName, const str &propName, const str &stringValue ) + : _objName( objName ), _propName( propName ), _stringValue( stringValue ), _type( VALUE_STRING ) { } + PendingDelta( const str &objName, const str &propName, const Vector &vectorValue ) + : _objName( objName ), _propName( propName ), _vectorValue( vectorValue ), _type( VALUE_VECTOR ) { } + + float getFloatValue() { return _floatValue ; } + const str& getStringValue() { return _stringValue ; } + const Vector& getVectorValue() { return _vectorValue ; } + const str& getObjectName() { return _objName ; } + const str& getPropertyName() { return _propName ; } + GameplayValueType getGameplayValueType() { return _type ; } +}; + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayProperty +// Base Class: Class +// +// Description: Object that has a key and a value which can be +// a string or a float +// +// Method of Use: Used in GameplayObject's +// +//-------------------------------------------------------------- +class GameplayProperty : public Class +{ +private: + str _name; + str _valuestr; + float _valuefloat; + Vector _valuevector; + bool _modified; + GameplayValueType _type; + +public: + + CLASS_PROTOTYPE( GameplayProperty ); + + GameplayProperty() + : _name(""), + _valuestr(""), + _valuefloat(1.0f), + _modified(false), + _type(VALUE_FLOAT), + _valuevector(vec_zero) + {} + + virtual ~GameplayProperty() {} + + // Parsing + bool parseProperty(Parser &gameplayFile, const str& type); + bool isModified( void ) { return _modified ; } + + // Accessors -- Gets + const str& getName() { return _name; } + const str& getStringValue() { return _valuestr; } + float getFloatValue() { return _valuefloat; } + const Vector& getVectorValue() { return _valuevector; } + bool getModified() { return _modified; } + GameplayValueType getType() { return _type; } + const str getFloatValueStr(); + + // Accessors -- Sets + void setName(const str& name); + void setModified(bool modified) { _modified = modified; } + void setType(GameplayValueType type) { _type = type; } + bool setStringValue(const str& valuestr); + bool setFloatValue(float valuefloat); + bool setVectorValue(const Vector& vector); + +#ifdef GAME_DLL +public: + void Archive(Archiver &arc); +#endif // GAME_DLL +}; + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayObject +// Base Class: Class +// +// Description: Object that has a name, and a container of +// GameplayProperty's. +// +// Method of Use: Used in GameplayDatabase +// +//-------------------------------------------------------------- +class GameplayObject : public Class +{ +private: + str _name; + str _category; + str _fullScopeName; + int _depth; + bool _modified ; + GameplayObject* _baseObject; + GameplayObject* _nextHashObject; + + Container _propertyList; + Container _subObjectList; + +public: + + CLASS_PROTOTYPE( GameplayObject ); + + GameplayObject(); + GameplayObject(int depth); + virtual ~GameplayObject(); + + // Parsing + bool parseObject(Parser &gameplayFile, const str& name); + + //Queries + bool hasProperty(const str& propname, bool localonly = false); + bool isModified( void ) { return _modified ; } + + // Accessors -- Gets + const str& getName() { return _name; } + const str& getCategory() { return _category; } + const str& getFullScopeName() { return _fullScopeName; } + const Container& getSubObjectList() { return _subObjectList; }; + const Container& getPropertyList() { return _propertyList; }; + GameplayObject* getBaseObject() { return _baseObject; } + GameplayObject* getNextObject() { return _nextHashObject; } + GameplayObject* getSubObject(const str& subobjname); + GameplayProperty* getProperty(const str& propname); + float getPropertyFloatValue(const str& propname); + const str getPropertyStringValue(const str& propname); + bool getModified(const str& propname); + int getNumberOfModifiedProperties( void ); + + // Accessors -- Sets + void setModified( bool modified ) { _modified = modified ; } + void setName(const str& name) { _name = name; } + void setCategory(const str& category) { _category = category; } + void setFullScopeName(const str& fullscope) { _fullScopeName = fullscope; } + void setBaseObject(GameplayObject* baseObject) { _baseObject = baseObject; } + void setNextObject(GameplayObject *nextObject) { _nextHashObject = nextObject; } + bool setFloatValue(const str& propname, float value, bool create = false); + bool setStringValue(const str& propname, const str& valuestr, bool create = false); + bool setVectorValue( const str& propname, const Vector& valuevector, bool create = false ); + + // Deletion + bool removeProperty(const str& propname); + +#ifdef GAME_DLL +public: + void Archive(Archiver &arc); +#endif // GAME_DLL +}; + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayDatabase +// Base Class: Listener +// +// Description: Database of GameplayObjects. Queries are made +// to this database to retrieve the data +// +// Method of Use: Used by the GameplayManager +// +//-------------------------------------------------------------- +class GameplayDatabase : public Listener +{ +private: + Container _pendingDeltaList ; + Container _objectList; + GameplayObject* _hashTable[HASH_SIZE]; + + // Saved off last object reference + GameplayObject *_lastObj; + str _lastObjName; + + // Private Functions + void _linkSubObjectsToBase(); + void _linkToBase(GameplayObject *object); + GameplayObject* _createFromScope(const str& scope); + bool _addToHashTable(GameplayObject *object); + void _buildHashTable(); + +public: + + CLASS_PROTOTYPE( GameplayDatabase ); + + GameplayDatabase(); + virtual ~GameplayDatabase(); + + // Parsing + bool parseFile(const str& filename); + + // Queries + bool hasObject(const str& objname); + + // Accessors -- Gets + GameplayObject* getObject(const str& objname); + GameplayObject* getRootObject(const str& objname); + GameplayObject* getSubObject(const str& objname, const str& subobjname); + int getNumberOfModifiedObjects( void ); + float getFloatValue(const str& objname, const str& propname); + const str getStringValue(const str& objname, const str& propname); + Container& getPendingDeltaList() { return _pendingDeltaList ; } + + // Accessors -- Sets + bool setFloatValue(const str& objname, const str& propname, float value, bool create = false); + bool setStringValue(const str& objname, const str& propname, const str& valuestr, bool create = false); + bool setVectorValue( const str& objname, const str& propname, const Vector& valuevalue, bool create = false ); + bool setBase(const str& objname, const str& baseobj); + bool clearPropertyOverrides(const str& objname); + void clearPendingDeltaList() { _pendingDeltaList.FreeObjectList(); } + +#ifdef GAME_DLL +public: + void Archive(Archiver &arc); +#endif // GAME_DLL +}; + +#endif diff --git a/Shared/qcommon/gameplayformulamanager.cpp b/Shared/qcommon/gameplayformulamanager.cpp new file mode 100644 index 0000000..c276bc2 --- /dev/null +++ b/Shared/qcommon/gameplayformulamanager.cpp @@ -0,0 +1,499 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/Shared/qcommon/gameplayformulamanager.cpp $ +// $Revision:: 4 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// GameplayFormulaManager.cpp: implementation of the GameplayFormulaManager class. +// +////////////////////////////////////////////////////////////////////// + +#ifdef GAME_DLL + +#include "gameplayformulamanager.h" +#include "gameplaymanager.h" + + +//////////////////////////////////////////////////////////////// +// +// GameplayFormulaData CLASS +// +//////////////////////////////////////////////////////////////// + +//-------------------------------------------------------------- +// +// Name: GameplayFormulaData +// Class: GameplayFormulaData +// +// Description: Constructor +// +// Parameters: Entity *primary -- Default 0 +// Entity *secondary -- Default 0 +// Entity *weapon -- Default 0 +// const str& attackType -- Default "" +// +// Returns: +// +//-------------------------------------------------------------- +GameplayFormulaData::GameplayFormulaData( Entity *pPrimary, Entity *pSecondary, Entity *pWeapon, const str& pAttackType ) +{ + primary = pPrimary; + secondary = pSecondary; + weapon = pWeapon; + attackType = pAttackType; +} + + + + + + +//////////////////////////////////////////////////////////////// +// +// GameplayFormulaVariable CLASS +// +//////////////////////////////////////////////////////////////// + +//-------------------------------------------------------------- +// +// Name: GameplayFormulaVariable +// Class: GameplayFormulaVariable +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayFormulaVariable::GameplayFormulaVariable() +{ + _categoryList.ClearObjectList(); +} + +//-------------------------------------------------------------- +// +// Name: ~GameplayFormulaVariable +// Class: GameplayFormulaVariable +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayFormulaVariable::~GameplayFormulaVariable() +{ + +} + + + + + +//////////////////////////////////////////////////////////////// +// +// GameplayFormulaOperand CLASS +// +//////////////////////////////////////////////////////////////// + +//-------------------------------------------------------------- +// +// Name: getResult +// Class: GameplayFormulaOperand +// +// Description: Gets the result of the operand given the data +// +// Parameters: const GameplayFormulaData& formulaData -- Data to use +// +// Returns: float -- The result, or 1.0 if there's a problem +// +//-------------------------------------------------------------- +float GameplayFormulaOperand::getResult(const GameplayFormulaData& formulaData) +{ + float finalResult = _constant; // Constant is factored in here. + str name; + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + + char firstobj[255], *sptr; + strcpy(firstobj, _object.c_str()); + sptr = strchr(firstobj, '.'); + sptr = 0; + + // Defines are a special case + if ( !strcmp(firstobj,"Defines") ) + { + finalResult *= gpm->getFloatValue(_property, "value"); + return finalResult; + } + + if ( !strcmp(firstobj, "Primary") && formulaData.primary ) + name = formulaData.primary->getArchetype(); + if ( !strcmp(firstobj, "Secondary") && formulaData.secondary ) + name = formulaData.secondary->getArchetype(); + if ( !strcmp(firstobj, "Weapon") && formulaData.weapon ) + name = formulaData.weapon->getArchetype(); + if ( !strcmp(firstobj, "AttackType") ) + name = formulaData.attackType; + + // The object was not one of the code-keywords, check the database + // itself for the object and property. + if ( !name.length() ) + { + finalResult *= gpm->getFloatValue(_object, _property); + return finalResult; + } + + if ( _inverse ) + finalResult *= (1.0f / gpm->getFloatValue(name, _property)); + else + finalResult *= gpm->getFloatValue(name, _property); + + return finalResult; +} + +//-------------------------------------------------------------- +// +// Name: paraseOperand +// Class: GameplayFormulaOperand +// +// Description: Parses the operand +// +// Parameters: Script &formulaFile -- The file to parse +// const str& token -- The first token +// +// Returns: bool +// +//-------------------------------------------------------------- +bool GameplayFormulaOperand::parseOperand(Script &formulaFile, const str& constant) +{ + const char *token; + _constant = (float)atof(constant); + + if ( !formulaFile.TokenAvailable(false) ) + return false; + token = formulaFile.GetToken(false); + if ( token[0] == '/' ) + _inverse = true; + + if ( !formulaFile.TokenAvailable(false) ) + return false; + token = formulaFile.GetToken(false); + + char *sptr, tmpstr[255]; + strcpy(tmpstr, token); + sptr = strrchr(tmpstr,'.'); + if ( sptr ) + { + *sptr = 0; + sptr++; + } + + _object = tmpstr; + _property = sptr; + + return true; +} + +//////////////////////////////////////////////////////////////// +// +// GameplayFormula CLASS +// +//////////////////////////////////////////////////////////////// + +//-------------------------------------------------------------- +// +// Name: GameplayFormula +// Class: GameplayFormula +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayFormula::GameplayFormula() +{ + _operandList.ClearObjectList(); +} + +//-------------------------------------------------------------- +// +// Name: ~GameplayFormula +// Class: GameplayFormula +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayFormula::~GameplayFormula() +{ + int i; + for ( i=1; i<=_operandList.NumObjects(); i++ ) + { + GameplayFormulaOperand *gpfo = _operandList.ObjectAt(i); + delete gpfo; + } + + _operandList.ClearObjectList(); +} + + +//-------------------------------------------------------------- +// +// Name: getResult +// Class: GameplayFormula +// +// Description: Gets the result of a formula given it's the data +// +// Parameters: const GameplayFormulaData& formulaData -- Data to use +// +// Returns: float -- The result, or 1.0 if there's a problem +// +//-------------------------------------------------------------- +float GameplayFormula::getResult(const GameplayFormulaData& formulaData) +{ + float finalResult = 1.0f; + + int i; + for ( i=1; i<=_operandList.NumObjects(); i++ ) + { + GameplayFormulaOperand *gpfo = _operandList.ObjectAt(i); + finalResult *= gpfo->getResult(formulaData); + } + + return finalResult; +} + +//-------------------------------------------------------------- +// +// Name: paraseFormula +// Class: GameplayFormula +// +// Description: Parses the formula +// +// Parameters: Script &formulaFile -- The file to parse +// const str& name -- The token +// +// Returns: +// +//-------------------------------------------------------------- +bool GameplayFormula::parseFormula(Script &formulaFile, const str& name) +{ + const char *token; + GameplayFormulaOperand *gpfo; + + if ( name != "FORMULA" ) + return false; + + if ( !formulaFile.TokenAvailable(false) ) + return false; + + token = formulaFile.GetToken(false); + setName(token); + + // Get the open brace + token = formulaFile.GetToken(true); + if ( token[0] != '{' ) + assert(0); + + while (formulaFile.TokenAvailable(true)) + { + token = formulaFile.GetToken(true); + + // If we have a close brace, we're done. + if ( token[0] == '}' ) + return true; + + gpfo = new GameplayFormulaOperand(); + if ( gpfo->parseOperand(formulaFile, token) ) + _operandList.AddObject(gpfo); + else + { + delete gpfo; + return false; + } + } + + // Premature end of file, missing close brace. + return false; +} + + +//////////////////////////////////////////////////////////////// +// +// GameplayFormulaManager CLASS +// +//////////////////////////////////////////////////////////////// + +//-------------------------------------------------------------- +// +// Name: GameplayFormulaManager +// Class: GameplayFormulaManager +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayFormulaManager::GameplayFormulaManager() +{ + _variableList.ClearObjectList(); + _formulaList.ClearObjectList(); +} + + +//-------------------------------------------------------------- +// +// Name: ~GameplayFormulaManager +// Class: GameplayFormulaManager +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayFormulaManager::~GameplayFormulaManager() +{ + int i; + for ( i=1; i<=_variableList.NumObjects(); i++ ) + { + GameplayFormulaVariable *gpfv = _variableList.ObjectAt(i); + delete gpfv; + } + + for ( i=1; i<=_formulaList.NumObjects(); i++ ) + { + GameplayFormula *gpf = _formulaList.ObjectAt(i); + delete gpf; + } + + _variableList.FreeObjectList(); + _formulaList.FreeObjectList(); +} + + +//-------------------------------------------------------------- +// +// Name: getFormulaResult +// Class: GameplayFormulaManager +// +// Description: Gets the result of a formula given it's name and the data +// +// Parameters: const str& formulaName -- Name of the formula to use +// const GameplayFormulaData& formulaData -- Data to use +// +// Returns: float -- The result, or 1.0 if there's a problem +// +//-------------------------------------------------------------- +float GameplayFormulaManager::getFormulaResult(const str& formulaName, const GameplayFormulaData& formulaData) +{ + float finalResult = 1.0f; + + int i; + for ( i=1; i<=_formulaList.NumObjects(); i++ ) + { + GameplayFormula *gpf = _formulaList.ObjectAt(i); + if ( gpf->getName() == formulaName ) + { + // Found the matching formula, get the result. + finalResult = gpf->getResult(formulaData); + return finalResult; + } + } + + return finalResult; +} + + +//-------------------------------------------------------------- +// +// Name: parseFile +// Class: GameplayFormulaManager +// +// Description: Reads and parses the formula file +// +// Parameters: const str& filename -- Name of the file +// +// Returns: bool -- sucessful parse or not +// +//-------------------------------------------------------------- +bool GameplayFormulaManager::parseFile(const str& filename) +{ + Script formulaFile; + const char *token; + GameplayFormula *gpformula; + + if ( !gi.FS_Exists(filename.c_str()) ) + return false; + + formulaFile.LoadFile(filename.c_str()); + + while (formulaFile.TokenAvailable(true)) + { + token = formulaFile.GetToken(false); + + // If the first token isn't a formula, there's a problem + if ( strcmp(token, "FORMULA") ) + return false; + + gpformula = new GameplayFormula(); + if ( gpformula->parseFormula(formulaFile, token) ) + _formulaList.AddObject(gpformula); + else + { + delete gpformula; + return false; + } + } + + return true; +} + + +//-------------------------------------------------------------- +// +// Name: hasFormula +// Class: GameplayFormulaManager +// +// Description: Checks to see if the formula exists +// +// Parameters: const str& formulaName +// +// Returns: bool +// +//-------------------------------------------------------------- +bool GameplayFormulaManager::hasFormula(const str& formulaName) +{ + int i; + for ( i=1; i<=_formulaList.NumObjects(); i++ ) + { + GameplayFormula *gpf = _formulaList.ObjectAt(i); + if ( gpf->getName() == formulaName ) + return true; + } + + return false; +} + +#endif // GAME_DLL + + diff --git a/Shared/qcommon/gameplayformulamanager.h b/Shared/qcommon/gameplayformulamanager.h new file mode 100644 index 0000000..c46d8cb --- /dev/null +++ b/Shared/qcommon/gameplayformulamanager.h @@ -0,0 +1,196 @@ +// GameplayFormulaManager.h: interface for the GameplayFormulaManager class. +// +////////////////////////////////////////////////////////////////////// + +#ifdef GAME_DLL + +class GameplayFormulaManager; +class GameplayFormula; +class GameplayFormulaVariable; +class GameplayFormulaData; +class GameplayFormulaOperand; + +#ifndef __GAMEPLAY_FORMULA_MANAGER_H__ +#define __GAMEPLAY_FORMULA_MANAGER_H__ + +#include +#include +#include +#include +#include + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayFormulaData +// Base Class: Class +// +// Description: Utility class to the GameplayFormulaManager +// +// Method of Use: The user will build on of these objects to pass +// into the GameplayManager query functions. +// +//-------------------------------------------------------------- +class GameplayFormulaData : public Class +{ +public: + GameplayFormulaData( Entity *pPrimary = 0, + Entity *pSecondary = 0, + Entity *pWeapon = 0, + const str& pAttackType = ""); + + Entity *primary; + Entity *secondary; + Entity *weapon; + str attackType;; +}; + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayFormulaVariable +// Base Class: Class +// +// Description: Variable that contains the list of categories that +// are specific to a variable type. +// +// Method of Use: GameplayFormula uses this class +// +//-------------------------------------------------------------- +class GameplayFormulaVariable : public Class +{ +private: + str _name; + Container _categoryList; + +public: + GameplayFormulaVariable(); + virtual ~GameplayFormulaVariable(); + + // Accessors + const str& getName() { return _name; } + void setName(const str& name) { _name = name; } +}; + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayFormulaOperand +// Base Class: Class +// +// Description: A operand in a formula +// +// Method of Use: GameplayFormula has a list of these to multiply together +// +//-------------------------------------------------------------- +class GameplayFormulaOperand : public Class +{ +private: + float _constant; + str _object; + str _property; + bool _inverse; + +public: + GameplayFormulaOperand() + : _constant(1.0f), + _inverse(false) + {} + virtual ~GameplayFormulaOperand() {} + + // Accessors + const str& getObjectName() { return _object; } + void setObjectName(const str& object) { _object = object; } + + const str& getPropertyName() { return _property; } + void setPropertyName(const str& property) { _property = property; } + + float getConstant() { return _constant; } + void setConstant(float constant) { _constant = constant; } + + bool getInverseFlag() { return _inverse; } + void setInverseFlag(bool inverse) { _inverse = inverse; } + + // Queries + float getResult(const GameplayFormulaData& formulaData); + + // Parsing + bool parseOperand(Script &formulaFile, const str& constant); +}; + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayFormula +// Base Class: Class +// +// Description: A formula in the for the GameplayManager +// +// Method of Use: GameplayFormulaManager requests data from this class +// +//-------------------------------------------------------------- +class GameplayFormula : public Class +{ +private: + str _name; + Container _operandList; + +public: + GameplayFormula(); + virtual ~GameplayFormula(); + + // Accessors + const str& getName() { return _name; } + void setName(const str& name) { _name = name; } + + // Queries + float getResult(const GameplayFormulaData& formulaData ); + + // Parsing + bool parseFormula(Script &formulaFile, const str& name); +}; + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayFormulaManager +// Base Class: Class +// +// Description: The manager for all the formulas. Accessed +// by the GameplayManager +// +// Method of Use: GameplayManager uses this class to access formulas +// +//-------------------------------------------------------------- +class GameplayFormulaManager : public Class +{ +private: + Container _variableList; + Container _formulaList; +public: + GameplayFormulaManager(); + virtual ~GameplayFormulaManager(); + + // Queries + float getFormulaResult(const str& formulaName, const GameplayFormulaData& formulaData); + bool hasFormula(const str& formulaName); + + // Parsing + bool parseFile(const str& filename); +}; + +#endif // __GAMEPLAY_FORMULA_MANAGER_H__ + +#endif // GAME_DLL diff --git a/Shared/qcommon/gameplaymanager.cpp b/Shared/qcommon/gameplaymanager.cpp new file mode 100644 index 0000000..77298e2 --- /dev/null +++ b/Shared/qcommon/gameplaymanager.cpp @@ -0,0 +1,850 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/Shared/qcommon/gameplaymanager.cpp $ +// $Revision:: 14 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// GameplayManager.cpp: implementation of the GameplayManager class. +// +////////////////////////////////////////////////////////////////////// + +#include "gameplaymanager.h" +#include +//#include + +// The Singleton +GameplayManager *GameplayManager::_theGameplayManager = 0; + +//-------------------------------------------------------------- +// +// Name: GameplayManager +// Class: GameplayManager +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayManager::GameplayManager() +{ + +} + +//-------------------------------------------------------------- +// +// Name: ~GameplayManager +// Class: GameplayManager +// +// Description: Destructor. +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +GameplayManager::~GameplayManager() +{ + +} + +//-------------------------------------------------------------- +// Name: Shutdown (static) +// Class: GameplayManager +// +// Description: Destroys the GPM variable +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +void GameplayManager::shutdown() +{ + if ( _theGameplayManager ) + { + delete _theGameplayManager; + _theGameplayManager = 0; + } +} + +//-------------------------------------------------------------- +// Name: IsReady (static) +// Class: GameplayManager +// +// Description: Determines if the GPM variable has been created yet +// +// Parameters: None +// +// Returns: bool -- Is ready or not +// +//-------------------------------------------------------------- +bool GameplayManager::isReady() +{ + if ( _theGameplayManager ) + return true; + + return false; +} + +//-------------------------------------------------------------- +// Name: getTheGameplayManager (static) +// Class: GameplayManager +// +// Description: Interface function to the GameplayManager singleton +// +// Parameters: None +// +// Returns: Pointer to the GameplayManager singleton +// +//-------------------------------------------------------------- +GameplayManager* GameplayManager::getTheGameplayManager() +{ + if ( _theGameplayManager ) + return _theGameplayManager; + + assert(0); // Something called this function before create(); + + return 0; +} + +//-------------------------------------------------------------- +// Name: Create (static) +// Class: GameplayManager +// +// Description: Creates the GPM singleton. +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +void GameplayManager::create() +{ + if ( _theGameplayManager ) + shutdown(); + + _theGameplayManager = new GameplayManager; + if (!_theGameplayManager) + { + // This probably won't happen unless we're out of memory + assert(0); + } + + if ( !_theGameplayManager->_gameplayDatabase.parseFile("global/gameplay.gdb") ) + { + // Parsing the database file failed! + assert(0); + Com_Error (ERR_DROP, "GameplayDatabase: Parsing failed. Contact programmer regarding this error."); + } +#ifdef GAME_DLL + if ( !_theGameplayManager->_gameplayFormulaManager.parseFile("global/gameplay.gpf") ) + { + // Parsing the formula file failed! + assert(0); + gi.Error(ERR_DROP, "GameplayFormulas: Parsing failed. Contact programmer regarding this error."); + } +#endif // GAME_DLL +} + + +//-------------------------------------------------------------- +// +// Name: hasObject +// Class: GameplayManager +// +// Description: Checks to see if an object is in the database +// +// Parameters: const str& objname -- Name of the object +// +// Returns: bool +// +//-------------------------------------------------------------- +bool GameplayManager::hasObject(const str& objname) +{ + if ( !objname.length() ) + return false; + return _gameplayDatabase.hasObject(objname); +} + + +//-------------------------------------------------------------- +// +// Name: isDefined +// Class: GameplayManager +// +// Description: Checks to see if the specified property +// exists as a define in the database. +// +// Parameters: const str& propname -- Property to find +// +// Returns: bool -- True if exists +// +//-------------------------------------------------------------- +bool GameplayManager::isDefined(const str& propname) +{ + GameplayObject *gpobject = _gameplayDatabase.getObject(propname); + if ( !gpobject ) + return false; + + if ( gpobject->hasProperty("value") ) + return true; + + return false; +} + +//-------------------------------------------------------------- +// +// Name: getDefine +// Class: GameplayManager +// +// Description: Returns the define as a string +// +// Parameters: const str& propname -- Property to find +// +// Returns: const str& -- the define as a string +// +//-------------------------------------------------------------- +const str GameplayManager::getDefine(const str& propname) +{ + GameplayObject *gpobject = _gameplayDatabase.getObject(propname); + if ( !gpobject ) + return ""; // Should never happen since we go through isDefined first + + str value = gpobject->getPropertyStringValue("value"); + if ( value == "" ) + { + float floatval; + char tmpstr[16]; + floatval = gpobject->getPropertyFloatValue("value"); + sprintf(tmpstr, "%g", floatval); + value = tmpstr; + } + + return value; +} + + + + +//-------------------------------------------------------------- +// +// Name: setFloatValue +// Class: GameplayManager +// +// Description: Sets float value of a property. +// +// Parameters: const str& objname -- Object name +// const str& propname -- Property name +// float value -- value +// bool create -- whether or not to create properties that are not found +// +// Returns: None +// +//-------------------------------------------------------------- +void GameplayManager::setFloatValue(const str& objname, const str& propname, float value, bool create) +{ +#ifdef GAME_DLL + bool valueChanged = _gameplayDatabase.setFloatValue(objname, propname, value, create); + if ( valueChanged ) + { + char message[256] ; + sprintf( message, "gdb_setfloatproperty %s %s %g\n", objname.c_str(), propname.c_str(), value ); + G_SendCommandToAllPlayers( message ); + } +#else + _gameplayDatabase.setFloatValue(objname, propname, value, create); +#endif // GAME_DLL +} + + +//-------------------------------------------------------------- +// +// Name: setStringValue +// Class: GameplayManager +// +// Description: Sets float value of a property. +// +// Parameters: const str& objname -- Object name +// const str& propname -- Property name +// const str& value -- string value +// bool create -- whether or not to create properties that are not found +// +// Returns: None +// +//-------------------------------------------------------------- +void GameplayManager::setStringValue(const str& objname, const str& propname, const str& valuestr, bool create) +{ +#ifdef GAME_DLL + bool valueChanged = _gameplayDatabase.setStringValue(objname, propname, valuestr, create); + + if ( valueChanged ) + { + char message[256] ; + sprintf( message, "gdb_setstringproperty %s %s %s\n", objname.c_str(), propname.c_str(), valuestr.c_str() ); + G_SendCommandToAllPlayers( message ); + } +#else + _gameplayDatabase.setStringValue(objname, propname, valuestr, create); +#endif // GAME_DLL +} + + +//-------------------------------------------------------------- +// +// Name: getFloatValue +// Class: GameplayManager +// +// Description: Gets the float value of the property. +// +// Parameters: const str& objname -- Object to retrieve the property from, supports scoping +// via the . symbol, example: "Object1.SubObject" +// +// Returns: float -- Float value of the property or 1.0 +// +//-------------------------------------------------------------- +float GameplayManager::getFloatValue(const str& objname, const str& propname) +{ + if ( !hasObject(objname) ) + return 1.0f; + + GameplayObject *gpobject = 0; + gpobject = _gameplayDatabase.getObject(objname); + + return gpobject->getPropertyFloatValue(propname); +} + + +//-------------------------------------------------------------- +// +// Name: getStringValue +// Class: GameplayManager +// +// Description: Gets the string value of the property. +// +// Parameters: const str& objname -- Object to retrieve the property from, supports scoping +// via the . symbol, example: "Object1.SubObject" +// +// Returns: const str -- String value of the property or "" +// +//-------------------------------------------------------------- +const str GameplayManager::getStringValue(const str& objname, const str& propname) +{ + if ( !hasObject(objname) ) + return ""; + + GameplayObject *gpobject = 0; + gpobject = _gameplayDatabase.getObject(objname); + + return gpobject->getPropertyStringValue(propname); +} + + + + + +//-------------------------------------------------------------- +// +// Name: hasProperty +// Class: GameplayManager +// +// Description: Checks to see if the property exists in the object +// +// Parameters: const str& objname -- Object to find +// const str& propname -- Property name to check for +// +// Returns: bool +// +//-------------------------------------------------------------- +bool GameplayManager::hasProperty(const str& objname, const str& propname) +{ + if ( !hasObject(objname) ) + return false; + + GameplayObject *gpobject = _gameplayDatabase.getObject(objname); + return gpobject->hasProperty(propname); +} + + +//-------------------------------------------------------------- +// +// Name: hasSubObject +// Class: GameplayManager +// +// Description: Checks to see if the specified object has the subobject +// +// Parameters: const str& objname -- Object name +// const str& subobject -- Subobject to look for +// +// Returns: bool +// +//-------------------------------------------------------------- +bool GameplayManager::hasSubObject(const str& objname, const str& subobject) +{ + if ( !objname.length() || !subobject.length()) + return false; + + GameplayObject *gpobject = 0; + gpobject = _gameplayDatabase.getObject(objname); + if ( !gpobject ) + return false; + + if ( !gpobject->getSubObject(subobject) ) + return false; + + return true; +} + +#ifdef GAME_DLL +//-------------------------------------------------------------- +// F O R M U L A S T U F F +//-------------------------------------------------------------- + +//-------------------------------------------------------------- +// +// Name: hasFormula +// Class: GameplayManager +// +// Description: Checks to see if the formula is in the database +// +// Parameters: const str& formulaName -- Formula to check for +// +// Returns: bool +// +//-------------------------------------------------------------- +bool GameplayManager::hasFormula(const str& formulaName) +{ + return _gameplayFormulaManager.hasFormula(formulaName); +} + + +//-------------------------------------------------------------- +// +// Name: calculate +// Class: GameplayManager +// +// Description: Queries the FormulaManager for the result given the formula +// name and some data. +// +// Parameters: const str& formulaName -- Name of the formula +// const GameplayFormulaData& formulaData -- Formula Data +// float multiplier -- optional multiplier that defaults to 1.0 +// +// Returns: flaot +// +//-------------------------------------------------------------- +float GameplayManager::calculate(const str& formulaName, const GameplayFormulaData& formulaData, float multiplier) +{ + float result = _gameplayFormulaManager.getFormulaResult(formulaName, formulaData) * multiplier; + return result; +} + + +//-------------------------------------------------------------- +// +// Name: setBase +// Class: GameplayManager +// +// Description: Sets the base object of objname to be baseobj. +// +// Parameters: const str& objname -- Object on which to set the base +// const str& baseobj -- Base object to reference +// +// Returns: bool +// +//-------------------------------------------------------------- +bool GameplayManager::setBase(const str& objname, const str& baseobj) +{ + return _gameplayDatabase.setBase(objname, baseobj); +} + + +//-------------------------------------------------------------- +// +// Name: clearPropertyOverrides +// Class: GameplayManager +// +// Description: Clears all properties in this object that override +// properties in the base object. +// +// Parameters: const str& objname -- Object to clear the properties for +// +// Returns: bool - successful or not +// +//-------------------------------------------------------------- +bool GameplayManager::clearPropertyOverrides(const str& objname) +{ + return _gameplayDatabase.clearPropertyOverrides(objname); +} + +//=============================================================== +// Name: processPendingMessages +// Class: GameplayManager +// +// Description: Sends all messages that haven't been sent about +// deltas in the database to the client. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void GameplayManager::processPendingMessages( void ) +{ + char message[256] ; + + Container &pendingDeltaList = _gameplayDatabase.getPendingDeltaList(); + for ( int pendingDeltaIdx = 1; pendingDeltaIdx <= pendingDeltaList.NumObjects(); pendingDeltaIdx++ ) + { + PendingDelta pendingDelta = pendingDeltaList.ObjectAt( pendingDeltaIdx ); + Vector vectorValue ; + float floatValue = 0.0f ; + str objName = pendingDelta.getObjectName(); + str propName = pendingDelta.getPropertyName(); + str stringValue ; + + switch ( pendingDelta.getGameplayValueType() ) + { + case VALUE_FLOAT: + floatValue = pendingDelta.getFloatValue(); + sprintf( message, "gdb_setfloatproperty %s %s %g\n", objName.c_str(), propName.c_str(), floatValue ); + break ; + case VALUE_STRING: + stringValue = pendingDelta.getStringValue(); + sprintf( message, "gdb_setstringproperty %s %s %s\n", objName.c_str(), propName.c_str(), stringValue.c_str() ); + break ; + case VALUE_VECTOR: + vectorValue = pendingDelta.getVectorValue(); + sprintf( message, "gdb_setvectorproperty %s %s %g %g %g\n", objName.c_str(), propName.c_str(), vectorValue.x, vectorValue.y, vectorValue.z ); + break ; + default: + break ; + } + G_SendCommandToAllPlayers( message ); + } + + _gameplayDatabase.clearPendingDeltaList(); +} + + +//-------------------------------------------------------------- +// +// Name: Archive +// Class: GameplayDatabase +// +// Description: Archive function. +// +// Parameters: Archiver &arc -- Archive reference +// +// Returns: None +// +//-------------------------------------------------------------- +void GameplayManager::Archive(Archiver &arc) +{ + _gameplayDatabase.Archive(arc); +} + + +#else + + + +//---------------------------------------------------------------- +// E X T E R N A L "C" A P I +//---------------------------------------------------------------- + +//----------------------------------------------------- +// +// Name: HasProperty +// Class: None +// +// Description: Checks if a property exists. +// +// Parameters: objectName - The object name +// propertyName - The property name +// +// Returns: If found, true is returned, otherwise false. +//----------------------------------------------------- +extern "C" qboolean HasGameplayProperty(const char* objectName, const char* propertyName) +{ + GameplayManager* gpm = GameplayManager::getTheGameplayManager(); + if(gpm == 0) + return 0; + + if(gpm->hasProperty(objectName, propertyName)) + return qtrue; + + return qfalse; +} + +//----------------------------------------------------- +// +// Name: GetGameplayStringProperty +// Class: None +// +// Description: Gets the string property in the gameplay database +// +// Parameters: objectName - The object name +// propertyName - The property name +// +// Returns: If found, the property string is returned, otherwise 0 +//----------------------------------------------------- +extern "C" void GetGameplayStringProperty(const char* objectName, const char* propertyName, char* buffer, int length) +{ + if(buffer == 0 || length <= 0) + return; + + GameplayManager* gpm = GameplayManager::getTheGameplayManager(); + if(gpm == 0) + return; + + str stringValue = gpm->getStringValue(objectName, propertyName); + + strncpy(buffer, stringValue.c_str(), length); +} + +//----------------------------------------------------- +// +// Name: SetGameplayStringProperty +// Class: None +// +// Description: Sets the string property in the gameplay database +// +// Parameters: objname - The object name +// propname - The property name +// valuestr - The value of the property. +// create - If true, it creates the property if the property does not exist +// +// Returns: None +//----------------------------------------------------- +extern "C" void SetGameplayStringProperty(const char* objectName, const char* propertyName, const char* stringValue, qboolean create) +{ + GameplayManager* gpm = GameplayManager::getTheGameplayManager(); + if(gpm == 0) + return; + + //Convert the qboolean to boolean + bool createProperty = false; + + if(create) + createProperty = true; + + gpm->setStringValue(objectName, propertyName, stringValue, createProperty); +} + + + +//----------------------------------------------------- +// +// Name: GetGameplayFloatProperty +// Class: None +// +// Description: Gets a float property from the gameplay datbase +// +// Parameters: objectName - the object name +// propertyName - the property name +// +// Returns: The found value. +//----------------------------------------------------- +extern "C" float GetGameplayFloatProperty(const char* objectName, const char* propertyName) +{ + GameplayManager* gpm = GameplayManager::getTheGameplayManager(); + if(gpm == 0) + return 1.0f; + + return gpm->getFloatValue(objectName, propertyName); +} + + +//----------------------------------------------------- +// +// Name: GetGameplayFloatPropertyCmd +// Class: None +// +// Description: Gets a float property from the gameplay datbase +// and prints it to the console. +// +// Parameters: +// +// Returns: The found value. +//----------------------------------------------------- +void GetGameplayFloatPropertyCmd( void ) +{ + GameplayManager* gpm = GameplayManager::getTheGameplayManager(); + + if ( gpm ) + { + const char *objectName = Cmd_Argv( 1 ); + const char *propertyName = Cmd_Argv( 2 ); + float floatValue = gpm->getFloatValue( objectName, propertyName ); + Com_Printf( "%s.%s: %g\n", objectName, propertyName, floatValue ); + } +} + +//----------------------------------------------------- +// +// Name: GetGameplayStringPropertyCmd +// Class: None +// +// Description: Gets a string property from the gameplay datbase +// and prints it to the console. +// +// Parameters: +// +// Returns: The found value. +//----------------------------------------------------- +void GetGameplayStringPropertyCmd( void ) +{ + GameplayManager* gpm = GameplayManager::getTheGameplayManager(); + if( gpm ) + { + const char *objectName = Cmd_Argv( 1 ); + const char *propertyName = Cmd_Argv( 2 ); + str stringValue = gpm->getStringValue( objectName, propertyName ); + Com_Printf( "%s.%s: %s\n", objectName, propertyName, stringValue.c_str() ); + } +} + + +//----------------------------------------------------- +// +// Name: SetGameplayFloatProperty +// Class: None +// +// Description: Sets a float property in the gameplay database +// +// Parameters: objectName - the object name +// propertyName - the property name +// value - the new float value +// create - If true, the value is created, otherwise the value is set. +// +// Returns: None +//----------------------------------------------------- +extern "C" void SetGameplayFloatProperty(const char* objectName, const char* propertyName, float value, qboolean create) +{ + GameplayManager* gpm = GameplayManager::getTheGameplayManager(); + if(gpm == 0) + return; + + //Convert the qboolean to boolean. + bool createProperty = false; + + if(create) + createProperty = true; + + gpm->setFloatValue(objectName, propertyName, value, createProperty); +} + + +//=============================================================== +// Name: SetGameplayStringProperty +// Class: None +// +// Description: Sets a string property in the gameplay database. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void SetGameplayStringProperty( void ) +{ + if ( Cmd_Argc() != 4 ) + { + Com_Printf( "Syntax: gdb_setstringproperty \n" ); + return ; + } + + const char *objectName = Cmd_Argv( 1 ); + const char *propertyName = Cmd_Argv( 2 ); + const char *stringValue = Cmd_Argv( 3 ); + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm ) return ; + + gpm->setStringValue( objectName, propertyName, stringValue, true ); +} + +//=============================================================== +// Name: SetGameplayFloatProperty +// Class: None +// +// Description: Sets a float property in the gameplay database. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void SetGameplayFloatProperty( void ) +{ + if ( Cmd_Argc() != 4 ) + { + Com_Printf( "Syntax: gdb_setfloatproperty \n" ); + return ; + } + + const char *objectName = Cmd_Argv( 1 ); + const char *propertyName = Cmd_Argv( 2 ); + const char *floatStringValue = Cmd_Argv( 3 ); + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm ) return ; + + float floatValue = atof( floatStringValue ); + gpm->setFloatValue( objectName, propertyName, floatValue, true ); +} + + +//=============================================================== +// Name: CreateGameplayManager +// Class: None +// +// Description: Creates the GameplayManager. This loads the +// Gameplay Database from disk. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +extern "C" void CreateGameplayManager( void ) +{ + GameplayManager::create(); + Cmd_AddCommand( "gdb_setfloatproperty", SetGameplayFloatProperty ); + Cmd_AddCommand( "gdb_setstringproperty", SetGameplayStringProperty ); + Cmd_AddCommand( "gdb_getfloatproperty", GetGameplayFloatPropertyCmd ); + Cmd_AddCommand( "gdb_getstringproperty", GetGameplayStringPropertyCmd ); +} + +//=============================================================== +// Name: ShutdownGameplayManager +// Class: None +// +// Description: Shuts down (destroys) the GameplayManager. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +extern "C" void ShutdownGameplayManager( void ) +{ + Cmd_RemoveCommand( "gdb_setfloatproperty" ); + Cmd_RemoveCommand( "gdb_setstringproperty" ); + Cmd_RemoveCommand( "gdb_getfloatproperty" ); + Cmd_RemoveCommand( "gdb_getstringproperty" ); + GameplayManager::shutdown(); +} + +#endif // GAME_DLL diff --git a/Shared/qcommon/gameplaymanager.h b/Shared/qcommon/gameplaymanager.h new file mode 100644 index 0000000..af6d11c --- /dev/null +++ b/Shared/qcommon/gameplaymanager.h @@ -0,0 +1,88 @@ +// GameplayManager.h: interface for the GameplayManager class. +// +////////////////////////////////////////////////////////////////////// +class GameplayManager; + +#ifndef __GAMEPLAYMANAGER_H__ +#define __GAMEPLAYMANAGER_H__ + +#ifdef GAME_DLL +#include +#include +#include +#include +#include +#include "gameplayformulamanager.h" +#endif // GAME_DLL + +#ifdef CGAME_DLL +#include "cg_local.h" +#endif // CGAME_DLL + + +#include "gameplaydatabase.h" + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayManager +// Base Class: Listener +// +// Description: Singlton class that handles gameplay elements +// +// Method of Use: Use in game code when things need to be resolved +// +//-------------------------------------------------------------- +class GameplayManager : public Listener +{ + //----------------------------------------------------------- + // D A T A B A S E S T U F F + //----------------------------------------------------------- + public: + // Static Member functions + static GameplayManager* getTheGameplayManager(); + static void shutdown(); + static bool isReady(); + static void create(); + + // Queries + bool hasProperty(const str& objname, const str& propname); + bool hasObject(const str& objname); + bool hasSubObject(const str& objname, const str& subobject); + bool isDefined(const str& propname); + + // Accessors + const str getDefine(const str& propname); + float getFloatValue(const str& objname, const str& propname); + const str getStringValue(const str& objname, const str& propname); + + // Mutators NOTE: These functions can only set values on root level objects! + void setFloatValue(const str& objname, const str& propname, float value, bool create = false); + void setStringValue(const str& objname, const str& propname, const str& valuestr, bool create = false); + bool setBase(const str& objname, const str& baseobj); + bool clearPropertyOverrides(const str& objname); + + protected: + GameplayManager(); + virtual ~GameplayManager(); + + private: + static GameplayManager *_theGameplayManager; // singleton + GameplayDatabase _gameplayDatabase; + +#ifdef GAME_DLL + //------------------------------------------------------------ + // F O R M U L A S T U F F + //------------------------------------------------------------ + public: + bool hasFormula(const str& formulaName); + float calculate(const str& formulaName, const GameplayFormulaData& formulaData, float multiplier = 1.0f); + + void processPendingMessages(); + void Archive(Archiver &arc); + private: + GameplayFormulaManager _gameplayFormulaManager; +#endif // GAME_DLL +}; + + +#endif diff --git a/Shared/qcommon/output.cpp b/Shared/qcommon/output.cpp new file mode 100644 index 0000000..ef8b9c3 --- /dev/null +++ b/Shared/qcommon/output.cpp @@ -0,0 +1,942 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/Shared/qcommon/output.cpp $ +// $Revision:: 14 $ +// $Author:: Steven $ +// $Date:: 10/06/02 7:10p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// + + +#include "output.h" +#include // Was uilib/str.h + +//================================================================ +// Name: DocFileOutput +// Class: DocFileOutput +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//================================================================ +DocFileOutput::DocFileOutput() +{ + typeFlag = 0; + fileptr = NULL; + classCount = 0; + eventCount = 0; +} + + +//================================================================ +// Name: ~DocFileOutput +// Class: DocFileOutput +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//================================================================ +DocFileOutput::~DocFileOutput() +{ + +} + + +//================================================================ +// Name: OutputClasses +// Class: DocFileOutput +// +// Description: Loops through all the classes and calls OutputClass on each +// (which will usually be a derived classes' version of it). +// +// Parameters: ClassDef *classlist -- The list of classes to loop through +// +// Returns: None +// +//================================================================ +void DocFileOutput::OutputClasses(ClassDef *classlist) +{ + ClassDef *c; + int num = 0; + + for( c = classlist->next; c != classlist; c = c->next ) + { + if ( num < MAX_CLASSES ) + { + OutputClass(c); + num++; + } + } +} + +//================================================================ +// Name: OutputClass +// Class: DocFileOutput +// +// Description: This function is normally overriden by a subclass. +// The subclass's OutputClass will call this function +// when its done with its own version. +// +// Parameters: ClassDef *in_class -- The class to output +// +// Returns: None +// +//================================================================ +void DocFileOutput::OutputClass(ClassDef *in_class) +{ + OutputEvents(in_class); + classCount++; +} + +//================================================================ +// Name: OutputEvents +// Class: DocFileOutput +// +// Description: Loops through all the events and calls OutputEvent on each +// (which will usually be a derived classes' version of it). +// This function also filters out the events based on the typeFlag +// +// Parameters: ClassDef in_class -- The class whose events we will loop through +// +// Returns: None +// +//================================================================ +void DocFileOutput::OutputEvents(ClassDef *in_class) +{ + ResponseDef *r; + int ev, i, num; + Event **events; + + num = Event::NumEventCommands(); + + events = new Event *[num]; + memset( events, 0, sizeof( Event * ) * num ); + + // gather event responses for this class + r = in_class->responses; + if ( r ) + { + for( i = 0; r[i].event != NULL; i++ ) + { + ev = ( int )*r[i].event; + if ( r[i].response ) + { + events[ev] = r[i].event; + } + } + } + + for( i = 1; i < num; i++ ) + { + int index; + + index = Event::MapSortedEventIndex( i ); + if ( events[index] ) + { + // Filtering + + if ( events[index]->GetFlags() != 0 ) + { + // Event is not default + + if ( ( events[index]->GetFlags() & EV_TIKIONLY ) && typeFlag == EVENT_SCRIPT_ONLY && !( events[index]->GetFlags() & EV_SCRIPTONLY ) ) + continue; + + if ( ( events[index]->GetFlags() & EV_SCRIPTONLY ) && typeFlag == EVENT_TIKI_ONLY && !( events[index]->GetFlags() & EV_TIKIONLY ) ) + continue; + + if ( ( events[index]->GetFlags() & EV_CODEONLY ) && ( typeFlag == EVENT_TIKI_ONLY || typeFlag == EVENT_SCRIPT_ONLY ) ) + continue; + + if ( ( events[index]->GetFlags() & EV_CHEAT ) && ( typeFlag == EVENT_TIKI_ONLY ) && !( events[index]->GetFlags() & EV_TIKIONLY ) ) + continue; + + if ( ( events[index]->GetFlags() & EV_CHEAT ) && ( typeFlag == EVENT_SCRIPT_ONLY ) && !( events[index]->GetFlags() & EV_SCRIPTONLY ) ) + continue; + + if ( ( events[index]->GetFlags() & EV_CONSOLE ) && ( typeFlag == EVENT_TIKI_ONLY || typeFlag == EVENT_SCRIPT_ONLY ) ) + continue; + } + + OutputEvent( events[index] ); + } + } + + delete[] events; +} + +//================================================================ +// Name: OutputEvent +// Class: DocFileOutput +// +// Description: This function is normally overriden by a subclass. +// The subclass's OutputEvent will call this function +// when its done with its own version. +// +// Parameters: Event *ev -- The event to output +// +// Returns: None +// +//================================================================ +void DocFileOutput::OutputEvent(Event *ev) +{ + OutputArguments(ev); + eventCount++; +} + +//================================================================ +// Name: OutputArguments +// Class: DocFileOutput +// +// Description: Loops through all the args and calls OutputArgument on each +// (which will usually be a derived classes' version of it). +// +// Parameters: Event *ev -- The event whose arguments we will loop through +// +// Returns: None +// +//================================================================ +void DocFileOutput::OutputArguments(Event *ev) +{ + int i; + for( i = 1; i <= ev->getNumArgDefs(); i++ ) + { + EventArgDef *evarg = ev->getArgDef(i); + OutputArgument(evarg); + } +} + +//================================================================ +// Name: OutputArgument +// Class: DocFileOutput +// +// Description: This function is normally overriden by a subclass. +// The subclass's OutputArgument will call this function +// when its done with its own version. +// +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void DocFileOutput::OutputArgument(EventArgDef *evarg) +{ + // Function does nothing. This is the last level of nesting, and there + // is nothing else to go print from here. +} + +//================================================================ +// Name: Write +// Class: DocFileOutput +// +// Description: Writes the file to disk +// +// Parameters: char *filename -- the filename to create +// +// Returns: None +// +//================================================================ +void DocFileOutput::Write(const char *filename, ClassDef *classlist, int ptypeFlag) +{ + char realname[255]; + sprintf(realname,"%s.%s",filename,GetExtension()); + fileptr = fopen(realname,"w"); + if ( !fileptr ) + return; + + // Store the type flag privately so we don't have to pass it around + typeFlag = ptypeFlag; + + // Start the writing process + OutputClasses(classlist); + + fclose(fileptr); +} + + +// ************************************************************************ +// * * +// * * +// * HTMLDocFileOutput Class * +// * * +// * * +// ************************************************************************ + + + + +//================================================================ +// Name: HTMLDocFileOutput +// Class: HTMLDocFileOutput +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//================================================================ +HTMLDocFileOutput::HTMLDocFileOutput() +{ + +} + +//================================================================ +// Name: HTMLDocFileOutput +// Class: ~HTMLDocFileOutput +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//================================================================ +HTMLDocFileOutput::~HTMLDocFileOutput() +{ + +} + +//================================================================ +// Name: OutputClasses +// Class: HTMLDocFileOutput +// +// Description: Override of the base class version. We do this +// so we can print header and footer information. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void HTMLDocFileOutput::OutputClasses(ClassDef *classlist) +{ + str class_title; +#if defined( GAME_DLL ) + class_title = "Game Module"; +#elif defined( CGAME_DLL ) + class_title = "Client Game Module"; +#else + class_title = "Client Module"; +#endif + + // Header Info + fprintf(fileptr, "\n"); + fprintf(fileptr, "\n"); + fprintf(fileptr, "%s Classes\n", class_title.c_str() ); + fprintf(fileptr, "\n"); + fprintf(fileptr, "\n"); + fprintf(fileptr, "

\n"); + fprintf(fileptr, "
%s Classes
\n", class_title.c_str() ); + fprintf(fileptr, "

\n"); + +#if defined( GAME_DLL ) + fprintf(fileptr, "

" ); + fprintf(fileptr, "Actor, " ); + fprintf(fileptr, "Animate, " ); + fprintf(fileptr, "Entity, " ); + fprintf(fileptr, "ScriptSlave, " ); + fprintf(fileptr, "Sentient, " ); + fprintf(fileptr, "Trigger, " ); + fprintf(fileptr, "World" ); + fprintf(fileptr, "

" ); +#endif + + // Print the body + DocFileOutput::OutputClasses(classlist); + + // Footer + fprintf(fileptr, "

\n"); + fprintf(fileptr, "%d %s Classes.
%d Events.\n", classCount, class_title.c_str(), eventCount ); + fprintf(fileptr, "

\n"); + fprintf(fileptr, "\n"); + fprintf(fileptr, "\n"); +} + +//================================================================ +// Name: OutputClass +// Class: HTMLDocFileOutput +// +// Description: Write the class output for this type of file +// +// Parameters: ClassDef *in_class -- Class to write out +// +// Returns: None +// +//================================================================ +void HTMLDocFileOutput::OutputClass(ClassDef *in_class) +{ + ClassDef *savedClass; + savedClass = in_class; + + fprintf(fileptr, "\n"); + if ( in_class->classID[ 0 ] ) + { + fprintf(fileptr, "

%s (%s)", in_class->classname, in_class->classname, in_class->classID ); + } + else + { + fprintf(fileptr, "

%s", in_class->classname, in_class->classname ); + } + + // print out lineage + for( in_class = in_class->super; in_class != NULL; in_class = in_class->super ) + { + fprintf(fileptr, " -> %s", in_class->classname, in_class->classname ); + } + fprintf(fileptr, "

\n"); + fprintf(fileptr, "
\n"); + + // Events + DocFileOutput::OutputClass(savedClass); + + fprintf(fileptr, "
\n"); +} + +//================================================================ +// Name: OutputEvent +// Class: HTMLDocFileOutput +// +// Description: Write the event output for this type of file +// +// Parameters: Event *ev -- Event to write out +// +// Returns: None +// +//================================================================ +void HTMLDocFileOutput::OutputEvent(Event *ev) +{ + int numargs; + const char *text; + + fprintf(fileptr, "\n

%s", ev->getName() ); + + numargs = ev->getNumArgDefs(); + + if ( numargs ) + { + fprintf(fileptr, "( " ); + DocFileOutput::OutputEvent(ev); + fprintf(fileptr, " )
\n" ); + } + else + { + // No arguments, do not call base class OutputEvent + fprintf(fileptr, "
\n" ); + } + + text = ev->GetDocumentation(); + + // Build a new documentation string, replaces the newlines with
\n + if ( text ) + { + char new_doc[1024]; + int old_index; + int new_index = 0; + + for ( old_index = 0 ; old_index < strlen ( text ) ; old_index++ ) + { + if ( text[old_index] == '\n' ) + { + new_doc[new_index ] = '<'; + new_doc[new_index + 1] = 'B'; + new_doc[new_index + 2] = 'R'; + new_doc[new_index + 3] = '>'; + new_doc[new_index + 4] = '\n'; + new_index += 5; + } + else + { + new_doc[new_index] = text[old_index]; + new_index++; + } + } + + new_doc[new_index] = 0; + + fprintf(fileptr, "

    %s
\n", new_doc ); + } +} + +//================================================================ +// Name: OutputArgument +// Class: HTMLDocFileOutput +// +// Description: Write the argument output for this type of file +// +// Parameters: EventArgDef *evarg -- EventArgs to write out +// +// Returns: None +// +//================================================================ +void HTMLDocFileOutput::OutputArgument(EventArgDef *evarg) +{ + if ( evarg->isOptional() ) + { + fprintf(fileptr, "[ " ); + } + + switch( evarg->getType() ) + { + case IS_ENTITY: + fprintf(fileptr, "Entity " ); + break; + case IS_VECTOR: + fprintf(fileptr, "Vector " ); + break; + case IS_INTEGER: + fprintf(fileptr, "Integer " ); + break; + case IS_FLOAT: + fprintf(fileptr, "Float " ); + break; + case IS_STRING: + fprintf(fileptr, "String " ); + break; + case IS_BOOLEAN: + fprintf(fileptr, "Boolean " ); + break; + } + fprintf(fileptr, "%s", evarg->getName() ); + + // print the range of the argument + OutputRange(evarg); + + if ( evarg->isOptional() ) + { + fprintf(fileptr, " ]" ); + } + + DocFileOutput::OutputArgument(evarg); +} + +//================================================================ +// Name: OutputRange +// Class: HTMLDocFileOutput +// +// Description: Writes the range of the event arg passed if any +// +// Parameters: EventArgDef *evarg -- Argument to check for a range +// +// Returns: None +// +//================================================================ +void HTMLDocFileOutput::OutputRange(EventArgDef *evarg) +{ + qboolean integer, single; + int numRanges, i; + + single = false; + integer = true; + numRanges = 1; + switch( evarg->getType() ) + { + case IS_VECTOR: + integer = false; + numRanges = 3; + break; + case IS_FLOAT: + integer = false; + break; + case IS_STRING: + single = true; + break; + } + for( i = 0; i < numRanges; i++ ) + { + if ( single ) + { + if ( !evarg->GetMinRangeDefault(i) ) + { + if ( integer ) + { + fprintf(fileptr, "<%d>", ( int )evarg->GetMinRange(i) ); + } + else + { + fprintf(fileptr, "<%.2f>", evarg->GetMinRange(i) ); + } + } + } + else + { + // both non-default + if ( !evarg->GetMinRangeDefault(i) && !evarg->GetMaxRangeDefault(i) ) + { + if ( integer ) + { + fprintf(fileptr, "<%d...%d>", ( int )evarg->GetMinRange(i), ( int )evarg->GetMaxRange(i) ); + } + else + { + fprintf(fileptr, "<%.2f...%.2f>", evarg->GetMinRange(i), evarg->GetMaxRange(i) ); + } + } + // max default + else if ( !evarg->GetMinRangeDefault(i) && evarg->GetMaxRangeDefault(i) ) + { + if ( integer ) + { + fprintf(fileptr, "<%d...max_integer>", ( int )evarg->GetMinRange(i) ); + } + else + { + fprintf(fileptr, "<%.2f...max_float>", evarg->GetMinRange(i) ); + } + } + // min default + else if ( evarg->GetMinRangeDefault(i) && !evarg->GetMaxRangeDefault(i) ) + { + if ( integer ) + { + fprintf(fileptr, "", ( int )evarg->GetMaxRange(i) ); + } + else + { + fprintf(fileptr, "", evarg->GetMaxRange(i) ); + } + } + } + } +} + + + +// ************************************************************************ +// * * +// * * +// * ToolDocFileOutput Class * +// * * +// * * +// ************************************************************************ + + + + +//================================================================ +// Name: ToolDocFileOutput +// Class: ToolDocFileOutput +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//================================================================ +ToolDocFileOutput::ToolDocFileOutput() +{ + randFlag = false; + colorFlag = false; +} + +//================================================================ +// Name: ToolDocFileOutput +// Class: ~ToolDocFileOutput +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//================================================================ +ToolDocFileOutput::~ToolDocFileOutput() +{ + +} + +//================================================================ +// Name: OutputClass +// Class: ToolDocFileOutput +// +// Description: Write the class output for this type of file +// +// Parameters: ClassDef *in_class -- Class to write out +// +// Returns: None +// +//================================================================ +void ToolDocFileOutput::OutputClass(ClassDef *in_class) +{ + ClassDef *savedClass; + savedClass = in_class; + + fprintf(fileptr, "\n"); + fprintf(fileptr, "%s\n", in_class->classname ); + fprintf(fileptr, "{\n"); + + // Events + DocFileOutput::OutputClass(savedClass); + if ( in_class->super != NULL ) + fprintf(fileptr, "\n\tincludes %s\n",in_class->super->classname); + + fprintf(fileptr, "}\n"); +} + +//================================================================ +// Name: OutputEvent +// Class: ToolDocFileOutput +// +// Description: Write the event output for this type of file +// +// Parameters: Event *ev -- Event to write out +// +// Returns: None +// +//================================================================ +void ToolDocFileOutput::OutputEvent(Event *ev) +{ + int numargs; + const char *text; + int i; + + fprintf(fileptr, "\n"); + fprintf(fileptr, "\t%s\n", ev->getName() ); + fprintf(fileptr, "\t{\n"); + + numargs = ev->getNumArgDefs(); + + if ( numargs ) + { + DocFileOutput::OutputEvent(ev); + } + + fprintf(fileptr, "\n\t\thelp\t\t\""); + + text = ev->GetDocumentation(); + for ( i=0; iGetDocumentation(); +} + +//================================================================ +// Name: OutputArgument +// Class: ToolDocFileOutput +// +// Description: Write the argument output for this type of file +// +// Parameters: EventArgDef *evarg -- EventArgs to write out +// +// Returns: None +// +//================================================================ +void ToolDocFileOutput::OutputArgument(EventArgDef *evarg) +{ + // Check for Randoms. If there is one, print out the "random" keyword + // and set the random flag, so next next time this function is called, + // it is assumed to be the random parameter's name. + if ( !strcmp(evarg->getName(), "[randomtype]") ) + { + fprintf(fileptr, "\t\trandom"); + randFlag = true; // Set the random flag + return; + } + + // If the random flag is set, we assume the previous call to this function + // was a [randomtype] parameter. We now output the name of the random parameter, + // which is assumed to be a float. (All randoms ARE floats, correct?) + if ( randFlag ) + { + fprintf(fileptr, "\t\t%s\t\"%s\"\n",evarg->getName(), evarg->getName()); + randFlag = false; + return; + } + + // If it's a vector that's supposed to specify a color, we directly print it here + if ( strstr(evarg->getName(), "color_") && evarg->getType() == IS_VECTOR ) + { + fprintf(fileptr, "\t\trgb\t\t%s\t\"%s\"\n",evarg->getName(), evarg->getName()); + return; + } + + // If the event takes an RGB color (three floats), we must do special casing to + // output a single color parameter. + // If we find color_red, we ASSUME it's the start of an RGB set. + if ( strstr(evarg->getName(), "color_red") && evarg->getType() != IS_VECTOR ) + { + char tmpstr[25], *sptr; + strcpy(tmpstr,evarg->getName()); + sptr = strtok(tmpstr,"_"); // color + sptr = strtok(NULL,"_"); // red + sptr = strtok(NULL,"_"); // potential name or NULL + + colorFlag = true; + if ( sptr == NULL ) + fprintf(fileptr, "\t\trgb\t\tcolor\t\"color\"\n"); + else + fprintf(fileptr, "\t\trgb\t\t%s\t\"%s\"\n", sptr, sptr); + return; + } + + // If we find color_blue, we ASSUME is the end of an RGB set + if ( colorFlag && strstr(evarg->getName(), "color_blue") ) + { + colorFlag = false; + return; + } + + // If the colorFlag is still set by the time we get here, we are in + // the process of ignoring parameters until the color is done. + if ( colorFlag ) + return; + + // If we reach this point it is assumed that it is not one of the special case parameters + // listed above. + if ( evarg->isOptional() ) + fprintf(fileptr, "\t\t|" ); + else + fprintf(fileptr, "\t\t"); + + switch( evarg->getType() ) + { + case IS_ENTITY: + fprintf(fileptr, "string\t\t" ); + break; + case IS_VECTOR: + fprintf(fileptr, "xyz\t\t" ); + break; + case IS_INTEGER: + fprintf(fileptr, "int\t\t" ); + break; + case IS_FLOAT: + fprintf(fileptr, "float\t\t" ); + break; + case IS_STRING: + fprintf(fileptr, "string\t\t" ); + break; + case IS_BOOLEAN: + fprintf(fileptr, "boolean\t\t" ); + break; + } + fprintf(fileptr, "%s\t", evarg->getName() ); + fprintf(fileptr, "\"%s\"\t", evarg->getName() ); + + // print the range of the argument + OutputRange(evarg); + fprintf(fileptr, "\n"); + + DocFileOutput::OutputArgument(evarg); +} + +//================================================================ +// Name: OutputRange +// Class: ToolDocFileOutput +// +// Description: Writes the range of the event arg passed if any +// +// Parameters: EventArgDef *evarg -- Argument to check for a range +// +// Returns: None +// +//================================================================ +void ToolDocFileOutput::OutputRange(EventArgDef *evarg) +{ + int i; + + // Event type is a vector + if ( evarg->getType() == IS_VECTOR ) + { + for ( i=0; i<3; i++ ) + { + if ( evarg->GetMinRangeDefault(i) || evarg->GetMaxRangeDefault(i) ) + return; + } + + fprintf(fileptr, "%.2f %.2f %.2f %.2f %.2f %.2f\n", + evarg->GetMinRange(0), evarg->GetMinRange(1), evarg->GetMinRange(2), + evarg->GetMaxRange(0), evarg->GetMaxRange(1), evarg->GetMaxRange(2) ); + + return; + } + + // Event type is a string + if ( evarg->getType() == IS_STRING ) + { + if ( !evarg->GetMinRangeDefault(0) ) + { + fprintf(fileptr, "%d", ( int )evarg->GetMinRange(0) ); + } + + return; + } + + // Default checks + if ( !evarg->GetMinRangeDefault(0) && !evarg->GetMaxRangeDefault(0) ) + { + if ( evarg->getType() == IS_FLOAT ) + fprintf(fileptr, "%.2f %.2f", evarg->GetMinRange(0), evarg->GetMaxRange(0) ); + else + fprintf(fileptr, "%d %d", ( int )evarg->GetMinRange(0), ( int )evarg->GetMaxRange(0) ); + } +} + +//================================================================ +// Name: PrintSubClasses (private) +// Class: ToolDocFileOutput +// +// Description: Recursive function that prints all the passed classes +// subclasses. +// +// Parameters: ClassDef *in_class -- the class to print the subclasses of +// +// Returns: None +// +//================================================================ +void ToolDocFileOutput::PrintSubClasses(ClassDef *in_class, ClassDef *classlist) +{ + ClassDef *c; + OutputClass(in_class); + for( c = classlist->next; c != classlist; c = c->next ) + { + if ( c->super == in_class ) + PrintSubClasses(c, classlist); + } +} + +//================================================================ +// Name: OutputClasses +// Class: ToolDocFileOutput +// +// Description: Override of the base class version. This is a special +// fuctions that loops through the classes in a specific way. +// For each root node, we recursively print all it's children. +// This way, all classes' super classes are printed first +// +// Parameters: ClassDef *classlist -- The list of classes to loop through +// +// Returns: None +// +//================================================================ +void ToolDocFileOutput::OutputClasses(ClassDef *classlist) +{ + ClassDef *c; + + // Make a container of all the root class names + for( c = classlist->next; c != classlist; c = c->next ) + { + if ( c->super == NULL ) + PrintSubClasses(c, classlist); + } +} diff --git a/Shared/qcommon/output.h b/Shared/qcommon/output.h new file mode 100644 index 0000000..935d29b --- /dev/null +++ b/Shared/qcommon/output.h @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/Shared/qcommon/output.h $ +// $Revision:: 10 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// + +class DocFileOutput; + +#ifndef __DOCFILEOUTPUT_H__ +#define __DOCFILEOUTPUT_H__ + +#if defined ( GAME_DLL ) +// class.h behaves different when the GAME_DLL define is set, so +// we need to act differently to. If we simply include class.h here, +// it includes g_local.h, which then includes game.h which fails to +// compile because class.h isn't fully read yet. To prevent this, +// we include g_local.h first, so that it can include things in the +// right order. +#include +#else +#include +#include +#endif + +// Base Class +class DocFileOutput + { + protected: + FILE *fileptr; + int typeFlag; + + int classCount; + int eventCount; + + public: + DocFileOutput(); + virtual ~DocFileOutput(); + + virtual void OutputClasses(ClassDef *classlist); + virtual void OutputClass(ClassDef *in_class); + virtual void OutputEvents(ClassDef *in_class); + virtual void OutputEvent(Event *ev); + virtual void OutputArguments(Event *ev); + virtual void OutputArgument(EventArgDef *evarg); + + virtual const char *GetExtension() { return "txt"; } + + void Write(const char *filename, ClassDef *classlist, int ptypeFlag); + }; + +// HTML Output +class HTMLDocFileOutput : public DocFileOutput + { + public: + HTMLDocFileOutput(); + ~HTMLDocFileOutput(); + + // Virtual Function Implementation + void OutputClass(ClassDef *in_class); + void OutputEvent(Event *ev); + void OutputArgument(EventArgDef *evarg); + + // Special override + void OutputClasses(ClassDef *classlist); + + void OutputRange(EventArgDef *evarg); + + const char *GetExtension() { return "html"; } + }; + +// Tool Output -- Output to the specific parser format that +// the tools use. +class ToolDocFileOutput : public DocFileOutput + { + private: + void PrintSubClasses(ClassDef *in_class, ClassDef *classlist); // recursive + bool randFlag; + bool colorFlag; + + public: + ToolDocFileOutput(); + ~ToolDocFileOutput(); + + // Virtual Function Implementation + void OutputClass(ClassDef *in_class); + void OutputEvent(Event *ev); + void OutputArgument(EventArgDef *evarg); + + // Special override + void OutputClasses(ClassDef *classlist); + + void OutputRange(EventArgDef *evarg); + + const char *GetExtension() { return "txt"; } + }; + +#endif // #ifndef __DOCFILEOUTPUT_H__ diff --git a/Shared/qcommon/platform.h b/Shared/qcommon/platform.h new file mode 100644 index 0000000..e2c8afb --- /dev/null +++ b/Shared/qcommon/platform.h @@ -0,0 +1,10 @@ +#ifndef PLATFORM_H +#define PLATFORM_H + +#ifdef LINUX + #define __declspec(x) + #define __cdecl + #define __stdcall +#endif + +#endif //PLATFORM_H diff --git a/Shared/qcommon/qfiles.h b/Shared/qcommon/qfiles.h new file mode 100644 index 0000000..36650ee --- /dev/null +++ b/Shared/qcommon/qfiles.h @@ -0,0 +1,1264 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/Shared/qcommon/qfiles.h $ +// $Revision:: 58 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:42a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __QFILES_H__ +#define __QFILES_H__ + +#pragma warning( disable : 4201 ) +#include + +// +// qfiles.h: quake file formats +// This file must be identical in the game and utils directories +// + +// added for BOTLIB +#define LAST_VISIBLE_CONTENTS 64 + +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 + +// 3-5 are non-axial planes snapped to the nearest +#define PLANE_ANYX 3 +#define PLANE_ANYY 4 +#define PLANE_ANYZ 5 +// end BOTLIB additions + +// surface geometry should not exceed these limits +#define SHADER_MAX_VERTEXES 3000 +#define SHADER_MAX_INDEXES (6*SHADER_MAX_VERTEXES) + + +// the maximum size of game reletive pathnames +#define MAX_QPATH 64 + + +/// Common macros used throughout engine and utils (no place else to put this stuff!) +#define BIT(x) (1<<(x)) // Squirrel + + +//--------------------------------------------------------------------------- +// Integral constants for lighting system (used by both tools and game) +//--------------------------------------------------------------------------- +enum +{ + DYNAMIC_GROUP_NONE = -1, + + MAX_LIGHT_EXCLUSIVE_NAMES = 4, +}; + + +enum +{ + SUBINFO_BUFFER_ID_BSP = 2, + SUBINFO_BUFFER_ID_VIS = 3, + SUBINFO_BUFFER_ID_LIGHT = 4, + + MAX_SUBINFO_BUFFERS = 10, // Maximum number of subinfo buffer types + MAX_SUBINFO_BUFFER_SIZE = 2084, // Maximum number of bytes per subinfo buffer +}; + + +/* +======================================================================== + +QVM files + +======================================================================== +*/ + +#define VM_MAGIC 0x12721444 +typedef struct { + int vmMagic; + + int instructionCount; + + int codeOffset; + int codeLength; + + int dataOffset; + int dataLength; + int litLength; // ( dataLength - litLength ) should be byteswapped on load + int bssLength; // zero filled memory appended to datalength +} vmHeader_t; + +/* +======================================================================== + +PCX files are used for 8 bit images + +======================================================================== +*/ + +typedef struct { + char manufacturer; + char version; + char encoding; + char bits_per_pixel; + unsigned short xmin,ymin,xmax,ymax; + unsigned short hres,vres; + unsigned char palette[48]; + char reserved; + char color_planes; + unsigned short bytes_per_line; + unsigned short palette_type; + char filler[58]; + unsigned char data; // unbounded +} pcx_t; + + +/* +======================================================================== + +TGA files are used for 24/32 bit images + +======================================================================== +*/ + +typedef struct _TargaHeader { + unsigned char id_length, colormap_type, image_type; + unsigned short colormap_index, colormap_length; + unsigned char colormap_size; + unsigned short x_origin, y_origin, width, height; + unsigned char pixel_size, attributes; +} TargaHeader; + +/* +======================================================================== + +FTX files are pre mipmapped files + +======================================================================== +*/ +#define FTX_EXTENSION ".ftx" + +typedef struct ftx_s { + int width; + int height; + int has_alpha; + // data follows + } ftx_t; + +/* +======================================================================== + +.MD3 triangle model file format + +======================================================================== +*/ + +#define MD3_IDENT (('3'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD3_VERSION 15 + +// limits +#define MD3_MAX_LODS 3 +#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) + +typedef struct md3Frame_s { + vec3_t bounds[2]; + vec3_t localOrigin; + float radius; + char name[16]; +} md3Frame_t; + +typedef struct md3Tag_s { + char name[MAX_QPATH]; // tag name + vec3_t origin; + vec3_t 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_QPATH]; // 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_QPATH]; + 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_QPATH]; // 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; + + +/* +============================================================================== + +MD4 file format + +============================================================================== +*/ + +#define MD4_IDENT (('4'<<24)+('P'<<16)+('D'<<8)+'I') +#define MD4_VERSION 1 +#define MD4_MAX_BONES 128 + +typedef struct { + int boneIndex; // these are indexes into the boneReferences, + float boneWeight; // not the global per-frame bone list + vec3_t offset; +} md4Weight_t; + +typedef struct { + vec3_t vertex; + vec3_t normal; + vec2_t texCoords; + int numWeights; + md4Weight_t weights[1]; // variable sized +} md4Vertex_t; + +typedef struct { + int indexes[3]; +} md4Triangle_t; + +typedef struct { + int ident; + + char name[MAX_QPATH]; // polyset name + char shader[MAX_QPATH]; + int shaderIndex; // for in-game use + + int ofsHeader; // this will be a negative number + + int numVerts; + int ofsVerts; + + int numTriangles; + int ofsTriangles; + + // Bone references are a set of ints representing all the bones + // present in any vertex weights for this surface. This is + // needed because a model may have surfaces that need to be + // drawn at different sort times, and we don't want to have + // to re-interpolate all the bones for each surface. + int numBoneReferences; + int ofsBoneReferences; + + int ofsEnd; // next surface follows +} md4Surface_t; + +typedef struct { + float matrix[3][4]; +} md4Bone_t; + +typedef struct { + vec3_t bounds[2]; // bounds of all surfaces of all LOD's for this frame + vec3_t localOrigin; // midpoint of bounds, used for sphere cull + float radius; // dist from localOrigin to corner + char name[16]; + md4Bone_t bones[1]; // [numBones] +} md4Frame_t; + +typedef struct { + int numSurfaces; + int ofsSurfaces; // first surface, others follow + int ofsEnd; // next lod follows +} md4LOD_t; + +typedef struct { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + // frames and bones are shared by all levels of detail + int numFrames; + int numBones; + int ofsFrames; // md4Frame_t[numFrames] + + // each level of detail has completely separate sets of surfaces + int numLODs; + int ofsLODs; + + int ofsEnd; // end of file +} md4Header_t; + + +/* +============================================================================== + + TIKI model file format + +============================================================================== +*/ + +#define TIKI_IDENT (('I'<<24)+('K'<<16)+('I'<<8)+'T') +#define TIKI_ANIM_IDENT ((' '<<24)+('N'<<16)+('A'<<8)+'T') +#define TIKI_ANIM_VERSION 2 + +// limits +#define TIKI_MAX_LODS 4 +#define TIKI_MAX_TRIANGLES 4096 // per surface +#define TIKI_MAX_VERTS 4000 +#define TIKI_MAX_SURFACE_VERTS 1200 // per surface +#define TIKI_MAX_SHADERS 256 // per surface +#define TIKI_MAX_FRAMES 1024 // per model (vertex models) +#define TIKI_MAX_SKEL_FRAMES 8192 // per model (skel models) +#define TIKI_MAX_SURFACES 32 // per model +#define TIKI_MAX_TAGS 16 // per frame + +#define MAX_SHADERNAME 64 + +typedef struct + { + unsigned short xyz[3]; + short normal; + } tikiXyzNormal_t; + +typedef struct tikiFrame_s + { + vec3_t bounds[2]; + vec3_t scale; // multiply by this + vec3_t offset; // and add by this + vec3_t delta; + float radius; + float frametime; + } tikiFrame_t; + +/* +** tikiSurface_t +** +** CHUNK SIZE +** header sizeof( md3Surface_t ) +** triangles[0] sizeof( md3Triangle_t ) * numTriangles +** st sizeof( md3St_t ) * numVerts +** XyzNormals sizeof( md3XyzNormal_t ) * numVerts * numFrames +*/ +typedef struct + { + int ident; // + + char name[MAX_QPATH]; // polyset name + + int numFrames; // all surfaces in a model should have the same + int numVerts; + int numModelVerts; + + int numTriangles; + int ofsTriangles; + + int ofsCollapseMap; // numVerts * int + + int ofsSt; // texture coords are common for all frames + int ofsXyzNormals; // numVerts * numFrames + + int ofsEnd; // next surface follows + } tikiSurface_t; + +typedef struct + { + vec3_t origin; + vec3_t axis[3]; + } tikiTagData_t; + +typedef struct + { + char name[MAX_QPATH]; // tag name + } tikiTag_t; + +typedef struct + { + int ident; + int version; + + char name[MAX_QPATH]; // model name + + int numFrames; + int numTags; + int numSurfaces; + float totaltime; + vec3_t totaldelta; + + int ofsFrames; // offset for first frame + int ofsSurfaces; // first surface, others follow + int ofsTags[ TIKI_MAX_TAGS ]; // tikiTag_t + numFrames * tikiTagData_t + + int ofsEnd; // end of file + } tikiHeader_t; + +/* +============================================================================== + + TIKI Skeleton model file format + +============================================================================== +*/ +#define TIKI_SKEL_IDENT ((' '<<24)+('L'<<16)+('K'<<8)+'S') +#define TIKI_SKEL_ANIM_IDENT (('N'<<24)+('A'<<16)+('K'<<8)+'S') +#define TIKI_SKEL_VERSION 4 +#define TIKI_SKEL_MAXBONES 100 +#define MAX_SKEL_MODELS 80 +#define TIKI_BONE_CACHE ( TIKI_SKEL_MAXBONES * MAX_SKEL_MODELS ) + +#define TIKI_MORPH_IDENT (('H'<<24)+('P'<<16)+('R'<<8)+'M') +#define TIKI_MORPH_VERSION 2 + +#define TIKI_SKEL_PARENTBONE -1 + +#define TIKI_BONEFLAG_LEG 1 + +#define TIKI_ANIM_NORMAL 0 +#define TIKI_ANIM_NO_OFFSETS 1 + +#define TIKI_BONE_OFFSET_MANTISSA_BITS ( 9 ) +#define TIKI_BONE_OFFSET_MAX_SIGNED_VALUE ( ( 1 << TIKI_BONE_OFFSET_MANTISSA_BITS ) - 1 ) +#define TIKI_BONE_OFFSET_SIGNED_SHIFT ( 15 - ( TIKI_BONE_OFFSET_MANTISSA_BITS ) ) +#define TIKI_BONE_OFFSET_MULTIPLIER ( ( 1 << ( TIKI_BONE_OFFSET_SIGNED_SHIFT ) ) - 1 ) +#define TIKI_BONE_OFFSET_MULTIPLIER_RECIPROCAL ( ( 1.0f ) / ( TIKI_BONE_OFFSET_MULTIPLIER ) ) + +#define TIKI_BONE_QUAT_FRACTIONAL_BITS ( 15 ) +#define TIKI_BONE_QUAT_MULTIPLIER ( ( 1 << ( TIKI_BONE_QUAT_FRACTIONAL_BITS ) ) - 1 ) +#define TIKI_BONE_QUAT_MULTIPLIER_RECIPROCAL ( ( 1.0f ) / ( TIKI_BONE_QUAT_MULTIPLIER ) ) + +typedef struct + { + int boneIndex; + float boneWeight; + vec3_t offset; + } skelWeight_t; + +typedef struct + { + vec3_t normal; + vec2_t texCoords; + int numWeights; + skelWeight_t weights[ 1 ]; // variable sized + } skelVertex_t; + +typedef struct + { + short shortQuat[ 4 ]; + short shortOffset[ 3 ]; + short padding_do_not_use; + } skelBone_t; + +typedef struct + { + float quat[ 4 ]; + float offset[ 3 ]; + float matrix[ 3 ][ 3 ]; + } skelBoneCache_t; + +typedef struct + { + vec3_t bounds[ 2 ]; + float radius; // dist to corner + vec3_t delta; + skelBone_t bones[ 1 ]; // variable sized + } skelFrame_t; + +typedef struct + { + int indexes[ 3 ]; + } skelTriangle_t; + +typedef struct + { + int vertex_index; + vec3_t delta; + } skelMorphVertex_t; + +typedef struct + { + char name[ MAX_QPATH ]; + + int numverts; + int ofsMorphVerts; + } skelMorph_t; + +typedef struct { + int ident; + char name[ MAX_QPATH ]; // polyset name + int numTriangles; + int numVerts; + int numModelVerts; + int ofsTriangles; + int ofsVerts; + int ofsCollapse; + int ofsEnd; // next surface follows + } skelSurface_t; + +typedef struct + { + int parent; + int flags; + char name[ MAX_QPATH ]; // bone name + } skelBoneName_t; + +#define MAX_BONE_REFERENCE_NAME_LENGTH 32 + +typedef struct + { + float length; + char name[ MAX_BONE_REFERENCE_NAME_LENGTH ]; + } skelBoneReference_t; + +typedef struct + { + int ident; + int version; + char name[ MAX_QPATH ]; // model name + + int numsurfaces; + int numbones; + + int ofsBones; + int ofsSurfaces; + int ofsBoneBaseFrame; + int ofsEnd; + } skelHeader_t; + +typedef struct + { + int ident; + int version; + char name[ MAX_QPATH ]; // anim name + + int type; + int numFrames; + int numbones; + float totaltime; + float frametime; + vec3_t totaldelta; + int ofsBoneRefs; + int ofsFrames; + } skelAnimHeader_t; + +typedef struct + { + int ident; + int version; + char name[ MAX_QPATH ]; // model name + + int numverts; + int nummorphs; + + int ofsVertexId; + int ofsMorphTargets; + } skelMorphHeader_t; + +//==================================== +// TIKI DEF STUFF +//==================================== + + +// +// frame cmd flags +// +#define TIKI_FRAME_CMD_EVERY_FRAME -1 +#define TIKI_FRAME_CMD_EXIT -2 +#define TIKI_FRAME_CMD_ENTRY -3 +#define TIKI_FRAME_CMD_LAST_SPECIAL -4 +#define TIKI_FRAME_CMD_END -5 + +#define TIKI_FRAME_CMD_MAXFRAMERATE ( 1000 / 50 ) + +#define MAX_ANIMDEFNAME 48 +#define MAX_ARGS_PER_CMD 32 + +#define MAX_ANIMFULLNAME 128 +#define MAX_DEFNAME 128 +#define MAX_SKELNAME 128 + +typedef struct +{ + int frame_num; + int num_args; + int ofs_args[MAX_ARGS_PER_CMD]; +} dtikicmd_t; + +#define MAX_EXPRESSION_NAME_LENGTH 32 + +typedef struct +{ + int morph_index; + float percent; +} dtikimorphtarget_t; + +typedef struct +{ + char alias[ MAX_EXPRESSION_NAME_LENGTH ]; + int num_morph_targets; + int ofs_morph_targets; +} dtikiexpression_t; + +#define MAX_TIKI_SKINS 4 +typedef struct + { + char name[MAX_QPATH]; // polyset name + char shader[MAX_TIKI_SKINS][MAX_SHADERNAME];// shader name +#ifdef UTILS + int shaderIndex[MAX_TIKI_SKINS]; // for in-game use +#else + qhandle_t hShader[MAX_TIKI_SKINS]; // for in-game use +#endif + int numskins; // number of skins for this surface + int flags; + float damage_multiplier; + } dtikisurface_t; + +typedef struct + { + int skelIndex; + int surfaceIndex; + int boneMappingIndex; + int morphMappingIndex; + char skelName[ MAX_SKELNAME ]; + char morphName[ MAX_SKELNAME ]; + } dtikiReplacedSurface_t; + +typedef struct +{ + int bone; + int surface; + float radius; + float extra_length; +} dtikibonemapping_t; + +// +// Animation flags +// +#define MDL_ANIM_DELTA_DRIVEN ( 1 << 0 ) +#define MDL_ANIM_DEFAULT_ANGLES ( 1 << 3 ) + +typedef struct +{ + char alias[MAX_ANIMDEFNAME]; // anim name from grabbing + char fullname[MAX_ANIMFULLNAME]; // the full path name of the animation + float weight; + int blendtime; + int handle; + int flags; + qboolean _needsSetup; + int num_client_cmds; + int ofs_client_cmds; + int num_server_cmds; + int ofs_server_cmds; +} dtikianimdef_t; + +// +// surface flags for models +// +// + +// +// Surface flags sent over the net when changed +// +#define MDL_SURFACE_SKINOFFSET_BIT0 ( 1 << 0 ) +#define MDL_SURFACE_SKINOFFSET_BIT1 ( 1 << 1 ) +#define MDL_SURFACE_NODRAW ( 1 << 2 ) +#define MDL_SURFACE_SURFACETYPE_BIT0 ( 1 << 3 ) +#define MDL_SURFACE_SURFACETYPE_BIT1 ( 1 << 4 ) +#define MDL_SURFACE_SURFACETYPE_BIT2 ( 1 << 5 ) +#define MDL_SURFACE_CROSSFADE_SKINS ( 1 << 6 ) +#define MDL_SURFACE_SKIN_NO_DAMAGE ( 1 << 7 ) + +// +// Surface flags which are static (not sent over net) +// +#define MDL_SURFACE_NOMIPMAPS ( 1 << 8 ) +#define MDL_SURFACE_NOPICMIP ( 1 << 9 ) +#define MDL_SURFACE_NOCUSTOMSHADER ( 1 << 10 ) + +typedef struct dtiki_s +{ + char name[MAX_DEFNAME]; + int num_surfaces; + int num_tags; + int num_anims; + int num_bone_mappings; + int ofs_bone_mappings; + int ofsAnimBoneMappings; + int num_expressions; + int ofs_expressions; + void *alias_list; + int num_client_initcmds; + int ofs_client_initcmds; + int num_server_initcmds; + int ofs_server_initcmds; + int ofs_surfaces; + int ofs_replacedSurfaces; + float load_scale; + float lod_scale; + float lod_bias; + float lod_start_distance; + float lod_start_percent_verts; + float lod_stop_distance; + float lod_stop_percent_verts; + float fade_dist_mod; + vec3_t boundingVolumeMin; + vec3_t boundingVolumeMax; + vec3_t light_offset; + vec3_t head_offset; + qboolean _loadAllAnims; + vec3_t load_origin; + vec3_t mins; + vec3_t maxs; + float radius; + int skelIndex; + char skelName[ MAX_SKELNAME ]; + char morphName[ MAX_SKELNAME ]; + unsigned int exclusive_light_name_hash; + int ofs_animdefs[ 1 ]; +} dtiki_t; + +/* +============================================================================== + + .BSP file format + +============================================================================== +*/ + + +#define BSP_HEADER (('!'<<24)+('2'<<16)+('F'<<8)+'E') + // little-endian "EF2!" +#define BSP_VERSION 20 + + +// there shouldn't be any problem with increasing these values at the +// expense of more memory allocation in the utilities +#define MAX_MAP_MODELS 0x400 +#define MAX_MAP_BRUSHES 0x8000 +#define MAX_MAP_ENTITIES 0x1000 +#define MAX_MAP_ENTSTRING 0x400000 // 0x80000 +#define MAX_MAP_SHADERS 0x400 + +#define MAX_MAP_SHADERSTRING 0x4000 +#define MAX_MAP_NUM_SHADERS 4096 + +#define MAX_MAP_AREAS 0x100 // MAX_MAP_AREA_BYTES in q_shared must match! +#define MAX_MAP_FOGS 0x100 +#define MAX_MAP_PLANES 0x20000 +#define MAX_MAP_NODES 0x20000 +#define MAX_MAP_BRUSHSIDES 0x40000 +#define MAX_MAP_LEAFS 0x20000 +#define MAX_MAP_LEAFFACES 0x20000 +#define MAX_MAP_LEAFBRUSHES 0x40000 +#define MAX_MAP_PORTALS 0x20000 +#define MAX_MAP_LIGHTING 0x1000000 +#define MAX_MAP_LIGHTGRID 0x800000 +#define MAX_MAP_VISIBILITY 0x200000 +#define MAX_MAP_SPHERE_LIGHTS 0x400 + +#define MAX_MAP_DRAW_SURFS 0x20000 +#define MAX_MAP_DRAW_VERTS 0x80000 +#define MAX_MAP_DRAW_INDEXES 0x80000 + +#define MAX_MAP_LIGHTING_VERTS 0x80000 +#define MAX_MAP_LIGHTING_SURFS 0x20000 +#define MAX_MAP_LIGHTING_VERTSURFS 0x20000 + +#define MAX_MAP_LIGHTDEFS MAX_MAP_DRAW_SURFS + +#define MAX_LIGHTING_GROUPS 32 // NOTE: do NOT change this... it is apparently used as "the max number of bitfields stored in an int" (grumble grumble). + +#define MAX_STATIC_LOD_MODELS 0x1000 + +//#define MAX_MAP_BOUNDS 8192 +//#define MIN_MAP_BOUNDS ( -MAX_MAP_BOUNDS ) +//#define MAP_SIZE ( MAX_MAP_BOUNDS - MIN_MAP_BOUNDS ) + +// key / value pair sizes in the entities lump +#define MAX_KEY 32 +#define MAX_VALUE 1024 + +// the editor uses these predefined yaw angles to orient entities up or down +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define DEFAULT_CURVE_SUBDIVISIONS 4 +//#define DEFAULT_LIGHTMAP_RESOLUTION 32 +#define DEFAULT_LIGHTMAP_RESOLUTION 16 +#define DEFAULT_TERRAIN_LIGHTMAP_RESOLUTION 32 +#define MIN_LIGHTMAP_RESOLUTION 4 +#define MAX_LIGHTMAP_RESOLUTION 128 + +#define LIGHTMAP_WIDTH 128 +#define LIGHTMAP_HEIGHT 128 + +#define LIGHTMAP_SIZE ( LIGHTMAP_WIDTH * LIGHTMAP_HEIGHT * 3 ) + +#define MIN_WORLD_COORD ( -65536 ) +#define MAX_WORLD_COORD ( 65536 ) + +#define WORLD_SIZE ( MAX_WORLD_COORD - MIN_WORLD_COORD ) + + +//============================================================================= + + +// in game version of lump +typedef struct { + void *buffer; + int length; +} gamelump_t; + +typedef struct { + int fileofs, filelen; +} lump_t; + +#define LUMP_SHADERS 0 +#define LUMP_PLANES 1 +#define LUMP_LIGHTMAPS 2 +#define LUMP_BASELIGHTMAPS 3 +#define LUMP_CONTLIGHTMAPS 4 +#define LUMP_SURFACES 5 +#define LUMP_DRAWVERTS 6 +#define LUMP_DRAWINDEXES 7 +#define LUMP_LEAFBRUSHES 8 +#define LUMP_LEAFSURFACES 9 +#define LUMP_LEAFS 10 +#define LUMP_NODES 11 +#define LUMP_BRUSHSIDES 12 +#define LUMP_BRUSHES 13 +#define LUMP_FOGS 14 +#define LUMP_MODELS 15 +#define LUMP_ENTITIES 16 +#define LUMP_VISIBILITY 17 +#define LUMP_LIGHTGRID 18 +#define LUMP_ENTLIGHTS 19 +#define LUMP_ENTLIGHTSVIS 20 +#define LUMP_LIGHTDEFS 21 +#define LUMP_BASELIGHTINGVERTS 22 +#define LUMP_CONTLIGHTINGVERTS 23 +#define LUMP_BASELIGHTINGSURFS 24 +#define LUMP_LIGHTINGSURFS 25 +#define LUMP_LIGHTINGVERTSURFS 26 +#define LUMP_LIGHTINGGROUPS 27 +#define LUMP_STATIC_LOD_MODELS 28 +#define LUMP_BSPINFO 29 +#define HEADER_LUMPS 30 + + +typedef struct { + int ident; + int version; + int checksum; + + lump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct { + float mins[3], maxs[3]; + int firstSurface, numSurfaces; + int firstBrush, numBrushes; +} dmodel_t; + +typedef struct { + char shader[MAX_QPATH]; + int surfaceFlags; + int contentFlags; + int subdivisions; +} dshader_t; + +// planes x^1 is allways the opposite of plane x + +typedef struct { + float normal[3]; + float dist; +} dplane_t; + +typedef struct { + int planeNum; + int children[2]; // negative numbers are -(leafs+1), not nodes + int mins[3]; // for frustom culling + int maxs[3]; +} dnode_t; + +typedef struct { + int cluster; // -1 = opaque cluster (brushes but no surfaces) + int area; + + int mins[3]; // for frustum culling + int maxs[3]; + + int numLeafSurfaces; + int firstLeafSurface; + + int firstLeafBrush; + int numLeafBrushes; +} dleaf_t; + +typedef struct { + int shaderNum; + int planeNum; // positive plane side faces out of the leaf +} dbrushside_t; + +typedef struct { + int numSides; + int firstSide; + int shaderNum; // the shader that determines the contents flags +} dbrush_t; + +typedef struct { + char shader[MAX_QPATH]; + int brushNum; + int visibleSide; // the brush side that ray tests need to clip against (-1 == none) +} dfog_t; + +#ifndef QERADIANT + +typedef struct { + vec3_t xyz; + float st[2]; + vec3_t normal; + byte color[4]; + float lodExtra; + union + { + float lightmap[2]; + int collapseMap; + }; +} drawVert_t; + +typedef struct { + byte color[3]; +} lightingVert_t; + +typedef struct { + int baseLightmapNum; + int baseLightmapX, baseLightmapY; + int baseLightmapWidth, baseLightmapHeight; + + int lastUpdatedFrame; + + int firstLightingSurface; + int numLightingSurfaces; + + int baseLightingVerts; + int firstLightingVertSurface; + int numLightingVertSurfaces; +} baseLightingSurf_t; + +typedef struct { + int lightmapNum; + + int lightmapX, lightmapY; + int lightGroupNum; + + byte color[3]; +} lightingSurf_t; + +typedef struct { + int lightGroupNum; + int firstLightingVert; + byte color[3]; +} lightingVertSurf_t; + +#define DYNAMIC_LIGHTMAP_BIT ( 1 << 30 ) + +#define MAX_LIGHT_GROUP_NAME_LENGTH 64 + +typedef struct { + char name[ MAX_LIGHT_GROUP_NAME_LENGTH ]; +} lightingGroup_t; + +typedef struct { + int totalNumberOfVerts; + float origin[3]; + float startDistance; + float startPerCentVerts; + float stopDistance; + float stopPerCentVerts; + float alphaFadeDistance; + int lodNumberOfVerts; + int frameCount; +} staticLodModel_t; + +#endif + +typedef enum { + MST_BAD, + MST_PLANAR, + MST_PATCH, + MST_TRIANGLE_SOUP, + MST_FLARE, + MST_TERRAIN +} mapSurfaceType_t; + +#define LIGHTMAP_NONE -1 +#define LIGHTMAP_NEEDED -2 +#define NUMBER_TERRAIN_VERTS 9 + +typedef struct { + int shaderNum; + int fogNum; + int surfaceType; + + int firstVert; + int numVerts; + + int firstIndex; + int numIndexes; + + int lightmapNum; + int lightmapX, lightmapY; + int lightmapWidth, lightmapHeight; + + vec3_t lightmapOrigin; + vec3_t lightmapVecs[3]; // for patches, [0] and [1] are lodbounds + + int patchWidth; + int patchHeight; + + float subdivisions; + + int baseLightingSurface; + // Terrain fields + int inverted; + int faceFlags[4]; +} dsurface_t; + +// the light grid may not contain the entire bounds of the world, to +// allow q3test2 like levels that float in the middle of a giant sky box +// to not waste huge amounts of time and space +typedef struct { + vec3_t origin; + vec3_t axis; + int bounds[3]; +} dlightGrid_t; + +typedef struct { + int lightIntensity; + int lightAngle; + int lightmapResolution; + qboolean twoSided; + qboolean lightLinear; + vec3_t lightColor; + float lightFalloff; + float backsplashFraction; + float backsplashDistance; + float lightSubdivide; + qboolean autosprite; +} dlightdef_t; + + +//--------------------------------------------------------------------------- +// mapspherelext_s (Squirrel) +// +// An extension of the struct which adds new lighting +// system characteristics to LUMP_ENTLIGHTS. +//--------------------------------------------------------------------------- +typedef struct mapspherelext_ mapspherelext_s; +struct mapspherelext_ +{ + vec3_t targetNormal; + float dotProduct_hotspot; + float dotProduct_penumbra; + float falloff_start_dist; + float falloff_end_dist; + float curveOffset; + float spherical_ambient; + float brightness; + int exclusiveLightNameCount; + unsigned int exclusiveLightNameHashList[ MAX_LIGHT_EXCLUSIVE_NAMES ]; +}; + + +//--------------------------------------------------------------------------- +// mapspherel_t (legacy) +//--------------------------------------------------------------------------- +typedef struct mapspherel_s mapspherel_t; +struct mapspherel_s +{ + vec3_t origin; + vec3_t color; +// float intensity; +// float min_intensity; + int leaf; + qboolean needs_trace; + qboolean lensflare; + int type; +// vec3_t spot_dir; +// float spot_radiusbydistance; + int group_num; + + mapspherelext_s extras; // additional set of fields added to support entity lights in the new system (semi-hack) +}; + + + +//--------------------------------------------------------------------------- +// bspSubInfoStruct_t +//--------------------------------------------------------------------------- +typedef struct bspSubInfoStruct_s bspSubInfoStruct_t; +struct bspSubInfoStruct_s +{ + int subInfoBufferID; + char subInfoBuffer[ MAX_SUBINFO_BUFFER_SIZE ]; +}; + + + +// +// Q3RADIANT defines +// +#define MAX_BRUSH_SIZE WORLD_SIZE + +/* +============================================================================== + + .WAL texture file format + +============================================================================== +*/ + + +#define MIPLEVELS 4 +typedef struct miptex_s +{ + char name[32]; + unsigned width, height; + unsigned offsets[MIPLEVELS]; // four mip maps stored + char animname[32]; // next frame in animation chain + int flags; + int contents; + int value; +} miptex_t; + + +/* +============================================================================== +LIGHTING DEFINITIONS +============================================================================== +*/ +#define LIGHTING_GRIDSIZE_X 192 +#define LIGHTING_GRIDSIZE_Y 192 +#define LIGHTING_GRIDSIZE_Z 320 +#define LIGHTING_GRIDSIZE { LIGHTING_GRIDSIZE_X, LIGHTING_GRIDSIZE_Y, LIGHTING_GRIDSIZE_Z } +#define LIGHTING_POINTSCALE 7500 +#define LIGHTING_LINEARSCALE ( 1.0f / 8000.0f ) +#define LIGHTING_SUNDIRECTION { 0.45, 0.3, 0.9 } +#define LIGHTING_SUNCOLOR { 100, 100, 92 } +#define LIGHTING_DEFAULT_INTENSITY 300.0f +#define LIGHTING_AREASCALE 0.25 +#define LIGHTING_FORMFACTORSCALE 3 + +#define LIGHTING_SPOTRADIUS 64 +#define LIGHTING_SPOTDISTANCE 64 +#define LIGHTING_RADIUSBYDISTANCE( rad, dist ) ( ( ( ( rad ) + 16 ) / ( dist ) ) ) +#define LIGHTING_SUNLIGHT( dot, sunColor, dest ) \ + { \ + float sunlight_scale = 2 * ( dot ); \ + if ( sunlight_scale > 1 ) sunlight_scale = 1; \ + VectorMA( ( dest ), (sunlight_scale + (1-sunlight_scale)/4), ( sunColor ), ( dest ) ); \ + } +#define LIGHTING_LINEARPOINTLIGHT( dot, photons, linearscale, distance, falloff ) \ + ( ( dot ) * ( photons ) * ( linearscale ) - ( ( distance ) / ( falloff ) ) ) +#define LIGHTING_POINTLIGHT( dot, photons, distance ) \ + ( ( dot ) * ( photons ) / ( ( distance ) * ( distance ) ) ) + +#define LIGHTING_DISTANCE_AT_POWER( lightintensity, power ) sqrt( LIGHTING_POINTSCALE * ( lightintensity ) / ( power ) ) + + +//--------------------------------------------------------------------------- +// lightFlags_e +//--------------------------------------------------------------------------- +enum lightFlags_ +{ + /// Spawnflags for light entities (Squirrel) + SF_LIGHT_LINEAR = BIT( 0 ), // 1 + SF_LIGHT_NO_ENTITIES = BIT( 1 ), // 2 + SF_LIGHT_NEEDS_TRACE = BIT( 2 ), // 4 + SF_LIGHT_SUN = BIT( 3 ), // 8 + SF_LIGHT_DYNAMIC = BIT( 4 ), // 16 + SF_LIGHT_LENSFLARE = BIT( 5 ), // 32 + SF_LIGHT_NO_WORLD = BIT( 6 ), // 64 +}; +typedef enum lightFlags_ lightFlags_e; + +typedef enum +{ + emit_point, + emit_area, + emit_spotlight, + emit_sun +} emittype_t; + +unsigned int SurfaceNameToType( const char *surfaceName ); +const char *SurfaceTypeToName( unsigned int surfaceType ); + +#endif + diff --git a/Shared/qcommon/quaternion.h b/Shared/qcommon/quaternion.h new file mode 100644 index 0000000..774b33d --- /dev/null +++ b/Shared/qcommon/quaternion.h @@ -0,0 +1,776 @@ +//----------------------------------------------------------------------------- +// Quaternion.h +// +// Author: Squirrel Eiserloh +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// Header for (and inline implementation of) a basic Quaternion class. +// This should ultimately be part of a yet-to-be-written general-purpose +// Rotation class at some point, time-permitting. +// +#ifndef _QUATERNION_H_ +#define _QUATERNION_H_ + +#include +#include "vector.h" +//#include "Matrix.h" + +#define UNUSED_ARG (void) + + + +//--------------------------------------------------------------------------- +// Quaternion +// +// ...in the form (x, y, z, w) such that q = xi + yj + zk + w, where: +// +// i x i = -1 i x j = k j x i = -k +// j x j = -1 and j x k = i and k x j = -i +// k = k = -1 k x i = j i x k = -j +// +// Note that i, j, and k are each imaginary numbers of different "flavors" +// representing mutually perpendicular unit vectors defining 3 of the +// 4 axes in quaternion 4-space. (The fourth axis is the real unit, 1.) +// +// In vector form, the quaternion would look like (s, v), where s is a +// scalar (equal to w) and v is a vector (x, y, z) in quaternion 4-space +// (giving position along the base unit axis vectors i, j, and k, +// respectively). +// +// The most useful quaternions are those that are of unit length +// (|q| = 1, or x*x + y*y + z*z + w*w = 1). This defines a set +// of points which make up a 4-dimensional "unit hypersphere", +// across the surface of which we will be interpolating. +// +// Note that Quaternion multiplication involves the vector cross +// product (v1 x v2), so it is NOT COMMUTATIVE. This means that +// q1 x q2 != q2 x q1. (Then again, matrix multiplication isn't +// commutative either, so suck it down.) +// +// "lhs" and "rhs" mean "left hand side" and "right hand side" for +// operator arguments, respectively. +//--------------------------------------------------------------------------- +class Quaternion +{ + //--------------------------------------------------------------------------- + // Member variables + //--------------------------------------------------------------------------- +private: + float _x; // coefficient for the i imaginary term + float _y; // coefficient for the j imaginary term + float _z; // coefficient for the k imaginary term + float _w; // coefficient for the real term + + //--------------------------------------------------------------------------- + // Accessors / Mutators + //--------------------------------------------------------------------------- +protected: + + //--------------------------------------------------------------------------- + // Implementation Methods + //--------------------------------------------------------------------------- +private: + + //--------------------------------------------------------------------------- + // Construction / Destruction + //--------------------------------------------------------------------------- +public: + ~Quaternion(); + explicit Quaternion(); // default constructor + Quaternion( const Quaternion& rhs ); // copy constructor + explicit Quaternion( const float x, const float y, const float z, const float w ); + explicit Quaternion( const float w, const Vector& vec ); + explicit Quaternion( const Vector& eulerAngles ); +// explicit Quaternion( const Matrix3x3& rotationMatrix ); +// explicit Quaternion( const Matrix4x4& transformMatrix ); + + //--------------------------------------------------------------------------- + // Interface Methods + //--------------------------------------------------------------------------- +public: + float CalcLength( void ) const; + float CalcLengthSquared( void ) const; + float Normalize( void ); + + void SetFromSV( const float w, const Vector& vec ); + void SetFromXYZW( const float x, const float y, const float z, const float w ); + void SetFromEuler( const Vector& eulerAngles ); +// void SetFromMatrix3x3( const Matrix3x3& rotationMatrix ); +// void SetFromMatrix4x4( const Matrix4x4& transformMatrix ); + + void GetToSV( float& w, Vector& vec ) const; + void GetToXYZW( float& x, float& y, float& z, float& w ) const; + void GetToEuler( Vector& eulerAngles ) const; +// void GetToMatrix3x3( Matrix3x3& rotationMatrix ) const; +// void GetToMatrix4x4( Matrix4x4& transformMatrix ) const; + + /// Self-modifying operators + const Quaternion& operator = ( const Quaternion& rhs ); + const Quaternion& operator += ( const Quaternion& rhs ); + const Quaternion& operator -= ( const Quaternion& rhs ); + const Quaternion& operator *= ( const float scale ); + const Quaternion& operator *= ( const Quaternion& rhs ); + const Quaternion& operator /= ( const float invScale ); + const Quaternion operator - () const; + + /// Construction operators + const Quaternion operator + ( const Quaternion& rhs ) const; + const Quaternion operator - ( const Quaternion& rhs ) const; + + bool operator == ( const Quaternion& rhs ) const; + bool operator != ( const Quaternion& rhs ) const; + +private: + const Quaternion operator * ( const float scale ) const; // multiply-by-right-scalar forbidden; use (float, Quaternion&) version instead +}; +//--------------------------------------------------------------------------- +// External Operators & Functions +//--------------------------------------------------------------------------- +const Quaternion operator * ( const Quaternion& lhs, const Quaternion& rhs ); +const Quaternion operator * ( const float scale, const Quaternion& rhs ); +const Quaternion operator / ( const Quaternion& lhs, const float invScale ); +const Quaternion CalcSlerp( const Quaternion& q1, const Quaternion& q2, const float fraction ); +const Quaternion CalcLerp( const Quaternion& q1, const Quaternion& q2, const float q2Fraction ); +const Quaternion CalcNoLerp( const Quaternion& q1, const Quaternion& q2, const float q2Fraction ); +float QuaternionDotProduct( const Quaternion& q1, const Quaternion& q2 ); + + + +//--------------------------------------------------------------------------- +// Destructor +//--------------------------------------------------------------------------- +inline Quaternion::~Quaternion() +{ +} + + +//--------------------------------------------------------------------------- +// Default constructor +//--------------------------------------------------------------------------- +inline Quaternion::Quaternion() +{ + // Do nothing; this should be used only by static array declarations +} + + +//--------------------------------------------------------------------------- +// Copy Constructor( Quaternion ) +//--------------------------------------------------------------------------- +inline Quaternion::Quaternion( const Quaternion& rhs ) +{ + SetFromXYZW( rhs._x, rhs._y, rhs._z, rhs._w ); +} + + +//--------------------------------------------------------------------------- +// Constructor( float, float, float, float ) +//--------------------------------------------------------------------------- +inline Quaternion::Quaternion( const float x, const float y, const float z, const float w ) +{ + SetFromXYZW( x, y, z, w ); +} + + +//--------------------------------------------------------------------------- +// Constructor( float, Vector ) +//--------------------------------------------------------------------------- +inline Quaternion::Quaternion( const float w, const Vector& vec ) +{ + SetFromSV( w, vec ); +} + + +//--------------------------------------------------------------------------- +// Constructor( Vector ) +//--------------------------------------------------------------------------- +inline Quaternion::Quaternion( const Vector& eulerAngles ) +{ + SetFromEuler( eulerAngles ); +} + + +//--------------------------------------------------------------------------- +// Constructor( Matrix3x3 ) +//--------------------------------------------------------------------------- +/* +inline Quaternion::Quaternion( const Matrix3x3& rotationMatrix ) +{ + SetFromMatrix3x3( rotationMatrix ); +} +*/ + +//--------------------------------------------------------------------------- +// Constructor( Matrix4x4 ) +//--------------------------------------------------------------------------- +/* +inline Quaternion::Quaternion( const Matrix4x4& transformMatrix ) +{ + SetFromMatrix4x4( transformMatrix ); +} +*/ + +//--------------------------------------------------------------------------- +// CalcLength() +//--------------------------------------------------------------------------- +inline float Quaternion::CalcLength( void ) const +{ + float length = (float) sqrt( CalcLengthSquared() ); + return( length ); +} + + +//--------------------------------------------------------------------------- +// CalcLengthSquared() +//--------------------------------------------------------------------------- +inline float Quaternion::CalcLengthSquared( void ) const +{ + float lengthSquared; + lengthSquared = (_x * _x) + (_y * _y) + (_z * _z) + (_w * _w); + return( lengthSquared ); +} + + +//--------------------------------------------------------------------------- +// Normalize +//--------------------------------------------------------------------------- +inline float Quaternion::Normalize( void ) +{ + /// Get the length of the quaternion 4d vector + float length = CalcLength(); + if( !length ) + return( 0.0f ); + + /// Divide each component by + *this /= length; + return( length ); +} + + +//--------------------------------------------------------------------------- +// SetFromSV +//--------------------------------------------------------------------------- +inline void Quaternion::SetFromSV( const float w, const Vector& vec ) +{ + SetFromXYZW( vec.x, vec.y, vec.z, w ); +} + + +//--------------------------------------------------------------------------- +// SetFromXYZW +//--------------------------------------------------------------------------- +inline void Quaternion::SetFromXYZW( const float x, const float y, const float z, const float w ) +{ + _x = x; + _y = y; + _z = z; + _w = w; +} + + +//--------------------------------------------------------------------------- +// SetFromEuler +//--------------------------------------------------------------------------- +inline void Quaternion::SetFromEuler( const Vector& eulerAngles ) +{ + // FIXME: THIS IS TEMPORARY HACKED STUFF FOR PROOF OF CONCEPT ONLY!!! + float quat[ 4 ]; + vec3_t eulerAng; + + eulerAngles.copyTo( eulerAng ); + EulerToQuat( eulerAng, quat ); + + _x = quat[ 0 ]; + _y = quat[ 1 ]; + _z = quat[ 2 ]; + _w = quat[ 3 ]; +} + + +//--------------------------------------------------------------------------- +// SetFromMatrix3x3 +//--------------------------------------------------------------------------- +/* +inline void Quaternion::SetFromMatrix3x3( const Matrix3x3& rotationMatrix ) +{ + // FIXME: stub + UNUSED_ARG rotationMatrix; +} +*/ + +//--------------------------------------------------------------------------- +// SetFromMatrix4x4 +//--------------------------------------------------------------------------- +/* +inline void Quaternion::SetFromMatrix4x4( const Matrix4x4& transformMatrix ) +{ + // FIXME: stub + UNUSED_ARG transformMatrix; +} +*/ + +//--------------------------------------------------------------------------- +// GetToSV +//--------------------------------------------------------------------------- +inline void Quaternion::GetToSV( float& w, Vector& vec ) const +{ + GetToXYZW( vec.x, vec.y, vec.z, w ); +} + + +//--------------------------------------------------------------------------- +// GetToXYZW +//--------------------------------------------------------------------------- +inline void Quaternion::GetToXYZW( float& x, float& y, float& z, float& w ) const +{ + x = _x; + y = _y; + z = _z; + w = _w; +} + + +//--------------------------------------------------------------------------- +// GetToEuler +//--------------------------------------------------------------------------- +inline void Quaternion::GetToEuler( Vector& eulerAngles ) const +{ + // FIXME: THIS IS TEMPORARY HACKED STUFF FOR PROOF OF CONCEPT ONLY!!! + float matrix[ 3 ][ 3 ]; + float quat[ 4 ]; + vec3_t eulerAng; + + quat[ 0 ] = _x; + quat[ 1 ] = _y; + quat[ 2 ] = _z; + quat[ 3 ] = _w; + + QuatToMat( quat, matrix ); + MatrixToEulerAngles( matrix, eulerAng ); + eulerAngles.setXYZ( eulerAng[0], eulerAng[1], eulerAng[2] ); +} + + +//--------------------------------------------------------------------------- +// GetToMatrix3x3 +//--------------------------------------------------------------------------- +/* +inline void Quaternion::GetToMatrix3x3( Matrix3x3& rotationMatrix ) const +{ + // FIXME: stub + UNUSED_ARG rotationMatrix; +} +*/ + +//--------------------------------------------------------------------------- +// GetToMatrix4x4 +//--------------------------------------------------------------------------- +/* +inline void Quaternion::GetToMatrix4x4( Matrix4x4& transformMatrix ) const +{ + // FIXME: stub + UNUSED_ARG transformMatrix; +} +*/ + +//--------------------------------------------------------------------------- +// operator = (Quaternion) +//--------------------------------------------------------------------------- +inline const Quaternion& Quaternion::operator = ( const Quaternion& rhs ) +{ + if( this == &rhs ) + return *this; + + _x = rhs._x; + _y = rhs._y; + _z = rhs._z; + _w = rhs._w; + return *this; +} + + +//--------------------------------------------------------------------------- +// operator += (Quaternion) +//--------------------------------------------------------------------------- +inline const Quaternion& Quaternion::operator += ( const Quaternion& rhs ) +{ + _x += rhs._x; + _y += rhs._y; + _z += rhs._z; + _w += rhs._w; + return *this; +} + + +//--------------------------------------------------------------------------- +// operator -= (Quaternion) +//--------------------------------------------------------------------------- +inline const Quaternion& Quaternion::operator -= ( const Quaternion& rhs ) +{ + _x -= rhs._x; + _y -= rhs._y; + _z -= rhs._z; + _w -= rhs._w; + return *this; +} + + +//--------------------------------------------------------------------------- +// operator *= (float) +//--------------------------------------------------------------------------- +inline const Quaternion& Quaternion::operator *= ( const float scale ) +{ + _x *= scale; + _y *= scale; + _z *= scale; + _w *= scale; + return *this; +} + + +//--------------------------------------------------------------------------- +// operator *= (Quaternion) +// +// Quaternion multiplication is NOT COMMUTATIVE, and is defined as +// follows, where "s" is the scalar component (w) and "v" is the vector +// component (x,y,z) (and 'x' means "3d vector cross product", '|' means +// "3d vector dot product"): +// +// q1 * q2 = q3 = (s3, v3) != q2 * q1 +// +// s3 = (s1 * s2) - (v1 | v2) +// v3 = (s1 * v2) + (s2 * v1) + (v1 x v2) +// +// i.e. Q(s1, v1) * Q(s2, v2) = Q(s3, v3) +//--------------------------------------------------------------------------- +inline const Quaternion& Quaternion::operator *= ( const Quaternion& rhs ) +{ + float s1, s2, s3; // see above comment + Vector v1, v2, v3; // see above comment + + /// Get both quaternions into (s,v) form + GetToSV( s1, v1 ); + rhs.GetToSV( s2, v2 ); + + /// Calculate the new scalar term (s3) + s3 = (s1 * s2) - Vector::Dot( v1, v2 ); + + /// Calculate the new vector term (v3) + Vector v1CrossV2; // temp variable for cross-product result, since our Vector class sucks + v1CrossV2.CrossProduct( v1, v2 ); // our vector class sucks + v3 = (s1 * v2) + (s2 * v1) + v1CrossV2; + + /// Set the new scalar and vector terms and return this + SetFromSV( s3, v3 ); + return *this; +} + + +//--------------------------------------------------------------------------- +// operator /= (float) +//--------------------------------------------------------------------------- +inline const Quaternion& Quaternion::operator /= ( const float invScale ) +{ + if( invScale ) + { + _x /= invScale; + _y /= invScale; + _z /= invScale; + _w /= invScale; + } + else + { + SetFromXYZW( 0.0f, 0.0f, 0.0f, 0.0f ); + } + + return *this; +} + + +//--------------------------------------------------------------------------- +// operator - (Quaternion) : unary minus +//--------------------------------------------------------------------------- +inline const Quaternion Quaternion::operator - () const +{ + return Quaternion( -_x, -_y, -_z, -_w ); +} + + +//--------------------------------------------------------------------------- +// operator == (Quaternion, Quaternion) +//--------------------------------------------------------------------------- +//inline bool operator == ( const Quaternion& lhs, const Quaternion& rhs ) +inline bool Quaternion::operator == ( const Quaternion& rhs ) const +{ + if( _x != rhs._x ) + return false; + + if( _y != rhs._y ) + return false; + + if( _z != rhs._z ) + return false; + + if( _w != rhs._w ) + return false; + + return true; +} + + +//--------------------------------------------------------------------------- +// operator != (Quaternion, Quaternion) +//--------------------------------------------------------------------------- +inline bool Quaternion::operator != ( const Quaternion& rhs ) const +{ + return( !(*this == rhs) ); +} + + +//--------------------------------------------------------------------------- +// operator - (Quaternion, Quaternion) +//--------------------------------------------------------------------------- +inline const Quaternion Quaternion::operator - ( const Quaternion& rhs ) const +{ + Quaternion difference( *this ); + difference -= rhs; + return( difference ); +} + +//--------------------------------------------------------------------------- +// operator + (Quaternion, Quaternion) +//--------------------------------------------------------------------------- +inline const Quaternion Quaternion::operator + ( const Quaternion& rhs ) const +//Quaternion Quaternion::operator + ( const Quaternion& rhs ) const +{ + Quaternion sum( *this ); + sum += rhs; + return( sum ); +} + + +//--------------------------------------------------------------------------- +// general operator * (Quaternion, Quaternion) +//--------------------------------------------------------------------------- +inline const Quaternion operator * ( const Quaternion& lhs, const Quaternion& rhs ) +{ + Quaternion product( lhs ); + product *= rhs; + return product; +} + + +//--------------------------------------------------------------------------- +// general operator * (float, Quaternion) +//--------------------------------------------------------------------------- +inline const Quaternion operator * ( const float scale, const Quaternion& rhs ) +{ + Quaternion scaled( rhs ); + scaled *= scale; + return scaled; +} + + +//--------------------------------------------------------------------------- +// general operator / (Quaternion, float) +//--------------------------------------------------------------------------- +inline const Quaternion operator / ( const Quaternion& lhs, const float invScale ) +{ + Quaternion scaled( lhs ); + if( invScale ) + { + scaled /= invScale; + } + else + { + scaled *= 0.0f; + } + return scaled; +} + + +//--------------------------------------------------------------------------- +// general CalcLerp (Quaternion, Quaternion, float) +// +// Performs a hypervector linear interpolation - or "lerp" - of two +// Quaternions and returns the resulting (newly constructed) Quaternion. +// +// is a value in the range [0,1] representing how much of +// to use in the interpolation; q1Fraction = 1- is the +// weighting given to . +// +// NOTE: This interpolation is faster, but less accurate, than CalcSlerp(). +// Use CalcSlerp() if the error incurred from CalcLerp() is noticeable. +// +// For optimization purposes, CalcLerp() assumes and are +// already normalized. +//--------------------------------------------------------------------------- +inline const Quaternion CalcLerp( const Quaternion& q1, const Quaternion& q2, const float q2Fraction ) +{ + const float q1Fraction = 1.0f - q2Fraction; + + /// Check if and are the same quaternion + if( &q1 == &q2 ) + return Quaternion( q1 ); + + /// Check if and are data-identical + if( q1 == q2 ) + return Quaternion( q1 ); + + /// Check if is at (or beyond) a boundary condition + if( q2Fraction <= 0.0f ) + return Quaternion( q1 ); + if( q2Fraction >= 1.0f ) + return Quaternion( q2 ); + + /// Create a new quaternion which represents the weighted average of and + Quaternion lerped( (q1Fraction * q1) + (q2Fraction * q2) ); + float lerpedLength = lerped.Normalize(); + + /// Check if the 4d vectors added up to 0.0 (degenerate case!) + if( !lerpedLength ) + { + /// Return whichever of or the parameter is currently closest to + return CalcNoLerp( q1, q2, q2Fraction ); + } + + return lerped; +} + + +//--------------------------------------------------------------------------- +// general CalcSlerp (Quaternion, Quaternion, float) +// +// Performs a (hyper)spherical linear interpolation - or "slerp" - of two +// Quaternions and returns the resulting (newly constructed) Quaternion. +// +// is a value in the range [0,1] representing how much of +// to use in the interpolation; q1Fraction = 1- is the +// weighting given to . +// +// For optimization purposes, CalcSlerp() assumes and are +// already normalized. +//--------------------------------------------------------------------------- +inline const Quaternion CalcSlerp( const Quaternion& q1, const Quaternion& q2, const float q2Fraction ) +{ + static const float SIN_OMEGA_EPSILON = 0.00001f; + const float q1Fraction = 1.0f - q2Fraction; + + /// Check if and are one and the same + if( &q1 == &q2 ) + return Quaternion( q1 ); + + /// Check if and are data-identical + if( q1 == q2 ) + return Quaternion( q1 ); + + /// Check if is at (or beyond) a boundary condition + if( q2Fraction <= 0.0f ) + return Quaternion( q1 ); + if( q2Fraction >= 1.0f ) + return Quaternion( q2 ); + + /// Calculate , the dot product (cosine) of the angle between the two 4d hypervectors + float cosOmega = QuaternionDotProduct( q1, q2 ); + + /// Create a copy of q2 and invert it if is negative (i.e. it's on the opposite side of the hypersphere) + Quaternion q2copy( q2 ); + if( cosOmega < 0.0f ) + { + /// Mirror the hypervector (and, therefore, the dot product) to be on the same side of the hypersphere as + cosOmega = -cosOmega; + q2copy *= -1.0f; + } + + /// Check if either or was not normalized + if( cosOmega > 1.0f ) + { + /// FIXME: One or both of or were evidently not normalized; this should be an error condition! + /// Return whichever of or the parameter is currently closest to + return CalcNoLerp( q1, q2, q2Fraction ); + } + + /// Check if and are close enough to use linear interpolation instead + if( (1.0f - cosOmega) > SIN_OMEGA_EPSILON ) + { + return CalcLerp( q1, q2, q2Fraction ); + } + + /// Check if and are nearly opposite on the hypersphere + if( (cosOmega + 1.0) < SIN_OMEGA_EPSILON ) + { + // FIXME: how should this case be handled? + // Watt & Watt does some voodoo-math which is clearly incorrect... + return CalcNoLerp( q1, q2, q2Fraction ); + } + + /// Calculate , the angle between and , based on the dot product (cosine) between and + const float omega = acos( cosOmega ); + const float sinOmega = sin( omega ); + + /// Check if is prohibitively small (since it will end up in the denominator later) + if( (sinOmega > -SIN_OMEGA_EPSILON) && (sinOmega < SIN_OMEGA_EPSILON) ) + { + /// Return whichever of or the parameter is currently closest to + return CalcNoLerp( q1, q2, q2Fraction ); + } + + /// Build a new quaternion, , which uses normal (hyper)spherical linear interpolation + Quaternion slerped; + float scale1 = sin( q1Fraction * omega ) / sinOmega; + float scale2 = sin( q2Fraction * omega ) / sinOmega; + slerped = (scale1 * q1) + (scale2 * q2); + + // FIXME: does need to be renormalized at this point?? + // (yes, but only because of floating-point drift, and it's the caller's reponsibility to do this) + + return slerped; +} + + +//--------------------------------------------------------------------------- +// general CalcNoLerp (Quaternion, Quaternion, float) +// +// This is a fake / stub quaternion interpolation function; it simply +// returns a copy of or based on which one +// indicates it is closer to. +//--------------------------------------------------------------------------- +inline const Quaternion CalcNoLerp( const Quaternion& q1, const Quaternion& q2, const float q2Fraction ) +{ + if( q2Fraction < 0.5f ) + { + return Quaternion( q1 ); + } + else + { + return Quaternion( q2 ); + } +} + + +//--------------------------------------------------------------------------- +// QuaternionDotProduct +// +// Calculates the dot product of and where both quaternions are +// taken as (probably unit) vectors in fourspace. +//--------------------------------------------------------------------------- +inline float QuaternionDotProduct( const Quaternion& q1, const Quaternion& q2 ) +{ + float x1, y1, z1, w1; + float x2, y2, z2, w2; + + q1.GetToXYZW( x1, y1, z1, w1 ); + q2.GetToXYZW( x2, y2, z2, w2 ); + + float dotProduct = (x1*x2) + (y1*y2) + (z1*z2) + (w1*w1); + return dotProduct; +} + + + +#endif // _QUATERNION_H_ diff --git a/Shared/qcommon/stringresource.hpp b/Shared/qcommon/stringresource.hpp new file mode 100644 index 0000000..a775bda --- /dev/null +++ b/Shared/qcommon/stringresource.hpp @@ -0,0 +1,119 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/uilib/console.h $ +// $Revision:: 2 $ +// $Author:: Steven $ +// $Date:: 10/10/01 10:29a $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// String resource system + +#ifndef STRING_RESOURCE_HPP +#define STRING_RESOURCE_HPP + + +#include +//#include "qcommon.h" +#include +#include + +//#include +//#include + +//The map types of the string. +//Global - this is a global string and is used for every level +//Level - this is a string specific for the level and therefore loaded when the level is loaded. +typedef enum StringType { GLOBAL, LEVEL }; +#define STRING_HASH_SIZE 1024 + + +//-------------------------- CLASS ---------------------------------- +// +// Name: StringMapEntry +// Base Class: None +// +// Description: This is the map entry that the String Resource contains. +// The StringMapEntry stores the string, id and the type of string +// that is used. +// +// Method Of Use: This is used internally by the String Resource class. +// +//------------------------------------------------------------------- +class StringEntry +{ + public: + StringEntry() { } + ~StringEntry() { } + + str& getString(void) { return _string; } + str& getStringId(void) { return _stringId; } + StringType getType(void) { return _type; } + StringEntry* getNext(void) { return _next; } + + void setString(const str& string) { _string = string; } + void setStringId(const str& stringId){ _stringId = stringId; } + void setType(StringType type) { _type = type; } + void setNext(StringEntry* next) { _next = next; } + + private: + str _string; + str _stringId; + StringType _type; + StringEntry* _next; +}; + + +//-------------------------- CLASS ---------------------------------- +// +// Name: StringResource +// Base Class: None +// +// Description: The String Resource is a system that loads a list of strings. Each of these strings +// are paired with an identifier. +// +// Method Of Use: This is used internally by the print statements, mainly center print. +// +//------------------------------------------------------------------- +class StringResource +{ + public: + static void createInstance(void); + static void deleteInstance(void); + static StringResource* getInstance(void) { if(_instance == 0) createInstance(); return _instance; } + + str& getStringResource(const str& stringId); + + void convertStringResourceIds(str& string); + void parseStringId(str& stringId); + void loadGlobalStrings(void); + void loadLevelStrings(const str& levelName); + void clearAllStrings(void); + void clearLevelStrings(void); + bool addStringEntry(StringEntry* stringEntry); + bool loadStrings(const str& fileName, StringType stringType); + void parseStringValue(str& stringValue); + + protected: + int getHashValue(const str& stringId); + + private: + StringResource(); + ~StringResource(); + + + static StringResource* _instance; + StringEntry* _stringHashTable[STRING_HASH_SIZE]; + str _notFoundString; + str _loadedLevel; + bool _globalLoaded; + bool _overwrite; + cvar_t* _languageCvar; +}; +#endif diff --git a/Shared/qcommon/tiki_script.h b/Shared/qcommon/tiki_script.h new file mode 100644 index 0000000..16d220f --- /dev/null +++ b/Shared/qcommon/tiki_script.h @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/Shared/qcommon/tiki_script.h $ +// $Revision:: 10 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// C++ implementaion of tokenizing text interpretation. Class accepts filename +// to load or pointer to preloaded text data. Standard tokenizing operations +// such as skip white-space, get string, get integer, get float, get token, +// and skip line are implemented. +// +// Note: all '//', '#', and ';' are treated as comments. Probably should +// make this behaviour toggleable. +// + +#ifndef __TIKI_SCRIPT_H__ +#define __TIKI_SCRIPT_H__ + +#if !defined( QERADIANT ) && !defined( UTILS ) +#define MAXTOKEN 256 +#endif +#define MAXMACROS 384 // was 256, increased for ef2 weapon lists + +typedef struct + { + char name[ MAXTOKEN ]; + char macro[ MAXTOKEN ]; + } tiki_macro_t; + +class TikiScript + { + public: + ~TikiScript(); + TikiScript(); + + char* buffer; + int length; + + void Close(); + const char* Filename(); + int GetLineNumber(); + void Reset(); + qboolean TokenAvailable( qboolean crossline ); + void UnGetToken(); + const char* GetToken( qboolean crossline ); + const char* GetLine( qboolean crossline ); + const char* GetRaw(); + const char* GetString( qboolean crossline ); + qboolean GetSpecific( const char *string ); + int GetInteger( qboolean crossline ); + double GetDouble( qboolean crossline ); + float GetFloat( qboolean crossline ); + void GetVector( qboolean crossline, vec3_t vec ); + int LinesInFile(); + void Parse( char *data, int length, const char *name ); + qboolean LoadFile( const char *name, qboolean quiet = qfalse ); + qboolean LoadFileFromTS( const char *name, const char * tikidata, qboolean quiet = qfalse ); + const char* Token(); + float EvaluateMacroMath(float value, float newval, char oper); + char* EvaluateMacroString(const char *theMacroString); + const char* GetMacroString(const char *theMacroName); + + protected: + qboolean error; + qboolean tokenready; + TikiScript* include; + TikiScript* parent; + + char filename[ MAXTOKEN ]; + const char* script_p; + const char* end_p; + tiki_macro_t macros[ MAXMACROS ]; + int nummacros; + + int line; + char token[ MAXTOKEN ]; + + qboolean releaseBuffer; + + qboolean AtComment(); + qboolean AtExtendedComment(); + qboolean AtCommand(); + qboolean AtString( qboolean crossline ); + qboolean ProcessCommand( qboolean crossline ); + qboolean Completed(); + void CheckOverflow(); + void Uninclude(); + const char* FindMacro( const char * macro ); + void AddMacro( const char *macro, const char *expansion ); + + qboolean SkipToEOL(); + void SkipWhiteSpace( qboolean crossline ); + void SkipNonToken( qboolean crossline ); + qboolean CommentAvailable( qboolean crossline ); + void SkipExtendedComment(); + }; + + +#endif diff --git a/UpgradeLog.htm b/UpgradeLog.htm new file mode 100644 index 0000000000000000000000000000000000000000..4ebc6992da18614b6f8ff5e2e4a18a80fc66827d GIT binary patch literal 18768 zcmeI4`)^do702i2O8p-$kYW>J8%R~DBnA;41u2v$rmfl-A;(@ng0ZQ0G0>3y>21HC zIX<2{v%9l*@nTGaR=a!m-kCG!{hG1=`=77Fxo{^u3b(`K@b~atxEq?V9R}ff*bO(r zZ^Lf_BNp^(LO-m9!_W&Gy50;2VPB(~@KRU1VL8l)D|%fGzYmu*?nga+DLB`|PT13T zPowtqInWHC8SF;TeYGaMj;rozo$I>l>vumwJ|EZE)7^ep*Z7{EuIYL~-#fYkCVjm( z1${xk!Rz|b>_IrMwOFO6IUDi%dBkH)m@w9`<8xVKd-}bg_4>LtP7J~e-RXsY>h6x{ zXVmR%-a`0Ru=m22My%FT|k(jeQ#KX;quQS{b(}S@`P4xq;>l;><=+b_H)eJdmy?{-cZVX(vjfz3LU= z=o;3D-u(4YGYnhSKm(c#XS6<_fu1f28^;KZLKhEo=Y{T%UUZsqU;Nk)=!nf2M5#j`76j(|f{2vy0=?)D zupK-C5>F#H^m|=Uur@|4g;(O%p62cA>WesXU!Sg(JA&}0GQYIe8ujoTT+rS`|2<)| zC!CVhmo;XyxH~WZ)r6n*&M~{I-*_IQW?mBG8l5$p$8s(OEch3F*08HRgA4N+rxpc& z5b418-96^`rRo|#SD&IGZE1{KV?NADT6@P*pNn#{HaE-ou`#B=WG{q+!Li*aq@C6cglm&0YPQ_}=n zWwO27xWpA=)`4E;=bitJa4b3YgYbP47KIlax}`Vtqg~`|J^8M=W2trJieY0>;G8n= zaoAi87lqI0r;EaYI7@cLOd~)$YA#d|8xdzr&>SC{+-F@<3Z7p}MjP?nq>%SbA4{iUa z;YS;JVtny1tzF`iNwAC7w`HAczQ>`qO+Ju+BD=u*_LRlU>(_zeQZGj5X+0>>kQ$O6 z4q1yqn#L2?eNPwfWn*f-!$#z|rd+%3Y3umaR<(?iI%H3EvuB0mj$Sj5${4-tQdYIp z2T)*rA`j^H8*>zHF*LQ_p8!K9L^J zPZ~)-*sJ`y3?}p5Wjh*rBi~p?y3r%2jeCat*t=2(}W(=^!3jl|}@Ftpu|1MRJB$Icl&fU|_x{pcaBH&Mr{F2B0; zYmy(f&J|f1_BN?okZZsvD?J@6wF-uu)H*oW?Lmy)Uct;AYkM=a>xKR!+gB64fqTbx zor>R1D<@w~WHb*z7d6)lyZSoiOEtZtsvIkqOAfhn0h-i3VKGkdbQL;Y&^K8;U| zvc1weU&#ku64V1!1w<=O8SLuU^UPnm6zWzDjl5&VpPO2c|t!aFP=RoOx-qhjK4m)76&bhwKc;s}Uz^`$LT(yX7=M zelo?+uv{B~_oKKYd=PbwPS0b(uyYg4Dxcb#*G8_nUqG@|)4mwILm%yXJAQL2!mjup z=IVFRH+Y_v_PE;C%R2cuu$%;!KFD_5gR-54VTd+jGOWA85zEB;jmNM(YBJ2fNyc=` z@CI8|xaG*nuyb!%!?_&Uh7E^Zmi}$$qjsd7TaqPRgpJ5$G%}q(>goQLcyA}B{C;21 z)}!vu;yln(S&K_$Q8^!6BNpvh?PZ+0Y*E%VXavbMgU$VOhmj-8za zGOed)XFj*|6QxC!^H+{$0;hZi{~a>eSS@dNb398*O5d?4IJ=_CB${ZZSLhitQuA)s zZTTBI!aZ?=cx6;h=0=;#H>3k?oSnyf&wAb5D!}&rt&RSmA#jfKgwLWDziZA)OwTpS z_I~sFt9=UOt0;}p^qqApU6JLi>t&VOXiLqHdvwA!` zU9szob)M9wZm7T6#_1$>GD}!_SMfshr?b?1mq#~!zc!LbN#D8h6FKH9El1*dTY1!Q zu2I_EE!p%&ky#Ke+xbyBCw1QQK0`lZ%et&zW@~vSPOpMdcvn9egfA}FN|vx4z8pbM zo)g^>-e{+ti13b8ne~)wpoc&QpPZk>o0M;s^V)E&-ZGjzD?>W%yf2SBIX{R!SRM41 z(KOFc#%EvoIJ3}{fqMP|MQx6!=w&pddZnb-sk2BrNpcBplHvbL)>KCFak50Ca&J2> zN4Y+-Gk^^9`M|TR>qaa410Ql0vU$s<*;$|xex}&N-WWAERX8VcZpS^R(soz%bW^tb zC(VN%q|crfaqCOPf^S;zR)^}ecCM8$G#xa3NnXpWs;z^0CNo<-%weB3UCgl?og3t1 z5(RK{d{+Bmbke$0-hEjShA(3zpzp)Z3VAhqtJVqPZ&aS?9?yuf3(~;+{JC{;IMq$1 z!70Kt8}Ew0FZ?6GT8HWSQ5eAqdS+0!DF0GcORf_&d&?t**;_lB zdEdSs0nUaue;^rIuMlnW3bk!Xqw3yiJUEfAjLFWSThcHx9bXrH&ADs4GVQ%;%PQOc zujF$M0gE5bLFo$n`R#nX>0y3?n+|cgO1B(z+%AT5po!XSr{Qxxl-<|<*ciudeb-o# z(>gg;*k8EVnOQPSq?K|+;-UE-{tC=8z;5|Xjz{v!AB$C_F3~tj&SHEibr>B>f2n4> z41L9rRBsbe$gJN?;Va3xx@k;I%eOCDqeYmrdA31ICs(X~HnnW1^>C3)~2Z^>Du;5f2j!DRqn^HzjtDlOE<_)c9fcIJsi47uQcYg z_4-g)y*^%bI{ohMY2U4$NLJDI)E|rIJ^OWp^LIGL&7v>k&`*|cq`KjK<{M?*uoU|# Yhe!Pz+w(&Qm$d(oPLrko-g6`T4@)FCxBvhE literal 0 HcmV?d00001 diff --git a/dlls/game/CameraPath.cpp b/dlls/game/CameraPath.cpp new file mode 100644 index 0000000..408b1e0 --- /dev/null +++ b/dlls/game/CameraPath.cpp @@ -0,0 +1,801 @@ +//----------------------------------------------------------------------------- +// CameraPath.cpp +// +// Author: Squirrel Eiserloh +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// Implementation file for the following camera-related classes: +// +// CameraPath Describes a single camera path through time - +// including camera orientation, position, +// and field-of-view - using either many simple +// reference keyframes or a few bspline nodes. +// Also owns the coordinate system through which +// all positions and orientations are transformed +// (for scripts with relative playback locales). +// +// CameraKeyFramePath The innards of a CameraPath object implemented +// using keyframes (as opposed to bspline nodes). +// Does not know about relative locales. +// +// CameraKeyFrame A single key frame item in a CameraKeyFramePath. +// Stores a time index / frame number, the camera'a +// position (as a Vector), orientation (as a +// Quaternion), and field-of-fiew (as a Scalar). +// + +#include "_pch_cpp.h" +#include "CameraPath.h" + + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// CameraKeyFrame +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +CLASS_DECLARATION( Class, CameraKeyFrame, NULL ) +{ + { NULL, NULL } +}; + +//----------------------------------------------------------------------------------------------- +// Name: CameraKeyFrame +// Class: CameraKeyFrame +// +// Description: Default Constructor +// +// Parameters: None +// +// Returns: N/a +// +//----------------------------------------------------------------------------------------------- +CameraKeyFrame::CameraKeyFrame() +: +_position( Vector( 0.0f, 0.0f, 0.0f ) ), +_orientation( Quaternion( 0.0f, 0.0f, 0.0f, 1.0f ) ), +_fovDegrees( 90.0f ), +_seconds( 0.0f ) +{ +} + + +//----------------------------------------------------------------------------------------------- +// Name: CameraKeyFrame +// Class: CameraKeyFrame +// +// Description: Constructor( Vector, Quaternion, float ) +// +// Parameters: position XYZ coordinates of camera +// orientation camera orientation as a quaternion +// fovDegrees horizontal fov of camera, in degrees +// +// Returns: n/a +// +//----------------------------------------------------------------------------------------------- +CameraKeyFrame::CameraKeyFrame( const Vector& position, const Quaternion& orientation, const float fovDegrees, const float seconds ) +: +_position( position ), +_orientation( orientation ), +_fovDegrees( fovDegrees ), +_seconds( seconds ) +{ +} + + +//----------------------------------------------------------------------------------------------- +// Name: ParseFrameInfo +// Class: CameraKeyFrame +// +// Description: Starting with the keyword "Frame", parses one single frame entry from a .KFC +// buffer (using a Script object) in its entirety. +// +// Parameters: int frameNumber, Script& cameraPathFile +// +// Returns: bool - true upon success +// +//----------------------------------------------------------------------------------------------- +bool CameraKeyFrame::ParseFrameInfo( int frameNumber, Script& cameraPathFile, float& totalPathTime ) +{ + const char* token; + + /// Read in the Frame number + token = cameraPathFile.GetToken( true ); + if( !stricmp( token, "Frame" ) ) + { + /// Make sure the frame number is the same as , as expected + int actualFrame = cameraPathFile.GetInteger( false ); + if( actualFrame != frameNumber ) + { + cameraPathFile.error( "CameraKeyFrame::ParseFrameInfo", "Incorrect Frame number (found %d, expecting %d)\n", frameNumber, actualFrame ); + return( false ); + } + } + else // bad identifier + { + gi.Printf( "Unexpected token %s in camera path file (expecting \"Frame\").\n", token ); + cameraPathFile.error( "CameraKeyFrame::ParseFrameInfo", "Unexpected token %s in camera path file (expecting \"Frame\").\n", token ); + return( false ); + } + + return( ParseFrameInfoBlock( cameraPathFile, totalPathTime ) ); +} + + +//----------------------------------------------------------------------------------------------- +// Name: ParseFrameInfoBlock +// Class: CameraKeyFrame +// +// Description: Parse in a single camera key frame from a .KFC file, from '{' to '}' +// +// Parameters: Script& cameraPathFile - parse object for .KFC file +// +// Returns: bool - true upon success +// +//----------------------------------------------------------------------------------------------- +bool CameraKeyFrame::ParseFrameInfoBlock( Script& cameraPathFile, float& totalPathTime ) +{ + const char* token; + float frameTimeInSeconds = DEFAULT_KEY_FRAME_LENGTH_SECONDS; + + /// Read the opening bracket + token = cameraPathFile.GetToken( true ); + if( *token != '{' ) + { + cameraPathFile.error( "CameraKeyFrame::ParseFrameInfoBlock", "Expected '{', found \"%s\"\n", token ); + return( false ); + } + + /// Read each entry in the Path info block + token = cameraPathFile.GetToken( true ); + while( *token != '}' ) + { + if( !stricmp( token, "fov" ) ) + { + /// Read in the horizontal field of view (fov) for this camera key frame + _fovDegrees = cameraPathFile.GetFloat( false ); + } + else if( !stricmp( token, "position" ) ) + { + /// Read in the position vector for this camera key frame + _position = cameraPathFile.GetVector( false ); + } + else if( !stricmp( token, "quaternion" ) ) + { + /// Read in the orientation of the camera as a quaternion + float x = cameraPathFile.GetFloat( false ); + float y = cameraPathFile.GetFloat( false ); + float z = cameraPathFile.GetFloat( false ); + float w = cameraPathFile.GetFloat( false ); + _orientation.SetFromXYZW( x, y, z, w ); + } + else // unknown token + { + gi.Printf( "Unexpected token %s in camera path file.\n", token ); + cameraPathFile.error( "CameraKeyFrame::ParseFrameInfoBlock", "Unexpected token %s in camera path file.\n", token ); + return( false ); + } + + token = cameraPathFile.GetToken( true ); + } + + _seconds = totalPathTime; + totalPathTime += frameTimeInSeconds; + return( true ); +} + + + +//----------------------------------------------------------------------------------------------- +// Name: Interpolate +// Class: CameraKeyFrame +// +// Description: Sets the position, orientation, and fov values in the current CameraKeyFrame +// object (this) to be a weighted average of two other key frames: and +// , where represents the weighting of the frame (and +// 1 - fraction is the weighting of the frame). +// +// Parameters: const CameraKeyFrame& before - key frame being approached as fraction -> 0 +// const CameraKeyFrame& after - key frame being approached as fraction -> 1 +// const float fraction - in the range [0,1]; the fraction of to be used +// +// Returns: const CameraKeyFrame& +// +//----------------------------------------------------------------------------------------------- +const CameraKeyFrame& CameraKeyFrame::Interpolate( const CameraKeyFrame& before, const CameraKeyFrame& after, const float fraction, const bool normalizeQuaternion ) +{ + /// Set up some convenience references + const Vector& pos1 = before.GetPosition(); + const Vector& pos2 = after.GetPosition(); + const Quaternion& quat1 = before.GetOrientation(); + const Quaternion& quat2 = after.GetOrientation(); + float fov1 = before.GetFOV(); + float fov2 = after.GetFOV(); + + /// Calculate interpolated position, orientation, and fov values for this new frame + + _position = pos1 + ( pos2 - pos1 ) * fraction; // linear componentwise interpolation + + _orientation = CalcSlerp( quat1, quat2, (double) fraction ); // (hyper)spherical linear interpolation + _fovDegrees = fov1 + (fraction * (fov2 - fov1) ); // linear float interpolation + + /// Check if the caller has requested the quaternion to be renormalized after interpolation (a good idea, but costly) + if( normalizeQuaternion ) + _orientation.Normalize(); + + return( *this ); +} + + +//----------------------------------------------------------------------------------------------- +// Name: GetEulerAngles +// Class: CameraKeyFrame +// +// Description: Returns the orientation of the key frame in Euler angles (pitch, yaw, roll). +// Note that orientations are internally stored as quaternions, and this function +// is mainly provided as a interface useful to the surrounding Euler-centric +// Quake / Fakk / Tiki code. +// +// Parameters: +// +// Returns: const Vector +// +//----------------------------------------------------------------------------------------------- +const Vector CameraKeyFrame::GetEulerAngles( void ) const +{ + Vector eulerAngles; + _orientation.GetToEuler( eulerAngles ); + return eulerAngles; +} + + + + +//----------------------------------------------------------------------------------------------- +// Name: TransformToPlaybackOffsets +// Class: CameraKeyFrame +// +// Description: Transforms a CameraKeyFrame object by (1) rotating it degrees +// (including orientations and positions) and then (2) translating it by +// world-units. +// +// Parameters: const float yawOffset - the amount of (yaw) rotation to apply +// const Vector& originOffset - the amount of translation to apply +// +// Returns: const CameraKeyFrame& - returns *this, now transformed +// +// NOTE: Because q_math.c is a C file and was written long ago, we use vec3_t and euler +// angles instead of Vector and Quaternion. +//----------------------------------------------------------------------------------------------- +const CameraKeyFrame& CameraKeyFrame::TransformToPlaybackOffsets( const float yawOffsetDegrees, const Vector& originOffset ) +{ + /// Rotate <_position> about the Z axis by + vec3_t zNormal = { 0.0f, 0.0f, 1.0f }; + vec3_t point; + vec3_t rotated; + _position.copyTo( point ); // Don't blame me for this. Our math library was written in C. + RotatePointAroundVector( rotated, zNormal, point, yawOffsetDegrees ); + _position = rotated; + + /// Rotate <_orientation> in a like manner + Vector eulerOrientation; + _orientation.GetToEuler( eulerOrientation ); + eulerOrientation.y += yawOffsetDegrees; + _orientation.SetFromEuler( eulerOrientation ); + _orientation.Normalize(); + + /// Translate <_position> by + _position += originOffset; // see how easy things can be in C++? + + return *this; +} + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// CameraKeyFramePath +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +CLASS_DECLARATION( Class, CameraKeyFramePath, NULL ) +{ + { NULL, NULL } +}; + + +//----------------------------------------------------------------------------------------------- +// Name: CameraKeyFramePath +// Class: CameraKeyFramePath +// +// Description: Default Constructor +// +// Parameters: None +// +// Returns: n/a +// +//----------------------------------------------------------------------------------------------- +CameraKeyFramePath::CameraKeyFramePath() +: +_isLoaded( false ), +_numKeyFrames( 0 ), +_keyFrameArray( NULL ), +_totalSeconds( 0.0f ) +{ +} + + +//----------------------------------------------------------------------------------------------- +// Name: Unload +// Class: CameraKeyFramePath +// +// Description: Destroys the key-framed camera path data and +// marks the object as dirty (_isLoaded = false). +// +// Parameters: void +// +// Returns: void +// +//----------------------------------------------------------------------------------------------- +void CameraKeyFramePath::Unload( void ) +{ + if( IsLoaded() ) + { + delete [] _keyFrameArray; + _keyFrameArray = NULL; + } + + _totalSeconds = 0.0f; + _isLoaded = false; +} + + +//----------------------------------------------------------------------------------------------- +// Name: CreateFrames +// Class: CameraKeyFramePath +// +// Description: Allocates CameraKeyFrame objects +// in a dynamic array. Previous data, if any, is +// deleted. +// +// Parameters: numberOfFrames - The number of key frames to be allocated. +// +// Returns: bool - true upon success +// +//----------------------------------------------------------------------------------------------- +bool CameraKeyFramePath::CreateFrames( const int numberOfFrames ) +{ + /// Unload the previous data (if any) + Unload(); + + /// Allocte CameraKeyFrame objects + _keyFrameArray = new CameraKeyFrame[ numberOfFrames ]; + if( !_keyFrameArray ) + return( false ); + + /// Set internal dependents + _numKeyFrames = numberOfFrames; + return( true ); +} + + +//----------------------------------------------------------------------------------------------- +// Name: ParsePathInfo +// Class: CameraKeyFramePath +// +// Description: Parses a "Path" entry and all subsequent "Frame" entries for that path. +// Parsing begins at the '{' following the "Path" keyword (already parsed). +// +// Parameters: Script& cameraPathFile - parsing object; keeps track of parse-offset, etc. +// +// Returns: bool - true upon success +// +//----------------------------------------------------------------------------------------------- +bool CameraKeyFramePath::ParsePathInfo( Script& cameraPathFile ) +{ + const char* token; + + /// Read the opening bracket + token = cameraPathFile.GetToken( true ); + if( *token != '{' ) + { + cameraPathFile.error( "CameraKeyFramePath::ParsePathInfo", "Expected '{', found \"%s\"\n", token ); + return( false ); + } + + /// Read each entry in the Path info block + token = cameraPathFile.GetToken( true ); + while( *token != '}' ) + { + if( !stricmp( token, "frameCount" ) ) + { + /// Read in the number of frames in the path + _numKeyFrames = cameraPathFile.GetInteger( false ); + assert( _numKeyFrames > 0 ); + } + else // unknown token + { + gi.Printf( "Unexpected token %s in camera path file.\n", token ); + cameraPathFile.error( "CameraKeyFramePath::ParsePathInfo", "Unexpected token %s in camera path file.\n", token ); + return( false ); + } + token = cameraPathFile.GetToken( true ); + } + + /// Allocate an array of CameraKeyFrame objects equal in number to what "frameCount" specified + CreateFrames( _numKeyFrames ); + + /// Loop through each key frame and let it parse itself + for( int i = 0; i < _numKeyFrames; i ++ ) + { + /// Tell each frame to parse itself + CameraKeyFrame& keyFrame = _keyFrameArray[ i ]; + bool success = keyFrame.ParseFrameInfo( i, cameraPathFile, _totalSeconds ); + if( !success ) + return( false ); + } + + /// Inform the object that it has been successfully loaded and is ready for use + _isLoaded = true; + return( true ); +} + + +//----------------------------------------------------------------------------------------------- +// Name: GetClosestFramesForTime +// Class: CameraKeyFramePath +// +// Description: Sets and pointers to the camera key frames closest in time +// to . Both pointers are guaranteed to always be set, though they +// may be identical (especially in cases where matches a frame exactly, +// or where is out of the time bounds of the path). +// +// Parameters: CameraKeyFrame*& before - pointer (by reference) to be set to the closest +// frame at or before +// +// CameraKeyFrame*& after - pointer (by reference) to be set to the closest +// frame at or after +// +// const float seconds - the time offset, in seconds, from the beginning of the +// camera path, around which the search for closest frames is centered +// +// Returns: void +// +//----------------------------------------------------------------------------------------------- +void CameraKeyFramePath::GetClosestFramesForTime( CameraKeyFrame*& before, CameraKeyFrame*& after, const float seconds ) +{ + int i; + + /// Find the frame closest to - but no greater than - + before = &_keyFrameArray[ 0 ]; + for( i = 0; i < _numKeyFrames; i ++ ) + { + CameraKeyFrame& frame = _keyFrameArray[ i ]; + if( frame.GetSeconds() > seconds ) + break; + + before = &frame; + } + + /// Find the frame closest to - but no less than - + after = &_keyFrameArray[ _numKeyFrames - 1 ]; + for( i = _numKeyFrames - 1; i >= 0; i -- ) + { + CameraKeyFrame& frame = _keyFrameArray[ i ]; + if( frame.GetSeconds() < seconds ) + break; + + after = &frame; + } + +} + + +//----------------------------------------------------------------------------------------------- +// Name: CreateInterpolatedFrameForTime +// Class: CameraKeyFramePath +// +// Description: Creates a new CameraKeyFrame object based on the best interpolated-approximation +// of the CameraKeyFramePath when evaluated at time. +// +// Parameters: const float seconds - time at which to evaluate the camera path +// +// Returns: const CameraKeyFrame - a new key frame, created by interpolating (if necessary) +// between the key frames in the camera path closest to . +// +//----------------------------------------------------------------------------------------------- +const CameraKeyFrame CameraKeyFramePath::CreateInterpolatedFrameForTime( const float seconds ) +{ + /// Check if is out-of-bounds for this camera path + if( seconds < 0.0f ) + { + /// Return a copy of the first key frame + return( _keyFrameArray[ 0 ] ); + } + else if( seconds > _totalSeconds ) + { + /// Return a copy of the last key frame + return( _keyFrameArray[ _numKeyFrames - 1 ] ); + } + + /// Get the two closest frames in time to ; one just before and one just after + CameraKeyFrame* closestFrameBefore = NULL; + CameraKeyFrame* closestFrameAfter = NULL; + GetClosestFramesForTime( closestFrameBefore, closestFrameAfter, seconds ); + assert( closestFrameBefore ); + assert( closestFrameAfter ); + + /// Check if both frames are identical; if so, simply return a copy of it / them + if( closestFrameBefore == closestFrameAfter ) + return( *closestFrameBefore ); + + /// Generate an interpolated frame based on where falls between those two frames + return( CreateInterpolatedFrame( *closestFrameBefore, *closestFrameAfter, seconds ) ); +} + + +//----------------------------------------------------------------------------------------------- +// Name: CreateInterpolatedFrame +// Class: CameraKeyFramePath +// +// Description: Creates a new CameraKeyFrame object based on the best interpolated-approximation +// of the CameraKeyFramePath when evaluated at time. NOTE: This method +// is called by CreateInterpolatedFrameForTime(), above, and does all the real +// interpolation work. +// +// Parameters: const CameraKeyFrame& before - closest frame shy of +// const CameraKeyFrame& after - closest frame past +// const float seconds - time index used to interpolate between and +// +// Returns: const CameraKeyFrame - new, interpolated key frame +// +//----------------------------------------------------------------------------------------------- +const CameraKeyFrame CameraKeyFramePath::CreateInterpolatedFrame( const CameraKeyFrame& before, const CameraKeyFrame& after, const float seconds ) +{ + /// Calculate the amount of time between the two reference frames + const float startTime = before.GetSeconds(); + const float endTime = after.GetSeconds(); + const float timeSpan = endTime - startTime; + assert( timeSpan >= 0.0f ); + assert( seconds >= startTime ); + assert( seconds <= endTime ); + + /// Calculation the interpolation fraction + float fraction; + if( timeSpan ) + { + fraction = (seconds - startTime) / timeSpan; + } + else + { + fraction = 0.0f; + } + + /// Create a new frame with interpolated values + CameraKeyFrame lerped; + lerped.Interpolate( before, after, fraction, true ); + return( lerped ); +} + + + +//----------------------------------------------------------------------------------------------- +// Name: GetPathLengthInSeconds +// Class: CameraKeyFramePath +// +// Description: Returns the total length of the camera path, in seconds. +// +// Parameters: void +// +// Returns: float - the total length of the camera path, in seconds. +// +//----------------------------------------------------------------------------------------------- +float CameraKeyFramePath::GetPathLengthInSeconds( void ) +{ + return( _totalSeconds ); +} + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// CameraPath +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +CLASS_DECLARATION( Class, CameraPath, NULL ) +{ + { NULL, NULL } +}; + +//----------------------------------------------------------------------------------------------- +// Name: CameraPath +// Class: CameraPath +// +// Description: Construct from .KFC file +// +// Parameters: fileName Name of the .KFC file to parse on construction of this object. +// +// Returns: n/a +// +//----------------------------------------------------------------------------------------------- +CameraPath::CameraPath( const str& fileName ) +: +_fileName( str( "" ) ), +_isLoaded( false ), +_keyFramePath( NULL ), +_yawPlaybackOffsetDegrees( 0.0f ), +_originPlaybackOffset( Vector( 0.0f, 0.0f, 0.0f ) ) +{ + LoadKeyFramedCameraFile( fileName ); +} + + +//----------------------------------------------------------------------------------------------- +// Name: LoadKeyFramedCameraFile +// Class: CameraPath +// +// Description: Loads a .KFC file and builds a dynamic array of camera key frames. +// Any previously loaded data in this object is destroyed. +// +// Parameters: fileName - Name of the .KFC file to load and parse. +// +// Returns: bool - true upon success +// +//----------------------------------------------------------------------------------------------- +bool CameraPath::LoadKeyFramedCameraFile( const str& fileName ) +{ + Script cameraPathFile; + const char* token; + bool success; // generic parsing return-value variable + str filePathName; + + /// Unload the previous data (if any) + Unload(); + + /// Store the filename in the object for duplicate-load later on + _fileName = fileName; + + /// Build the new file path name + filePathName = "cams/"; + filePathName += fileName; + filePathName += ".kfc"; + + /// Load the file into a buffer (in the Script object) + cameraPathFile.LoadFile( filePathName.c_str() ); + + /// Parse each token in turn until no more remain + while( cameraPathFile.TokenAvailable( true ) ) + { + /// Read the next token-word and take the appropriate action + token = cameraPathFile.GetToken( true ); + if( !stricmp( token, "Path" ) ) + { + /// Read in the path info and its subsequent key frame data + success = ParsePathInfo( cameraPathFile ); + if( !success ) + return( false ); + } + else // unknown token + { + gi.Printf( "Unexpected token %s in camera path file %s.\n", token, _fileName.c_str() ); + cameraPathFile.error( "CameraPath::LoadKeyFramedCameraFile", "Unexpected token %s in camera path file %s.\n", token, _fileName.c_str() ); + return( false ); + } + } + + /// Inform the object that it has been successfully loaded and is ready for use + _isLoaded = true; + return( true ); +} + + +//----------------------------------------------------------------------------------------------- +// Name: Unload +// Class: CameraPath +// +// Description: Destroys the camera path data and marks the object as dirty (_isLoaded = false). +// +// Parameters: void +// +// Returns: void +// +//----------------------------------------------------------------------------------------------- +void CameraPath::Unload( void ) +{ + if( IsLoaded() ) + { + _isLoaded = false; + delete _keyFramePath; + _keyFramePath = NULL; + } +} + + +//----------------------------------------------------------------------------------------------- +// Name: ParsePathInfo +// Class: CameraPath +// +// Description: Parses the Path #, allocates a new path object, and passes parsing duties onto it. +// +// Parameters: Script& cameraPathFile - parsing object +// +// Returns: bool - true upon success +// +//----------------------------------------------------------------------------------------------- +bool CameraPath::ParsePathInfo( Script& cameraPathFile ) +{ + /// Read the path number + int pathNumber = cameraPathFile.GetInteger( false ); + assert( pathNumber == 0 ); // FIXME: this is only temporary, until we allow more than one path per .kfc + + /// Create a new key frame camera path and pass parsing duties on to it + _keyFramePath = new CameraKeyFramePath; + bool success = _keyFramePath->ParsePathInfo( cameraPathFile ); + return( success ); +} + + +//----------------------------------------------------------------------------------------------- +// Name: GetInterpolatedFrameForTime +// Class: CameraPath +// +// Description: Returns a newly created temporary object; the resulting camera key frame when +// the CameraPath is evaluated at . +// +// Parameters: const float seconds - time index at which to evaluate the camera path +// +// Returns: const CameraKeyFrame - key frame created as a result of interpolation / evaluation +// +//----------------------------------------------------------------------------------------------- +const CameraKeyFrame CameraPath::GetInterpolatedFrameForTime( const float seconds ) +{ + CameraKeyFrame transformedKeyFrame( _keyFramePath->CreateInterpolatedFrameForTime( seconds ) ); + transformedKeyFrame.TransformToPlaybackOffsets( _yawPlaybackOffsetDegrees, _originPlaybackOffset ); + return( transformedKeyFrame ); +} + + +//----------------------------------------------------------------------------------------------- +// Name: SetPlaybackOffsets +// Class: CameraPath +// +// Description: Tells a CameraPath object what offsets to use when reporting interpolations / +// evaluations. is applied first to all positions and orientations; +// is applied last (to positions only). +// +// Parameters: const float yawOffsetDegrees - yaw rotation to apply to all positions & orientations +// const Vector& originOffset - offset translation applied to all positions, post-rotation +// +// Returns: void +// +//----------------------------------------------------------------------------------------------- +void CameraPath::SetPlaybackOffsets( const float yawOffsetDegrees, const Vector& originOffset ) +{ + _yawPlaybackOffsetDegrees = yawOffsetDegrees; + _originPlaybackOffset = originOffset; +} + + + +//----------------------------------------------------------------------------------------------- +// Name: GetPathLengthInSeconds +// Class: CameraPath +// +// Description: Returns the total length of the camera path, in seconds. +// +// Parameters: void +// +// Returns: float - the total length of the camera path, in seconds +// +//----------------------------------------------------------------------------------------------- +float CameraPath::GetPathLengthInSeconds( void ) +{ + return( _keyFramePath->GetPathLengthInSeconds() ); +} + + diff --git a/dlls/game/CameraPath.h b/dlls/game/CameraPath.h new file mode 100644 index 0000000..fbbdb5a --- /dev/null +++ b/dlls/game/CameraPath.h @@ -0,0 +1,293 @@ +//----------------------------------------------------------------------------- +// CameraPath.h +// +// Author: Squirrel Eiserloh +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// Header file for the following camera-related classes: +// +// CameraPath Describes a single camera path through time - +// including camera orientation, position, +// and field-of-view - using either many simple +// reference keyframes or a few bspline nodes. +// Also owns the coordinate system through which +// all positions and orientations are transformed +// (for scripts with relative playback locales). +// +// CameraKeyFramePath The innards of a CameraPath object implemented +// using keyframes (as opposed to bspline nodes). +// Does not know about relative locales. +// +// CameraKeyFrame A single key frame item in a CameraKeyFramePath. +// Stores a time index / frame number, the camera'a +// position (as a Vector), orientation (as a +// Quaternion), and field-of-fiew (as a Scalar). +// +#ifndef _CAMERA_PATH_H_ +#define _CAMERA_PATH_H_ + + +/// Included headers +#include "class.h" +#include + +/// forward class declarations +class CameraPath; +class CameraKeyFramePath; +class CameraKeyFrame; + +const float DEFAULT_KEY_FRAME_LENGTH_SECONDS = 0.05f; + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// CameraKeyFrame +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +class CameraKeyFrame : public Class +{ + /// Member variables +private: + Vector _position; // the camera's position in xyz coordinates + Quaternion _orientation; // the camera's orientation, as a quaternion + float _fovDegrees; // the camera's horizontal field of view + float _seconds; // time (path start = 0) at which the key frame occurs + + /// Accessors / Mutators +public: + inline const Vector& GetPosition( void ) const { return _position; } + inline const Quaternion& GetOrientation( void ) const { return _orientation; } + inline const float GetFOV( void ) const { return _fovDegrees; } + inline const float GetSeconds( void ) const { return _seconds; } + + /// Construction / Destruction +public: + CLASS_PROTOTYPE( CameraKeyFrame ); + CameraKeyFrame(); // default constructor + CameraKeyFrame( const Vector& position, const Quaternion& orientation, const float fovDegrees, const float seconds ); + /// Interface methods +public: + bool ParseFrameInfo( int frameNumber, Script& cameraPathFile, float& frameLengthInSeconds ); + const CameraKeyFrame& Interpolate( const CameraKeyFrame& before, const CameraKeyFrame& after, const float fraction, const bool normalizeQuaternion ); + const Vector GetEulerAngles( void ) const; + const CameraKeyFrame& TransformToPlaybackOffsets( const float yawOffsetDegrees, const Vector& originOffset ); + + void Archive( Archiver &arc ); + + /// Implementation methods +private: + bool ParseFrameInfoBlock( Script& cameraPathFile, float& frameLengthInSeconds ); +}; + +//=============================================================== +// Name: Archive +// Class: CameraKeyFrame +// +// Description: Archives the data of a single camera key frame. +// +// Parameters: Archiver& -- reference to archive object for storing data. +// +// Returns: None +// +//=============================================================== +inline void CameraKeyFrame::Archive +( + Archiver &arc +) +{ + Class::Archive( arc ); + + float x = 0.0f ; + float y = 0.0f ; + float z = 0.0f ; + float w = 0.0f ; + + + arc.ArchiveVector( &_position ); + + if ( arc.Saving() ) + { + _orientation.GetToXYZW( x, y, z, w ); + } + + arc.ArchiveFloat( &x ); + arc.ArchiveFloat( &y ); + arc.ArchiveFloat( &z ); + arc.ArchiveFloat( &w ); + + if ( arc.Loading() ) + { + _orientation.SetFromXYZW( x, y, z, w ); + } + + arc.ArchiveFloat( &_fovDegrees ); + arc.ArchiveFloat( &_seconds ); +} + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// CameraKeyFramePath +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +class CameraKeyFramePath : public Class +{ + /// Member variables +private: + bool _isLoaded; + int _numKeyFrames; + CameraKeyFrame* _keyFrameArray; + float _totalSeconds; + + /// Accessors / Mutators +public: + inline const bool IsLoaded( void ) const { return _isLoaded; } +private: + inline const int GetNumKeyFrames( void ) const { return _numKeyFrames; } + + /// Construction / Destruction +public: + CLASS_PROTOTYPE( CameraKeyFramePath ); + CameraKeyFramePath(); // default constructor + + /// Interface methods +public: + void Unload( void ); + bool ParsePathInfo( Script& cameraPathFile ); + const CameraKeyFrame CreateInterpolatedFrameForTime( const float seconds ); + float GetPathLengthInSeconds( void ); + + void Archive( Archiver &arc ); + + /// Implementation methods +private: + void GetClosestFramesForTime( CameraKeyFrame*& before, CameraKeyFrame*& after, const float seconds ); + bool CreateFrames( const int numberOfFrames ); + const CameraKeyFrame CreateInterpolatedFrame( const CameraKeyFrame& before, const CameraKeyFrame& after, const float seconds ); +}; + + + +//=============================================================== +// Name: Archive +// Class: CameraKeyFramePath +// +// Description: Archives the data of a archive key frame path. +// +// Parameters: Archiver& -- reference to the archive object +// +// Returns: None +// +//=============================================================== +inline void CameraKeyFramePath::Archive +( + Archiver &arc +) +{ + Class::Archive( arc ); + + arc.ArchiveBool( &_isLoaded ); + arc.ArchiveInteger( &_numKeyFrames ); + + if ( arc.Loading() ) + { + _keyFrameArray = new CameraKeyFrame[ _numKeyFrames ]; + } + + for ( int frameIdx = 0; frameIdx < _numKeyFrames; ++frameIdx ) + { + arc.ArchiveObject( &_keyFrameArray[ frameIdx ] ); + } + + arc.ArchiveFloat( &_totalSeconds ); +} + + + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// CameraPath +// +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +class CameraPath : public Class +{ + /// Member variables +private: + bool _isLoaded; + str _fileName; + CameraKeyFramePath* _keyFramePath; + float _yawPlaybackOffsetDegrees; + Vector _originPlaybackOffset; + + /// Accessors / Mutators +public: + inline const bool IsLoaded( void ) const { return _isLoaded; } + + /// Construction / Destruction +public: + CLASS_PROTOTYPE( CameraPath ); + CameraPath( const str& fileName ); + CameraPath() : _isLoaded( false ), _keyFramePath( 0 ), _yawPlaybackOffsetDegrees( 0 ) { }; + + /// Interface methods +public: + void Unload( void ); + bool Reload( void ); + const CameraKeyFrame GetInterpolatedFrameForTime( const float seconds ); + void SetPlaybackOffsets( const float yawOffsetDegrees, const Vector& originOffset ); + float GetPathLengthInSeconds( void ); + + void Archive( Archiver &arc ); + + /// Implementation methods +private: + bool LoadKeyFramedCameraFile( const str& fileName ); + bool ParsePathInfo( Script& cameraPathFile ); +}; + + +//=============================================================== +// Name: Archive +// Class: CameraPath +// +// Description: Archives the data of the camera path. +// +// Parameters: Archiver& -- reference to archiver storing data. +// +// Returns: None +// +//=============================================================== +inline void CameraPath::Archive +( + Archiver &arc +) +{ + Class::Archive( arc ); + + arc.ArchiveBool( &_isLoaded ); + arc.ArchiveString( &_fileName ); + + //arc.ArchiveObjectPointer( (Class**)&_keyFramePath ); + + if ( arc.Loading() ) + { + _keyFramePath = ( CameraKeyFramePath*)arc.ReadObject(); + } + else + { + arc.ArchiveObject( _keyFramePath ); + } + + arc.ArchiveFloat( &_yawPlaybackOffsetDegrees ); + arc.ArchiveVector( &_originPlaybackOffset ); +} + + +#endif // _CAMERA_PATH_H_ diff --git a/dlls/game/CinematicArmature.cpp b/dlls/game/CinematicArmature.cpp new file mode 100644 index 0000000..ceaf396 --- /dev/null +++ b/dlls/game/CinematicArmature.cpp @@ -0,0 +1,2830 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/CinematicArmature.cpp $ +// $Revision:: 44 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// Implementation of Cinematic Armature classes. +// + +#include "_pch_cpp.h" +#include "CinematicArmature.h" + +bool CinematicArmature::_debug = false ; + +//---------------------------------------------------------------- +// C I N E M A T I C A R M A T U R E +//---------------------------------------------------------------- +CinematicArmature theCinematicArmature ; + +Event EV_CinematicArmature_PlayCinematic +( + "playCinematic", + EV_CONSOLE | EV_SCRIPTONLY, + "sS", + "cinematic originname", + "Play the current cinematic or the specified one once." +); +Event EV_CinematicArmature_PlayCinematicAt +( + "playCinematicAt", + EV_CONSOLE | EV_SCRIPTONLY, + "sVF", + "cinematic origin yaw", + "Play a cinematic at the specified origin with optional yaw rotatioin." +); +Event EV_CinematicArmature_Debug +( + "debug", + EV_CONSOLE | EV_SCRIPTONLY, + NULL, + NULL, + "Turns on debugging information for cinematics." +); + +CLASS_DECLARATION( Listener, CinematicArmature, NULL ) +{ + { &EV_CinematicArmature_PlayCinematic, &CinematicArmature::playCinematic }, + { &EV_CinematicArmature_PlayCinematicAt, &CinematicArmature::playCinematicAt }, + { &EV_CinematicArmature_Debug, &CinematicArmature::debugCinematics }, + + { NULL, NULL } +}; + + +//=============================================================== +// Name: CinematicArmature +// Class: CinematicArmature +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +CinematicArmature::CinematicArmature() : + _cinematic( NULL ) +{ +} + + +//=============================================================== +// Name: ~CinematicArmature +// Class: CinematicArmature +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +CinematicArmature::~CinematicArmature( void ) +{ + deleteAllCinematics(); + _cinematicList.FreeObjectList(); + _cinematic = NULL ; +} + + +//=============================================================== +// Name: deleteAllCinematics +// Class: CinematicArmature +// +// Description: Deletes all the cinematics currently in the +// list. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicArmature::deleteAllCinematics( void ) +{ + int numCinematics = _cinematicList.NumObjects(); + for (int cinematicIdx = 1; cinematicIdx <= numCinematics; cinematicIdx++) + { + Cinematic *cinematic = _cinematicList.ObjectAt(cinematicIdx); + delete cinematic ; + } +} + + +//=============================================================== +// Name: clearCinematicsList +// Class: CinematicArmature +// +// Description: Removes all the outstanding cinematics in the +// list. Leaves the mediocre ones. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicArmature::clearCinematicsList( void ) +{ + _cinematicList.ClearObjectList(); +// deleteAllCinematics(); +} + + + +//=============================================================== +// Name: getCinematic +// Class: CinematicArmature +// +// Description: Retrieves the cinematic with the specified name. +// +// Parameters: const str& -- name of the cinematic to retrieve. +// +// Returns: Cinematic* -- pointer to the specified cinematic. +// Returns 0 (NULL) if not found. +// +//=============================================================== +Cinematic* CinematicArmature::getCinematicByName( const str &cinematicName ) +{ + int numCinematics = _cinematicList.NumObjects(); + for (int cinematicIndex = 1; cinematicIndex <= numCinematics; cinematicIndex++) + { + Cinematic *cinematic = _cinematicList.ObjectAt(cinematicIndex); + if (cinematic->targetname == cinematicName) + return cinematic ; + } + return 0 ; // NULL +} + + + +//=============================================================== +// Name: playCinematic +// Class: CinematicArmature +// +// Description: Plays (starts) the specified cinematic. This +// event also loads the cinematic if it is not already +// loaded (where as start will give an error). This +// event is a shorthand for loading and starting a +// cinematic. +// +// Parameters: Event* -- EV_CinematicArmature_PlayCinematic +// +// Returns: None +// +//=============================================================== +void CinematicArmature::playCinematic( Event *ev ) +{ + if ( ev->NumArgs() >= 1) + { + str cinematicName = ev->GetString( 1 ); + _cinematic = getCinematicByName( cinematicName ) ; + if ( !_cinematic ) + { + if ( !loadCinematic( cinematicName )) + { + ev->Error("playCinematic: Unable to load cinematic %s\n", cinematicName.c_str() ); + return ; + } + } + } + else if ( !_cinematic ) + { + ev->Error("startCinematic: No current cinematic to start!" ); + return ; + } + + assert( _cinematic ) ; + + str originName ; + if ( ev->NumArgs() == 2 ) + { + originName = ev->GetString( 2 ); + } + + _cinematic->startAtNamedOrigin( originName ); +} + +//=============================================================== +// Name: playCinematicAt +// Class: CinematicArmature +// +// Description: Plays a cinematic at a dynamically specified origin +// and yaw rotation. +// +// Parameters: Event* -- EV_CinematicArmature_PlayCinematicAt +// +// Returns: None +// +//=============================================================== +void CinematicArmature::playCinematicAt( Event *ev ) +{ + if ( ev->NumArgs() >= 1) + { + str cinematicName = ev->GetString( 1 ); + _cinematic = getCinematicByName( cinematicName ) ; + if ( !_cinematic ) + { + if ( !loadCinematic( cinematicName )) + { + ev->Error("playCinematicat: Unable to load cinematic %s\n", cinematicName.c_str() ); + return ; + } + } + } + else if ( !_cinematic ) + { + ev->Error("playCinematicat: No current cinematic to start!" ); + return ; + } + + assert( _cinematic ) ; + Vector origin( 0.0f, 0.0f, 0.0f ) ; + float yaw = 0.0f ; + + if ( ev->NumArgs() >= 2 ) + { + origin = ev->GetVector( 2 ); + } + + if ( ev->NumArgs() == 3 ) + { + yaw = ev->GetFloat( 3 ); + } + + _cinematic->startAtOrigin( origin, yaw ); +} + + +//=============================================================== +// Name: loadCinematic +// Class: CinematicArmature +// +// Description: Loads a cinematic from disk. This will always +// reload the cinematic. +// +// Parameters: const str& -- name of the cinematic to load (.cin file) +// +// Returns: bool -- true if load was successful. +// +//=============================================================== +bool CinematicArmature::loadCinematic( const str &cinematicName ) +{ + CinematicPtr cinematic = 0 ; + str filename; + + filename = "cins/"; + filename += cinematicName ; + filename += ".cin"; + + cinematic = new Cinematic ( filename ); + if ( !cinematic->load() ) + { + return false ; + } + + _cinematicList.AddObject(cinematic); + _cinematic = cinematic ; // current cinematic gets set + + return true ; +} + +//=============================================================== +// Name: debugCinematics +// Class: CinematicArmature +// +// Description: Toggles debugging info on cinematics +// +// Parameters: Event * -- not used +// +// Returns: None +// +//=============================================================== +void CinematicArmature::debugCinematics( Event *ev ) +{ + _debug = !_debug ; +} + + +//=============================================================== +// Name: createCinematic +// Class: CinematicArmature +// +// Description: Creates a new cinematic and returns it. The cinematic +// created is loaded from the .cin file with the same +// name. +// +// Parameters: const str &cinematicName +// +// Returns: Cinematic* -- the Cinematic created. If the +// cinematic isn't found, returns 0. +// +//=============================================================== +Cinematic* CinematicArmature::createCinematic( const str &cinematicName ) +{ + if ( loadCinematic( cinematicName ) ) + { + return _cinematic ; + } + + return 0; +} + + + +//--------------------------------------------------------------- +// C I N E M A T I C A C T O R +//--------------------------------------------------------------- +CLASS_DECLARATION( Listener, CinematicActor, NULL ) +{ + { &EV_Actor_ControlLost, &CinematicActor::actorControlLostEvent }, + { &EV_Actor_BehaviorFinished, &CinematicActor::actorBehaviorFinishedEvent }, + + { NULL, NULL } +}; + + +//=============================================================== +// Name: CinematicActor +// Class: CinematicActor +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +CinematicActor::CinematicActor( void ) : + _name(""), + _tiki(""), + _anim(""), + _moveAnim("walk"), + _snapToSpot( false ), + _ignorePain( true ), + _ignoreSight( true ), + _ignoreSound( true ), + _origin(0.0f, 0.0f, 0.0f), + _yaw( 0.0f ), + _isAtSpot( false ), + _isAnimDone( false ), + _hasActorControl( false ), + _rootActor( false ), + _removeAfter( false ), + _afterBehavior( CINEMATIC_ACTOR_AFTER_BEHAVIOR_LEAVE_WITH_AI ), + _state( CINEMATIC_ACTOR_STATE_INACTIVE ), + _actor( 0 ) +{ +} + + +//=============================================================== +// Name: reset +// Class: CinematicActor +// +// Description: Resets the internal state of the actor to what it +// is before a cinematic begins. Useful for ensuring +// that the cinematic can be replayed correctly. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicActor::reset( void ) +{ + _state = CINEMATIC_ACTOR_STATE_INACTIVE ; + _hasActorControl = false ; + _isAtSpot = false ; + _isAnimDone = false ; +} + +//=============================================================== +// Name: setAnim +// Class: CinematicActor +// +// Description: Sets the animation for this actor. Also caches +// the animation. +// +// Parameters: const str& -- the animation name. +// +// Returns: None +// +//=============================================================== +void CinematicActor::setAnim( const str &anim ) +{ + _anim = anim ; + CacheResource( _anim ); +} + +//=============================================================== +// Name: setAfterBehavior +// Class: CinematicActor +// +// Description: Sets the behavior enumeration from the specified +// string for the actor. The after behavior controls +// what the actor does after the cinematic completes. +// +// Parameters: const str& -- string description of after behaviors. +// +// Returns: None +// +//=============================================================== +void CinematicActor::setAfterBehavior( const str &actorBehaviorName ) +{ + if ( actorBehaviorName == "REMOVE" ) + { + _afterBehavior = CINEMATIC_ACTOR_AFTER_BEHAVIOR_REMOVE_FROM_GAME ; + _removeAfter = true ; + } + else if ( actorBehaviorName == "LEAVE" || actorBehaviorName == "AI" ) // for legacy reasons, leave means leave with ai + { + _afterBehavior = CINEMATIC_ACTOR_AFTER_BEHAVIOR_LEAVE_WITH_AI ; + } + else if ( actorBehaviorName == "NOAI" ) + { + _afterBehavior = CINEMATIC_ACTOR_AFTER_BEHAVIOR_LEAVE_NO_AI ; + } + else if ( actorBehaviorName == "FREEZE" ) + { + _afterBehavior = CINEMATIC_ACTOR_AFTER_BEHAVIOR_LEAVE_FREEZE ; + } + else if ( actorBehaviorName == "KILL" ) + { + _afterBehavior = CINEMATIC_ACTOR_AFTER_BEHAVIOR_KILL ; + } + else // for legacy reasons default is to kill them. + { + _afterBehavior = CINEMATIC_ACTOR_AFTER_BEHAVIOR_KILL ; + } +} + + + + +//=============================================================== +// Name: handleControlLostEvent +// Class: CinematicActor +// +// Description: Handles the EV_Actor_ControlLost event. +// This disables the control flag. +// +// Parameters: Event* -- the event caught +// +// Returns: None +// +//=============================================================== +void CinematicActor::actorControlLostEvent( Event *ev ) +{ + _hasActorControl = false ; +} + + +//=============================================================== +// Name: actorBehaviorFinishedEvent +// Class: CinematicActor +// +// Description: Handles the EV_Actor_BehaviorFinished event. +// Snaps the actor to the final origin. +// +// Parameters: Event* -- the event that triggered this function. +// +// Returns: None +// +//=============================================================== +void CinematicActor::actorBehaviorFinishedEvent( Event *ev ) +{ + assert( _actor ); + + switch ( _state ) + { + case CINEMATIC_ACTOR_STATE_MOVING: + _actor->setOrigin( _origin ); + _turnActor( true ); + break ; + case CINEMATIC_ACTOR_STATE_TURNING: + _isAtSpot = true ; + _state = CINEMATIC_ACTOR_STATE_PLAYING ; + break ; + case CINEMATIC_ACTOR_STATE_PLAYING: + _isAnimDone = true ; + _state = CINEMATIC_ACTOR_STATE_FINISHED ; + break ; + default: + // if its not one of the above, probably something + // has gone terribly wrong (actor killed) and we need + // to mark this CinematicActor as no longer available. + break ; + } +} + + +//=============================================================== +// Name: turnActor +// Class: CinematicActor +// +// Description: Turns the actor to face the starting orientation. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicActor::_turnActor( bool useAnims ) +{ + assert( _actor ); + + Event *ev = new Event( EV_Actor_TurnTo ); + ev->AddFloat( _yaw ); + ev->AddInteger( useAnims ); + _actor->PostEvent( ev, 0.0f ); + _state = CINEMATIC_ACTOR_STATE_TURNING ; +} + + +//=============================================================== +// Name: locateActor +// Class: CinematicActor +// +// Description: Locates or spawns the real actor. If the actor +// is not already around, spawns a new one. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicActor::_locateActor( void ) +{ + if ( !_alwaysSpawn ) + { + if ( _actor ) return ; + _actor = Actor::FindActorByName( _name ); + } + else + { + _actor = 0 ; + } + + if ( !_actor || _actor->checkActorDead() ) + { + _spawn(); + _isAtSpot = true ; + } + + assert( _actor ); + if ( !_actor) + { + gi.Error( ERR_DROP, "Unable to locate or spawn cinematic actor %s\n", _name.c_str() ); + } +} + + + +//=============================================================== +// Name: takeControlOfActor +// Class: CinematicActor +// +// Description: Takes control of the actor. Turns off AI. Sets +// the stimuli the actor responds to. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicActor::takeControlOfActor( const Vector &origin, float yaw ) +{ + Event *ev = 0 ; + + _origin = origin + _originOffset ; + _yaw = yaw + _yawOffset ; + + _locateActor(); + _actor->TurnAIOff(); + _actor->RequestControl( this, Actor::ACTOR_CONTROL_LOCKED ); + + ev = new Event( EV_Actor_RespondTo ); + ev->AddString( "pain" ); + ev->AddInteger( doesIgnorePain() ? 0 : 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_PermanentlyRespondTo ); + ev->AddString( "pain" ); + ev->AddInteger( doesIgnorePain() ? 0 : 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_RespondTo ); + ev->AddString( "sight" ); + ev->AddInteger( doesIgnoreSight() ? 0 : 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_PermanentlyRespondTo ); + ev->AddString( "sight" ); + ev->AddInteger( doesIgnoreSight() ? 0 : 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_RespondTo ); + ev->AddString( "sound" ); + ev->AddInteger( doesIgnoreSound() ? 0 : 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_PermanentlyRespondTo ); + ev->AddString( "sound" ); + ev->AddInteger( doesIgnoreSound() ? 0 : 1 ); + _actor->ProcessEvent( ev ); + + _actor->edict->svflags |= SVF_BROADCAST; +} + + +//=============================================================== +// Name: releaseControlOfActor +// Class: CinematicActor +// +// Description: Releases control of the actor and turns AI +// back on. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicActor::releaseControlOfActor( void ) +{ + assert( _actor ); + Event *ev = 0 ; + + _actor->ReleaseControl( this ); + + _actor->edict->svflags &= ~SVF_BROADCAST; + + switch ( _afterBehavior ) + { + case CINEMATIC_ACTOR_AFTER_BEHAVIOR_REMOVE_FROM_GAME: + _actor->ProcessEvent( EV_Remove ); + _actor = 0 ; + break ; + + case CINEMATIC_ACTOR_AFTER_BEHAVIOR_LEAVE_FREEZE: + ev = new Event( EV_Actor_SetStickToGround ); + ev->AddInteger( 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_SetSimplifiedThink ) ; + ev->AddInteger( 0 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_SetUseGravity ); + ev->AddInteger( 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_SetMovementMode ); + ev->AddString( "normal" ); + _actor->ProcessEvent( ev ); + + // This little magic freeze the current animation on the + // last frame of the cinematic anim. This is intended + // for cinematics which need to appear to pause at + // the end (like for a dialog choice). + + if ( _actor->edict->s.torso_anim & ANIM_BLEND ) + _actor->animate->StopAnimatingAtEnd( torso ); + + if ( _actor->edict->s.anim & ANIM_BLEND ) + _actor->animate->StopAnimatingAtEnd( legs ); + + break ; + + case CINEMATIC_ACTOR_AFTER_BEHAVIOR_LEAVE_NO_AI: + ev = new Event( EV_Actor_SetStickToGround ); + ev->AddInteger( 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_SetSimplifiedThink ) ; + ev->AddInteger( 0 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_SetUseGravity ); + ev->AddInteger( 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_SetMovementMode ); + ev->AddString( "normal" ); + _actor->ProcessEvent( ev ); + break ; + + case CINEMATIC_ACTOR_AFTER_BEHAVIOR_LEAVE_WITH_AI: + ev = new Event( EV_Actor_SetStickToGround ); + ev->AddInteger( 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_SetSimplifiedThink ) ; + ev->AddInteger( 0 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_SetUseGravity ); + ev->AddInteger( 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_SetMovementMode ); + ev->AddString( "normal" ); + _actor->ProcessEvent( ev ); + + _actor->TurnAIOn(); + break ; + + case CINEMATIC_ACTOR_AFTER_BEHAVIOR_KILL: + _actor->ProcessEvent( EV_Actor_Dead ); + _actor = 0 ; + break ; + + default: + break ; + } +} + + + +//=============================================================== +// Name: getToPosition +// Class: CinematicActor +// +// Description: Moves a cinematic actor to their starting spot +// for a cinematic. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicActor::getToPosition( void ) +{ + if ( !doesSnapToSpot() && !isAtSpot() ) + { + // Move to our starting spot + Event *ev = new Event( EV_Actor_WalkTo ); + ev->AddVector( _origin ); + ev->AddString( _moveAnim ); + _actor->PostEvent( ev, 0.1f ); + _state = CINEMATIC_ACTOR_STATE_MOVING ; + } + else if ( !isAtSpot() ) + { + _actor->setOrigin( _origin ); + _actor->NoLerpThisFrame(); + _turnActor( !doesSnapToSpot() ); + } + else + { + _turnActor( !doesSnapToSpot() ); + } +} + + + +//=============================================================== +// Name: spawn +// Class: CinematicActor +// +// Description: Spawns an actor with the specified tiki. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicActor::_spawn( void ) +{ + ClassDef *cls; + Event *ev; + SpawnArgs args; + + if ( !strstr( _tiki.c_str(), ".tik" ) ) + { + _tiki += ".tik"; + } + + args.setArg( "model", _tiki.c_str() ); + cls = args.getClassDef(); + + if ( !cls || !checkInheritance( &Actor::ClassInfo, cls ) ) + { + gi.Error(ERR_DROP, "%s is not a valid Actor", _tiki.c_str() ); + return; + } + + _actor = ( Actor* )cls->newInstance(); + + ev = new Event( EV_Model ); + ev->AddString( _tiki.c_str() ); + _actor->ProcessEvent( ev ); //, EV_SPAWNARG ); + + ev = new Event( EV_SetOrigin ); + ev->AddVector( _origin ); + _actor->ProcessEvent( ev ); //, EV_SPAWNARG ); + + ev = new Event( EV_SetAngles ); + ev->AddVector( Vector( 0.0f, _yaw, 0.0f ) ); + _actor->ProcessEvent( ev ); //, EV_SPAWNARG ); + + ev = new Event( EV_Actor_SetUseGravity ); + ev->AddInteger( 0 ); + _actor->ProcessEvent( ev ); + + _actor->SetTargetName( _name.c_str() ); +} + + + +//=============================================================== +// Name: playAnimation +// Class: CinematicActor +// +// Description: Kicks off this actor's cinematic animation as +// specified in the cinematic file. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicActor::playAnimation( void ) +{ + Event *ev ; + _actor->velocity = Vector::Identity(); + + ev = new Event( EV_Actor_SetUseGravity ); + ev->AddInteger( 0 ); + _actor->ProcessEvent( ev ); + + + ev = new Event( EV_Actor_SetSimplifiedThink ) ; + ev->AddInteger( 1 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_SetStickToGround ); + ev->AddInteger( 0 ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_SetMovementMode ); + ev->AddString( "anim" ); + _actor->ProcessEvent( ev ); + + ev = new Event( EV_Actor_Anim ); + ev->AddString( _anim ); + _actor->ProcessEvent( ev ); +} + + +//=============================================================== +// Name: debug +// Class: CinematicActor +// +// Description: Draws a debug box around the actor's origin. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicActor::debug( void ) +{ + G_DrawXYBox( _origin, 8.0f, 0.8f, 0.6f, 0.3f, 1.0f ); +} + + +//=============================================================== +// Name: parse +// Class: CinematicActor +// +// Description: Parses the data for an actor out of the specified +// Cinematic file. +// +// Parameters: Script& -- the cinematic file being parsed. We +// depend on being at the start of the +// actor data (after the opening). +// +// Returns: bool -- true upon success +// +//=============================================================== +bool CinematicActor::parse( Script &cinematicFile ) +{ + const char *token = cinematicFile.GetToken( false ); + Vector origin; + + while ( token ) + { + if ( stricmp( token, "name" )==0 ) + { + setName( cinematicFile.GetString( false ) ); + } + else if ( stricmp( token, "tiki" )==0 ) + { + setTiki( cinematicFile.GetString( false ) ); + } + else if ( stricmp( token, "anim" )==0 ) + { + setAnim( cinematicFile.GetString( false ) ); + } + else if ( stricmp( token, "origin")==0 ) + { + setOriginOffset( cinematicFile.GetVector( false ) ); + } + else if ( stricmp( token, "yaw" )==0 ) + { + setYawOffset( cinematicFile.GetFloat( false ) ); + } + else if ( stricmp( token, "moveanim" )==0 ) + { + setMoveAnim( cinematicFile.GetString( false ) ); + } + else if ( stricmp( token, "snap" )==0 ) + { + setSnapToSpot( cinematicFile.GetBoolean( false ) ? true : false ); + } + else if ( stricmp( token, "nopain" )==0 ) + { + setIgnorePain( cinematicFile.GetBoolean( false ) ? true : false ); + } + else if ( stricmp( token, "nosight")==0 ) + { + setIgnoreSight( cinematicFile.GetBoolean( false ) ? true : false ); + } + else if ( stricmp( token, "nosound")== 0 ) + { + setIgnoreSound( cinematicFile.GetBoolean( false ) ? true : false ); + } + else if ( stricmp( token, "removeAfter") == 0 ) + { + setRemoveAfter( cinematicFile.GetBoolean( false ) ? true : false ); + } + else if ( stricmp( token, "rootActor" ) == 0 ) + { + setRootActor( cinematicFile.GetBoolean( false ) ? true : false ); + } + else if ( stricmp( token, "afterBehavior") == 0 ) + { + setAfterBehavior( cinematicFile.GetToken( false ) ); + } + else if ( stricmp( token, "alwaysSpawn" ) == 0 ) + { + setAlwaysSpawn( cinematicFile.GetBoolean( false ) ? true : false ); + } + else if (stricmp( token, "}")==0 ) + { + break ; + } + + token = cinematicFile.GetToken( true ); + } + + return true ; +} + + + +//--------------------------------------------------------------- +// C I N E M A T I C C A M E R A +//--------------------------------------------------------------- +Event EV_CinematicCamera_StopPlaying +( + "cinematicCameraStopPlaying", + EV_CODEONLY, + NULL, + NULL, + "Stops a cinematic camera." +); + + +CLASS_DECLARATION( Listener, CinematicCamera, NULL ) +{ + { &EV_CinematicCamera_StopPlaying, &CinematicCamera::_handleStopPlayingEvent }, + + { NULL, NULL } +}; + + +//=============================================================== +// Name: CinematicCamera +// Class: CinematicCamera +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +CinematicCamera::CinematicCamera( void ) : + _moveType( CAMERA_MOVE_TYPE_FOLLOW_ANIM ), + _lookType( CAMERA_LOOK_TYPE_WATCH_ANIM ), + _camera( 0 ), + _playing( false ), + _selfRemoving( true ), + _name( "" ), + _camFile( "" ), + _moveActor( "" ), + _lookActor( "" ), + _originOffset( 0.0f, 0.0f, 0.0f ), + _yawOffset( 0.0f ) +{ +} + +//=============================================================== +// Name: setMoveType +// Class: CinematicCamera +// +// Description: Sets the movement type of a cinematic camera. +// Sets it by string value. The movement type +// determines how this camera moves during the +// cinematic. +// +// Parameters: const str& -- the string version of a movement type. +// +// Returns: None +// +//=============================================================== +void CinematicCamera::setMoveType( const str &moveType ) +{ + if ( moveType == "STATIC" ) + { + _moveType = CAMERA_MOVE_TYPE_STATIC ; + } + else if ( moveType == "ANIM" ) + { + _moveType = CAMERA_MOVE_TYPE_FOLLOW_ANIM ; + } + else if ( moveType == "PLAYER" ) + { + _moveType = CAMERA_MOVE_TYPE_FOLLOW_PLAYER ; + } + else if ( moveType == "ACTOR" ) + { + _moveType = CAMERA_MOVE_TYPE_FOLLOW_ACTOR ; + } +} + + +//=============================================================== +// Name: setLookType +// Class: CinematicCamera +// +// Description: Sets the look type of a cinematic camera. +// Sets it by string value. The look type +// determines how this camera watches during the +// cinematic. +// +// Parameters: const str& -- the string version of a look type. +// +// Returns: None +// +//=============================================================== +void CinematicCamera::setLookType( const str &lookType ) +{ + if ( lookType == "ANIM" ) + { + _lookType = CAMERA_LOOK_TYPE_WATCH_ANIM ; + } + else if ( lookType == "PLAYER" ) + { + _lookType = CAMERA_LOOK_TYPE_WATCH_PLAYER ; + } + else if ( lookType == "ACTOR" ) + { + _lookType = CAMERA_LOOK_TYPE_WATCH_ACTOR ; + } +} + + + +//=============================================================== +// Name: startMoving +// Class: CinematicCamera +// +// Description: Starts the camera moving along its path +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicCamera::start( void ) +{ + _playing = true ; + _camera->PostEvent( EV_Camera_CameraThink, FRAMETIME ); +} + + +//=============================================================== +// Name: cut +// Class: CinematicCamera +// +// Description: Cues this camera for playing. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicCamera::cut( void ) +{ + _camera->Cut( NULL ); + SetCamera( _camera, 0.0f ); // switch time +} + + +//=============================================================== +// Name: reset +// Class: CinematicCamera +// +// Description: Resets the internal state of the camera back to +// the start. Currently this doesn't do much. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicCamera::reset( void ) +{ + _playing = false ; +} + + + +//=============================================================== +// Name: takeControlOfCamera +// Class: CinematicCamera +// +// Description: Takes control of the camera. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicCamera::takeControlOfCamera( const Vector &origin, float yaw ) +{ + _locateCamera(); + _camera->SetPlaybackOffsets( _yawOffset + yaw, _originOffset + origin ); +} + +//=============================================================== +// Name: releaseControlOfCamera +// Class: CinematicCamera +// +// Description: Release the camera +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicCamera::releaseControlOfCamera( void ) +{ + if ( !_camera) return ; + + CancelPendingEvents(); + if ( isSelfRemoving() ) + { + _camera->PostEvent( EV_Remove, 0.0f ); + } + _camera = 0 ; + _playing = 0 ; +} + + +//=============================================================== +// Name: _locateCamera +// Class: CinematicCamera +// +// Description: Locates or spawns the real camera. If the camera +// is not already around, spawns a new one. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicCamera::_locateCamera( void ) +{ + if ( _camera ) return ; + + if ( !_camera ) + { + _spawn(); + } + + return ; +} + + +//=============================================================== +// Name: _spawn +// Class: CinematicCamera +// +// Description: Spawns a camera with the specified tiki. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicCamera::_spawn( void ) +{ + _camera = new Camera ; + assert( _camera ); + + str cameraName( _name ); + cameraName += "Camera" ; + + _camera->SetTargetName( cameraName.c_str() ); + _camera->ProcessPendingEvents(); + if ( _moveType == CAMERA_MOVE_TYPE_FOLLOW_ANIM ) + { + Event *event = new Event( EV_Camera_LoadKFC ); + event->AddString( _camFile ); + _camera->ProcessEvent( event ); + + float pathLengthInSeconds = _camera->GetPathLengthInSeconds(); + event = new Event( EV_CinematicCamera_StopPlaying ); + PostEvent( event, pathLengthInSeconds ); + } +} + + +//=============================================================== +// Name: _handleStopPlayingEvent +// Class: CinematicCamera +// +// Description: Handles the stop playing event. This event tells +// the camara that it is done playing its cinematic +// sequence. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicCamera::_handleStopPlayingEvent( Event *ev ) +{ + _playing = false ; +} + + + +//=============================================================== +// Name: parse +// Class: CinematicCamera +// +// Description: Parses the data for a camera out of the specified +// Cinematic file. +// +// Parameters: Script& -- the cinematic file being parsed. We +// depend on being at the start of the +// camera data (after the opening). +// +// Returns: bool -- true upon success +// +//=============================================================== +bool CinematicCamera::parse( Script &cinematicFile ) +{ + const char *token = cinematicFile.GetToken( false ); + + while ( token ) + { + if ( stricmp( token, "name" )==0 ) + { + token = cinematicFile.GetString( false ); + setName( token ); + } + else if ( stricmp( token, "camfile" )==0 ) + { + token = cinematicFile.GetString( false ); + setCamFile( token ); + } + else if ( stricmp( token, "movetype" ) == 0 ) + { + token = cinematicFile.GetString( false ); + setMoveType( token ); + } + else if ( stricmp( token, "looktype" ) == 0 ) + { + token = cinematicFile.GetString( false ); + setLookType( token ); + } + else if ( stricmp( token, "moveactor" ) == 0 ) + { + token = cinematicFile.GetString( false ); + setMoveActor( token ); + } + else if ( stricmp( token, "lookactor" ) == 0 ) + { + token = cinematicFile.GetString( false ); + setLookActor( token ); + } + else if ( stricmp( token, "origin" ) == 0 ) + { + setOriginOffset( cinematicFile.GetVector( false ) ); + } + else if ( stricmp( token, "yaw" ) == 0 ) + { + setYawOffset( cinematicFile.GetFloat( false ) ) ; + } + else if ( stricmp( token, "selfRemoving" ) == 0 ) + { + setSelfRemovingFlag( cinematicFile.GetBoolean( false ) ? true : false ); + } + else if (stricmp( token, "}")==0 ) + { + break ; + } + + token = cinematicFile.GetToken( true ); + } + + return true ; +} + + + +//--------------------------------------------------------------- +// C I N E M A T I C C U T +//--------------------------------------------------------------- +Event EV_CinematicCut_FadeOut +( + "cinematiccutfadeout", + EV_CODEONLY, + "", + "", + "Fades out leading to a cinematic camera cut." +); +Event EV_CinematicCut_Cut +( + "cinematiccut", + EV_CODEONLY, + "", + "", + "Makes a cinematic camera cut." +); + +CLASS_DECLARATION( Listener, CinematicCut, NULL ) +{ + { &EV_CinematicCut_FadeOut, &CinematicCut::_handleFadeOutEvent }, + { &EV_CinematicCut_Cut, &CinematicCut::_handleCutEvent }, + + { NULL, NULL } +}; + + +//=============================================================== +// Name: CinematicCut +// Class: CinematicCut +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +CinematicCut::CinematicCut( void ) : + _cinematicCamera( 0 ), + _frame( 0 ), + _fadeOut( 0 ), + _fadeIn( 0 ), + _cameraName( "" ) +{ +} + + + +//=============================================================== +// Name: cut +// Class: CinematicCut +// +// Description: Invokes this camera cut +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicCut::postEvents( void ) +{ + Event *ev = 0 ; + + if ( _fadeOut ) + { + ev = new Event( EV_CinematicCut_FadeOut ); + int fadeOutFrame = _frame - _fadeOut ; + PostEvent( ev, fadeOutFrame * 0.05 ); + } + + ev = new Event( EV_CinematicCut_Cut ); + PostEvent( ev, _frame * 0.05 ); +} + + + +//=============================================================== +// Name: stop +// Class: CinematicCut +// +// Description: Cancels all outstanding events. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicCut::stop( void ) +{ + CancelPendingEvents(); +} + + + +//=============================================================== +// Name: parse +// Class: CinematicCut +// +// Description: Parses the data for a cut out of the specified +// Cinematic file. +// +// Parameters: Script& -- the cinematic file being parsed. We +// depend on being at the start of the +// cut block (after the opening). +// +// Returns: bool -- true upon success +// +//=============================================================== +bool CinematicCut::parse( Script &cinematicFile ) +{ + const char *token = cinematicFile.GetToken( false ); + bool lerp = false ; + int value = 0 ; + + while ( token ) + { + if ( stricmp( token, "toCamera" )==0 ) + { + token = cinematicFile.GetString( false ); + setCameraName( token ); + } + else if ( stricmp( token, "fadeOut" )==0 ) + { + value = cinematicFile.GetInteger( false ); + setFadeOut( value ); + } + else if ( stricmp( token, "fadeIn" ) == 0 ) + { + value = cinematicFile.GetInteger( false ); + setFadeIn( value ); + } + else if ( stricmp( token, "frame" ) == 0 ) + { + value = cinematicFile.GetInteger( false ); + setFrame( value ); + } + else if ( stricmp( token, "lerp" ) == 0 ) + { + lerp = cinematicFile.GetBoolean( false ) ? true : false ; + setLerpFlag( lerp ); + } + else if (stricmp( token, "}")==0 ) + { + break ; + } + + token = cinematicFile.GetToken( true ); + } + + return true ; +} + + +//--------------------------------------------------------------- +// P R O T E C T E D M E T H O D S +//--------------------------------------------------------------- + + +//=============================================================== +// Name: handleFadeOutEvent +// Class: CinematicCut +// +// Description: Handles the EV_CinematicCut_FadeOut event. This +// triggers the fade out on the current camera, leading +// up to this camera cut. +// +// Parameters: Event* -- the event that triggered this call. +// +// Returns: +// +//=============================================================== +void CinematicCut::_handleFadeOutEvent( Event *ev ) +{ + level.m_fade_color = Vector( 0.0f, 0.0f, 0.0f ); + level.m_fade_time_start = _fadeOut * 0.05 ; + level.m_fade_time = _fadeOut * 0.05 ; + level.m_fade_alpha = 1.0f ; + level.m_fade_type = fadeout; + level.m_fade_style = alphablend; +} + + +//=============================================================== +// Name: handleCutEvent +// Class: CinematicCut +// +// Description: Handles the EV_CinematicCut_Cut event. This +// triggers the actual camera cut, plus does the +// fade in if any specified. +// +// Parameters: Event* -- the event that triggered this call. +// +// Returns: None +// +//=============================================================== +void CinematicCut::_handleCutEvent( Event *ev ) +{ + if ( _cinematicCamera ) + { + _cinematicCamera->cut(); + } + else + { + SetCamera( NULL, 0.0f ); + } + + if ( _fadeIn ) + { + level.m_fade_color = Vector( 0.0f, 0.0f, 0.0f ); + level.m_fade_time_start = _fadeIn * 0.05 ; + level.m_fade_time = _fadeIn * 0.05 ; + level.m_fade_alpha = 1.0f ; + level.m_fade_type = fadein ; + level.m_fade_style = alphablend ; + } +} + + + +//--------------------------------------------------------------- +// C I N E M A T I C O R I G I N +//--------------------------------------------------------------- +CLASS_DECLARATION( Listener, CinematicOrigin, NULL ) +{ + { NULL, NULL } +}; + + +//=============================================================== +// Name: CinematicOrigin +// Class: CinematicOrigin +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +CinematicOrigin::CinematicOrigin( void ) : + _origin( 0.0f, 0.0f, 0.0f ), + _yaw( 0.0f ), + _name( "" ) +{ +} + + +//=============================================================== +// Name: debug +// Class: CinematicOrigin +// +// Description: Draws a debug box around this origin. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void CinematicOrigin::debug( void ) +{ + G_DrawXYBox( _origin, 8.0f, 0.8f, 0.6f, 0.8f, 1.0f ); +} + + + +//=============================================================== +// Name: parse +// Class: CinematicOrigin +// +// Description: Parses the data for an origin out of the specified +// Cinematic file. +// +// Parameters: Script& -- the cinematic file being parsed. We +// depend on being at the start of the +// origin block (after the opening). +// +// Returns: bool -- true upon success +// +//=============================================================== +bool CinematicOrigin::parse( Script &cinematicFile ) +{ + const char *token = cinematicFile.GetToken( false ); + + while ( token ) + { + if ( stricmp( token, "name" )==0 ) + { + setName( cinematicFile.GetString( false ) ); + } + else if ( stricmp( token, "origin" )==0 ) + { + setOrigin( cinematicFile.GetVector( false ) ); + } + else if ( stricmp( token, "yaw" ) == 0 ) + { + setYaw( cinematicFile.GetFloat( false ) ); + } + else if (stricmp( token, "}")==0 ) + { + break ; + } + + token = cinematicFile.GetToken( true ); + } + + return true ; +} + + +//----------------------------------------------------------- +// C I N E M A T I C +//----------------------------------------------------------- +Event EV_Cinematic_Begin +( + "beginCinematic", + EV_DEFAULT, + "SB", + "originName callStartThreadFlag", + "Begins the cinematic at the specified named origin. If the" + "origin name isn't specified, begins at 0,0,0 with 0 degrees of yaw rotation." +); + +Event EV_Cinematic_BeginAt +( + "beginCinematicAt", + EV_DEFAULT, + "VFB", + "originVector yawRotation callStartThreadFlag", + "Begins the cinematic at the specified origin with the specified rotation." + "If they are not specified, begins at 0,0,0 with 0 degrees of yaw rotation." +); + +Event EV_Cinematic_End +( + "endCinematic", + EV_DEFAULT | EV_CONSOLE, + "B", + "callEndThreadFlag", + "Stop the cinematic." +); + +Event EV_Cinematic_SetBeginThread +( + "setbeginthread", + EV_DEFAULT, + "s", + "threadName", + "Sets the thread to call when the cinematic begins." +); + +Event EV_Cinematic_SetEndThread +( + "setendthread", + EV_DEFAULT, + "s", + "threadName", + "Sets the thread to call when the cinematic ends." +); + + +CLASS_DECLARATION( Entity, Cinematic, "cinematic" ) +{ + { &EV_Cinematic_Begin, &Cinematic::handleBeginEvent }, + { &EV_Cinematic_BeginAt, &Cinematic::handleBeginAtEvent }, + { &EV_Cinematic_End, &Cinematic::handleEndEvent }, + { &EV_Cinematic_SetBeginThread, &Cinematic::handleSetBeginThreadEvent }, + { &EV_Cinematic_SetEndThread, &Cinematic::handleSetEndThreadEvent }, + + { NULL, NULL } +}; + + +//=============================================================== +// Name: Cinematic +// Class: Cinematic +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +Cinematic::Cinematic( ) +{ + init(); +} + + +//=============================================================== +// Name: Cinematic +// Class: Cinematic +// +// Description: Constructor. +// +// Parameters: const str& -- filename of cinematic file. +// +// Returns: None +// +//=============================================================== +Cinematic::Cinematic( const str &filename ) : + _filename( filename), + _looping( false ), + _playing( false ), + _callStartThreadFlag( true ), + _resetCamera( true ), + _stage( CINEMATIC_STAGE_UNLOADED ) +{ + init(); +} + +//=============================================================== +// Name: ~Cinematic +// Class: Cinematic +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +Cinematic::~Cinematic( void ) +{ + _actorList.FreeObjectList(); + _cameraList.FreeObjectList(); + _cutList.FreeObjectList(); + _originList.FreeObjectList(); +} + + +//=============================================================== +// Name: init +// Class: Cinematic +// +// Description: Initializes a newly constructed cinematic. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void Cinematic::init() +{ + turnThinkOn(); + + _stage = CINEMATIC_STAGE_UNLOADED ; + + setSolidType( SOLID_NOT ); + setMoveType( MOVETYPE_NONE ); + + if ( sv_showcameras->integer ) + { + setModel( "cinematic.tik" ); + showModel(); + } + else + { + hideModel(); + } +} + +//=============================================================== +// Name: reset +// Class: Cinematic +// +// Description: Resets the internal state of the cinematic to +// its state before the cinematic begins. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void Cinematic::reset( void ) +{ + int numCinematicActors = _actorList.NumObjects(); + for ( int cinematicActorIdx = 1; cinematicActorIdx <= numCinematicActors; ++cinematicActorIdx ) + { + CinematicActor *cinematicActor = _actorList.ObjectAt( cinematicActorIdx ); + cinematicActor->reset(); + } + + int numCinematicCameras = _cameraList.NumObjects(); + for ( int cinematicCameraIdx = 1; cinematicCameraIdx <= numCinematicCameras; ++cinematicCameraIdx ) + { + CinematicCamera *cinematicCamera = _cameraList.ObjectAt( cinematicCameraIdx ); + cinematicCamera->reset(); + } + + _stage = CINEMATIC_STAGE_READY ; + _playing = false ; +} + + + +//=============================================================== +// Name: Think +// Class: Cinematic +// +// Description: Think routine for a cinematic +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void Cinematic::Think() +{ + switch ( _stage ) + { + case CINEMATIC_STAGE_WAITING_FOR_ACTORS: + if ( areActorsAtTheirPlaces() ) + start() ; + break ; + case CINEMATIC_STAGE_ANIMATING: + if ( checkForCompletion() ) + stop() ; + break ; + default: + break ; + } + + if ( CinematicArmature::isInDebugMode() ) + { + debug(); + } +} + + +//=============================================================== +// Name: getCinematicActorByName +// Class: Cinematic +// +// Description: Retrieve an actor by its name. Returns 0 (NULL) +// if the actor isn't found. +// +// This retrieves the first actor with the name. +// +// Parameters: const str& -- name of the actor to retrieve. +// +// Returns: CinematicCamera* -- pointer to the actor with the +// specified name. 0 (NULL) if +// not found. +// +//=============================================================== +CinematicActor* Cinematic::getCinematicActorByName( const str &actorName ) +{ + int numActors = _actorList.NumObjects(); + for ( int actorIdx = 1; actorIdx <= numActors; ++actorIdx ) + { + CinematicActor *cinematicActor = _actorList.ObjectAt( actorIdx ); + if ( cinematicActor->getName() == actorName ) + { + return cinematicActor ; + } + } + + return 0 ; // NULL +} + + +//=============================================================== +// Name: getCinematicCameraByName +// Class: Cinematic +// +// Description: Retrieve a camera from its name. Returns 0 (NULL) +// if the camera isn't found. This is used by the +// cinematic to attach cameras to camera cuts. +// +// This retrieves the first camera with the name. +// +// Parameters: const str& -- name of the camera to retrieve. +// +// Returns: CinematicCamera* -- pointer to the camera with the +// specified name. 0 (NULL) if +// not found. +// +//=============================================================== +CinematicCamera* Cinematic::getCinematicCameraByName( const str &cameraName ) +{ + int numCameras = _cameraList.NumObjects(); + for ( int cameraIdx = 1; cameraIdx <= numCameras; ++cameraIdx ) + { + CinematicCamera *cinematicCamera = _cameraList.ObjectAt( cameraIdx ); + if ( cinematicCamera->getName() == cameraName ) + { + return cinematicCamera ; + } + } + + return 0 ; // NULL +} + +//=============================================================== +// Name: getCinematicOriginByName +// Class: Cinematic +// +// Description: Retrieve an origin by its name. Returns 0 (NULL) +// if the origin isn't found. This is used by the +// cinematic to support the playback of a cinematic +// at multiple origins in the world. +// +// This retrieves the first origin with the name. +// +// Parameters: const str& -- name of the origin to retrieve. +// +// Returns: CinematicOrigin* -- pointer to the origin with the +// specified name. 0 (NULL) if +// not found. +// +//=============================================================== +CinematicOrigin* Cinematic::getCinematicOriginByName( const str &originName ) +{ + int numOrigins = _originList.NumObjects(); + for ( int originIdx = 1; originIdx <= numOrigins; ++originIdx ) + { + CinematicOrigin *cinematicOrigin = _originList.ObjectAt( originIdx ); + if ( str::icmp( cinematicOrigin->getName().c_str(), originName.c_str() )==0 ) + { + return cinematicOrigin ; + } + } + + return 0 ; // NULL +} + + + +//=============================================================== +// Name: _startAnimation +// Class: Cinematic +// +// Description: Begins a cinematic actually playing. At the point +// this is called, all actors are in their places. +// All cameras are loaded. Now we tell all the actors +// to begin their animations, and the cameras to +// begin their playing. +// +// We set our state variable accordingly. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void Cinematic::_startAnimation( void ) +{ + int numCinematicActors = _actorList.NumObjects(); + for (int cinematicActorIdx = 1; cinematicActorIdx <= numCinematicActors; ++cinematicActorIdx ) + { + CinematicActor *cinematicActor = _actorList.ObjectAt( cinematicActorIdx ); + cinematicActor->playAnimation(); + } + + int numCameras = _cameraList.NumObjects(); + for ( int cameraIdx = 1; cameraIdx <= numCameras; ++cameraIdx ) + { + CinematicCamera *cinematicCamera = _cameraList.ObjectAt( cameraIdx ); + cinematicCamera->start(); + } + + int numCuts = _cutList.NumObjects(); + for ( int cutIdx = 1; cutIdx <= numCuts; ++cutIdx ) + { + CinematicCut *cinematicCut = _cutList.ObjectAt( cutIdx ); + cinematicCut->postEvents(); + } +} + +//=============================================================== +// Name: _endAnimation +// Class: Cinematic +// +// Description: Ends the currently playing cinematic +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void Cinematic::_endAnimation( void ) +{ + int numCinematicActors = _actorList.NumObjects(); + for (int cinematicActorIdx = 1; cinematicActorIdx <= numCinematicActors; ++cinematicActorIdx ) + { + CinematicActor *cinematicActor = _actorList.ObjectAt( cinematicActorIdx ); + cinematicActor->releaseControlOfActor(); + } + + // Tell CinematicCameras to delete their cameras here + int numCinematicCameras = _cameraList.NumObjects(); + for ( int cinematicCameraIdx = 1; cinematicCameraIdx <= numCinematicCameras; ++cinematicCameraIdx ) + { + CinematicCamera *cinematicCamera = _cameraList.ObjectAt( cinematicCameraIdx ); + cinematicCamera->releaseControlOfCamera(); + } + + int numCinematicCuts = _cutList.NumObjects(); + for ( int cinematicCutIdx = 1; cinematicCutIdx <= numCinematicCuts; ++cinematicCutIdx ) + { + CinematicCut *cinematicCut = _cutList.ObjectAt( cinematicCutIdx ); + cinematicCut->stop(); + } + + // Restore the player's camera immediately if we've taken it + if ( doesResetCamera() && numCinematicCuts ) + { + SetCamera( NULL, 0.0f ); + } +} + + +//=============================================================== +// Name: isCompletion +// Class: Cinematic +// +// Description: Checks to see if a cinematic is completed, done, +// finished, over, kaput, no more, el tora mea braca. +// +// Parameters: None +// +// Returns: bool -- true if completed. +// +//=============================================================== +bool Cinematic::checkForCompletion( void ) +{ + int numCinematicActors = _actorList.NumObjects(); + for (int cinematicActorIdx = 1; cinematicActorIdx <= numCinematicActors; ++cinematicActorIdx ) + { + CinematicActor *cinematicActor = _actorList.ObjectAt( cinematicActorIdx ); + if ( !cinematicActor->isAnimDone() ) return false ; + } + + int numCinematicCameras = _cameraList.NumObjects(); + for ( int cinematicCameraIdx = 1; cinematicCameraIdx <= numCinematicCameras; ++cinematicCameraIdx ) + { + CinematicCamera *cinematicCamera = _cameraList.ObjectAt( cinematicCameraIdx ); + if ( !cinematicCamera->isAnimDone() ) return false ; + } + + return true ; +} + + + +//=============================================================== +// Name: areActorsAtTheirPlaces +// Class: Cinematic +// +// Description: Checks to see if all the actors are at their +// places. +// +// Parameters: None +// +// Returns: bool -- true +// +//=============================================================== +bool Cinematic::areActorsAtTheirPlaces( void ) +{ + int numActors = _actorList.NumObjects(); + for (int actorIdx = 1; actorIdx <= numActors; actorIdx++) + { + CinematicActor *actor = _actorList.ObjectAt( actorIdx ); + if ( !actor->isAtSpot() ) return false ; + } + + return true ; +} + + +//=============================================================== +// Name: startAtNamedOrigin +// Class: Cinematic +// +// Description: Starts the cinematic at the origin specified by +// name. If the name isn't specified (or found) +// the cinematic's origin is the world origin. +// +// Cinematics can have origins named in their cinematic +// file. This enables an artist to determine the best +// spots for the cinematic to play, and to alias them +// to a name the game scripter can use to play them +// back. +// +// Parameters: const str& -- the name of the origin to use. +// +// Returns: None +// +//=============================================================== +void Cinematic::startAtNamedOrigin( const str &originName, bool callStartThread ) +{ + CinematicOrigin *cinematicOrigin = getCinematicOriginByName( originName ); + Vector origin( 0.0f, 0.0f, 0.0f ); + float yaw = 0.0f ; + + if ( cinematicOrigin ) + { + origin = cinematicOrigin->getOrigin(); + yaw = cinematicOrigin->getYaw(); + } + + startAtOrigin( origin, yaw, callStartThread ); +} + + + +//=============================================================== +// Name: prepareToStart +// Class: Cinematic +// +// Description: This begins by +// finding the actors involved in the scene. Actors +// that are not available are spawned at their +// designated spot. +// +// Actors are then told to run to their spots (marks +// in stage terminology). Once all actors are at +// their marks, the actual animation begins. +// +// Parameters: None +// +// Returns: float -- the level time the cinematic was begun. +// +//=============================================================== +void Cinematic::startAtOrigin( const Vector &origin, float yaw, bool callStartThread ) +{ + reset(); + + int numActors = _actorList.NumObjects(); + for ( int actorIdx = 1; actorIdx <= numActors; ++actorIdx ) + { + CinematicActor *cinematicActor = _actorList.ObjectAt( actorIdx ); + cinematicActor->takeControlOfActor( origin, yaw ); + cinematicActor->getToPosition(); + } + + int numCameras = _cameraList.NumObjects(); + for ( int cameraIdx = 1; cameraIdx <= numCameras; ++cameraIdx ) + { + CinematicCamera *cinematicCamera = _cameraList.ObjectAt( cameraIdx ); + cinematicCamera->takeControlOfCamera( origin, yaw ); + } + + int numCuts = _cutList.NumObjects(); + for ( int cutIdx = 1; cutIdx <= numCuts; ++cutIdx ) + { + CinematicCut *cut = _cutList.ObjectAt( cutIdx ); + CinematicCamera *camera = getCinematicCameraByName( cut->getCameraName() ); + + cut->setCinematicCamera( camera ); + } + + _stage = CINEMATIC_STAGE_WAITING_FOR_ACTORS ; + _playing = true ; + _callStartThreadFlag = callStartThread ; +} + + +//=============================================================== +// Name: start +// Class: Cinematic +// +// Description: Starts the cinematic animation. Calls the +// start thread if a start thread has been specified +// and start thread call flag is set (passed in). +// +// When this function is called, the real cinematic +// can begin because all of the actors are found and +// at their places. +// +// Parameters: bool -- call the start thread if specified (defaults +// to true). +// +// Returns: None +// +//=============================================================== +void Cinematic::start( bool callStartThread ) +{ + if ( _startThread.length() && callStartThread ) + { + ExecuteThread( _startThread ); + } + + _stage = CINEMATIC_STAGE_ANIMATING ; + + _startAnimation(); +} + + + +//=============================================================== +// Name: stop +// Class: Cinematic +// +// Description: Stops the playing of the current cinematic. All +// actors are switched back to AI mode. Remaining +// events are removed from the queue. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void Cinematic::stop( bool callEndThread ) +{ + // Make sure we don't stop this cinematic more than once + + if ( !_playing ) + return; + + _playing = false; + + _endAnimation(); + + if ( _stopThread.length() && callEndThread ) + { + ExecuteThread( _stopThread ); + } + + _stage = CINEMATIC_STAGE_FINISHED ; +} + + +//=============================================================== +// Name: handleBeginEvent +// Class: Cinematic +// +// Description: Starts the playback of this cinematic. +// +// Parameters: Event* -- this event. No arguments. +// +// Returns: None +// +//=============================================================== +void Cinematic::handleBeginEvent( Event *ev ) +{ + bool callStartThread = true ; + str originName ; + + if ( ev->NumArgs() == 1 ) + { + originName = ev->GetString( 1 ); + } + if ( ev->NumArgs() == 2 ) + { + callStartThread = ev->GetBoolean( 2 ); + } + + startAtNamedOrigin( originName, callStartThread ); +} + +//=============================================================== +// Name: handleBeginAtEvent +// Class: Cinematic +// +// Description: Begins the playback of this cinematic at the +// optionally specified origin and yaw rotation. +// +// Parameters: Event* -- EV_Cinematic_BeginAt event. +// - Arg 1 Optional vector origin (default 0,0,0) +// - Arg 2 Optional float yaw rotation (default 0 degrees) +// +// Returns: None +// +//=============================================================== +void Cinematic::handleBeginAtEvent( Event *ev ) +{ + Vector origin( 0.0f, 0.0f, 0.0f ); + float yawRotation = 0.0f ; + bool callStartThread = true ; + + if ( ev->NumArgs() >= 1 ) + { + origin = ev->GetVector( 1 ); + } + if ( ev->NumArgs() == 2 ) + { + yawRotation = ev->GetFloat( 2 ); + } + if ( ev->NumArgs() == 3 ) + { + callStartThread = ev->GetBoolean( 3 ); + } + + startAtOrigin( origin, yawRotation, callStartThread ); +} + + + +//=============================================================== +// Name: handleEndEvent +// Class: Cinematic +// +// Description: Ends the playback of this cinematic. +// +// Parameters: Event* -- EV_Cinematic_End +// +// Returns: None +// +//=============================================================== +void Cinematic::handleEndEvent( Event *ev ) +{ + bool callEndThread = true ; + + if ( ev->NumArgs() == 1 ) + { + callEndThread = ev->GetBoolean( 1 ); + } + + stop( callEndThread ); +} + + +//=============================================================== +// Name: handleSetBeginThreadEvent +// Class: Cinematic +// +// Description: Sets the name of the thread for this cinematic. +// This thread will get called at the beginning of the +// cinematic (after the actors reach their places). +// +// Parameters: Event* -- the event that triggered this call. The +// first argument must be a string, naming +// the play thread. +// +// Returns: None +// +//=============================================================== +void Cinematic::handleSetBeginThreadEvent( Event *ev ) +{ + assert( ev ); + + if (ev->NumArgs() != 1) + { + ev->Error( "Usage: setStartThread "); + return ; + } + + setStartThread( ev->GetString( 1 ) ); +} + + +//=============================================================== +// Name: handleSetEndThreadEvent +// Class: Cinematic +// +// Description: Sets the name of the stop thread for this cinematic. +// This thread will get called when the cinematic +// stops. +// +// Parameters: Event* -- the event that triggered this call. The +// first argument must be a string, naming +// the stop thread. +// +// Returns: None +// +//=============================================================== +void Cinematic::handleSetEndThreadEvent( Event *ev ) +{ + assert( ev ); + + if (ev->NumArgs() != 1) + { + ev->Error( "Usage: setStopThread "); + return ; + } + + setStopThread( ev->GetString( 1 ) ); +} + + +//=============================================================== +// Name: debug +// Class: Cinematic +// +// Description: Calls debug on all actors, cameras, and cuts. +// Opportunity for them to output debug information +// such as drawing lines on the screen. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void Cinematic::debug( void ) +{ + int numActors = _actorList.NumObjects(); + for ( int actorIdx = 1; actorIdx <= numActors; ++actorIdx ) + { + CinematicActor *cinematicActor = _actorList.ObjectAt( actorIdx ); + cinematicActor->debug(); + } + + int numCameras = _cameraList.NumObjects(); + for ( int cameraIdx = 1; cameraIdx <= numCameras; ++numCameras ) + { + CinematicCamera *cinematicCamera = _cameraList.ObjectAt( cameraIdx ); + cinematicCamera->debug(); + } + + int numCuts = _cutList.NumObjects(); + for ( int cutIdx = 1; cutIdx <= numCuts; ++numCuts ) + { + CinematicCut *cinematicCut = _cutList.ObjectAt( cutIdx ); + cinematicCut->debug(); + } + + int numOrigins = _originList.NumObjects(); + for ( int originIdx = 1; originIdx <= numOrigins; ++numOrigins ) + { + CinematicOrigin *cinematicOrigin = _originList.ObjectAt( originIdx ); + cinematicOrigin->debug(); + } +} + + +//=============================================================== +// Name: load +// Class: Cinematic +// +// Description: Loads the specified cinematic from file. +// +// Parameters: None +// +// Returns: bool -- true if successfully loaded. False otherwise. +// +//=============================================================== +bool Cinematic::load( void ) +{ + Script cinematicFile ; + const char *token ; + + if ( !gi.FS_Exists( _filename.c_str() ) ) + { + gi.Printf( "File %s not found.\n", _filename.c_str() ); + return false ; + } + + cinematicFile.LoadFile( _filename.c_str()); + while ( cinematicFile.TokenAvailable( true ) ) + { + token = cinematicFile.GetToken( true ); + if (stricmp( token, "Actors")==0) + { + if ( !parseActors( cinematicFile ) ) return false ; + } + else if (stricmp( token, "Cameras")==0) + { + if ( !parseCameras( cinematicFile ) ) return false ; + } + else if (stricmp( token, "Objects")==0) + { + if ( !parseObjects( cinematicFile ) ) return false ; + } + else if (stricmp( token, "Origins")==0) + { + if ( !parseOrigins( cinematicFile ) ) return false ; + } + else + { + gi.Printf("Unexpected token %s in cinematic file %s.\n", token, _filename.c_str()); + return false ; + } + } + + _stage = CINEMATIC_STAGE_READY ; + return true ; +} + + +//=============================================================== +// Name: parseActors +// Class: Cinematic +// +// Description: Parse the Actors block from a cinematic file. +// +// Parameters: Script& -- the cinematic file being parsed. +// +// Returns: bool -- true upon success. +// +//=============================================================== +bool Cinematic::parseActors( Script &cinematicFile ) +{ + if (!parseOpenBlock(cinematicFile, "Actors")) return false ; + const char *token = cinematicFile.GetToken( true ); + while ( token ) + { + if ( stricmp( token, "}" ) == 0 ) break ; + if ( stricmp( token, "Actor" ) == 0) + { + if (!parseActor( cinematicFile )) + return false ; + } + + token = cinematicFile.GetToken( true ); + } + + return true ; +} + + +//=============================================================== +// Name: parseActor +// Class: Cinematic +// +// Description: Parse an actor block from a cinematic file. +// +// Parameters: Script& -- the cinematic file being parsed. +// +// Returns: bool -- true upon success. +// +//=============================================================== +bool Cinematic::parseActor( Script &cinematicFile ) +{ + if (!parseOpenBlock( cinematicFile, "Actor" ) ) return false ; + + CinematicActor *actor = new CinematicActor ; + if (!actor->parse( cinematicFile )) return false ; + + _actorList.AddObject( actor ); + return true ; +} + + +//=============================================================== +// Name: parseCameras +// Class: Cinematic +// +// Description: Parses the cameras block from a cinematic file. +// +// Parameters: Script& -- the cinematic file being parsed +// +// Returns: bool -- true upon success. +// +//=============================================================== +bool Cinematic::parseCameras( Script &cinematicFile ) +{ + if (!parseOpenBlock(cinematicFile, "Cameras")) return false ; + const char *token = cinematicFile.GetToken( true ); + while ( token ) + { + if ( strcmp( token, "}" ) == 0 ) break ; + + if ( stricmp( token, "Camera" ) == 0) + { + if ( !parseCamera( cinematicFile )) return false ; + } + else if ( stricmp( token, "Cut" ) == 0 ) + { + if ( !parseCut( cinematicFile ) )return false ; + } + else if ( stricmp( token, "ResetCamera" ) == 0 ) + { + setResetCameraFlag( cinematicFile.GetBoolean( false ) ? true : false ); + } + + token = cinematicFile.GetToken( true ); + } + + return true ; +} + + +//=============================================================== +// Name: parseCamera +// Class: Cinematic +// +// Description: Parses a camera block. +// +// Parameters: Script& -- the cinematic file being parsed. +// +// Returns: bool -- true upon success. +// +//=============================================================== +bool Cinematic::parseCamera( Script &cinematicFile ) +{ + if ( !parseOpenBlock( cinematicFile, "Camera" ) ) return false ; + + CinematicCamera *cinematicCamera = new CinematicCamera(); + if (!cinematicCamera->parse( cinematicFile )) return false ; + + _cameraList.AddObject( cinematicCamera ); + return true ; +} + +//=============================================================== +// Name: parseCut +// Class: Cinematic +// +// Description: Parses a cut block. +// +// Parameters: Script& -- the cinematic file being parsed. +// +// Returns: bool -- true upon success. +// +//=============================================================== +bool Cinematic::parseCut( Script &cinematicFile ) +{ + if ( !parseOpenBlock( cinematicFile, "Cut" ) ) return false ; + + CinematicCut *cinematicCut = new CinematicCut(); + if (!cinematicCut->parse( cinematicFile )) return false ; + + _cutList.AddObject( cinematicCut ); + return true ; +} + + +//=============================================================== +// Name: parseObjects +// Class: Cinematic +// +// Description: Parse the objects block from a cinematic file. +// +// Parameters: Script& -- the cinematic file being parsed +// +// Returns: bool -- true upon success. +// +//=============================================================== +bool Cinematic::parseObjects( Script &cinematicFile ) +{ + if (!parseOpenBlock(cinematicFile, "Objects")) return false ; + const char *token = cinematicFile.GetToken( true ); + while ( token ) + { + if (strcmp( token, "}") == 0) break ; + if (stricmp( token, "Object" ) == 0) + { + if (!parseObject( cinematicFile )) + return false ; + } + + token = cinematicFile.GetToken( true ); + } + + return true ; +} + + +//=============================================================== +// Name: parseObject +// Class: Cinematic +// +// Description: Parses an object block. +// +// Parameters: Script& -- the cinematic file being parsed. +// +// Returns: bool -- true upon success. +// +//=============================================================== +bool Cinematic::parseObject( Script &cinematicFile ) +{ + if (!parseOpenBlock( cinematicFile, "Object" ) ) return false ; + // + return true ; +} + +//=============================================================== +// Name: parseOrigins +// Class: Cinematic +// +// Description: Parse the Origins block from a cinematic file. +// +// Parameters: Script& -- the cinematic file being parsed. +// +// Returns: bool -- true upon success. +// +//=============================================================== +bool Cinematic::parseOrigins( Script &cinematicFile ) +{ + if (!parseOpenBlock(cinematicFile, "Origins")) return false ; + const char *token = cinematicFile.GetToken( true ); + while ( token ) + { + if ( stricmp( token, "}" ) == 0 ) break ; + if ( stricmp( token, "Origin" ) == 0) + { + if ( !parseOrigin( cinematicFile )) + return false ; + } + + token = cinematicFile.GetToken( true ); + } + + return true ; +} + + +//=============================================================== +// Name: parseOrigin +// Class: Cinematic +// +// Description: Parse an origin block from a cinematic file. +// +// Parameters: Script& -- the cinematic file being parsed. +// +// Returns: bool -- true upon success. +// +//=============================================================== +bool Cinematic::parseOrigin( Script &cinematicFile ) +{ + if ( !parseOpenBlock( cinematicFile, "Origin" ) ) return false ; + + CinematicOrigin *cinematicOrigin = new CinematicOrigin(); + if ( !cinematicOrigin->parse( cinematicFile )) return false ; + + _originList.AddObject( cinematicOrigin ); + return true ; +} + + +//=============================================================== +// Name: parseOpenBlock +// Class: Cinematic +// +// Description: Parses the next token and determines if it is the +// expected open block token (default is "{"). Returns +// true on success, false otherwise. +// +// Parameters: Script& -- the script to pull tokens from +// const str& -- the name of the block we're opening +// const str& -- the expected open token (default "{") +// +// Returns: bool -- true upon success. +// +//=============================================================== +bool Cinematic::parseOpenBlock( Script &cinematicFile, const str &blockName, const str &openToken ) +{ + const char *token = cinematicFile.GetToken( true ); + + if (strcmp( token, openToken.c_str()) != 0) + { + gi.Error(ERR_DROP, "Expected %s to open %s block, found %s. Aborting parsing.", openToken.c_str(), blockName.c_str(), token); + return false ; + } + + return true ; +} + + +//=============================================================== +// Name: parseCloseBlock +// Class: Cinematic +// +// Description: Parses the next token and determines if it is the +// expected close block token (default is "}"). Returns +// true on success, false otherwise. +// +// Parameters: Script& -- the script to pull tokens from +// const str& -- the name of the block we're closing +// const str& -- the expected open token (default "}") +// +// Returns: bool -- true upon success +// +//=============================================================== +bool Cinematic::parseCloseBlock( Script &cinematicFile, const str &blockName, const str &closeToken ) +{ + const char *token = cinematicFile.GetToken( true ); + + if (strcmp( token, closeToken.c_str()) != 0) + { + gi.Error(ERR_DROP, "Expected %s to close %s block, found %s. Aborting parsing.", closeToken.c_str(), blockName.c_str(), token); + return false ; + } + + return false ; +} + diff --git a/dlls/game/CinematicArmature.h b/dlls/game/CinematicArmature.h new file mode 100644 index 0000000..dfe4ade --- /dev/null +++ b/dlls/game/CinematicArmature.h @@ -0,0 +1,765 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/CinematicArmature.h $ +// $Revision:: 24 $ +// $Author:: Steven $ +// $Date:: 5/16/03 8:27p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: Classes related to supporting Cinematic Armature. The +// CinematicArmature itself is a singleton that governs +// the loading, cacheing, playing, and stopping of +// cinematics. The Cinematic class represents a single +// cinematic with actors, cameras, objects, and associated +// events. +// + + +// Forward declarations +class Cinematic ; +class CinematicArmature ; +class CinematicActor ; +class CinematicCamera ; + +#ifndef __CINEMATIC_ARMATURE_H_ +#define __CINEMATIC_ARMATURE_H_ + +#include "g_local.h" +#include "actor.h" +#include "container.h" + +extern CinematicArmature theCinematicArmature ; +extern Event EV_Cinematic_CinematicThink; +extern Event EV_Cinematic_Start ; +extern Event EV_Cinematic_Stop ; +extern Event EV_Cinematic_Loop ; +extern Event EV_Cinematic_Pause ; +extern Event EV_Cinematic_Continue ; + +typedef SafePtr CinematicPtr; + + +//----------------------------------------------------------------- +// CinematicActor -- Represents an actor in a cinematic. This +// stores data about the actor participating in +// the cinematic, including their name, tiki, +// origin at the start of the cinematic, etc. +// It also stores a pointer to the Actor class +// once the cinematic begins. +//----------------------------------------------------------------- +class CinematicActor : public Listener +{ +public: + + typedef enum + { + CINEMATIC_ACTOR_STATE_INACTIVE, + CINEMATIC_ACTOR_STATE_MOVING, + CINEMATIC_ACTOR_STATE_TURNING, + CINEMATIC_ACTOR_STATE_PLAYING, + CINEMATIC_ACTOR_STATE_FINISHED + } CinematicActorState ; + + typedef enum + { + CINEMATIC_ACTOR_AFTER_BEHAVIOR_REMOVE_FROM_GAME, + CINEMATIC_ACTOR_AFTER_BEHAVIOR_LEAVE_WITH_AI, + CINEMATIC_ACTOR_AFTER_BEHAVIOR_LEAVE_NO_AI, + CINEMATIC_ACTOR_AFTER_BEHAVIOR_LEAVE_FREEZE, + CINEMATIC_ACTOR_AFTER_BEHAVIOR_KILL, + NUM_CINEMATIC_ACTOR_AFTER_BEHAVIORS + } CinematicActorAfterBehavior ; + + CLASS_PROTOTYPE( CinematicActor ); + CinematicActor(); + ~CinematicActor() { } + + bool isAtSpot( void ) { return _isAtSpot ; } + bool isAnimDone( void ) { return _isAnimDone ; } + bool isRootActor( void ) { return _rootActor ; } + bool doesSnapToSpot( void ) { return _snapToSpot ; } + bool doesIgnorePain( void ) { return _ignorePain ; } + bool doesIgnoreSight( void ) { return _ignoreSight ; } + bool doesIgnoreSound( void ) { return _ignoreSound ; } + bool doesRemoveAfterCinematic( void ) { return _removeAfter ; } + + const str& getName() { return _name ; } + const str& getTiki() { return _tiki ; } + const str& getAnim() { return _anim ; } + const str& getMoveAnim() { return _moveAnim ; } + Vector getOrigin() { return _origin ; } + ActorPtr getActor() { return _actor ; } + CinematicActorAfterBehavior getAfterBehavior() { return _afterBehavior ; } + + void setName( const str &name) { _name = name ; } + void setTiki( const str &tiki) { _tiki = tiki ; } + void setMoveAnim( const str &anim) { _moveAnim = anim ; } + void setOriginOffset( const Vector &off) { _originOffset = off ; } + void setYawOffset( float yawOffset ) { _yawOffset = yawOffset ; } + void setSnapToSpot( bool snapToSpot ) { _snapToSpot = snapToSpot ; } + void setIgnorePain( bool ignorePain ) { _ignorePain = ignorePain ; } + void setIgnoreSight( bool ignoreSight ) { _ignoreSight = ignoreSight ; } + void setIgnoreSound( bool ignoreSound ) { _ignoreSound = ignoreSound ; } + void setRemoveAfter( bool removeAfter ) { _removeAfter = removeAfter ; } + void setRootActor( bool rootActor ) { _rootActor = rootActor ; } + void setAlwaysSpawn( bool alwaysSpawn ) { _alwaysSpawn = alwaysSpawn ; } + void setAfterBehavior( const str &s ); + void setAnim( const str &anim); + + bool parse( Script &cinematicFile ); + void getToPosition( void ); + void takeControlOfActor( const Vector &origin=Vector( 0.0f, 0.0f, 0.0f ), float yaw=0.0f ); + void releaseControlOfActor( void ); + void playAnimation( void ); + void reset( void ); + void debug( void ); + void Archive( Archiver &arc ); + + // Event handlers + void actorControlLostEvent( Event *ev ); + void actorBehaviorFinishedEvent( Event *ev ); + +protected: + void _spawn(); + void _turnActor( bool useAnims ); + void _locateActor(); + +private: + str _name ; + str _tiki ; + str _anim ; + str _moveAnim ; + Vector _origin ; + Vector _originOffset ; + ActorPtr _actor ; + float _yaw ; + float _yawOffset ; + bool _snapToSpot ; + bool _ignorePain ; + bool _ignoreSight ; + bool _ignoreSound ; + bool _isAtSpot ; + bool _isAnimDone ; + bool _hasActorControl ; + bool _rootActor ; + bool _removeAfter ; + bool _alwaysSpawn ; + CinematicActorAfterBehavior _afterBehavior ; + CinematicActorState _state ; +}; + +//=============================================================== +// Name: Archive +// Class: CinematicActor +// +// Description: Archives (loads or saves) data of a Cinematic actor. +// +// Parameters: Archiver& -- reference to archiver object to store the data. +// +// Returns: None +// +//=============================================================== +inline void CinematicActor::Archive +( + Archiver &arc +) +{ + Listener::Archive( arc ); + + arc.ArchiveString( &_name ); + arc.ArchiveString( &_tiki ); + arc.ArchiveString( &_anim ); + arc.ArchiveString( &_moveAnim ); + arc.ArchiveVector( &_origin ); + arc.ArchiveVector( &_originOffset ); + + // Archive safe pointer to the actor ( actor is already archived as an entity) + arc.ArchiveSafePointer( &_actor ); + + arc.ArchiveFloat( &_yaw ); + arc.ArchiveFloat( &_yawOffset ); + arc.ArchiveBool( &_snapToSpot ); + arc.ArchiveBool( &_ignorePain ); + arc.ArchiveBool( &_ignoreSight ); + arc.ArchiveBool( &_ignoreSound ); + arc.ArchiveBool( &_isAtSpot ); + arc.ArchiveBool( &_isAnimDone ); + arc.ArchiveBool( &_hasActorControl ); + arc.ArchiveBool( &_rootActor ); + arc.ArchiveBool( &_removeAfter ); + arc.ArchiveBool( &_alwaysSpawn ); + + // Enumerations + ArchiveEnum( _afterBehavior, CinematicActorAfterBehavior ); + ArchiveEnum( _state, CinematicActorState ); +} + + + + +//----------------------------------------------------------------- +// CinematicCamera -- Represents a single cinematic camera. This is +// a wrapper around a regular camera. The cinematic +// camera is given stage directions, including when +// to cut to the next camera. It is also usually +// instantiated at load time, and delays actual +// instantiation of the real camera until the +// cinematic begins. +//----------------------------------------------------------------- +class CinematicCamera : public Listener +{ + public: + typedef enum + { + CAMERA_MOVE_TYPE_STATIC, + CAMERA_MOVE_TYPE_FOLLOW_ANIM, + CAMERA_MOVE_TYPE_FOLLOW_PLAYER, + CAMERA_MOVE_TYPE_FOLLOW_ACTOR, + } CameraMoveType ; + + typedef enum + { + CAMERA_LOOK_TYPE_WATCH_ANIM, + CAMERA_LOOK_TYPE_WATCH_PLAYER, + CAMERA_LOOK_TYPE_WATCH_ACTOR, + } CameraLookType ; + + CLASS_PROTOTYPE( CinematicCamera ); + CinematicCamera(); + ~CinematicCamera() { } + + bool isAnimDone() { return !_playing ; } + bool isSelfRemoving( void ) { return _selfRemoving ; } + + const str& getName( void ) { return _name ; } + const str& getCamFIle( void ) { return _camFile ; } + const str& getMoveActor( void ) { return _moveActor ; } + const str& getLookActor( void ) { return _lookActor ; } + void getToPosition( void ); + + void setName( const str &name ) { _name = name ; } + void setCamFile( const str &camFile ) { _camFile = camFile ; } + void setOriginOffset( const Vector &originOffset ) { _originOffset = originOffset ; } + void setYawOffset( float yawOffset ) { _yawOffset = yawOffset ; } + void setMoveActor( const str &moveActor ) { _moveActor = moveActor ; } + void setLookActor( const str &lookActor ) { _lookActor = lookActor ; } + void setMoveType( CameraMoveType moveType ) { _moveType = moveType ; } + void setLookType( CameraLookType lookType ) { _lookType = lookType ; } + void setSelfRemovingFlag( bool selfRemoving ) { _selfRemoving = selfRemoving ; } + void setMoveType( const str &moveType ); + void setLookType( const str &lookType ); + + void start( void ); + void cut( void ); + void takeControlOfCamera( const Vector &origin=Vector( 0.0f, 0.0f, 0.0f ), float yaw=0.0f ); + void releaseControlOfCamera(); + bool parse( Script &cinematicFile ); + void reset( void ); + void debug( void ) { } + + void Archive( Archiver &arc ); + + protected: + void _locateCamera( void ); + void _spawn( void ); + void _handleStopPlayingEvent( Event *ev ); + + private: + CameraMoveType _moveType ; + CameraLookType _lookType ; + CameraPtr _camera ; + Vector _originOffset ; + float _yawOffset ; + bool _playing ; + bool _selfRemoving ; + str _name ; + str _camFile ; + str _moveActor ; + str _lookActor ; + +}; + + +//=============================================================== +// Name: Archive +// Class: CinematicCut +// +// Description: Archives (loads or saves) the data of a Cinematic Camera +// +// Parameters: Archiver& -- reference to Archiver that stores the data. +// +// Returns: None +// +//=============================================================== +inline void CinematicCamera::Archive +( + Archiver &arc +) +{ + Listener::Archive( arc ); + + // Enumerations + ArchiveEnum( _moveType, CameraMoveType ); + ArchiveEnum( _lookType, CameraLookType ); + + // Archive off pointer to the camera (camera is archived as normal entity + arc.ArchiveSafePointer( &_camera ); + + arc.ArchiveVector( &_originOffset ); + arc.ArchiveFloat( &_yawOffset ); + arc.ArchiveBool( &_playing ); + arc.ArchiveBool( &_selfRemoving ); + arc.ArchiveString( &_name ); + arc.ArchiveString( &_camFile ); + arc.ArchiveString( &_moveActor ); + arc.ArchiveString( &_lookActor ); +} + + + +//----------------------------------------------------------------- +// CinematicCut -- Represents a single cinematic camera cut. +//----------------------------------------------------------------- +class CinematicCut : public Listener +{ + public: + CLASS_PROTOTYPE( CinematicCut ); + CinematicCut(); + ~CinematicCut() { } + + bool doesLerp( void ) { return _lerpFlag ; } + + CinematicCamera *getCinematicCamera( void ) { return _cinematicCamera ; } + const str &getCameraName( void ) { return _cameraName ; } + int getFrame( void ) { return _frame ; } + int getFadeOut( void ) { return _fadeOut ; } + int getFadeIn( void ) { return _fadeIn ; } + + void setCinematicCamera( CinematicCamera *camera ) { _cinematicCamera = camera ; } + void setCameraName( const str &name ) { _cameraName = name ; } + void setFrame( unsigned int frame ) { _frame = frame ; } + void setFadeOut( unsigned int fadeOut ) { _fadeOut = fadeOut ; } + void setFadeIn( unsigned int fadeIn ) { _fadeIn = fadeIn ; } + void setLerpFlag( bool lerpFlag ) { _lerpFlag = lerpFlag ; } + + void postEvents( void ); + bool parse( Script &cinematicFile ); + void reset( void ); + void stop( void ); + void debug( void ) { } + + void Archive( Archiver &arc ); + + protected: + void _locateCamera( void ); + void _spawn( void ); + void _handleFadeOutEvent( Event *event ); + void _handleCutEvent( Event *event ); + + private: + CinematicCamera *_cinematicCamera ; + bool _lerpFlag ; + int _frame ; + int _fadeOut ; + int _fadeIn ; + str _cameraName ; +}; + + +//=============================================================== +// Name: Archive +// Class: CinematicCut +// +// Description: Archives (loads or saves) a Cinematic Cut's data. +// +// Parameters: Archiver& -- reference to object archiving the data. +// +// Returns: None +// +//=============================================================== +inline void CinematicCut::Archive +( + Archiver &arc +) +{ + Listener::Archive( arc ); + + arc.ArchiveObjectPointer( ( Class**) &_cinematicCamera ); + arc.ArchiveBool( &_lerpFlag ); + arc.ArchiveInteger( &_frame ); + arc.ArchiveInteger( &_fadeOut ); + arc.ArchiveInteger( &_fadeIn ); + arc.ArchiveString( &_cameraName ); +} + + + +//------------------------------------------------------------------ +// CinematicOrigin -- Specifies an origin for a given cinematic. +// This enables the cinematic to be played +// relative to this origin, rather than in +// absolute coordinates on a map. +//------------------------------------------------------------------ +class CinematicOrigin : public Listener +{ + public: + CLASS_PROTOTYPE( CinematicOrigin ); + CinematicOrigin(); + ~CinematicOrigin() { } + + Vector getOrigin( void ) { return _origin ; } + const str& getName( void ) { return _name ; } + float getYaw( void ) { return _yaw ; } + + void setOrigin( const Vector origin ) { _origin = origin ; } + void setYaw( float yaw ) { _yaw = yaw ; } + void setName( const str &name ) { _name = name ; } + + bool parse( Script &cinematicFile ); + void debug( void ); + + void Archive( Archiver &arc ); + private: + Vector _origin ; + str _name ; + float _yaw ; +}; + + + +//=============================================================== +// Name: Archive +// Class: CinematicOrigin +// +// Description: Arhives (loads or saves) a cinematic origin. +// +// Parameters: Archiver& -- reference to archive object. +// +// Returns: None +// +//=============================================================== +inline void CinematicOrigin::Archive +( + Archiver &arc +) +{ + Listener::Archive( arc ); + + arc.ArchiveVector( &_origin ); + arc.ArchiveString( &_name ); + arc.ArchiveFloat( &_yaw ); +} + + + +//----------------------------------------------------------------- +// Cinematic -- Represents a single cinematic. A cinematic is made +// up of actors, cameras, and other objects. The +// cinematic is responsible for coordinating these +// entities working together to recreate the cinematic +// in the game. The data for the cinematic is read +// from a text file when the Cinematic object is +// instantiated by the CinematicArmature (the coordinator +// of all cinematics). +//----------------------------------------------------------------- +class Cinematic : public Entity +{ +public: + CLASS_PROTOTYPE( Cinematic ); + + typedef enum + { + CINEMATIC_STAGE_UNLOADED, + CINEMATIC_STAGE_READY, + CINEMATIC_STAGE_WAITING_FOR_ACTORS, + CINEMATIC_STAGE_ANIMATING, + CINEMATIC_STAGE_FINISHED, + } CinematicStage ; + + Cinematic(); + Cinematic( const str &filename ); + virtual ~Cinematic(); + + bool doesResetCamera( void ) { return _resetCamera ; } + + str& getStartThread( void ) { return _startThread ; } + str& getStopThread( void ) { return _stopThread ; } + str& getFilename( void ) { return _filename ; } + + void setResetCameraFlag( bool resetCamera ) { _resetCamera = resetCamera ; } + void setLooping( bool looping ) { _looping = looping ; } + void setFilename( const str &filename ) { _filename = filename ; } + void setStartThread( const str &startThread ) { _startThread = startThread ; } + void setStopThread( const str &stopThread ) { _stopThread = stopThread ; } + void setStartThreadEvent( Event *ev ); + void setStopThreadEvent( Event *ev ); + + void debug( void ); + bool load( void ); + void start( bool callStartThread = true ); + void stop( bool callEndThread = true ); + void reset( void ); + void startAtNamedOrigin( const str &originName, bool callStartThread = true ); + void startAtOrigin( const Vector& origin=Vector( 0.0f, 0.0f, 0.0f ), float yaw=0.0f, bool callStartThread = true ); + + void Think(); + virtual void Archive( Archiver &arc ); + +protected: + CinematicActor *getCinematicActorByName( const str &name ); + CinematicCamera *getCinematicCameraByName( const str &cameraName ); + CinematicOrigin *getCinematicOriginByName( const str &originName ); + + void handleBeginEvent( Event *ev ); + void handleBeginAtEvent( Event *ev ); + void handleEndEvent( Event *ev ); + void handleSetBeginThreadEvent( Event *ev ); + void handleSetEndThreadEvent( Event *ev ); + + void init(); + + bool parseActors( Script &cinematicFile ); + bool parseActor( Script &cinematicFile ); + bool parseCameras( Script &cinematicFile ); + bool parseCamera( Script &cinematicFile ); + bool parseCut( Script &cinematicFile ); + bool parseObjects( Script &cinematicFile ); + bool parseObject( Script &cinematicFile ); + bool parseOrigins( Script &cinematicFile ); + bool parseOrigin( Script &cinematicFile ); + bool parseOpenBlock( Script &cinematicFile, const str &blockName, const str &openToken="{" ); + bool parseCloseBlock( Script &cinematicFile, const str &blockName, const str &closeToken="}" ); + + bool areActorsAtTheirPlaces(); + bool checkForCompletion(); + + void _startAnimation(); + void _endAnimation(); + +private: + Container _actorList ; + Container _cameraList ; + Container _cutList ; + Container _originList ; + str _filename ; + str _startThread; + str _stopThread ; + bool _looping ; + bool _playing ; + bool _callStartThreadFlag ; + bool _resetCamera ; + CinematicStage _stage ; +}; + + +//=============================================================== +// Name: Archive +// Class: Cinematic +// +// Description: Archives (loads or saves) a cinematic's data. +// +// Parameters: Archiver& -- reference to object archiving data. +// +// Returns: None +// +//=============================================================== +inline void Cinematic::Archive +( + Archiver &arc +) +{ + Entity::Archive( arc ); + + // Archive actors + int numActors = _actorList.NumObjects(); + arc.ArchiveInteger( &numActors ); + + if ( arc.Loading() ) + { + _actorList.Resize( numActors ); + for ( int actorIdx = 1; actorIdx <= numActors; ++actorIdx ) + { + CinematicActor *actor = 0 ; + _actorList.AddObject( actor ); + CinematicActor **actorPtr = &_actorList.ObjectAt( actorIdx ); + *actorPtr = (CinematicActor*)arc.ReadObject(); + } + } + else + { + for ( int actorIdx = 1; actorIdx <= numActors; ++actorIdx ) + { + CinematicActor *actor = _actorList.ObjectAt( actorIdx ); + arc.ArchiveObject( actor ); + } + } + + // Archive cameras + int numCameras = _cameraList.NumObjects(); + arc.ArchiveInteger( &numCameras ); + + if ( arc.Loading() ) + { + _cameraList.Resize( numCameras ); + for ( int cameraIdx = 1; cameraIdx <= numCameras; ++cameraIdx ) + { + CinematicCamera *camera = 0 ; + _cameraList.AddObject( camera ); + CinematicCamera **cameraPtr = &_cameraList.ObjectAt( cameraIdx ); + *cameraPtr = (CinematicCamera*)arc.ReadObject(); + } + } + else + { + for ( int cameraIdx = 1; cameraIdx <= numCameras; ++cameraIdx ) + { + CinematicCamera *camera = _cameraList.ObjectAt( cameraIdx ); + arc.ArchiveObject( camera ); + } + } + + // Archive cuts + int numCuts = _cutList.NumObjects(); + arc.ArchiveInteger( &numCuts ); + + if ( arc.Loading() ) + { + _cutList.Resize( numCuts ); + for ( int cutIdx = 1; cutIdx <= numCuts; ++cutIdx ) + { + CinematicCut *cut = 0 ; + _cutList.AddObject( cut ); + CinematicCut **cutPtr = &_cutList.ObjectAt( cutIdx ); + *cutPtr = (CinematicCut*)arc.ReadObject(); + } + } + else + { + for ( int cutIdx = 1; cutIdx <= numCuts; ++cutIdx ) + { + CinematicCut *cut = _cutList.ObjectAt( cutIdx ); + arc.ArchiveObject( cut ); + } + } + + // Archive origins + int numOrigins = _originList.NumObjects(); + arc.ArchiveInteger( &numOrigins ); + + if ( arc.Loading() ) + { + _cameraList.Resize( numCameras ); + for ( int originIdx = 1; originIdx <= numOrigins; ++originIdx ) + { + CinematicOrigin *origin = 0 ; + _originList.AddObject( origin ); + CinematicOrigin **originPtr = &_originList.ObjectAt( originIdx ); + *originPtr = (CinematicOrigin*)arc.ReadObject(); + } + } + else + { + for ( int originIdx = 1; originIdx <= numOrigins; ++originIdx ) + { + CinematicOrigin *origin = _originList.ObjectAt( originIdx ); + arc.ArchiveObject( origin ); + } + } + + // Archive private members + arc.ArchiveString( &_filename ); + arc.ArchiveString( &_startThread ); + arc.ArchiveString( &_stopThread ); + arc.ArchiveBool( &_playing ); + arc.ArchiveBool( &_looping ); + arc.ArchiveBool( &_callStartThreadFlag ); + arc.ArchiveBool( &_resetCamera ); + + // Enumerations + ArchiveEnum( _stage, CinematicStage ); +} + + +//---------------------------------------------------------------- +// CinematicArmature -- Coordinates the creation, playing, and +// removal of cinematics. +//---------------------------------------------------------------- +class CinematicArmature : public Listener +{ + public: + CLASS_PROTOTYPE(CinematicArmature ); + + CinematicArmature(); + ~CinematicArmature(); + + static bool isInDebugMode( void ) { return _debug ; } + + virtual void Archive( Archiver &arc ); + void clearCinematicsList(); + + Cinematic* createCinematic( const str &cinematicName ); + void deleteAllCinematics(); + + protected: + Cinematic* getCinematicByName( const str &cinematicName ); + + void setStartThread( Event *ev ); + void setStopThread( Event *ev ); + + void debugCinematics( Event *ev ); + void playCinematic( Event *ev ); + void playCinematicAt( Event *ev ); + void loadCinematic( Event *ev ); + bool loadCinematic( const str &cinematicName ); + + private: + Container _cinematicList ; + Cinematic *_cinematic ; + static bool _debug ; +}; + + +inline void CinematicArmature::Archive +( + Archiver &arc +) +{ + Listener::Archive( arc ); + + int numCinematics = _cinematicList.NumObjects(); + arc.ArchiveInteger( &numCinematics ); + + if ( arc.Loading() ) + { + _cinematicList.Resize( numCinematics ); + for ( int cinematicIdx = 1; cinematicIdx <= numCinematics; ++cinematicIdx ) + { + Cinematic *cinematic = 0 ; + CinematicPtr *cinematicPtr = 0 ; + + _cinematicList.AddObject( cinematic ); + cinematicPtr = &_cinematicList.ObjectAt( cinematicIdx ); + arc.ArchiveSafePointer( cinematicPtr ); + } + } + else + { + for ( int cinematicIdx = 1; cinematicIdx <= numCinematics; ++cinematicIdx ) + { + CinematicPtr *cinematic = &_cinematicList.ObjectAt( cinematicIdx ); + arc.ArchiveSafePointer( cinematic ); + } + } + + arc.ArchiveObjectPointer( (Class**) &_cinematic ); + arc.ArchiveBool( &_debug ); +} + + + +#endif /* _CINEMATIC_ARMATURE_H_ */ diff --git a/dlls/game/DamageModification.cpp b/dlls/game/DamageModification.cpp new file mode 100644 index 0000000..03e48c0 --- /dev/null +++ b/dlls/game/DamageModification.cpp @@ -0,0 +1,526 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/DamageModification.cpp $ +// $Revision:: 13 $ +// $Date:: 10/10/02 2:39p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DamageModification.cpp: implementation of the DamageModification class. +// +////////////////////////////////////////////////////////////////////// + +#include "_pch_cpp.h" +#include "DamageModification.hpp" +#include "actor.h" + + +// ------------ DamageModificationSystem ----------------------------- + +//-------------------------------------------------------------- +// +// Name: DamageModificationSystem +// Class: DamageModificationSystem +// +// Description: Constructor / Destructor +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +DamageModificationSystem::DamageModificationSystem() +{ + damageModifiers.ClearObjectList(); +} + +DamageModificationSystem::~DamageModificationSystem() +{ + int i; + for ( i=1; i<=damageModifiers.NumObjects(); i++ ) + { + DamageModifier *damMod = damageModifiers.ObjectAt(i); + delete damMod; + } + + damageModifiers.FreeObjectList(); +} + +//-------------------------------------------------------------- +// +// Name: AddDamageModifier +// Class: DamageModificationSystem +// +// Description: Adds a new damage modifier to the internal +// list. +// +// Parameters: const str &damagemodtype -- String version of the DamageModifierType +// const str &value -- String value to be determined by the type +// float multiplier -- the multiplier to this damage mod. +// +// Returns: None +// +//-------------------------------------------------------------- +void DamageModificationSystem::addDamageModifier( const str &damagemodtype, const str &value, float multiplier, + float chance, float painBaseLine ) +{ + DamageModifier *newMod = 0; + + if ( damagemodtype == "tikiname" ) + newMod = new DamageModifierTikiName(TIKI_NAME, value, multiplier, chance, painBaseLine ); + + else if ( damagemodtype == "name" ) + newMod = new DamageModifierName(NAME, value, multiplier, chance, painBaseLine ); + + else if ( damagemodtype == "group" ) + { + int groupVal = atoi(value); + newMod = new DamageModifierGroup(GROUP, groupVal, multiplier, chance, painBaseLine ); + } + + else if ( damagemodtype == "actortype" ) + { + int actortype = Actor::ActorTypeStringToInt(value); + newMod = new DamageModifierActorType(ACTOR_TYPE, actortype, multiplier, chance, painBaseLine ); + } + + else if ( damagemodtype == "targetname" ) + newMod = new DamageModifierTargetName(TARGETNAME, value, multiplier, chance, painBaseLine ); + + else if ( damagemodtype == "damagetype" ) + { + int damagetypeVal = MOD_NameToNum(value); + newMod = new DamageModifierDamageType(GROUP, damagetypeVal, multiplier, chance, painBaseLine ); + } + + damageModifiers.AddObject(newMod); +} + + +//-------------------------------------------------------------- +// +// Name: AddDamageModifier +// Class: DamageModificationSystem +// +// Description: Directly adds a premade DamageModifier object to the list +// (this is currently only called from archive loading) +// +// Parameters: DamageModifier *newModifier -- Modifier to add to the list +// +// Returns: None +// +//-------------------------------------------------------------- +void DamageModificationSystem::addDamageModifier(DamageModifier *newModifier) +{ + if ( newModifier ) + damageModifiers.AddObject(newModifier); +} + +//-------------------------------------------------------------- +// +// Name: resolveDamage +// Class: DamageModificationSystem +// +// Description: The main damage resolving function +// +// Parameters: Damage &damage -- The damage class to resolve +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModificationSystem::resolveDamage(Damage &damage) +{ + int i; + for ( i=1; i<=damageModifiers.NumObjects(); i++ ) + { + DamageModifier *damageMod = damageModifiers.ObjectAt( i ); + damageMod->resolveDamage(damage); + } + + damage.showPain = false; + for ( i=1; i<=damageModifiers.NumObjects(); i++ ) + { + DamageModifier *damageMod = damageModifiers.ObjectAt( i ); + damageMod->resolvePain(damage); + } +} + + +// ------------ Damage ----------------------------------------- + +//-------------------------------------------------------------- +// +// Name: Damage +// Class: Damage +// +// Description: Constructor / Destructor +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +Damage::Damage() +{ + damage = 0.0f; + inflictor = 0; + attacker = 0; + position = vec_zero; + direction = vec_zero; + normal = vec_zero; + knockback = 0; + dflags = 0; + meansofdeath = -1; + surfaceNumber = -1; + boneNumber = -1; + showPain = false; + weapon = 0; +} + +// Constructor that takes the typical damage event as the initializer +Damage::Damage( Event *ev ) +{ + damage = ev->GetFloat ( 1 ); + inflictor = ev->GetEntity ( 2 ); + attacker = ev->GetEntity ( 3 ); + position = ev->GetVector ( 4 ); + direction = ev->GetVector ( 5 ); + normal = ev->GetVector ( 6 ); + knockback = ev->GetInteger( 7 ); + dflags = ev->GetInteger( 8 ); + meansofdeath = ev->GetInteger( 9 ); + + if ( ev->NumArgs() > 9 ) + surfaceNumber = ev->GetInteger( 10 ); + else + surfaceNumber = -1; + + if ( ev->NumArgs() > 10 ) + boneNumber = ev->GetInteger( 11 ); + else + boneNumber = -1; + + if ( ev->NumArgs() > 11 ) + weapon = ev->GetEntity( 12 ); + else + weapon = 0; +} + +Damage::~Damage() +{ + +} + + +// ------------ DamageModifierTikiName ------------------------- + +//-------------------------------------------------------------- +// +// Name: resolveDamage +// Class: DamageModifierTikiName +// +// Description: Resolve damage for tiki name type modifiers +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierTikiName::resolveDamage(Damage &damage) +{ + if ( damage.attacker->model == _tikiName ) + { + float rand = G_Random(); + if ( rand < getChance() ) + damage.damage *= getMultiplier(); + else + damage.damage = 0.0f; + } +} + +//-------------------------------------------------------------- +// +// Name: resolvePain +// Class: DamageModifierTikiName +// +// Description: Resolve pain for this damage +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierTikiName::resolvePain(Damage &damage) +{ + if ( damage.attacker->model == _tikiName ) + { + if ( G_Random() <= (damage.damage / getPainBaseLine() ) ) + damage.showPain = true; + } +} + + +// ------------ DamageModifierName ------------------------- + +//-------------------------------------------------------------- +// +// Name: resolveDamage +// Class: DamageModifierName +// +// Description: Resolve damage for name type modifiers +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierName::resolveDamage(Damage &damage) +{ + if ( damage.attacker->isSubclassOf( Actor ) ) + { + Actor *act = (Actor *)damage.attacker; + if ( act->name == _name ) + { + float rand = G_Random(); + if ( rand < getChance() ) + damage.damage *= getMultiplier(); + else + damage.damage = 0.0f; + } + } +} + +//-------------------------------------------------------------- +// +// Name: resolvePain +// Class: DamageModifierName +// +// Description: Resolve pain for this damage +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierName::resolvePain(Damage &damage) +{ + if ( damage.attacker->isSubclassOf( Actor ) ) + { + Actor *act = (Actor *)damage.attacker; + if ( act->name == _name ) + { + if ( G_Random() <= (damage.damage / getPainBaseLine() ) ) + damage.showPain = true; + } + } +} + +// ------------ DamageModifierGroup ------------------------- + +//-------------------------------------------------------------- +// +// Name: resolveDamage +// Class: DamageModifierGroup +// +// Description: Resolve damage for group type modifiers +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierGroup::resolveDamage(Damage &damage) +{ + if ( damage.attacker->GetGroupID() == _group ) + { + float rand = G_Random(); + if ( rand < getChance() ) + damage.damage *= getMultiplier(); + else + damage.damage = 0.0f; + } +} + +//-------------------------------------------------------------- +// +// Name: resolvePain +// Class: DamageModifierGroup +// +// Description: Resolve pain for this damage +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierGroup::resolvePain(Damage &damage) +{ + if ( damage.attacker->GetGroupID() == _group ) + { + if ( G_Random() <= (damage.damage / getPainBaseLine() ) ) + damage.showPain = true; + } +} + + + +// ------------ DamageModifierActorType ------------------------- + +//-------------------------------------------------------------- +// +// Name: resolveDamage +// Class: DamageModifierActorType +// +// Description: Resolve damage for ActorType type modifiers +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierActorType::resolveDamage(Damage &damage) +{ + if ( damage.attacker->isSubclassOf( Actor ) ) + { + Actor *act = (Actor *)damage.attacker; + if ( act->actortype == _actortype ) + { + float rand = G_Random(); + if ( rand < getChance() ) + damage.damage *= getMultiplier(); + else + damage.damage = 0.0f; + } + } +} + +//-------------------------------------------------------------- +// +// Name: resolvePain +// Class: DamageModifierActorType +// +// Description: Resolve pain for this damage +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierActorType::resolvePain(Damage &damage) +{ + if ( damage.attacker->isSubclassOf( Actor ) ) + { + Actor *act = (Actor *)damage.attacker; + if ( act->actortype == _actortype ) + { + if ( G_Random() <= (damage.damage / getPainBaseLine() ) ) + damage.showPain = true; + } + } +} + + +// ------------ DamageModifierTargetName ------------------------- + +//-------------------------------------------------------------- +// +// Name: resolveDamage +// Class: DamageModifierTargetName +// +// Description: Resolve damage for TargetName type modifiers +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierTargetName::resolveDamage(Damage &damage) +{ + if ( damage.attacker->targetname == _targetname ) + { + float rand = G_Random(); + if ( rand < getChance() ) + damage.damage *= getMultiplier(); + else + damage.damage = 0.0f; + } +} + +//-------------------------------------------------------------- +// +// Name: resolvePain +// Class: DamageModifierTargetName +// +// Description: Resolve pain for this damage +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierTargetName::resolvePain(Damage &damage) +{ + if ( damage.attacker->targetname == _targetname ) + { + if ( G_Random() <= (damage.damage / getPainBaseLine() ) ) + damage.showPain = true; + + } +} + + + +// ------------ DamageModifierDamageType ------------------------- + +//-------------------------------------------------------------- +// +// Name: resolveDamage +// Class: DamageModifierDamageType +// +// Description: Resolve damage for DamageType type modifiers +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierDamageType::resolveDamage(Damage &damage) +{ + if ( damage.meansofdeath == _damagetype ) + { + float rand = G_Random(); + if ( rand < getChance() ) + damage.damage *= getMultiplier(); + else + damage.damage = 0.0f; + } + +} + +//-------------------------------------------------------------- +// +// Name: resolvePain +// Class: DamageModifierDamageType +// +// Description: Resolve pain from this damage +// +// Parameters: Damage &damage -- Damage reference to modify +// +// Returns: None (reference above) +// +//-------------------------------------------------------------- +void DamageModifierDamageType::resolvePain(Damage &damage) +{ + if ( damage.meansofdeath == _damagetype ) + { + if ( G_Random() <= ( damage.damage / getPainBaseLine() ) ) + damage.showPain = true; + + } +} diff --git a/dlls/game/DamageModification.hpp b/dlls/game/DamageModification.hpp new file mode 100644 index 0000000..570fbbc --- /dev/null +++ b/dlls/game/DamageModification.hpp @@ -0,0 +1,381 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: $ +// $Revision:: $ +// $Date:: $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// + +// DamageModification.hpp: interface for the Damage Modification System +// +////////////////////////////////////////////////////////////////////// + +class DamageModificationSystem; +class Damage; +class DamageModifier; +class DamageModifierTikiName; +class DamageModifierName; +class DamageModifierGroup; +class DamageModifierActorType; +class DamageModifierTargetName; +class DamageModifierDamageType; + +#ifndef __DAMAGEMODIFICATION_H__ +#define __DAMAGEMODIFICATION_H__ + +#include "g_local.h" + +// Damage Types enumeration +typedef enum + { + UNDEFINED = 0, + TIKI_NAME, + NAME, + GROUP, + ACTOR_TYPE, + TARGETNAME, + DAMAGE_TYPE // MOD values + } DamageModifierType; + + +//------------------------- CLASS ------------------------------ +// +// Name: DamageModificationSystem +// Base Class: Class +// +// Description: The main system for damage modification +// +// Method of Use: Used in the damage system +//-------------------------------------------------------------- +class DamageModificationSystem : public Class +{ +private: + Container damageModifiers; + +public: + DamageModificationSystem(); + virtual ~DamageModificationSystem(); + + void addDamageModifier(const str &damagemodtype, const str &value, float multiplier, float chance, float painBaseLine ); + void addDamageModifier(DamageModifier *newModifier); + void resolveDamage(Damage &damage); + Container& getModifierList() { return damageModifiers; } +}; + + +//------------------------- CLASS ------------------------------ +// +// Name: Damage +// Base Class: Class +// +// Description: The actual damage information that's passed around +// +// Method of Use: Used as a parameter to resolveDamage on the main +// DamageModificationSystem class. +//-------------------------------------------------------------- +class Damage : public Class +{ +public: + float damage; + Entity *inflictor; + Entity *attacker; + Vector position; + Vector direction; + Vector normal; + int knockback; + int dflags; + int meansofdeath; + int surfaceNumber; + int boneNumber; + bool showPain; + Entity *weapon; + + Damage(); + Damage( Event *ev ); + virtual ~Damage(); + +}; + + +//------------------------- CLASS ------------------------------ +// +// Name: DamageModifier +// Base Class: Class +// +// Description: DamageModifier information (type, resistance, etc) +// +// Method of Use: There is a container of this class in DamageModificationSystem +//-------------------------------------------------------------- +class DamageModifier : public Class +{ +friend class DamageModificationSystem; + +private: + float _multiplier; + float _chance; + float _painBaseLine; + +protected: + DamageModifierType _type; + + virtual void resolveDamage(Damage &damage) = 0; + virtual void resolvePain(Damage &damage) = 0; + +public: + DamageModifier() + : _type(UNDEFINED), + _multiplier(0.0f) + { } + DamageModifier(DamageModifierType type, float multiplier, float chance, float painBaseLine ) + : _type(type), + _multiplier(multiplier), + _chance(chance), + _painBaseLine(painBaseLine) + { } + + virtual ~DamageModifier() { } + + DamageModifierType getType() { return _type; } + float getMultiplier() { return _multiplier; } + float getPainBaseLine() { return _painBaseLine; } + float getChance() { return _chance; } + + void Archive( Archiver &arc ); +}; + +inline void DamageModifier::Archive(Archiver &arc) +{ + Class::Archive(arc); + + // _type is archived by the caller of this function + //DamageModifierType _type; + + arc.ArchiveFloat( &_multiplier ); + arc.ArchiveFloat( &_chance ); + arc.ArchiveFloat( &_painBaseLine ); +} + + +//------------------------- CLASS ------------------------------ +// +// Name: DamageModifierTikiName +// Base Class: DamageModifier +// +// Description: Sub-class to resolve damage modification +// of tiki name type. +// +// Method of Use: +//-------------------------------------------------------------- +class DamageModifierTikiName : public DamageModifier +{ +private: + str _tikiName; + +protected: + /*virtual*/ void resolveDamage(Damage &damage); + /*virtual*/ void resolvePain(Damage &damage); + +public: + DamageModifierTikiName() { _type = TIKI_NAME; } + DamageModifierTikiName(DamageModifierType type, const str &tikiname, float multiplier, float chance, float painBaseLine ) + : DamageModifier(type, multiplier, chance, painBaseLine ), + _tikiName(tikiname) + { } + virtual ~DamageModifierTikiName() { } + + void Archive( Archiver &arc ); +}; + +inline void DamageModifierTikiName::Archive(Archiver &arc) +{ + DamageModifier::Archive(arc); + arc.ArchiveString( &_tikiName ); +} + +//------------------------- CLASS ------------------------------ +// +// Name: DamageModifierName +// Base Class: DamageModifier +// +// Description: Sub-class to resolve damage modification +// of name type. +// +// Method of Use: +//-------------------------------------------------------------- +class DamageModifierName : public DamageModifier +{ +private: + str _name; + +protected: + /*virtual*/ void resolveDamage(Damage &damage); + /*virtual*/ void resolvePain(Damage &damage); + +public: + DamageModifierName() { _type = NAME; } + DamageModifierName(DamageModifierType type, const str &name, float multiplier, float chance, float painBaseLine ) + : DamageModifier(type, multiplier, chance, painBaseLine ), + _name(name) + { } + virtual ~DamageModifierName() { } + + void Archive( Archiver &arc ); +}; + +inline void DamageModifierName::Archive(Archiver &arc) +{ + DamageModifier::Archive(arc); + arc.ArchiveString( &_name ); +} + +//------------------------- CLASS ------------------------------ +// +// Name: DamageModifierGroup +// Base Class: DamageModifier +// +// Description: Sub-class to resolve damage modification +// of group type. +// +// Method of Use: +//-------------------------------------------------------------- +class DamageModifierGroup : public DamageModifier +{ +private: + int _group; + +protected: + /*virtual*/ void resolveDamage(Damage &damage); + /*virtual*/ void resolvePain(Damage &damage); + +public: + DamageModifierGroup() { _type = GROUP; } + DamageModifierGroup(DamageModifierType type, int group, float multiplier, float chance, float painBaseLine ) + : DamageModifier(type, multiplier, chance, painBaseLine ), + _group(group) + { } + virtual ~DamageModifierGroup() { } + + void Archive( Archiver &arc ); +}; + +inline void DamageModifierGroup::Archive(Archiver &arc) +{ + DamageModifier::Archive(arc); + arc.ArchiveInteger( &_group ); +} + +//------------------------- CLASS ------------------------------ +// +// Name: DamageModifierActorType +// Base Class: DamageModifier +// +// Description: Sub-class to resolve damage modification +// of actortype type. +// +// Method of Use: +//-------------------------------------------------------------- +class DamageModifierActorType : public DamageModifier +{ +private: + int _actortype; + +protected: + /*virtual*/ void resolveDamage(Damage &damage); + /*virtual*/ void resolvePain(Damage &damage); + +public: + DamageModifierActorType() { _type = ACTOR_TYPE; } + DamageModifierActorType(DamageModifierType type, int actortype, float multiplier, float chance, float painBaseLine) + : DamageModifier(type, multiplier, chance, painBaseLine ), + _actortype(actortype) + { } + virtual ~DamageModifierActorType() { } + + void Archive( Archiver &arc ); +}; + +inline void DamageModifierActorType::Archive(Archiver &arc) +{ + DamageModifier::Archive(arc); + arc.ArchiveInteger( &_actortype ); +} + +//------------------------- CLASS ------------------------------ +// +// Name: DamageModifierTargetName +// Base Class: DamageModifier +// +// Description: Sub-class to resolve damage modification +// of targetname type. +// +// Method of Use: +//-------------------------------------------------------------- +class DamageModifierTargetName : public DamageModifier +{ +private: + str _targetname; + +protected: + /*virtual*/ void resolveDamage(Damage &damage); + /*virtual*/ void resolvePain(Damage &damage); + +public: + DamageModifierTargetName() { _type = TARGETNAME; } + DamageModifierTargetName(DamageModifierType type, const str &targetname, float multiplier, float chance, float painBaseLine) + : DamageModifier(type, multiplier, chance, painBaseLine ), + _targetname(targetname) + { } + virtual ~DamageModifierTargetName() { } + + void Archive( Archiver &arc ); +}; + +inline void DamageModifierTargetName::Archive(Archiver &arc) +{ + DamageModifier::Archive(arc); + arc.ArchiveString( &_targetname ); +} + +//------------------------- CLASS ------------------------------ +// +// Name: DamageModifierDamageType +// Base Class: DamageModifier +// +// Description: Sub-class to resolve damage modification +// of targetname type. +// +// Method of Use: +//-------------------------------------------------------------- +class DamageModifierDamageType : public DamageModifier +{ +private: + int _damagetype; + +protected: + /*virtual*/ void resolveDamage(Damage &damage); + /*virtual*/ void resolvePain(Damage &damage); + +public: + DamageModifierDamageType() { _type = DAMAGE_TYPE; } + DamageModifierDamageType(DamageModifierType type, int damagetype, float multiplier, float chance, float painBaseLine) + : DamageModifier(type, multiplier, chance, painBaseLine ), + _damagetype(damagetype) + { } + virtual ~DamageModifierDamageType() { } + + void Archive( Archiver &arc ); +}; + +inline void DamageModifierDamageType::Archive(Archiver &arc) +{ + DamageModifier::Archive(arc); + arc.ArchiveInteger( &_damagetype ); +} + + +#endif // !defined(__DAMAGEMODIFICATION_H__) diff --git a/dlls/game/FollowPath.cpp b/dlls/game/FollowPath.cpp new file mode 100644 index 0000000..f18b4d0 --- /dev/null +++ b/dlls/game/FollowPath.cpp @@ -0,0 +1,1389 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/FollowPath.cpp $ +// $Revision:: 47 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// Provides base functionality for path following with avoidance +// + +#include "_pch_cpp.h" +#include "actor.h" +#include "FollowPath.h" + +//---------------------------------------------------------------- +// Name: DrawBox +// Class: none +// +// Description: draws a box in the xy plane +// +// Parameters: +// Vector center - the point about which the box is centered +// float width - the width of the box +// float r, g, b - the color values for the box +// float alpha - the amount of alpha applied to the box +// +// Returns: None +//---------------------------------------------------------------- + +void DrawBox(const Vector ¢er, const float width, const float r, const float g, const float b, const float alpha ) +{ + float squareSize=width/2.0f; + Vector topLeftPoint = center + Vector(-squareSize, -squareSize, 0); + Vector topRightPoint = center + Vector(+squareSize, -squareSize, 0); + Vector bottomRightPoint = center + Vector(+squareSize, +squareSize, 0); + Vector bottomLeftPoint = center + Vector(-squareSize, +squareSize, 0); + G_DebugLine( topLeftPoint, topRightPoint, r, g, b, alpha ); + G_DebugLine( topRightPoint, bottomRightPoint, r, g, b, alpha ); + G_DebugLine( bottomRightPoint, bottomLeftPoint, r, g, b, alpha ); + G_DebugLine( bottomLeftPoint, topLeftPoint, r, g, b, alpha ); +} + +//------------------------- CLASS ------------------------------ +// +// Name: FollowNode +// Base Class: Class +// +// Description: FollowNodes are a lightweight class meant to +// store translation information for the FollowNodePath class +// +// Method of Use: FollowNodes should only be created by +// FollowNodePaths but other classes my need access a node's +// translation. +// +//-------------------------------------------------------------- + +CLASS_DECLARATION( Class, FollowNode, NULL ) +{ + { NULL, NULL } +}; + +//---------------------------------------------------------------- +// Name: FollowNode +// Class: FollowNode +// +// Description: default constructor +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +FollowNode::FollowNode ( void ): + _position( 0, 0, 0 ), + _isTemporary( false ), + _isJumpNode( false ), + _jumpAngle( 45.0f ) +{ +} + +//---------------------------------------------------------------- +// Name: FollowNode +// Class: FollowNode +// +// Description: constructor +// +// Parameters: +// Vector position - translation of the FollowNode +// bool isTemporary - flag identifying temporary nodes +// +// Returns: None +//---------------------------------------------------------------- +FollowNode::FollowNode ( const Vector &position, const bool isTemporary, const bool isJumpNode, const float jumpAngle ): + _position( position ), + _isTemporary( isTemporary ), + _isJumpNode( isJumpNode ), + _jumpAngle( jumpAngle ) + +{ +} + +//------------------------- CLASS ------------------------------ +// +// Name: FollowNodePath +// Base Class: Class +// +// Description: FollowNodePaths are simple paths that support the +// concept of temporary path nodes (allowing temporary insertion +// nodes into the path). +// +// Method of Use: FollowNodePaths should primarily be used by +// FollowPath classes, although other classes may need access to +// most of the members of the class +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Class, FollowNodePath, NULL ) +{ + { NULL, NULL } +}; + +//---------------------------------------------------------------- +// Name: FollowNodePath +// Class: FollowNodePath +// +// Description: default constructor +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +FollowNodePath::FollowNodePath(): + _nodes(), + _currentNode(NULL) +{ +} + +//---------------------------------------------------------------- +// Name: RemoveNode +// Class: FollowNodePath +// +// Description: removes node from path while maintaining +// the correct current node +// +// Parameters: FollowNodePtr node - pointer to the node to +// be removed +// +// Returns: None +//---------------------------------------------------------------- +void FollowNodePath::RemoveNode(FollowNodePtr node) +{ + if (node == GetCurrentNode()) + { + FollowNodePtr newCurrentNode=GetNextNode(GetCurrentNode()); + if (newCurrentNode == NULL) + { + newCurrentNode=GetPreviousNode(GetCurrentNode()); + } + SetCurrentNode(newCurrentNode); + } + _nodes.RemoveObject(node); +} + + +//---------------------------------------------------------------- +// Name: InsertNode +// Class: FollowNodePath +// +// Description: inserts a node into the path while maintaining +// the correct current node +// +// Parameters: +// FollowNodePtr node - pointer to the node to +// be inserted +// int index - the index in the list where the +// node is to be inserted +// +// Returns: None +//---------------------------------------------------------------- +void FollowNodePath::InsertNode(FollowNodePtr node, const int index) +{ + int currentNodeIndex=GetNodeIndex(GetCurrentNode()); + _nodes.InsertObjectAt(index, node); + if (currentNodeIndex >= index) + { + SetCurrentNode(GetNodeAt(currentNodeIndex+1)); + } +} + +//---------------------------------------------------------------- +// Name: GetPreviousNode +// Class: FollowNodePath +// +// Description: returns a pointer to the node preceding the +// one passed to the method +// +// Parameters: +// FollowNodePtr node - node following the node +// to be returned +// +// Returns: the node preceding 'node' +//---------------------------------------------------------------- +FollowNodePtr FollowNodePath::GetPreviousNode(const FollowNodePtr node ) const +{ + int nodeIndex=GetNodeIndex(node); + if (nodeIndex > 1) + { + return GetNodeAt(nodeIndex - 1); + } + return NULL; +} + +//---------------------------------------------------------------- +// Name: GetNextNode +// Class: FollowNodePath +// +// Description: returns a pointer to the node following the +// one passed to the method +// +// Parameters: +// FollowNodePtr node - node preceding the node +// to be returned +// +// Returns: the node following 'node' +//---------------------------------------------------------------- +FollowNodePtr FollowNodePath::GetNextNode(const FollowNodePtr node ) const +{ + int nodeIndex=GetNodeIndex(node); + if (nodeIndex < NumNodes()) + { + return GetNodeAt(nodeIndex + 1); + } + return NULL; +} + +//---------------------------------------------------------------- +// Name: Evaluate +// Class: FollowNodePath +// +// Description: ensures that the path maintains the correct +// current node as the actor follows the path +// +// Parameters: +// Actor self - the actor following the path +// Vector goalPosition - the place that the path +// is taking the Actor +// +// Returns: Steering::ReturnValue used to return success +// or reason for failure +//---------------------------------------------------------------- +const Steering::ReturnValue FollowNodePath::Evaluate( Actor &self, const Vector &goalPosition ) +{ + Steering::ReturnValue returnValue = Steering::EVALUATING; + if ( + ( NumNodes() < 1 ) && + ( SetPath( self, self.origin, goalPosition ) != PATH_CREATION_SUCCESS ) + ) + { + return Steering::FAILED_NO_PATH; + } + else + { + returnValue = AdvanceCurrentNode( self ); + + if (RetreatCurrentNode( self, goalPosition ) == Steering::FAILED_NO_PATH) + { + returnValue = Steering::FAILED_NO_PATH; + } + + if ( g_showactorpath->integer ) + { + Draw(); + } + } + return returnValue; +} + +//---------------------------------------------------------------- +// Name: SetPath +// Class: FollowNodePath +// +// Description: builds a new path for an Actor to follow +// +// Parameters: +// Actor self - the actor following the path +// Vector from - the place that the path starts +// Vector to - the place that the path leads to +// +// Returns: bool that is false if no path is created +//---------------------------------------------------------------- +unsigned int const FollowNodePath::SetPath(Actor &self, const Vector &from, const Vector &to) +{ + // Find the end node + PathNode *goalNode = thePathManager.NearestNode( to, &self ); + + if ( !goalNode ) + { + return PATH_CREATION_FAILED_END_NODE_NOT_FOUND; + } + + // Find the start node + PathNode *node = thePathManager.NearestNode( from, &self ); + if ( !node || ( goalNode == node ) ) + { + return PATH_CREATION_FAILED_START_NODE_NOT_FOUND; + } + + // Find the best _path + StandardMovePath find; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + find.heuristic.can_jump = self.CanJump() != 0; + + Clear(); + + Path *newpath = find.FindPath( node, goalNode ); + if ( newpath != NULL ) + { + BuildFromPathNodes( newpath, self ); + delete newpath; + newpath = NULL; + if ( self.GetActorFlag( ACTOR_FLAG_STRICTLY_FOLLOW_PATHS ) ) + { + SetCurrentNode( FirstNode() ); + } + else + { + if ( !FindNewCurrentNode( self ) ) + { + SetCurrentNode( FirstNode() ); + } + } + } + else + { + SetCurrentNode( NULL ); + return PATH_CREATION_FAILED_NODES_NOT_CONNECTED; + } + + assert ( NumNodes() > 0 ); + //AddNode( new FollowNode( to, false ) ); + return PATH_CREATION_SUCCESS; +} + +//---------------------------------------------------------------- +// Name: Clear +// Class: FollowNodePath +// +// Description: removes all FollowNodes from the path and +// deletes them +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +void FollowNodePath::Clear() +{ + for (int i=NumNodes(); i>0; i--) + { + FollowNodePtr node=GetNodeAt(i); + RemoveNode( node ); + delete node; + } + _currentNode=NULL; +} + +//---------------------------------------------------------------- +// Name: Draw +// Class: FollowNodePath +// +// Description: draws all nodes, all connections and the goal +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +void FollowNodePath::Draw( void ) const +{ + if (NumNodes() == 0) + { + return; + } + + float squareSize=8.0f; + Vector verticalOffset(0,0,16); + DrawBox(GetNodeAt(1)->GetPosition() + verticalOffset, squareSize , 0, 1, 0, 1); + for (int i=NumNodes(); i>1; i--) + { + if ( GetNodeAt(i)->GetIsTemporary() ) + { + DrawBox(GetNodeAt(i)->GetPosition() + verticalOffset, squareSize , 1, 0, 1, 1); + } + else + { + DrawBox(GetNodeAt(i)->GetPosition() + verticalOffset, squareSize , 0, 1, 0, 1); + } + + if (i%2) + { + G_DebugLine( GetNodeAt(i)->GetPosition() + verticalOffset, GetNodeAt(i-1)->GetPosition() + verticalOffset, 0.0f, 1.0f, 0.0f, 1.0f ); + } + else + { + G_DebugLine( GetNodeAt(i)->GetPosition() + verticalOffset, GetNodeAt(i-1)->GetPosition() + verticalOffset, 0.0f, 0.5f, 0.0f, 1.0f ); + } + } + if (_currentNode!=NULL) + { + DrawBox(_currentNode->GetPosition() + verticalOffset, squareSize * 2, 1, 1, 1, 1); + } + DrawBox(LastNode()->GetPosition() + verticalOffset, squareSize * 4, 1, 0, 0, 1); +} + +//---------------------------------------------------------------- +// Name: BuildFromPathNodes +// Class: FollowNodePath +// +// Description: builds a FollowNode path from a PathNode path +// +// Parameters: +// Actor self - the actor following the path +// Vector from - the place that the path starts +// Vector to - the place that the path leads to +// +// Returns: bool that is false if no path is created +//---------------------------------------------------------------- +void FollowNodePath::BuildFromPathNodes(Path *path, const Actor &self) +{ + assert(path->NumNodes() > 0); + + int sourceNodeNumber=1; + int sourceNumNodes=path->NumNodes(); + + + AddNode (new FollowNode(path->GetNode(sourceNodeNumber++)->origin)); + PathNodePtr currentPathNode = NULL; + PathNodePtr nextPathNode = NULL; + while(sourceNodeNumber < sourceNumNodes) + { + bool isJumpNode = false; + float jumpAngle = 0.0f; + + currentPathNode = path->GetNode(sourceNodeNumber); + + if ( sourceNodeNumber < path->NumNodes() && (currentPathNode->nodeflags & AI_JUMP) && currentPathNode->target.length() ) + { + nextPathNode = path->GetNode(sourceNodeNumber + 1); + if (currentPathNode->target == nextPathNode->targetname) + { + isJumpNode = true; + jumpAngle = currentPathNode->jumpAngle; + } + } + + AddNode ( new FollowNode( currentPathNode->origin, false, isJumpNode, jumpAngle ) ); + sourceNodeNumber++; + } + AddNode (new FollowNode( path->GetNode(sourceNumNodes)->origin)); +} + +//---------------------------------------------------------------- +// Name: FindNewCurrentNode +// Class: FollowNodePath +// +// Description: sets the current node to the node furthest +// down the path that the Actor can see +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: bool that is true if a new current node is set +//---------------------------------------------------------------- +const bool FollowNodePath::FindNewCurrentNode(const Actor &self) +{ + int mask; + int currentNodeIndex = GetNodeIndex( GetCurrentNode() ); + for ( + int currentTestNodeNumber = NumNodes(); + currentTestNodeNumber > currentNodeIndex; + currentTestNodeNumber-- + ) + { + FollowNodePtr testnode = GetNodeAt(currentTestNodeNumber); + if (currentTestNodeNumber == 1) + { + SetCurrentNode(testnode); + return true; + } + + mask = self.edict->clipmask & ~CONTENTS_BODY; + + trace_t trace = self.Trace( self.origin, testnode->GetPosition(), mask, "FollowPath" ); + + if ( !trace.startsolid && ( trace.fraction == 1.0f ) && self.movementSubsystem->CanWalkTo( testnode->GetPosition() ) ) + { + SetCurrentNode(testnode); + return true; + } + } + return false; +} + +//---------------------------------------------------------------- +// Name: AdvanceCurrentNode +// Class: FollowNodePath +// +// Description: sets the current node to the next node if the +// Actor gets close to the current node +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: Steering::ReturnValue returns reason for failure +//---------------------------------------------------------------- +const Steering::ReturnValue FollowNodePath::AdvanceCurrentNode( Actor const &self ) +{ + if ( GetCurrentNode() != NULL ) + { + // Advance current node if possible + FollowNodePtr nextNode = GetNextNode( GetCurrentNode() ); + + // check if the distance remaining is less than the distance we'll travel + float radius = max( self.total_delta.lengthXY(), 16.0f ); + if ( Vector::DistanceXY( self.origin, GetCurrentNode()->GetPosition() ) <= radius ) + { + if ( nextNode != NULL ) + { + SetCurrentNode ( nextNode ); + return Steering::EVALUATING_REACHED_NODE; + } + else + { + Clear(); + return Steering::SUCCESS; + } + } + return Steering::EVALUATING; + } + return Steering::FAILED_NO_PATH; +} + +//---------------------------------------------------------------- +// Name: RetreatCurrentNode +// Class: FollowNodePath +// +// Description: search backwards from the current node searching +// for a node that is not blocked by the world +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: Steering::ReturnValue returns reason for failure +//---------------------------------------------------------------- +const Steering::ReturnValue FollowNodePath::RetreatCurrentNode(Actor &self, const Vector &goalPosition) +{ + int mask; + + if ( GetCurrentNode() == NULL ) + { + return Steering::FAILED_NO_PATH; + } + + if ( GetCurrentNodeIndex() == 1 ) + { + return Steering::EVALUATING; + } + + // Build the correct mask (the actor's normal mask without body) + + mask = self.edict->clipmask & ~CONTENTS_BODY; + + if ( !GetPreviousNode(GetCurrentNode() )->GetIsJumpNode() ) + { + //Vector myPosition( self.origin ); + trace_t traceToCurrentNode = self.Trace( self.origin, GetCurrentNode()->GetPosition(), mask, "FollowPath" ); + + if ( traceToCurrentNode.startsolid || ( traceToCurrentNode.fraction < 1.0f ) ) + { + gi.WDPrintf( "Couldn't complete path, building new one for %s (%d)\n" , self.TargetName(), self.entnum ); + + if ( SetPath( self, self.origin, goalPosition ) != PATH_CREATION_SUCCESS ) + { + return Steering::FAILED_NO_PATH; + } + } + } + return Steering::EVALUATING; +} + + +//------------------------- CLASS ------------------------------ +// +// Name: FollowPath +// Base Class: Steering +// +// Description: FollowPath is the base class for and Steering +// classes that need to navigate any significant distance through +// the level. +// +// Method of Use: Never instantiate an object of type FollowPath. +// If the TikiEngine architecture allowed, this cless would be an +// interface class. If you need FollowPath behavior either use an +// existing FollowPath subclass or derive a new class from a +// FollowPath class. +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Steering, FollowPath, NULL ) +{ + { NULL, NULL } +}; + +//---------------------------------------------------------------- +// Name: FollowPath +// Class: FollowPath +// +// Description: default constructor +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +FollowPath::FollowPath(): + _avoidanceDistance(96.0f), + _jumping( false ) +{ +} + +//---------------------------------------------------------------- +// Name: Begin +// Class: FollowPath +// +// Description: Initializes variables necessary to Evaluate +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: None +//---------------------------------------------------------------- +void FollowPath::Begin(Actor &self) +{ + _desiredHeading = self.movementSubsystem->getMoveDir().toAngles(); + _jumping = false; +} + +//---------------------------------------------------------------- +// Name: SetSteeringForceAndDesiredHeading +// Class: FollowPath +// +// Description: Sets absolute direction that Actor should turn to +// +// Parameters: +// Actor self - the actor following the path +// EulerAngles steeringForce - deltaRotation that +// Actor should turn +// +// Returns: None +//---------------------------------------------------------------- +void FollowPath::SetSteeringForceAndDesiredHeading( Actor &self, const Vector &steeringForce ) +{ + _desiredHeading = self.movementSubsystem->getMoveDir().toAngles() + steeringForce; + _desiredHeading.EulerNormalize360(); + SetSteeringForce( steeringForce ); +} + +//---------------------------------------------------------------- +// Name: DoneTurning +// Class: FollowPath +// +// Description: test used to determine if the Actor is facing +// the desired heading +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: bool - true if the Actor is facing the desired +// direction +//---------------------------------------------------------------- +const bool FollowPath::DoneTurning( Actor &self ) const +{ + Vector deltaHeading( _desiredHeading - self.movementSubsystem->getMoveDir().toAngles() ); + deltaHeading.EulerNormalize(); + return fabs( deltaHeading[YAW] ) < 1.0f ; +} + + +//---------------------------------------------------------------- +// Name: GotoCurrentNode +// Class: FollowPath +// +// Description: steers the Actor towards the current FollowNode +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: Steering::ReturnValue returns reason for failure +//---------------------------------------------------------------- +const Steering::ReturnValue FollowPath::GotoCurrentNode( Actor &self ) +{ + float traceLength = min(Vector::DistanceXY( self.origin, GetCurrentNode()->GetPosition() ), GetAvoidanceDistance() ); + Vector traceEnd = self.origin + ( self.movementSubsystem->getMoveDir() * traceLength ); + trace_t horizontalTrace; + trace_t verticalTrace; + stepmoveresult_t result = self.movementSubsystem->IsMoveValid( horizontalTrace, verticalTrace, self.origin, traceEnd ); + if ( result == STEPMOVE_STUCK ) + { + return Steering::FAILED; + } + if ( result == STEPMOVE_BLOCKED_BY_ENTITY ) + { + return AvoidObstacle( self, horizontalTrace ); + } + + SteerToCurrentNode( self ); + return Steering::EVALUATING; + +} + +//---------------------------------------------------------------- +// Name: GotoGoal +// Class: FollowPath +// +// Description: steers the Actor towards the final goal +// Parameters: +// Actor self - the actor following the path +// +// Returns: Steering::ReturnValue returns reason for failure +//---------------------------------------------------------------- +const Steering::ReturnValue FollowPath::GotoGoal( Actor &self ) +{ + float traceLength = min(Vector::DistanceXY( self.origin, GetGoalPosition() ), GetAvoidanceDistance() ); + Vector traceEnd = self.origin + ( self.movementSubsystem->getMoveDir() * traceLength ); + trace_t horizontalTrace; + trace_t verticalTrace; + stepmoveresult_t result = self.movementSubsystem->IsMoveValid( horizontalTrace, verticalTrace, self.origin, traceEnd ); + if ( result == STEPMOVE_STUCK ) + { + return Steering::FAILED; + } + if ( result == STEPMOVE_BLOCKED_BY_ENTITY ) + { + return AvoidObstacle( self, horizontalTrace, true ); + } + + SetSteeringForceAndDesiredHeading + ( + self, + self.movementSubsystem->SteerTowardsPoint + ( + GetGoalPosition(), + vec_zero, + self.movementSubsystem->getMoveDir(), + self.movementSubsystem->getMoveSpeed() + ) + ); + return Steering::EVALUATING; +} + +//---------------------------------------------------------------- +// Name: Evaluate +// Class: FollowPath +// +// Description: attempts to move the Actor to the goal position +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: Steering::ReturnValue returns reason for failure +//---------------------------------------------------------------- +const Steering::ReturnValue FollowPath::Evaluate( Actor &self ) +{ + ResetForces(); + + + if ( _jumping ) + { + if ( _jump.Evaluate( self ) == Steering::SUCCESS) + { + _jump.End( self ); + _jumping = false; + self.SetAnim( _oldAnim ); + } + return Steering::EVALUATING; + } + + + if (AtDestination( self )) + { + FreePath(); + + //if ( g_showactorpath->integer ) + // gi.WDPrintf("Success %d\n" , self.entnum ); + + return Steering::SUCCESS; + } + + + Steering::ReturnValue returnValue = Steering::EVALUATING; + + trace_t traceToGoal; + trace_t verticalTrace; + stepmoveresult_t moveResult = self.movementSubsystem->IsMoveValid( traceToGoal, verticalTrace, self.origin, GetGoalPosition() ); + + if ( + ( + !self.GetActorFlag(ACTOR_FLAG_STRICTLY_FOLLOW_PATHS) && + (( moveResult == STEPMOVE_OK) || + ( traceToGoal.entityNum != ENTITYNUM_WORLD && traceToGoal.entityNum != ENTITYNUM_NONE && ClearTraceToGoal( self, traceToGoal, GetGoalRadius() ) ) + ) && self.movementSubsystem->CanWalkTowardsPoint( GetGoalPosition() ) ) + ) + { + FreePath(); + SteerToGoal( self ); + } + else + { + if ( DoneTurning( self ) ) + { + returnValue = GetPath().Evaluate( self, GetGoalPosition() ); + + int currentNodeIndex = GetPath().GetCurrentNodeIndex(); + if ( currentNodeIndex > 1 ) + { + FollowNodePtr lastNode = GetPath().GetNodeAt( currentNodeIndex - 1 ); + const Vector currentNodePosition = GetPath().GetCurrentNode()->GetPosition(); + const float distanceFromActorToCurrentNode = Vector::DistanceXY( self.origin, currentNodePosition ); + const float distanceBetweenNodes = Vector::DistanceXY( lastNode->GetPosition(), currentNodePosition ); + if ( lastNode->GetIsJumpNode() && ( distanceFromActorToCurrentNode > 0.5f * distanceBetweenNodes ) ) + { + _jumping = true; + _oldAnim = self.animname; + _jump.SetGoal( GetCurrentNode()->GetPosition() ); + _jump.SetLaunchAngle( lastNode->GetJumpAngle() ); + _jump.Begin( self ); + return Steering::EVALUATING; + } + } + if ( returnValue == Steering::EVALUATING ) + { + returnValue = GotoCurrentNode( self ); + } + + else if ( returnValue == Steering::FAILED_NO_PATH && moveResult != STEPMOVE_BLOCKED_BY_WORLD ) + { + returnValue = GotoGoal( self ); + } + + } + else + { + Vector newSteeringForce( _desiredHeading - self.movementSubsystem->getMoveDir().toAngles() ); + newSteeringForce.EulerNormalize(); + SetSteeringForce( newSteeringForce ); + } + } + + + if ( returnValue == Steering::EVALUATING ) + { + self.movementSubsystem->Accelerate( GetSteeringForce() ); + return Steering::EVALUATING; + } + else + { + return returnValue; + } +} + +//---------------------------------------------------------------- +// Name: ShowInfo +// Class: FollowPath +// +// Description: prints out useful debug info for the class +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: None +//---------------------------------------------------------------- +void FollowPath::ShowInfo( Actor &self ) +{ + Steering::ShowInfo( self ); + + gi.Printf( "\npath : ( %f, %f, %f ) to ( %f, %f, %f )\n", + _path.FirstNode()->GetPosition().x, _path.FirstNode()->GetPosition().y, _path.FirstNode()->GetPosition().z, + _path.LastNode()->GetPosition().x, _path.LastNode()->GetPosition().y, _path.LastNode()->GetPosition().z ); + + if ( GetCurrentNode() ) + { + gi.Printf( "currentNode: ( %f, %f, %f )\n", + GetCurrentNode()->GetPosition().x, GetCurrentNode()->GetPosition().y, GetCurrentNode()->GetPosition().z ); + } + else + { + gi.Printf( "currentNode: NULL\n" ); + } +} + +//---------------------------------------------------------------- +// Name: DeleteTemporaryPathNodes +// Class: FollowPath +// +// Description: deletes all temporary FollowNodes in the path +// and removes them from the path +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +void FollowPath::DeleteTemporaryPathNodes(void) +{ + int numNodes=GetPath().NumNodes(); + int i; + for (i=numNodes; i>0; i--) + { + FollowNodePtr nodeToRemove=GetPath().GetNodeAt(i); + if (nodeToRemove->GetIsTemporary()) + { + GetPath().RemoveNode(nodeToRemove); + } + } +} + +//---------------------------------------------------------------- +// Name: ClearTraceToGoal +// Class: FollowPath +// +// Description: test to determine if Actor can move directly +// to the goal +// +// Parameters: +// Actor self - the Actor trying to get to the goal +// trace_t trace - trace that travels from the +// Actor to the goal +// +// Returns: bool that is true if the trace reaches the goal +//---------------------------------------------------------------- +const bool FollowPath::ClearTraceToGoal( Actor &self, const trace_t &traceToGoal, const float radius ) const +{ + // Totally clear path + bool traceToGoalIsClear = traceToGoal.fraction == 1.0f; + + // Path is blocked but obstracle is inside goal radius + float distanceToEdgeOfGoal = Vector::DistanceXY( self.origin, GetGoalPosition() ) - radius; + float lengthOfTrace = Vector::DistanceXY( self.origin, Vector( traceToGoal.endpos ) ); + bool traceGotInsideGoalRadius = ( distanceToEdgeOfGoal < lengthOfTrace ); + + return ( !traceToGoal.startsolid && ( traceToGoalIsClear || traceGotInsideGoalRadius) ); +} + +//---------------------------------------------------------------- +// Name: BuildAvoidancePath +// Class: FollowPath +// +// Description: attempts to build a temporary path around one +// side of the obstacle +// +// Parameters: +// Actor self - the actor following the path +// bool passOnTheLeft - should the path be created +// on the left side if the obstacle +// Vector obstaclePosition - center point of the +// obstacle +// float avoidanceRadius - distance used to avoid +// obstacle +// +// Returns: bool that is true if a path is successfully +// created +//---------------------------------------------------------------- +const bool FollowPath::BuildAvoidancePath(Actor &self, const bool passOnTheLeft, const Vector &obstaclePosition, const float avoidanceRadius, const bool pursueGoal ) +{ + DeleteTemporaryPathNodes(); + Vector myPosition(self.origin); + float distanceToGoal=Vector::DistanceXY(myPosition, GetGoalPosition()); + Vector parallelDirection(self.movementSubsystem->getMoveDir()); + if ( distanceToGoal < avoidanceRadius * 2.0f || pursueGoal ) + { + parallelDirection = GetGoalPosition() - myPosition; + } + else + { + int currentNodeIndex = GetPath().GetNodeIndex(GetPath().GetCurrentNode()); + if (currentNodeIndex < GetPath().NumNodes()) + { + parallelDirection = GetPath().GetNodeAt(currentNodeIndex + 1)->GetPosition() - GetPath().GetCurrentNode()->GetPosition(); + } + } + + parallelDirection.normalize(); + + Vector perpendicularDirection; + perpendicularDirection.CrossProduct(parallelDirection, Vector(0,0,1)); + if (passOnTheLeft) + { + perpendicularDirection *= -1.0f; + } + + Vector firstPathNodeLocation(obstaclePosition + (avoidanceRadius * (perpendicularDirection - parallelDirection))); + trace_t testTraceForFirstPoint; + trace_t verticalTrace; + stepmoveresult_t firstMoveResult = self.movementSubsystem->IsMoveValid( testTraceForFirstPoint, verticalTrace, self.origin, firstPathNodeLocation ); + + if ( ( firstMoveResult == STEPMOVE_OK || ClearTraceToGoal( self, testTraceForFirstPoint, avoidanceRadius/2.0f ) ) ) + { + Vector secondPathNodeLocation(obstaclePosition + (avoidanceRadius * (perpendicularDirection + parallelDirection))); + trace_t testTraceForSecondPoint; + stepmoveresult_t secondMoveResult = self.movementSubsystem->IsMoveValid( testTraceForSecondPoint, verticalTrace, firstPathNodeLocation, secondPathNodeLocation ); + + if ( ( secondMoveResult == STEPMOVE_OK || ClearTraceToGoal( self, testTraceForSecondPoint, avoidanceRadius/2.0f ) ) ) + { + Vector thirdPathNodeLocation(obstaclePosition + (2.0f * avoidanceRadius * parallelDirection)); + trace_t testTraceForThirdPoint; + stepmoveresult_t thirdMoveResult = self.movementSubsystem->IsMoveValid( testTraceForThirdPoint, verticalTrace, secondPathNodeLocation, thirdPathNodeLocation ); + + if ( ( thirdMoveResult == STEPMOVE_OK || ClearTraceToGoal( self, testTraceForThirdPoint, avoidanceRadius/2.0f ) ) ) + { + int insertionPoint=GetPath().GetNodeIndex(GetPath().GetCurrentNode()); + if (insertionPoint > 0) + { + FollowNodePtr nextNode=GetPath().GetNodeAt(insertionPoint); + while (nextNode != NULL && (Vector::DistanceXY(nextNode->GetPosition(), myPosition) < Vector::DistanceXY(myPosition, thirdPathNodeLocation))) + { + insertionPoint++; + if (insertionPoint <= GetPath().NumNodes()) + { + nextNode=GetPath().GetNodeAt(insertionPoint); + } + else + { + nextNode=NULL; + } + } + } + else + { + insertionPoint=1; + } + if ( Vector::DistanceXY(myPosition, firstPathNodeLocation) < distanceToGoal) + { + if (Vector::DistanceXY(myPosition, secondPathNodeLocation) < distanceToGoal) + { + if (Vector::DistanceXY(myPosition, thirdPathNodeLocation) < distanceToGoal) + { + InsertPathNode(new FollowNode(thirdPathNodeLocation, true), insertionPoint); + } + InsertPathNode(new FollowNode(secondPathNodeLocation, true), insertionPoint); + } + InsertPathNode(new FollowNode(firstPathNodeLocation, true), insertionPoint); + SetSteeringForceAndDesiredHeading( self, + self.movementSubsystem->SteerTowardsPoint ( + firstPathNodeLocation, // Target Position + vec_zero, // Target Acceleration + self.movementSubsystem->getMoveDir(), // Actor Move Direction + self.movementSubsystem->getMoveSpeed()) // Actor Move Speed + ); + return true; + } + } + } + } + return false; +} + +//---------------------------------------------------------------- +// Name: AvoidObstacle +// Class: FollowPath +// +// Description: attempts to build a temporary path around one +// side of the obstacle +// +// Parameters: +// Actor self - the actor following the path +// trace_t trace - the trace that hits the obstacle +// +// Returns: Steering::ReturnValue returns reason for failure +//---------------------------------------------------------------- +// +// Given that there is an obstacle in the actor's way, the actor will attempt +// trace a route around the obstacle and then insert new temporary path nodes +// into the path to allow the actor to navigate around the obstacle without +// having to do larges amounts of complex steering every frame. +// +// The plan is to pick a point (#1) that will get the actor away from the obstacle +// and will also allow the actor to pass the obstacle. Point 2 is passed the +// obstacle in the sense that it is further down a path that is parallel to the +// direction from the actor to the obstacle. The third point is chosen to safely +// get the actor back on the path. +// +// +//----------------------------------------------------------------------------- +// +// +// O-> X 3----------> +// \ / +// 1---2 +// +// +// Actor O-> +// Obstacle X +// First New Path Node 1 +// Second New Path Node 2 +// Third New Path Node 3 +// +// +//----------------------------------------------------------------------------- +const Steering::ReturnValue FollowPath::AvoidObstacle(Actor &self, const trace_t &trace, const bool pursueGoal ) +{ + Entity *hitEntity = G_GetEntity( trace.entityNum ); + assert( hitEntity->entnum != ENTITYNUM_WORLD ); + if ( hitEntity == NULL ) + { + return Steering::FAILED; + } + + Vector obstaclePosition( hitEntity->origin ); + float avoidanceRadius( hitEntity->mins.lengthXY() + self.mins.lengthXY() ); + + bool passOnLeft = false; + switch( self._steeringDirectionPreference ) + { + case STEER_RIGHT_ALWAYS: + passOnLeft = false; + break; + case STEER_LEFT_ALWAYS: + passOnLeft = true; + break; + case STEER_RANDOMLY: + { + int random = rand() % 2; + if ( random ) + { + passOnLeft = false; + } + else + { + passOnLeft = true; + } + } + break; + case STEER_BEST: + // To be implemented + // Intended to allow obstable avoidance to make a guess as to which way will get to goal better + passOnLeft = false; + break; + } + + bool foundPath = BuildAvoidancePath(self, passOnLeft, obstaclePosition, avoidanceRadius, pursueGoal ); + + if (!foundPath) + { + foundPath = BuildAvoidancePath(self, !passOnLeft, obstaclePosition, avoidanceRadius, pursueGoal ); + } + if (foundPath) + { + return Steering::EVALUATING; + } + else + { + return Steering::FAILED; + } +} + +//---------------------------------------------------------------- +// Name: TraceBlockedByEntity +// Class: FollowPath +// +// Description: test that determines if there is an entity that +// we will collide with anywhere along the trace +// +// Parameters: +// Actor self - the actor following the path +// trace_t trace - the trace that hits the obstacle +// +// Returns: bool that is true if there is a blocking Entity +//---------------------------------------------------------------- +const bool FollowPath::TraceBlockedByEntity(Actor &self, const trace_t & trace ) const +{ + if ( !trace.startsolid && (trace.fraction == 1.0f || trace.entityNum == ENTITYNUM_WORLD)) + { + return false; + } + + Entity *obstacleEntity = G_GetEntity( trace.entityNum ); + assert( obstacleEntity != NULL ); + + if (obstacleEntity->velocity.lengthXY() < Vector::Epsilon()) + { + return true; + } + + Vector relativeVelocity = self.velocity - obstacleEntity->velocity; + + float rateOfClosure = self.movementSubsystem->getMoveDir() * relativeVelocity; + + // This means that the two entities are moving towards each other + if ( rateOfClosure >= 0.0f ) + { + /* Code to check if the obstacle will move out of the way quickly enough + Vector myPosition(self.origin); + float myRadius = self.maxs.length(); + + Vector obstaclePosition = trace.endpos; + float obstacleRadius = obstacleEntity->maxs.length(); + + Vector obstacleDirection = obstaclePosition - myPosition; + float obstacleDistance = obstacleDirection.length(); + obstacleDirection /= obstacleDistance; // normalize + + Vector perpendicularDirection; + perpendicularDirection.CrossProduct(obstacleDirection, Vector(0,0,1)); + + float timeUntilCollision = obstacleDistance / self.total_delta.length(); // Adequate approximation + + Vector obstacleVelocity(obstacleEntity->velocity); + float parallelSpeed=Vector::Dot(obstacleVelocity, obstacleDirection); + float perpendicularSpeed=Vector::Dot(obstacleVelocity, perpendicularDirection); + + float parallelMove = parallelSpeed * timeUntilCollision; + float perpendicularMove = perpendicularSpeed * timeUntilCollision; + + // If this is true then the obstacle entity will not get out of the way fast + // enoughm so we need to avoid them + if (perpendicularMove < parallelMove + myRadius + obstacleRadius) + */ + { + return true; + } + } + return false; +} + +//---------------------------------------------------------------- +// Name: CheckBlocked +// Class: FollowPath +// +// Description: test that determines if the Actor is blocked +// by a door +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: bool that is true if the Actor is blocked +//---------------------------------------------------------------- +const bool FollowPath::CheckBlocked( Actor &self ) +{ + trace_t trace = self.Trace( GetAvoidanceDistance() , "FollowPath" ); + + if ( trace.ent ) + { + Entity *door = trace.ent->entity; + if ( door && door->isSubclassOf( Door ) ) + { + Event *ev = new Event( EV_Use ); + ev->AddEntity( &self ); + door->PostEvent( ev, 0 ); + + return true; + } + } + return false; +} + +//---------------------------------------------------------------- +// Name: ReturnBlockingObject +// Class: FollowPath +// +// Description: returns a code based on the type of Entity +// that is blocking the Actor +// +// Parameters: +// trace_t trace - the trace that hits the obstacle +// +// Returns: Steering::ReturnValue returns reason for failure +//---------------------------------------------------------------- +const Steering::ReturnValue FollowPath::ReturnBlockingObject(const trace_t &trace) const +{ + if (trace.entityNum == ENTITYNUM_WORLD) + { + return Steering::FAILED_BLOCKED_BY_WORLD; + } + else + { + Entity *traceEnt = G_GetEntity( trace.entityNum ); + if ( traceEnt && traceEnt->isSubclassOf( Actor) ) + { + Actor *traceActor = static_cast(traceEnt); + switch (traceActor->actortype) + { + case IS_ENEMY: + return Steering::FAILED_BLOCKED_BY_ENEMY; + break; + case IS_CIVILIAN: + return Steering::FAILED_BLOCKED_BY_CIVILIAN; + break; + case IS_FRIEND: + return Steering::FAILED_BLOCKED_BY_FRIEND; + break; + case IS_TEAMMATE: + return Steering::FAILED_BLOCKED_BY_TEAMMATE; + break; + } + } + } + return Steering::ERROR; +} + +//---------------------------------------------------------------- +// Name: AtDestination +// Class: FollowPath +// +// Description: test that determines if the Actor is close to +// the goal +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: bool that is true if the Actor is at the goal +//---------------------------------------------------------------- +const bool FollowPath::AtDestination(Actor &self) const +{ + //float dist = Vector::DistanceXY(GetGoalPosition(), self.origin); + //gi.WDPrintf( "Distance: %f , %d\n" , dist , self.entnum ); + + + //Check if we are in range + if ((Vector::DistanceXY(GetGoalPosition(), self.origin)) <= GetRadius()) + { + return true; + } + + return false; +} + +//---------------------------------------------------------------- +// Name: SteerToCurrentNode +// Class: FollowPath +// +// Description: steers the Actor towards the current FollowNode +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: None +//---------------------------------------------------------------- +void FollowPath::SteerToCurrentNode(Actor &self ) +{ + assert ( GetCurrentNode() != NULL ); + + SetSteeringForceAndDesiredHeading + ( + self, + self.movementSubsystem->SteerTowardsPoint + ( + GetCurrentNode()->GetPosition(), + vec_zero, + self.movementSubsystem->getMoveDir(), + self.movementSubsystem->getMoveSpeed() + ) + ); +} + +//---------------------------------------------------------------- +// Name: SteerToGoal +// Class: FollowPath +// +// Description: steers the Actor towards the goal +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: None +//---------------------------------------------------------------- +void FollowPath::SteerToGoal( Actor &self ) +{ + Vector newSteeringForce(self.movementSubsystem->SteerTowardsPoint( + GetGoalPosition(), + vec_zero, + self.movementSubsystem->getMoveDir(), + self.movementSubsystem->getMoveSpeed() + ) ); + + SetSteeringForceAndDesiredHeading( self, newSteeringForce ); +} diff --git a/dlls/game/FollowPath.h b/dlls/game/FollowPath.h new file mode 100644 index 0000000..3782dbc --- /dev/null +++ b/dlls/game/FollowPath.h @@ -0,0 +1,267 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/FollowPath.h $ +// $Revision:: 20 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// Provides base functionality for path following with avoidance +// + +#ifndef __FOLLOW_PATH_H__ +#define __FOLLOW_PATH_H__ + +#include "g_local.h" +#include "entity.h" +#include "path.h" +#include "steering.h" + +class Actor; + +//------------------------- CLASS ------------------------------ +// +// Name: FollowNode +// Base Class: Class +// +// Description: FollowNodes are a lightweight class meant to +// store translation information for the FollowNodePath class +// +// Method of Use: FollowNodes should only be created by +// FollowNodePaths but other classes my need access a node's +// translation. +// +//-------------------------------------------------------------- + +class FollowNode : public Class +{ +public: + CLASS_PROTOTYPE( FollowNode ); + + FollowNode( void ); + FollowNode( const Vector &position, const bool isTemporary=false, const bool isJumpNode=false, const float jumpAngle=45.0f ); + Vector const & GetPosition( void ) const { return _position; } + const bool GetIsTemporary( void ) const { return _isTemporary; } + const bool GetIsJumpNode( void ) const { return _isJumpNode; } + void SetIsJumpNode( const bool isJumpNode ) { _isJumpNode = isJumpNode; } + const float GetJumpAngle( void ) const { return _jumpAngle; } + void SetJumpAngle( const float jumpAngle ) + { + _jumpAngle = jumpAngle; + } + + virtual void Archive( Archiver &arc ); + +private: + Vector _position; + bool _isTemporary; + bool _isJumpNode; + float _jumpAngle; + +}; + +inline void FollowNode::Archive( Archiver &arc ) +{ + Class::Archive( arc ); + + arc.ArchiveVector( &_position ); + arc.ArchiveBool( &_isTemporary ); + arc.ArchiveBool( &_isJumpNode ); + arc.ArchiveFloat( &_jumpAngle ); +} +typedef SafePtr FollowNodePtr; + +//------------------------- CLASS ------------------------------ +// +// Name: FollowNodePath +// Base Class: Class +// +// Description: FollowNodePaths are simple paths that support the +// concept of temporary path nodes (allowing temporary insertion +// nodes into the path). +// +// Method of Use: FollowNodePaths should primarily be used by +// FollowPath classes, although other classes may need access to +// most of the members of the class +// +//-------------------------------------------------------------- + +enum PathCreationReturnCodes +{ + PATH_CREATION_SUCCESS, + PATH_CREATION_FAILED_DEGENERATE_PATH, + PATH_CREATION_FAILED_NODES_NOT_CONNECTED, + PATH_CREATION_FAILED_START_NODE_NOT_FOUND, + PATH_CREATION_FAILED_END_NODE_NOT_FOUND, +}; +class FollowNodePath : public Class +{ +public: + CLASS_PROTOTYPE( FollowNodePath ); + + FollowNodePath(); + ~FollowNodePath() { Clear(); } + void AddNode(FollowNodePtr node) { _nodes.AddObject(node); } + void RemoveNode(FollowNodePtr node); + void InsertNode(FollowNodePtr node, const int index); + FollowNodePtr GetPreviousNode(const FollowNodePtr node) const; + FollowNodePtr GetNextNode(const FollowNodePtr node) const; + const Steering::ReturnValue Evaluate( Actor &self, const Vector &goalPosition ); + unsigned int const SetPath( Actor &self, const Vector &from, const Vector &to ); + void Clear(); + void Draw( void ) const; + + void SetCurrentNode(FollowNodePtr node) { _currentNode = node; } + FollowNodePtr GetCurrentNode(void) const { return _currentNode; } + int GetCurrentNodeIndex(void) const { return GetNodeIndex( GetCurrentNode() ); } + FollowNodePtr GetNodeAt(const int index) const { return _nodes.ObjectAt(index); } + int const GetNodeIndex(const FollowNodePtr node) const { return _nodes.IndexOfObject(node); } + FollowNodePtr FirstNode(void) const { return _nodes.ObjectAt(1); } + FollowNodePtr LastNode(void) const { return _nodes.ObjectAt(NumNodes()); } + int const NumNodes(void) const { return _nodes.NumObjects(); } + + virtual void Archive( Archiver &arc ); + +private: + void BuildFromPathNodes(Path *path, const Actor &self); + const bool FindNewCurrentNode( const Actor &self ); + const Steering::ReturnValue AdvanceCurrentNode( const Actor &self ); + const Steering::ReturnValue RetreatCurrentNode( Actor &self, const Vector &goalPosition ); + + Container _nodes; + FollowNodePtr _currentNode; + +}; + +inline void FollowNodePath::Archive( Archiver &arc ) +{ + Class::Archive( arc ); + + int i; + int num; + FollowNode *followNode; + if ( arc.Saving() ) + { + num = _nodes.NumObjects(); + + arc.ArchiveInteger( &num ); + + for( i = 1 ; i <= num ; i++ ) + { + followNode = _nodes.ObjectAt( i ); + arc.ArchiveObject( followNode ); + } + } + else + { + arc.ArchiveInteger( &num ); + + _nodes.ClearObjectList(); + _nodes.Resize( num ); + + for( i = 1 ; i <= num ; i++ ) + { + followNode = new FollowNode; + _nodes.AddObject( followNode ); + arc.ArchiveObject( followNode ); + } + } + + arc.ArchiveSafePointer( &_currentNode ); +} + +//------------------------- CLASS ------------------------------ +// +// Name: FollowPath +// Base Class: Steering +// +// Description: FollowPath is the base class for and Steering +// classes that need to navigate any significant distance through +// the level. +// +// Method of Use: Never instantiate an object of type FollowPath. +// If the TikiEngine architecture allowed, this cless would be an +// interface class. If you need FollowPath behavior either use an +// existing FollowPath subclass or derive a new class from a +// FollowPath class. +// +//-------------------------------------------------------------- +class FollowPath : public Steering +{ +public: + CLASS_PROTOTYPE( FollowPath ); + + FollowPath(); + virtual ~FollowPath() { DeleteTemporaryPathNodes(); } + virtual const Vector & GetGoalPosition(void) const { assert (false); return vec_zero; } + virtual const float GetGoalRadius(void) const { assert (false); return 0.0f; } + virtual FollowNodePath & GetPath(void) { return _path; } + virtual const float GetRadius (void) const { return _radius; } + virtual void SetRadius (const float radius) { _radius = radius; } + virtual const float GetAvoidanceDistance (void) const { return _avoidanceDistance; } + virtual void SetAvoidanceDistance (const float avoidanceDistance) { _avoidanceDistance = avoidanceDistance; } + virtual void SetSteeringForceAndDesiredHeading( Actor &self, const Vector &steeringForce ); + + virtual void Begin( Actor &self ); + virtual const ReturnValue Evaluate( Actor &self ); + virtual void ShowInfo( Actor &self ); + + virtual void Archive( Archiver &arc ); + + +protected: + virtual void DeleteTemporaryPathNodes(void); + virtual const bool ClearTraceToGoal( Actor &self, const trace_t &traceToGoal, const float radius ) const; + virtual const bool DoneTurning( Actor &self ) const; + virtual const ReturnValue GotoCurrentNode( Actor &self ); + virtual const ReturnValue GotoGoal( Actor &self ); + virtual const bool BuildAvoidancePath(Actor &self, const bool passOnTheLeft, const Vector &obstaclePosition, const float avoidanceRadius, const bool pursueGoal ); + virtual const ReturnValue AvoidObstacle( Actor &self, const trace_t & trace, const bool pursueGoal=false ); + virtual const bool TraceBlockedByEntity(Actor &self, const trace_t & trace ) const; + virtual const bool CheckBlocked(Actor &self); + virtual const ReturnValue ReturnBlockingObject(const trace_t &trace) const; + virtual const bool AtDestination(Actor &self) const; + virtual void SteerToCurrentNode(Actor &self); + virtual void SteerToGoal( Actor &self ); + virtual void FreePath( void ) { _path.Clear(); } + virtual void AddPathNode(FollowNodePtr node) { GetPath().AddNode( node ); } + virtual void InsertPathNode(FollowNodePtr node, const int index) { GetPath().InsertNode(node, index); SetCurrentNode(node); } + virtual FollowNodePtr GetCurrentNode(void) const { return _path.GetCurrentNode(); } + virtual void SetCurrentNode(FollowNodePtr node) { _path.SetCurrentNode(node); } + +private: + float _radius; + FollowNodePath _path; + float _avoidanceDistance; + Vector _desiredHeading; + Jump _jump; + qboolean _jumping; + str _oldAnim; +}; + +inline void FollowPath::Archive +( + Archiver &arc + ) + +{ + Steering::Archive( arc ); + + arc.ArchiveFloat( &_radius ); + arc.ArchiveObject( &_path ); + arc.ArchiveFloat( &_avoidanceDistance ); + arc.ArchiveVector( &_desiredHeading ); + arc.ArchiveObject( &_jump ); + arc.ArchiveBoolean( &_jumping ); + arc.ArchiveString( &_oldAnim ); +} + +#endif // FollowPath diff --git a/dlls/game/FollowPathToEntity.cpp b/dlls/game/FollowPathToEntity.cpp new file mode 100644 index 0000000..b4f46d3 --- /dev/null +++ b/dlls/game/FollowPathToEntity.cpp @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/FollowPathToEntity.cpp $ +// $Revision:: 12 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// Specialization of FollowPath used to follow moving entities +// + +#include "_pch_cpp.h" +#include "actor.h" +#include "FollowPathToEntity.h" + +//------------------------- CLASS ------------------------------ +// +// Name: FollowPathToEntity +// Base Class: FollowPath +// +// Description: FollowPathToEntity is a specialization of +// FollowPath used when the Actor must move to a moving Entity +// +// Method of Use: Behaviors Aggregate FollowPath classes to +// handle long term motion to a goal +// +//-------------------------------------------------------------- +CLASS_DECLARATION( FollowPath, FollowPathToEntity, NULL ) +{ + { NULL, NULL } +}; + +//---------------------------------------------------------------- +// Name: FollowPathToEntity +// Class: FollowPathToEntity +// +// Description: default constructor +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +FollowPathToEntity::FollowPathToEntity(): + _targetEntity(NULL), + _oldGoalPosition(0,0,0) +{ +} + +//---------------------------------------------------------------- +// Name: SetGoal +// Class: FollowPathToEntity +// +// Description: sets goal entity and creates a path to it +// +// Parameters: +// Entity entity - goal entity +// float radius - how close actor needs to get to goal +// Actor self - the Actor trying to get to the goal +// +// Returns: None +//---------------------------------------------------------------- +void FollowPathToEntity::SetGoal(Entity *entity, const float radius, Actor &self) +{ + SetRadius(radius); + if ( + ( _targetEntity == NULL ) || + ( _targetEntity->entnum != entity->entnum) + ) + { + _targetEntity = entity; + _oldGoalPosition = GetGoalPosition(); + GetPath().SetPath(self, self.origin, GetGoalPosition()); + } +} + +//---------------------------------------------------------------- +// Name: ClearTraceToGoal +// Class: FollowPathToEntity +// +// Description: test to determine if Actor can move directly +// to the goal +// +// Parameters: +// Actor self - the Actor trying to get to the goal +// trace_t trace - trace that travels from the +// Actor to the goal +// +// Returns: bool that is true if the trace reaches the goal +//---------------------------------------------------------------- +const bool FollowPathToEntity::ClearTraceToGoal( Actor &self, const trace_t &traceToGoal, const float radius ) const +{ + assert (_targetEntity != NULL); + + // Path is only obstructed by the goal object itself + bool traceHitGoal = ( + (_targetEntity->entnum == traceToGoal.entityNum) && + (traceToGoal.entityNum != ENTITYNUM_NONE) + ); + + + return ( FollowPath::ClearTraceToGoal( self, traceToGoal, radius ) || (!traceToGoal.startsolid && traceHitGoal ) ); +} + +//---------------------------------------------------------------- +// Name: Evaluate +// Class: FollowPathToEntity +// +// Description: attempts to move the Actor to the goal position +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: Steering::ReturnValue returns reason for failure +//---------------------------------------------------------------- +const Steering::ReturnValue FollowPathToEntity::Evaluate( Actor &self) +{ + if (Vector::Distance( _oldGoalPosition, GetGoalPosition()) > GetAvoidanceDistance() ) + { + _oldGoalPosition = GetGoalPosition(); + GetPath().SetPath(self, self.origin, GetGoalPosition() ); + } + + + return FollowPath::Evaluate( self); +} diff --git a/dlls/game/FollowPathToEntity.h b/dlls/game/FollowPathToEntity.h new file mode 100644 index 0000000..e0cadfe --- /dev/null +++ b/dlls/game/FollowPathToEntity.h @@ -0,0 +1,67 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/FollowPathToEntity.h $ +// $Revision:: 7 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// Specialization of FollowPath used to follow moving entities +// + +#ifndef __FOLLOW_PATH_TO_ENTITY_H__ +#define __FOLLOW_PATH_TO_ENTITY_H__ + +#include "FollowPath.h" + +class Actor; + +//------------------------- CLASS ------------------------------ +// +// Name: FollowPathToEntity +// Base Class: FollowPath +// +// Description: FollowPathToEntity is a specialization of +// FollowPath used when the Actor must move to a moving Entity +// +// Method of Use: Behaviors Aggregate FollowPath classes to +// handle long term motion to a goal +// +//-------------------------------------------------------------- +class FollowPathToEntity : public FollowPath +{ +public: + CLASS_PROTOTYPE( FollowPathToEntity ); + + FollowPathToEntity(); + virtual void SetGoal( Entity *ent, const float radius, Actor &self ); + virtual const Vector & GetGoalPosition(void) const { assert (_targetEntity != NULL); return _targetEntity->origin; } + virtual const float GetGoalRadius(void) const { return Vector(_targetEntity->mins).lengthXY(); } + virtual const ReturnValue Evaluate( Actor &self ); + virtual void Archive( Archiver &arc ); + +protected: + virtual const bool ClearTraceToGoal( Actor &self, const trace_t &traceToGoal, const float radius ) const; + +private: + EntityPtr _targetEntity; + Vector _oldGoalPosition; +}; + +inline void FollowPathToEntity::Archive( Archiver &arc ) +{ + FollowPath::Archive( arc ); + + arc.ArchiveSafePointer( &_targetEntity ); + arc.ArchiveVector( &_oldGoalPosition ); +} + +#endif // FollowPathToEntity.h diff --git a/dlls/game/FollowPathToPoint.cpp b/dlls/game/FollowPathToPoint.cpp new file mode 100644 index 0000000..333b653 --- /dev/null +++ b/dlls/game/FollowPathToPoint.cpp @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/FollowPathToPoint.cpp $ +// $Revision:: 9 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// Specialization of FollowPath used to follow moving entities +// + +#include "_pch_cpp.h" +#include "FollowPathToPoint.h" +#include "actor.h" + +//------------------------- CLASS ------------------------------ +// +// Name: FollowPathToPoint +// Base Class: FollowPath +// +// Description: FollowPathToEntity is a specialization of +// FollowPath used when the Actor must move to a stationary point +// +// Method of Use: Behaviors Aggregate FollowPath classes to +// handle long term motion to a goal +// +//-------------------------------------------------------------- +CLASS_DECLARATION( FollowPath, FollowPathToPoint, NULL ) +{ + { NULL, NULL } +}; + +//---------------------------------------------------------------- +// Name: FollowPathToPoint +// Class: FollowPathToPoint +// +// Description: default constructor +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +FollowPathToPoint::FollowPathToPoint():_targetPoint(0,0,0) +{ +} + +//---------------------------------------------------------------- +// Name: SetGoal +// Class: FollowPathToPoint +// +// Description: sets goal point and creates a path to it +// +// Parameters: +// Vector targetPoint - goal position +// float radius - how close actor needs to get to goal +// Actor self - the Actor trying to get to the goal +// +// Returns: None +//---------------------------------------------------------------- +void FollowPathToPoint::SetGoal(const Vector &targetPoint, const float radius, Actor &self) +{ + SetRadius(radius); + if (!Vector::CloseEnough(_targetPoint, targetPoint, 1.0f)) + { + _targetPoint = targetPoint; + GetPath().SetPath(self, self.origin, GetGoalPosition()); + } +} diff --git a/dlls/game/FollowPathToPoint.h b/dlls/game/FollowPathToPoint.h new file mode 100644 index 0000000..8a93d5c --- /dev/null +++ b/dlls/game/FollowPathToPoint.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/FollowPathToPoint.h $ +// $Revision:: 6 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Specialization of FollowPath used to follow a path to a static point +// + +#ifndef __FOLLOW_PATH_TO_POINT_H__ +#define __FOLLOW_PATH_TO_POINT_H__ + +#include "FollowPath.h" + +//------------------------- CLASS ------------------------------ +// +// Name: FollowPathToPoint +// Base Class: FollowPath +// +// Description: FollowPathToEntity is a specialization of +// FollowPath used when the Actor must move to a stationary point +// +// Method of Use: Behaviors Aggregate FollowPath classes to +// handle long term motion to a goal +// +//-------------------------------------------------------------- +class FollowPathToPoint : public FollowPath +{ +public: + CLASS_PROTOTYPE( FollowPathToPoint ); + + FollowPathToPoint(); + virtual void SetGoal( const Vector &targetPoint, const float radius, Actor &self ); + virtual const Vector & GetGoalPosition( void ) const { return _targetPoint; } + virtual const float GetGoalRadius(void) const { return 16.0f; } + virtual void Archive( Archiver &arc ); + +private: + Vector _targetPoint; +}; + +inline void FollowPathToPoint::Archive( Archiver &arc ) +{ + FollowPath::Archive( arc ); + + arc.ArchiveVector( &_targetPoint ); +} + +#endif // FollowPathToPoint.h diff --git a/dlls/game/GameplayDatabase.h b/dlls/game/GameplayDatabase.h new file mode 100644 index 0000000..cd7fcdb --- /dev/null +++ b/dlls/game/GameplayDatabase.h @@ -0,0 +1,192 @@ +// GameplayDatabase.h: interface for the GameplayDatabase class. +// +////////////////////////////////////////////////////////////////////// + +class GameplayDatabase; +class GameplayObject; +class GameplayProperty; + +#ifndef __GAMEPLAYDATABASE_H__ +#define __GAMEPLAYDATABASE_H__ + +#ifdef GAME_DLL +#include "g_local.h" +#endif // GAME_DLL +//------------------------- CLASS ------------------------------ +// +// Name: GameplayProperty +// Base Class: Class +// +// Description: Object that has a key and a value which can be +// a string or a float +// +// Method of Use: Used in GameplayObject's +// +//-------------------------------------------------------------- +class GameplayProperty : public Class +{ +private: + str _name; + str _valuestr; + float _valuefloat; + bool _modified; + +public: + GameplayProperty() + : _name(""), + _valuestr(""), + _valuefloat(1.0f), + _modified(false) + {} + + virtual ~GameplayProperty() {} + + // Parsing + bool parseProperty(Script &gameplayFile, const str& name); + + // Accessors -- Gets + const str& getName() { return _name; } + const str& getStringValue() { return _valuestr; } + float getFloatValue() { return _valuefloat; } + bool getModified() { return _modified; } + const str getFloatValueStr(); + + // Accessors -- Sets + void setName(const str& name) { _name = name; } + void setModified(bool modified) { _modified = modified; } + bool setStringValue(const str& valuestr); + bool setFloatValue(float valuefloat); + + // Archiving + void Archive(Archiver &arc); +}; + +inline void GameplayProperty::Archive(Archiver &arc) +{ + arc.ArchiveString(&_name); + arc.ArchiveString(&_valuestr); + arc.ArchiveFloat(&_valuefloat); + arc.ArchiveBool(&_modified); +} + + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayObject +// Base Class: Class +// +// Description: Object that has a name, and a container of +// GameplayProperty's. +// +// Method of Use: Used in GameplayDatabase +// +//-------------------------------------------------------------- +class GameplayObject : public Class +{ +private: + str _name; + str _category; + int _depth; + + GameplayObject* _baseObject; + + Container _propertyList; + Container _subObjectList; + +public: + GameplayObject(); + GameplayObject(int depth); + virtual ~GameplayObject(); + + // Parsing + bool parseObject(Script &gameplayFile, const str& name); + + //Queries + bool hasProperty(const str& propname); + + // Accessors -- Gets + const str& getName() { return _name; } + const str& getCategory() { return _category; } + const Container& getSubObjectList() { return _subObjectList; }; + GameplayObject* getBaseObject() { return _baseObject; } + GameplayObject* getSubObject(const str& subobjname); + GameplayProperty* getProperty(const str& propname); + float getPropertyFloatValue(const str& propname); + const str getPropertyStringValue(const str& propname); + bool getModified(const str& propname); + + // Accessors -- Sets + void setName(const str& name) { _name = name; } + void setCategory(const str& category) { _category = category; } + void setBaseObject(GameplayObject* baseObject) { _baseObject = baseObject; } + bool setFloatValue(const str& propname, float value, bool create = false); + bool setStringValue(const str& propname, const str& valuestr, bool create = false); + + // Archiving + void Archive(Archiver &arc); +}; + +inline void GameplayObject::Archive(Archiver &arc) +{ + // TODO: Archive the container of GameplayProperties + // TODO: Archive the container of GameplayObjects +} + + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayDatabase +// Base Class: Listener +// +// Description: Database of GameplayObjects. Queries are made +// to this database to retrieve the data +// +// Method of Use: Used by the GameplayManager +// +//-------------------------------------------------------------- +class GameplayDatabase : public Listener +{ +private: + Container _objectList; + GameplayObject *_lastObj; + str _lastObjName; // Full scope for quick compare + + // Private Functions + void linkSubObjectsToBase(); + GameplayObject* createFromScope(const str& scope); + +public: + GameplayDatabase(); + virtual ~GameplayDatabase(); + + // Parsing + bool parseFile(const str& filename); + + // Queries + bool hasObject(const str& objname); + + // Accessors -- Gets + GameplayObject* getObject(const str& objname); + GameplayObject* getRootObject(const str& objname); + GameplayObject* getSubObject(const str& objname, const str& subobjname); + float getFloatValue(const str& objname, const str& propname); + const str getStringValue(const str& objname, const str& propname); + + // Accessors -- Sets + bool setFloatValue(const str& objname, const str& propname, float value, bool create = false); + bool setStringValue(const str& objname, const str& propname, const str& valuestr, bool create = false); + + // Archiving + void Archive(Archiver &arc); +}; + +inline void GameplayDatabase::Archive(Archiver &arc) +{ + // TODO: Archive the container of GameplayObjects +} + + + +#endif diff --git a/dlls/game/GameplayFormulaManager.h b/dlls/game/GameplayFormulaManager.h new file mode 100644 index 0000000..c629845 --- /dev/null +++ b/dlls/game/GameplayFormulaManager.h @@ -0,0 +1,193 @@ +// GameplayFormulaManager.h: interface for the GameplayFormulaManager class. +// +////////////////////////////////////////////////////////////////////// + +class GameplayFormulaManager; +class GameplayFormula; +class GameplayFormulaVariable; +class GameplayFormulaData; +class GameplayFormulaOperand; + +#ifndef __GAMEPLAY_FORMULA_MANAGER_H__ +#define __GAMEPLAY_FORMULA_MANAGER_H__ + +#include "g_local.h" +#include "actor.h" +#include "weapon.h" +#include "entity.h" +#include "player.h" + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayFormulaData +// Base Class: Class +// +// Description: Utility class to the GameplayFormulaManager +// +// Method of Use: The user will build on of these objects to pass +// into the GameplayManager query functions. +// +//-------------------------------------------------------------- +class GameplayFormulaData : public Class +{ +public: + GameplayFormulaData( Entity *pPrimary = 0, + Entity *pSecondary = 0, + Entity *pWeapon = 0, + const str& pAttackType = ""); + + Entity *primary; + Entity *secondary; + Entity *weapon; + str attackType;; +}; + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayFormulaVariable +// Base Class: Class +// +// Description: Variable that contains the list of categories that +// are specific to a variable type. +// +// Method of Use: GameplayFormula uses this class +// +//-------------------------------------------------------------- +class GameplayFormulaVariable : public Class +{ +private: + str _name; + Container _categoryList; + +public: + GameplayFormulaVariable(); + virtual ~GameplayFormulaVariable(); + + // Accessors + const str& getName() { return _name; } + void setName(const str& name) { _name = name; } +}; + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayFormulaOperand +// Base Class: Class +// +// Description: A operand in a formula +// +// Method of Use: GameplayFormula has a list of these to multiply together +// +//-------------------------------------------------------------- +class GameplayFormulaOperand : public Class +{ +private: + float _constant; + str _object; + str _property; + bool _inverse; + +public: + GameplayFormulaOperand() + : _constant(1.0f), + _inverse(false) + {} + virtual ~GameplayFormulaOperand() {} + + // Accessors + const str& getObjectName() { return _object; } + void setObjectName(const str& object) { _object = object; } + + const str& getPropertyName() { return _property; } + void setPropertyName(const str& property) { _property = property; } + + float getConstant() { return _constant; } + void setConstant(float constant) { _constant = constant; } + + bool getInverseFlag() { return _inverse; } + void setInverseFlag(bool inverse) { _inverse = inverse; } + + // Queries + float getResult(const GameplayFormulaData& formulaData); + + // Parsing + bool parseOperand(Script &formulaFile, const str& constant); +}; + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayFormula +// Base Class: Class +// +// Description: A formula in the for the GameplayManager +// +// Method of Use: GameplayFormulaManager requests data from this class +// +//-------------------------------------------------------------- +class GameplayFormula : public Class +{ +private: + str _name; + Container _operandList; + +public: + GameplayFormula(); + virtual ~GameplayFormula(); + + // Accessors + const str& getName() { return _name; } + void setName(const str& name) { _name = name; } + + // Queries + float getResult(const GameplayFormulaData& formulaData ); + + // Parsing + bool parseFormula(Script &formulaFile, const str& name); +}; + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayFormulaManager +// Base Class: Class +// +// Description: The manager for all the formulas. Accessed +// by the GameplayManager +// +// Method of Use: GameplayManager uses this class to access formulas +// +//-------------------------------------------------------------- +class GameplayFormulaManager : public Class +{ +private: + Container _variableList; + Container _formulaList; +public: + GameplayFormulaManager(); + virtual ~GameplayFormulaManager(); + + // Queries + float getFormulaResult(const str& formulaName, const GameplayFormulaData& formulaData); + bool hasFormula(const str& formulaName); + + // Parsing + bool parseFile(const str& filename); +}; + +#endif + diff --git a/dlls/game/GameplayManager.h b/dlls/game/GameplayManager.h new file mode 100644 index 0000000..0217ac9 --- /dev/null +++ b/dlls/game/GameplayManager.h @@ -0,0 +1,84 @@ +// GameplayManager.h: interface for the GameplayManager class. +// +////////////////////////////////////////////////////////////////////// +class GameplayManager; + +#ifndef __GAMEPLAYMANAGER_H__ +#define __GAMEPLAYMANAGER_H__ + +#ifdef GAME_DLL +#include "g_local.h" +#include "actor.h" +#include "weapon.h" +#include "player.h" +#include "GameplayFormulaManager.h" +#endif // GAME_DLL + +#ifdef CGAME_DLL +#include "cg_local.h" +#endif // CGAME_DLL + + +#include "GameplayDatabase.h" + +//------------------------- CLASS ------------------------------ +// +// Name: GameplayManager +// Base Class: Listener +// +// Description: Singlton class that handles gameplay elements +// +// Method of Use: Use in game code when things need to be resolved +// +//-------------------------------------------------------------- +class GameplayManager : public Listener +{ + //----------------------------------------------------------- + // D A T A B A S E S T U F F + //----------------------------------------------------------- + public: + // Static Member functions + static GameplayManager* getTheGameplayManager(); + static void shutdown(); + static bool isReady(); + static void create(); + + // Queries + bool hasProperty(const str& objname, const str& propname); + bool hasObject(const str& objname); + bool hasSubObject(const str& objname, const str& subobject); + bool isDefined(const str& propname); + + // Accessors + const str getDefine(const str& propname); + float getFloatValue(const str& objname, const str& propname); + const str getStringValue(const str& objname, const str& propname); + GameplayObject *getObject(const str& objname); + + // Mutators NOTE: These functions can only set values on root level objects! + void setFloatValue(const str& objname, const str& propname, float value, bool create = false); + void setStringValue(const str& objname, const str& propname, const str& valuestr, bool create = false); + + protected: + GameplayManager(); + virtual ~GameplayManager(); + + private: + static GameplayManager *_theGameplayManager; // singleton + GameplayDatabase _gameplayDatabase; + +#ifdef GAME_DLL + //------------------------------------------------------------ + // F O R M U L A S T U F F + //------------------------------------------------------------ + public: + bool hasFormula(const str& formulaName); + float calculate(const str& formulaName, const GameplayFormulaData& formulaData, float multiplier = 1.0f); + + private: + GameplayFormulaManager _gameplayFormulaManager; +#endif // GAME_DLL +}; + + +#endif diff --git a/dlls/game/GoDirectlyToPoint.cpp b/dlls/game/GoDirectlyToPoint.cpp new file mode 100644 index 0000000..ee083ee --- /dev/null +++ b/dlls/game/GoDirectlyToPoint.cpp @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/GoDirectlyToPoint.cpp $ +// $Revision:: 6 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// + +#include "_pch_cpp.h" +#include "actor.h" +#include "GoDirectlyToPoint.h" + + +//------------------------- CLASS ------------------------------ +// +// Name: GoDirectlyToPoint +// Base Class: Class +// +// Description: Simple Steering class that moves directly to the +// desired point without any regard for obstacle or world +// avoidance. +// +// Method of Use: This is an appropiate steering method iff +// some guarentee is made that the Actor will not collide with +// anything while travelling to the target point. +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Steering, GoDirectlyToPoint, NULL ) +{ + { NULL, NULL } +}; + +//---------------------------------------------------------------- +// Name: GoDirectlyToPoint +// Class: GoDirectlyToPoint +// +// Description: default constructor +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +GoDirectlyToPoint::GoDirectlyToPoint() +{ +} + +//---------------------------------------------------------------- +// Name: Begin +// Class: GoDirectlyToPoint +// +// Description: Initializes variables necessary to Evaluate +// +// Parameters: +// Actor self - the actor moving to a point +// +// Returns: None +//---------------------------------------------------------------- +void GoDirectlyToPoint::Begin(Actor &self) +{ +} + +//---------------------------------------------------------------- +// Name: Evaluate +// Class: GoDirectlyToPoint +// +// Description: attempts to move the Actor to the goal position +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: Steering::ReturnValue returns reason for failure +//---------------------------------------------------------------- +const Steering::ReturnValue GoDirectlyToPoint::Evaluate( Actor &self ) +{ + ResetForces(); + + if (AtDestination( self )) + { + return Steering::SUCCESS; + } + + Vector newSteeringForce = self.movementSubsystem->SteerTowardsPoint + ( + _destination, + vec_zero, + self.movementSubsystem->getMoveDir(), + self.movementSubsystem->getMoveSpeed() + ); + self.movementSubsystem->Accelerate( newSteeringForce ); + + return Steering::EVALUATING ; +} + +//---------------------------------------------------------------- +// Name: ShowInfo +// Class: GoDirectlyToPoint +// +// Description: prints out useful debug info for the class +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: None +//---------------------------------------------------------------- +void GoDirectlyToPoint::ShowInfo( Actor &self ) +{ + Steering::ShowInfo( self ); + + gi.Printf( "\n destination : ( %f, %f, %f ) \n", _destination.x, _destination.y, _destination.z ); +} + + + +//---------------------------------------------------------------- +// Name: AtDestination +// Class: GoDirectlyToPoint +// +// Description: test that determines if the Actor is close to +// the goal +// +// Parameters: +// Actor self - the actor following the path +// +// Returns: bool that is true if the Actor is at the goal +//---------------------------------------------------------------- +const bool GoDirectlyToPoint::AtDestination(const Actor &self) const +{ + //Check if we are in range + if ((Vector::DistanceXY( _destination, self.origin ) ) <= _radius ) + { + return true; + } + + return false; +} diff --git a/dlls/game/GoDirectlyToPoint.h b/dlls/game/GoDirectlyToPoint.h new file mode 100644 index 0000000..faaa2b7 --- /dev/null +++ b/dlls/game/GoDirectlyToPoint.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/GoDirectlyToPoint.h $ +// $Revision:: 4 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Simple Steering class that moves directly to the desired point +// without any regard for obstacle or world avoidance +// + +#ifndef __GO_DIRECTLY_TO_POINT__ +#define __GO_DIRECTLY_TO_POINT__ + +#include "g_local.h" +#include "steering.h" + +class Actor; + +//------------------------- CLASS ------------------------------ +// +// Name: GoDirectlyToPoint +// Base Class: Class +// +// Description: Simple Steering class that moves directly to the +// desired point without any regard for obstacle or world +// avoidance. +// +// Method of Use: This is an appropiate steering method iff +// some guarentee is made that the Actor will not collide with +// anything while travelling to the target point. +// +//-------------------------------------------------------------- + +class GoDirectlyToPoint : public Steering +{ +public: + CLASS_PROTOTYPE( GoDirectlyToPoint ); + + GoDirectlyToPoint(); + virtual ~GoDirectlyToPoint() { } + virtual void Begin( Actor &self ); + virtual const ReturnValue Evaluate( Actor &self ); + virtual void ShowInfo( Actor &self ); + + void SetDestination( const Vector &destination ) { _destination = destination; } + void SetRadius( const float radius ) { _radius = radius; } + virtual void Archive( Archiver &arc ); + const bool AtDestination( const Actor &self ) const; + +private: + Vector _destination; + float _radius; +}; + +inline void GoDirectlyToPoint::Archive( Archiver &arc ) + +{ + Steering::Archive( arc ); + arc.ArchiveVector( &_destination ); + arc.ArchiveFloat( &_radius ); + +} + +#endif // GoDirectlyToPoint diff --git a/dlls/game/Linklist.h b/dlls/game/Linklist.h new file mode 100644 index 0000000..6f55706 --- /dev/null +++ b/dlls/game/Linklist.h @@ -0,0 +1,118 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/Linklist.h $ +// $Revision:: 3 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// +// WARNING: This file is shared between game, cgame and possibly the user interface. +// It is instanced in each one of these directories because of the way that SourceSafe works. +// + +#ifndef __linklist_h +#define __linklist_h +#ifdef __cplusplus +extern "C" { +#endif + + +#define NewNode(type) ((type *)Z_Malloc(sizeof(type))) + +#define LL_New(rootnode,type,next,prev) \ + { \ + (rootnode) = NewNode(type); \ + (rootnode)->prev = (rootnode); \ + (rootnode)->next = (rootnode); \ + } + +#define LL_Add(rootnode, newnode, next, prev) \ + { \ + (newnode)->next = (rootnode); \ + (newnode)->prev = (rootnode)->prev; \ + (rootnode)->prev->next = (newnode); \ + (rootnode)->prev = (newnode); \ + } +//MED +#define LL_AddFirst(rootnode, newnode, next, prev) \ + { \ + (newnode)->prev = (rootnode); \ + (newnode)->next = (rootnode)->next; \ + (rootnode)->next->prev = (newnode); \ + (rootnode)->next = (newnode); \ + } + +#define LL_Transfer(oldroot,newroot,next,prev) \ + { \ + if (oldroot->prev != oldroot) \ + { \ + oldroot->prev->next = newroot; \ + oldroot->next->prev = newroot->prev; \ + newroot->prev->next = oldroot->next; \ + newroot->prev = oldroot->prev; \ + oldroot->next = oldroot; \ + oldroot->prev = oldroot; \ + } \ + } + +#define LL_Reverse(root,type,next,prev) \ + { \ + type *newend,*trav,*tprev; \ + \ + newend = root->next; \ + for(trav = root->prev; trav != newend; trav = tprev) \ + { \ + tprev = trav->prev; \ + LL_Move(trav,newend,next,prev); \ + } \ + } + + +#define LL_Remove(node,next,prev) \ + { \ + node->prev->next = node->next; \ + node->next->prev = node->prev; \ + node->next = node; \ + node->prev = node; \ + } + +#define LL_SortedInsertion(rootnode,insertnode,next,prev,type,sortparm) \ + { \ + type *hoya; \ + \ + hoya = rootnode->next; \ + while((hoya != rootnode) && (insertnode->sortparm > hoya->sortparm)) \ + { \ + hoya = hoya->next; \ + } \ + LL_Add(hoya,insertnode,next,prev); \ + } + +#define LL_Move(node,newroot,next,prev) \ + { \ + LL_Remove(node,next,prev); \ + LL_Add(newroot,node,next,prev); \ + } + +#define LL_Empty(list,next,prev) \ + ( \ + ((list)->next == (list)) && \ + ((list)->prev == (list)) \ + ) + +#define LL_Free(list) Z_Free(list) +#define LL_Reset(list,next,prev) (list)->next = (list)->prev = (list) + +#ifdef __cplusplus +} + +#endif +#endif diff --git a/dlls/game/MoveRandomDirection.cpp b/dlls/game/MoveRandomDirection.cpp new file mode 100644 index 0000000..8e118b9 --- /dev/null +++ b/dlls/game/MoveRandomDirection.cpp @@ -0,0 +1,434 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/MoveRandomDirection.cpp $ +// $Revision:: 6 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// MoveRandomDirection Implementation +// +// PARAMETERS: +// +// ANIMATIONS: +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "MoveRandomDirection.hpp" +#include + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, MoveRandomDirection, NULL ) + { + { &EV_Behavior_Args, &MoveRandomDirection::SetArgs }, + { NULL, NULL } + }; + + +MoveRandomDirection::MoveRandomDirection() +{ + anim = ""; + _forever = false; + _faceEnemy = false; + _dist = 1024.0f; + _minDistance = MIN_RANDOM_DIRECTION_DESTINATION; + _mode = RANDOM_MOVE_ANYWHERE; + _forever = false; + _faceEnemy = false; +} + +MoveRandomDirection::~MoveRandomDirection() +{ +} + + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: MoveRandomDirection +// +// Description: Sets Parameters +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void MoveRandomDirection::SetArgs( Event *ev ) +{ + int parmCount = ev->NumArgs(); + + if ( parmCount > 0 ) anim = ev->GetString( 1 ); + if ( parmCount > 1 ) SetDistance( ev->GetFloat( 2 ) ); + if ( parmCount > 2 ) SetMinDistance( ev->GetFloat( 3 ) ); + if ( parmCount > 3 ) SetMode( ev->GetInteger( 4 ) ); + if ( parmCount > 4 ) _forever = ev->GetBoolean( 5 ); + if ( parmCount > 5 ) _faceEnemy = ev->GetBoolean( 6 ); +} + + + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: MoveRandomDirection +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void MoveRandomDirection::Begin( Actor &self ) +{ + findDestination( self ); + + if ( _foundGoodDestination ) + { + float radius = 16.0f; + + if ( _faceEnemy ) + self.movementSubsystem->setFaceEnemy( true ); + + setLegAnim( self ); + + _chase.SetDistance( radius ); + _chase.SetPoint( _destination ); + _chase.SetAnim( anim ); + _chase.Begin( self ); + + //Setup Torso Anim if appropriate + if ( !self.torsoBehavior ) + setTorsoAnim( self ); + } +} + + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: MoveRandomDirection +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t MoveRandomDirection::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t result; + + if ( !_foundGoodDestination ) + { + self.SetAnim( "idle" , NULL , legs ); + if ( _forever ) + Begin( self ); + else + return BEHAVIOR_FAILED; + } + + result = _chase.Evaluate( self ); + if ( result == BEHAVIOR_SUCCESS ) + { + if ( _forever ) + Begin( self ); + else + return BEHAVIOR_SUCCESS; + } + + if ( result != BEHAVIOR_EVALUATING ) + { + if ( _forever ) + Begin( self ); + else + return BEHAVIOR_FAILED; + } + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: MoveRandomDirection +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void MoveRandomDirection::End(Actor &self) +{ + self.SetAnim( "idle" , NULL , legs ); + if ( _faceEnemy ) + self.movementSubsystem->setFaceEnemy( false ); + + self.movementSubsystem->setMovingBackwards( false ); +} + + + +//-------------------------------------------------------------- +// Name: _chooseRandomDirection() +// Class: MoveRandomDirection +// +// Description: Picks a random position Vector based on +// the _mode +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: Vector +//-------------------------------------------------------------- +Vector MoveRandomDirection::_chooseRandomDirection(Actor &self) +{ + float yaw; + Vector destination; + Vector start; + trace_t trace; + + switch ( _mode ) + { + case RANDOM_MOVE_IN_FRONT: + yaw = G_Random( 90.0f ) - 45.0f; + break; + + case RANDOM_MOVE_IN_BACK: + yaw = G_Random( 90.0f ) - 45.0f; + yaw = AngleNormalize180( yaw + 180); + break; + + default: + yaw = G_Random(360.0f); + break; + } + + start = self.origin; + start.z += 16.0f; + destination = self.angles; + destination[YAW] = AngleNormalize180(destination[YAW]); + destination[YAW] += yaw; + destination.AngleVectors( &destination ); + + Vector debug; + debug = self.angles; + debug.AngleVectors ( &debug ); + debug *= 296.0f; + debug.z = start.z; + debug += start; + + destination *= _dist; + destination += start; + destination.z = start.z; + trace = G_Trace( start , self.mins, self.maxs, destination, &self, self.edict->clipmask, false, "MoveRandomDirection: _chooseRandomDirection" ); + + Vector size1 = self.mins; + Vector size2 = self.maxs; + Vector actorSize; + + size1.z = 0; + size2.z = 0; + + actorSize = size2 - size1; + float mungeValue = actorSize.length(); + Vector startToDest = trace.endpos - start; + float dist = startToDest.length(); + dist-= mungeValue; + + startToDest.normalize(); + startToDest *= dist; + startToDest = startToDest + start; + + + //destination = trace.endpos; + destination = startToDest; + //G_DebugLine( start , destination , 1.0 , 1.0 , 1.0 , 1.0); + return destination; +} + + + +//-------------------------------------------------------------- +// Name: _getDistanceToDestination() +// Class: MoveRandomDirection +// +// Description: Returns the the distance to the destination +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: float +//-------------------------------------------------------------- +float MoveRandomDirection::_getDistanceToDestination(Actor &self) +{ + Vector selfToDestination; + + selfToDestination = _destination - self.origin; + + return selfToDestination.length(); +} + + + +//-------------------------------------------------------------- +// Name: SetDistance() +// Class: MoveRandomDirection +// +// Description: Mutator +// +// Parameters: float dist +// +// Returns: None +//-------------------------------------------------------------- +void MoveRandomDirection::SetDistance( float dist ) +{ + _dist = dist; +} + + + +//-------------------------------------------------------------- +// Name: SetAnim() +// Class: MoveRandomDirection +// +// Description: Mutator +// +// Parameters: const str &moveAnim +// +// Returns: None +//-------------------------------------------------------------- +void MoveRandomDirection::SetAnim( const str &moveAnim ) +{ + anim = moveAnim; +} + + + +//-------------------------------------------------------------- +// Name: SetMode() +// Class: MoveRandomDirection +// +// Description: Mutator +// +// Parameters: unsigned int mode +// +// Returns: None +//-------------------------------------------------------------- +void MoveRandomDirection::SetMode( unsigned int mode ) +{ + _mode = mode; +} + + + +//-------------------------------------------------------------- +// Name: SetMinDistance() +// Class: MoveRandomDirection +// +// Description: Mutator +// +// Parameters: float dist +// +// Returns: None +//-------------------------------------------------------------- +void MoveRandomDirection::SetMinDistance( float dist ) +{ + _minDistance = dist; +} + +void MoveRandomDirection::findDestination( Actor &self ) +{ + int attempts; + + _destination = _chooseRandomDirection(self); + + attempts = 0; + _foundGoodDestination = true; + while ( _getDistanceToDestination(self) < _minDistance && attempts < 5 ) + { + _destination = _chooseRandomDirection(self); + _foundGoodDestination = false; + attempts += 1; + if ( _getDistanceToDestination(self) > _minDistance ) + _foundGoodDestination = true; + } +} + +void MoveRandomDirection::setLegAnim( Actor &self ) +{ + str newAnim; + + if ( _faceEnemy ) + { + Vector selfToDestinationAngles = _destination - self.origin; + Vector animAngles = self.movementSubsystem->getAnimDir(); + float yawDiff; + + selfToDestinationAngles = selfToDestinationAngles.toAngles(); + animAngles = animAngles.toAngles(); + yawDiff = AngleNormalize180(selfToDestinationAngles[YAW] - animAngles[YAW] ); + + if ( yawDiff >= -25.0 && yawDiff <= 25.0 ) + { + newAnim = self.combatSubsystem->GetAnimForMyWeapon( "CombatWalk" ); + } + + if ( yawDiff >= -135.0 && yawDiff <= -25.0 ) + { + newAnim = self.combatSubsystem->GetAnimForMyWeapon( "CombatRStrafe" ); + } + + if ( yawDiff >= 25.0 && yawDiff <= 135.0f ) + { + newAnim = self.combatSubsystem->GetAnimForMyWeapon( "CombatLStrafe" ); + } + + if ( yawDiff >= 135.0 && yawDiff <= 180.0f ) + { + newAnim = self.combatSubsystem->GetAnimForMyWeapon( "CombatBackpedal" ); + self.movementSubsystem->setMovingBackwards( true ); + } + + if ( yawDiff <= -135.0 && yawDiff >= -180.0 ) + { + newAnim = self.combatSubsystem->GetAnimForMyWeapon( "CombatBackpedal" ); + self.movementSubsystem->setMovingBackwards( true ); + } + + } + + if ( newAnim.length() ) + anim = newAnim; +} + +void MoveRandomDirection::setTorsoAnim( Actor &self ) +{ + if ( self.enemyManager->HasEnemy() ) + _torsoAnim = self.combatSubsystem->GetAnimForMyWeapon( "CombatGunIdle" ); + else + _torsoAnim = self.combatSubsystem->GetAnimForMyWeapon( "IdleGunIdle" ); + + if ( _torsoAnim.length() ) + { + self.SetAnim( _torsoAnim, NULL , torso ); + } +} + diff --git a/dlls/game/MoveRandomDirection.hpp b/dlls/game/MoveRandomDirection.hpp new file mode 100644 index 0000000..b44c5fa --- /dev/null +++ b/dlls/game/MoveRandomDirection.hpp @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/MoveRandomDirection.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// MoveRandomDirection Behavior Definition +// +//-------------------------------------------------------------------------------- + + +//============================== +// Forward Declarations +//============================== +class MoveRandomDirection; + +#ifndef __MOVE_RANDOM_DIRECTION___ +#define __MOVE_RANDOM_DIRECTION___ + +#include "behavior.h" +#include "behaviors_general.h" + + +#define MIN_RANDOM_DIRECTION_DESTINATION 64.0f + +//------------------------- CLASS ------------------------------ +// +// Name: MoveRandomDirection +// Base Class: Behavior +// +// Description: A replacement for Wander -- Utilizes fewer traces +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class MoveRandomDirection : public Behavior + { + public: + typedef enum + { + RANDOM_MOVE_ANYWHERE, + RANDOM_MOVE_IN_FRONT, + RANDOM_MOVE_IN_BACK, + } randomMoveModes_t; + + private: // Parameters + str anim; + + protected: + Vector _chooseRandomDirection ( Actor &self ); + float _getDistanceToDestination ( Actor &self ); + void findDestination ( Actor &self ); + void setLegAnim ( Actor &self ); + void setTorsoAnim ( Actor &self ); + + public: + CLASS_PROTOTYPE( MoveRandomDirection ); + + MoveRandomDirection(); + ~MoveRandomDirection(); + + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + + // Accessors + void SetDistance( float dist ); + void SetMinDistance( float dist ); + void SetAnim( const str &moveAnim ); + void SetMode( unsigned int mode ); + + private: + GotoPoint _chase; + + Vector _destination; + unsigned int _mode; + float _dist; + float _minDistance; + float _nextChangeTime; + bool _foundGoodDestination; + bool _forever; + bool _faceEnemy; + str _torsoAnim; + + }; + +inline void MoveRandomDirection::Archive( Archiver &arc ) + { + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString( &anim ); + + // Archive Components + arc.ArchiveObject( &_chase ); + + // Archive Member Vars + arc.ArchiveVector( &_destination ); + arc.ArchiveUnsigned( &_mode ); + arc.ArchiveFloat( &_dist ); + arc.ArchiveFloat( &_minDistance ); + arc.ArchiveFloat( &_nextChangeTime ); + arc.ArchiveBool( &_foundGoodDestination ); + arc.ArchiveBool( &_forever ); + arc.ArchiveBool( &_faceEnemy ); + arc.ArchiveString( &_torsoAnim ); + } + + + + +#endif /* __MOVE_RANDOM_DIRECTION___ */ + diff --git a/dlls/game/PlayAnim.cpp b/dlls/game/PlayAnim.cpp new file mode 100644 index 0000000..b880347 --- /dev/null +++ b/dlls/game/PlayAnim.cpp @@ -0,0 +1,442 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/PlayAnim.cpp $ +// $Revision:: 7 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// PlayAnim Implementation +// +// PARAMETERS: +// +// +// ANIMATIONS: +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "PlayAnim.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, PlayAnim, NULL ) + { + { &EV_Behavior_Args, &PlayAnim::SetArgs }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: PlayAnim() +// Class: PlayAnim +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +PlayAnim::PlayAnim() +{ + _legAnim = "idle"; + _torsoAnim = ""; + _minTime = 0.0f; + _maxTime = 0.0f; + _endTime = 0.0f; +} + +//-------------------------------------------------------------- +// Name: ~PlayAnim() +// Class: PlayAnim +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +PlayAnim::~PlayAnim() +{ +} + + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: PlayAnim +// +// Description: +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void PlayAnim::SetArgs( Event *ev ) +{ + _legAnim = ev->GetString( 1 ); + if ( ev->NumArgs() > 1 ) _torsoAnim = ev->GetString( 2 ); + if ( ev->NumArgs() > 2 ) _minTime = ev->GetFloat( 3 ); + if ( ev->NumArgs() > 3 ) _maxTime = ev->GetFloat( 4 ); + +} + + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: PlayAnim +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void PlayAnim::Begin( Actor &self ) +{ + init( self ); +} + + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: PlayAnim +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t PlayAnim::Evaluate( Actor &self ) +{ + + BehaviorReturnCode_t stateResult; + + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case PLAYANIM_SETUP: + //--------------------------------------------------------------------- + stateResult = evaluateStateSetup(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( PLAYANIM_ANIMATE ); + + break; + + //--------------------------------------------------------------------- + case PLAYANIM_ANIMATE: + //--------------------------------------------------------------------- + stateResult = evaluateStateAnimate(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( PLAYANIM_SUCCESS ); + break; + + //--------------------------------------------------------------------- + case PLAYANIM_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + break; + + //--------------------------------------------------------------------- + case PLAYANIM_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: PlayAnim +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void PlayAnim::End(Actor &self) +{ + self.RemoveAnimDoneEvent(); +} + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: CloseInOnEnemy +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void PlayAnim::transitionToState( PlayAnimStates_t state ) +{ + switch( state ) + { + case PLAYANIM_SETUP: + setInternalState( state , "PLAYANIM_SETUP" ); + setupStateSetup(); + break; + + case PLAYANIM_ANIMATE: + setInternalState( state , "PLAYANIM_ANIMATE" ); + setupStateAnimate(); + break; + + case PLAYANIM_SUCCESS: + setInternalState( state , "PLAYANIM_SUCCESS" ); + break; + + case PLAYANIM_FAILED: + setInternalState( state , "PLAYANIM_FAILED" ); + break; + } +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: PlayAnim +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void PlayAnim::setInternalState( PlayAnimStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: PlayAnim +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PlayAnim::init( Actor &self ) +{ + str legAnim, torsoAnim; + ScriptVariable *var = NULL; + + //Check for State Var's first + legAnim = self.GetStateVar( _legAnim ); + if ( legAnim.length() ) + _legAnim = legAnim; + + + torsoAnim = self.GetStateVar( _torsoAnim ); + if ( torsoAnim.length() ) + _torsoAnim = torsoAnim; + + + var = self.entityVars.GetVariable( _legAnim ); + if ( var ) + { + legAnim = var->stringValue(); + if ( legAnim.length() ) + _legAnim = legAnim; + } + + + var = self.entityVars.GetVariable( _torsoAnim ); + if ( var ) + { + torsoAnim = var->stringValue(); + if ( torsoAnim.length() ) + _torsoAnim = torsoAnim; + } + + + transitionToState(PLAYANIM_SETUP); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: PlayAnim +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void PlayAnim::think() +{ +} + + +//-------------------------------------------------------------- +// Name: setupStateSetup() +// Class: PlayAnim +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void PlayAnim::setupStateSetup() +{ + if ( _minTime ) + _endTime = level.time + _minTime; + + if ( _maxTime ) + _endTime = _endTime + G_Random( _maxTime - _minTime ); + + if ( _legAnim.length() ) + { + if ( !_endTime ) + { + if ( !GetSelf()->SetAnim( _legAnim, EV_Actor_EndBehavior ) ) + { + GetSelf()->PostEvent( EV_Actor_EndBehavior, 0.0f ); + } + } + else + { + if ( !GetSelf()->SetAnim( _legAnim, NULL ) ) + { + GetSelf()->PostEvent( EV_Actor_EndBehavior, 0.0f ); + } + } + + GetSelf()->ClearTorsoAnim(); + } + + if ( _torsoAnim.length() ) + { + if ( !_endTime ) + { + if ( !GetSelf()->SetAnim( _torsoAnim, EV_Actor_EndBehavior ) ) + { + GetSelf()->PostEvent( EV_Actor_EndBehavior, 0.0f ); + } + } + else + { + if ( !GetSelf()->SetAnim( _torsoAnim, NULL ) ) + { + GetSelf()->PostEvent( EV_Actor_EndBehavior, 0.0f ); + } + } + } +} + +//-------------------------------------------------------------- +// Name: evaluateStateApproach() +// Class: PlayAnim +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t PlayAnim::evaluateStateSetup() +{ + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateApproach() +// Class: PlayAnim +// +// Description: Failure Handler for State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void PlayAnim::failureStateSetup( const str& failureReason ) +{ +} + + +//-------------------------------------------------------------- +// Name: setupStateSetup() +// Class: PlayAnim +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void PlayAnim::setupStateAnimate() +{ +} + +//-------------------------------------------------------------- +// Name: evaluateStateApproach() +// Class: PlayAnim +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t PlayAnim::evaluateStateAnimate() +{ + if ( _endTime ) + { + if ( level.time >= _endTime ) + return BEHAVIOR_SUCCESS; + } + + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateApproach() +// Class: PlayAnim +// +// Description: Failure Handler for State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void PlayAnim::failureStateAnimate( const str& failureReason ) +{ +} + diff --git a/dlls/game/PlayAnim.hpp b/dlls/game/PlayAnim.hpp new file mode 100644 index 0000000..1a6cf1e --- /dev/null +++ b/dlls/game/PlayAnim.hpp @@ -0,0 +1,137 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/PlayAnim.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CloseInOnEnemyWhileFiringWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class PlayAnim; + +#ifndef __PLAY_ANIM_HPP__ +#define __PLAY_ANIM_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: CloseInOnEnemyWhileFiringWeapon +// Base Class: Behavior +// +// Description: Makes the actor move closer to its current enemy +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class PlayAnim : public Behavior +{ + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + PLAYANIM_SETUP, + PLAYANIM_ANIMATE, + PLAYANIM_SUCCESS, + PLAYANIM_FAILED + } PlayAnimStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _legAnim; + str _torsoAnim; + float _minTime; + float _maxTime; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( PlayAnimStates_t state ); + void setInternalState ( PlayAnimStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + + void setupStateSetup (); + BehaviorReturnCode_t evaluateStateSetup (); + void failureStateSetup ( const str& failureReason ); + + void setupStateAnimate (); + BehaviorReturnCode_t evaluateStateAnimate (); + void failureStateAnimate ( const str& failureReason ); + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( PlayAnim ); + + PlayAnim(); + ~PlayAnim(); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + void setAnim ( const str &animName ) { _legAnim = animName; } + void setTorsoAnim( const str &animName ) { _torsoAnim = animName; } + void setMinTime ( float minTime ) { _minTime = minTime; } + void setMaxTime ( float maxTime ) { _maxTime = maxTime; } + + //------------------------------------- + // Components + //------------------------------------- + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + PlayAnimStates_t _state; + float _endTime; + +}; + +inline void PlayAnim::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_legAnim ); + arc.ArchiveString ( &_torsoAnim ); + arc.ArchiveFloat ( &_minTime ); + arc.ArchiveFloat ( &_maxTime); + + + // Archive Components + + // Archive Member Variables + ArchiveEnum ( _state, PlayAnimStates_t ); + arc.ArchiveFloat ( &_endTime ); +} + +#endif /* __PLAY_ANIM_HPP__ */ + + + + + diff --git a/dlls/game/PlayerStart.cpp b/dlls/game/PlayerStart.cpp new file mode 100644 index 0000000..bd381e6 --- /dev/null +++ b/dlls/game/PlayerStart.cpp @@ -0,0 +1,122 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/PlayerStart.cpp $ +// $Revision:: 7 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Player start location entity declarations +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "trigger.h" +#include "PlayerStart.h" + +/*****************************************************************************/ +/*QUAKED info_player_start (0.75 0.75 0) (-16 -16 0) (16 16 96) + +The normal starting point for a level. + +"angle" - the direction the player should face +"thread" - the thread that should be called when spawned at this position + +******************************************************************************/ + +Event EV_PlayerStart_SetThread +( + "thread", + EV_SCRIPTONLY, + "s", + "thread", + "Set the thread to execute when this player start is used" +); + +CLASS_DECLARATION( Entity, PlayerStart, "info_player_start" ) +{ + { &EV_SetAngle, &PlayerStart::SetAngle }, + { &EV_PlayerStart_SetThread, &PlayerStart::SetThread }, + + { NULL, NULL } +}; + +void PlayerStart::SetAngle( Event *ev ) +{ + angles = Vector( 0.0f, ev->GetFloat( 1 ), 0.0f ); +} + +void PlayerStart::SetThread( Event *ev ) +{ + thread = ev->GetString( 1 ); +} + +str PlayerStart::getThread( void ) +{ + return thread; +} + +/*****************************************************************************/ +/* saved out by quaked in region mode + +******************************************************************************/ + +CLASS_DECLARATION( PlayerStart, TestPlayerStart, "testplayerstart" ) +{ + { NULL, NULL } +}; + +/*****************************************************************************/ +/*QUAKED info_player_deathmatch (0.75 0.75 1) (-16 -16 0) (16 16 96) + +potential spawning position for deathmatch games + +"angle" - the direction the player should face +"thread" - the thread that should be called when spawned at this position +"spawnpoint_type" - the named type of this spawnpoint +******************************************************************************/ + +Event EV_PlayerDeathmatchStart_SetType +( + "spawnpoint_type", + EV_DEFAULT, + "s", + "spawnpointType", + "Sets the named type of this spawnpoint" +); + +CLASS_DECLARATION( PlayerStart, PlayerDeathmatchStart, "info_player_deathmatch" ) +{ + { &EV_PlayerDeathmatchStart_SetType, &PlayerDeathmatchStart::SetType }, + + { NULL, NULL } +}; + +void PlayerDeathmatchStart::SetType( Event *ev ) +{ + _type = ev->GetString( 1 ); +} + +/*****************************************************************************/ +/*QUAKED info_player_intermission (0.75 0.75 0) (-16 -16 0) (16 16 96) + +viewing point in between deathmatch levels + +******************************************************************************/ + +CLASS_DECLARATION( Camera, PlayerIntermission, "info_player_intermission" ) +{ + { NULL, NULL } +}; + +PlayerIntermission::PlayerIntermission() +{ + currentstate.watch.watchPath = false; +} diff --git a/dlls/game/PlayerStart.h b/dlls/game/PlayerStart.h new file mode 100644 index 0000000..4d5572f --- /dev/null +++ b/dlls/game/PlayerStart.h @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/PlayerStart.h $ +// $Revision:: 4 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Player start location entity declarations +// + +#ifndef __PLAYERSTART_H__ +#define __PLAYERSTART_H__ + +#include "g_local.h" +#include "entity.h" +#include "camera.h" +#include "navigate.h" + +class PlayerStart : public Entity + { + private: + str thread; + public: + CLASS_PROTOTYPE( PlayerStart ); + + void SetAngle( Event *ev ); + void SetThread( Event *ev ); + str getThread( void ); + virtual void Archive(Archiver &arc); + }; + +inline void PlayerStart::Archive (Archiver &arc) + { + Entity::Archive( arc ); + + arc.ArchiveString(&thread); + } + +class TestPlayerStart : public PlayerStart + { + public: + CLASS_PROTOTYPE( TestPlayerStart ); + }; + +class PlayerDeathmatchStart : public PlayerStart + { + private: + void SetType( Event *ev ); + + public: + str _type; + + PlayerDeathmatchStart() {}; + + CLASS_PROTOTYPE( PlayerDeathmatchStart ); + + }; + +class PlayerIntermission : public Camera + { + public: + CLASS_PROTOTYPE( PlayerIntermission ); + PlayerIntermission(); + }; + +#endif /* PlayerStart.h */ diff --git a/dlls/game/RageAI.cpp b/dlls/game/RageAI.cpp new file mode 100644 index 0000000..b1afe84 --- /dev/null +++ b/dlls/game/RageAI.cpp @@ -0,0 +1,1169 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/RageAI.cpp $ +// $Revision:: 51 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// The Rage AI System, will house several components that will make the AI function +// such as the Strategos, Personality, etc... +// +#include "_pch_cpp.h" +//#include "g_local.h" +#include "RageAI.h" +#include "player.h" + +void Strategos::DoArchive( Archiver &arc , Actor *actor ) +{ + Archive( arc ); +} + +void Strategos::Archive( Archiver &arc ) +{ + arc.ArchiveFloat( &_sightBasedHate ); + arc.ArchiveFloat( &_nextEvaluateTime ); + arc.ArchiveFloat( &_evaluateInterval ); +} + +//================================================================================= +// Default Strategos Implementation +//================================================================================= + +// +// Name: DefaultStrategos() +// Parameters: None +// Description: Constructor +// +DefaultStrategos::DefaultStrategos() +{ + // Should always use other constructor + gi.Error( ERR_FATAL, "DefaultStrategos::DefaultStrategos -- Default Constructor Called" ); +} + + +// +// Name: DefaultStrategos() +// Parameters: Actor *actor +// Description: Constructor +// +DefaultStrategos::DefaultStrategos( Actor *actor ) + : _checkInConeDistMax( 128 ) +{ + //Initialize our Actor + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "DefaultStrategos::DefaultStrategos -- actor is NULL" ); + + SetSightBasedHate( DEFAULT_SIGHT_BASED_HATE ); + SetEvaluateInterval( DEFAULT_EVALUATE_INTERVAL ); + SetNextEvaluateTime( 0.0f ); +} + +// +// Name: ~DefaultStrategos +// Parameters: None +// Description: Destructor +// +DefaultStrategos::~DefaultStrategos() +{ + +} + +// +// Name: NotifySightStatusChanged +// Parameters: Entity *enemy +// qboolean canSee +// Description: Adjusts hate based on the canSee value +// +void DefaultStrategos::NotifySightStatusChanged( Entity *enemy , qboolean canSee ) +{ + //if ( canSee ) + // act->enemyManager->AdjustHate( enemy , GetSightBasedHate() ); + //else + // act->enemyManager->AdjustHate( enemy , -GetSightBasedHate() ); +} + + + +// +// Name: NotifyDamageChanged +// Parameters: Entity *enemy +// float damage +// Description: Adjusts hate based on the damage value +// +void DefaultStrategos::NotifyDamageChanged( Entity *enemy , float damage ) +{ + //act->enemyManager->AdjustHate( enemy , damage ); +} + + + +// +// Name: Evaluate() +// Parameters: None +// Description: Evaluates the world to determine enemy and strategy +// +void DefaultStrategos::Evaluate() +{ + // Here we will be doing all sorts of evaluation, however, there right now, + // all we have is enemy stuff, so we'll just use that + _EvaluateWorld(); + //_EvaluateEnemies(); + _EvaluatePackages(); +} + + +// +// Name: _EvaluateEnemies +// Parameters: None +// Description: Sets the currentEnemy +// +void DefaultStrategos::_EvaluateEnemies() +{ + if ( act->enemyManager->GetCurrentEnemy() && (level.time < GetNextEvaluateTime() || act->enemyManager->IsLockedOnCurrentEnemy() )) + return; + + // For right now, all we're going to do is set our enemy to the enemy + // Highest on the hate list. I plan to add more sophisticated heuristics later + // but right now, I want to make sure the system is up and running. + act->enemyManager->FindHighestHateEnemy(); + SetNextEvaluateTime( level.time + GetEvaluateInterval() ); +} + + + +// +// Name: _EvaluatePackages +// Parameters: None +// Description: Determines which behavior package to execute +// +void DefaultStrategos::_EvaluatePackages() +{ + + if ( !act->fuzzyEngine || !act->fuzzyEngine_active ) + return; + + if ( PackageList.NumObjects() < 1 ) + return; + + act->packageManager->EvaluatePackages(act->fuzzyEngine ); + + int newPackageIndex; + newPackageIndex = act->packageManager->GetHighestScoringPackage(); + + BehaviorPackageType_t* package; + + package = PackageList.ObjectAt( newPackageIndex ); + + // If we have a package AND our current statemap name is NOT the same as our selected package name + if ( package && Q_stricmp( act->statemap_name.c_str(), package->stateFile.c_str() ) ) + { + Event *event; + + event = new Event( EV_Actor_Statemap ); + event->AddString( package->stateFile.c_str() ); //FileName + event->AddString( "START" ); //IdleState + event->AddInteger( 0 ); //Loading( false ) + event->AddInteger( 1 ); //packageChanged ( true ) + act->ProcessEvent( event ); + + //act->packageManager->SetLastExecutionTime(newPackageIndex); + act->packageManager->UpdateCurrentPackageIndex( newPackageIndex ); + act->statemap_name = package->stateFile; + } +} + + +void DefaultStrategos::_EvaluateWorld() +{ + _CheckForInTheWay(); + _CheckForInConeOfFire(); +} + +void DefaultStrategos::_CheckForInConeOfFire() +{ + Vector dir; + Vector check; + float relativeYaw; + float distance; + Sentient *teammate; + Player *player; + Actor *actor; + + player = NULL; + teammate = NULL; + + + // Should really loop through all teammates, but I'm most concerned about the + // player right now. + for ( int i = 1 ; i <= TeamMateList.NumObjects() ; i++ ) + { + teammate = TeamMateList.ObjectAt( i ); + + if ( !teammate || teammate->entnum == act->entnum ) + continue; + + dir = act->origin - teammate->origin; + //dir = teammate->origin - act->origin; + distance = dir.length(); + + if ( distance < _checkInConeDistMax ) + { + dir = dir.toAngles(); + + if ( teammate->isSubclassOf( Player ) ) + { + _checkYawMin = -5.0f; + _checkYawMax = 5.0f; + + + player = (Player*)teammate; + check = player->GetVAngles(); + } + else + { + + _checkYawMin = -5.0f; + _checkYawMax = 5.0f; + + actor = (Actor*)teammate; + check = actor->movementSubsystem->getAnimDir(); + check = check.toAngles(); + } + + + relativeYaw = AngleNormalize180( AngleNormalize180(check[YAW]) - AngleNormalize180(dir[YAW]) ); + + if ( (relativeYaw <= _checkYawMax) && (relativeYaw >= _checkYawMin ) ) + { + act->SetActorFlag( ACTOR_FLAG_IN_CONE_OF_FIRE , true ); + + if ( teammate->isSubclassOf(Player) && distance <= 128 ) + act->SetActorFlag( ACTOR_FLAG_IN_PLAYER_CONE_OF_FIRE , true ); + + return; + } + } + } + + act->SetActorFlag( ACTOR_FLAG_IN_CONE_OF_FIRE , false ); + act->SetActorFlag( ACTOR_FLAG_IN_PLAYER_CONE_OF_FIRE , false ); + +} + +void DefaultStrategos::_CheckForInTheWay() +{ + Player *player; + Vector playerToSelf; + Vector playerVelocity; + Vector dir; + Vector check; + float relativeYaw; + float dist; + + player = GetPlayer( 0 ); + + if (!player) + return; + + playerToSelf = act->origin - player->origin; + dist = playerToSelf.length(); + + playerVelocity = player->velocity; + + float speed; + speed = playerVelocity.length(); + speed = speed * .75; + + if ( dist > 200 ) + return; + + if ( dist > speed ) + return; + + if ( DotProduct( playerVelocity , playerToSelf ) <= 0 ) + return; + + if ( act->EntityHasFireType( player, FT_BULLET ) || act->EntityHasFireType( player, FT_PROJECTILE ) ) + { + _checkYawMin = -30.0f; + _checkYawMax = 30.0f; + + check = player->GetVAngles(); + dir = playerToSelf.toAngles(); + + relativeYaw = AngleNormalize180( AngleNormalize180(check[YAW]) - AngleNormalize180(dir[YAW]) ); + + if ( (relativeYaw <= _checkYawMax) && (relativeYaw >= _checkYawMin ) ) + { + act->AddStateFlag( STATE_FLAG_TOUCHED_BY_PLAYER ); + act->AddStateFlag( STATE_FLAG_IN_THE_WAY ); + } + + } + +} + +float DefaultStrategos::GetCheckYawMax() +{ + return _checkYawMax; +} + +float DefaultStrategos::GetCheckYawMin() +{ + return _checkYawMin; +} + +float DefaultStrategos::GetCheckInConeDistMax() +{ + return _checkInConeDistMax; +} + +void DefaultStrategos::SetCheckInConeDistMax( float distance ) +{ + assert( distance >= 0 ); + _checkInConeDistMax = distance; +} + +// +// Name: Attack +// Parameters: Entity *enemy +// Description: Sets the current enemy, and locks on to it +// +void DefaultStrategos::Attack( Entity *enemy ) +{ + act->enemyManager->SetCurrentEnemy( enemy ); + act->enemyManager->LockOnCurrentEnemy( true ); +} + + +//-------------------------------------------------------------- +// Name: SetBehaviorPackage() +// Class: DefaultStrategos +// +// Description: Sets the actor's statemap to the specified behavior package +// +// Parameters: const str &packageName +// +// Returns: None +//-------------------------------------------------------------- +void DefaultStrategos::SetBehaviorPackage( const str &packageName ) +{ + int index; + BehaviorPackageType_t* package; + + index = act->packageManager->GetPackageIndex( packageName ); + if ( index == -1 ) + { + assert ( index != -1 ); + gi.WDPrintf( "Actor %s, Does not have package %s registered\n" , act->targetname.c_str() , packageName.c_str() ); + } + + + package = PackageList.ObjectAt( index ); + + // If we have a package AND our current statemap name is NOT the same as our selected package name + if ( package && stricmp( act->statemap_name.c_str(), package->stateFile.c_str() ) ) + { + Event *event; + + event = new Event( EV_Actor_Statemap ); + event->AddString( package->stateFile.c_str() ); //FileName + event->AddString( "START" ); //IdleState + event->AddInteger( 0 ); //Loading( false ) + event->AddInteger( 1 ); //packageChanged ( true ) + act->ProcessEvent( event ); + act->packageManager->UpdateCurrentPackageIndex( index ); + act->statemap_name = package->stateFile; + } +} + +// +// Name: DoArchive +// Parameters: Archiver &arc +// Actor *actor +// Description: Sets the Actor pointer during archiving +// +void DefaultStrategos::DoArchive( Archiver &arc , Actor *actor ) +{ + //Initialize our Actor + Archive( arc ); + + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "DefaultStrategos::DoArchive -- actor is NULL" ); + + //SetSightBasedHate( DEFAULT_SIGHT_BASED_HATE ); + //SetEvaluateInterval( DEFAULT_EVALUATE_INTERVAL ); + //SetNextEvaluateTime( 0.0f ); +} + +void DefaultStrategos::Archive( Archiver &arc ) +{ + Strategos::Archive( arc ); + + arc.ArchiveFloat( &_checkYawMin ); + arc.ArchiveFloat( &_checkYawMax ); + arc.ArchiveFloat( &_checkInConeDistMax ); +} + + +//================================================================================= +// PackageManager Implementation +//================================================================================= + + +// +// Name: PackageManager +// Parameters: None +// Description: Constructor +// +PackageManager::PackageManager() +{ + // Should always use other constructor + gi.Error( ERR_FATAL, "PackageManager::PackageManager -- Default Constructor Called" ); +} + + + +// +// Name: PackageManager +// Parameters: Actor *actor +// Description: Constructor +// +PackageManager::PackageManager( Actor *actor ) +{ + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "PackageManager::PackageManager -- actor is NULL" ); + + _currentPackageIndex = -1; +} + + + +// +// Name: ~PackageManager +// Parameters: None +// Description: Destructor +// +PackageManager::~PackageManager() +{ + +} + + + +// +// Name: RegisterPackage +// Parameters: str &packageName +// Description: Adds the package to the _BehaviorPackages container +// +void PackageManager::RegisterPackage( const str &packageName ) +{ + BehaviorPackageEntry_t pEntry; + BehaviorPackageType_t *package; + BehaviorPackageEntry_t *checkEntry; + + for ( int i = 1 ; i <= PackageList.NumObjects() ; i++ ) + { + package = PackageList.ObjectAt( i ); + + if ( !Q_stricmp( packageName.c_str() , package->packageName.c_str() ) ) + { + // We have a match, let's check to make sure we're not doubling up + for ( int j = 1 ; j <= _BehaviorPackages.NumObjects() ; j++ ) + { + checkEntry = &_BehaviorPackages.ObjectAt( j ); + + if ( checkEntry->packageIndex == i ) + return; + } + + // We don't have a match, so lets add the package + pEntry.currentScore = 0; + pEntry.lastScore = 0; + pEntry.lastTimeExecuted = 0.0f; + pEntry.priority = 0.001f; + pEntry.packageIndex = i; + + _BehaviorPackages.AddObject( pEntry ); + + // Make sure state machine gets cached properly + + CacheResource( package->stateFile, act ); + G_CacheStateMachineAnims( act, package->stateFile ); + + return; + } + } +} + + + +// +// Name: UnregisterPackage +// Parameters: str &packageName +// Description: Removes the package from the _BehaviorPackages container +// +void PackageManager::UnregisterPackage( const str &packageName ) +{ + BehaviorPackageEntry_t pEntry; + BehaviorPackageType_t *package; + int index = 0; + int i; + + for ( i = 1 ; i <= PackageList.NumObjects() ; i++ ) + { + package = PackageList.ObjectAt( i ); + if ( !Q_stricmp( packageName.c_str() , package->packageName.c_str() ) ) + index = i; + } + + for ( i = _BehaviorPackages.NumObjects() ; i > 0 ; i-- ) + { + pEntry = _BehaviorPackages.ObjectAt( i ); + if ( pEntry.packageIndex == index ) + _BehaviorPackages.RemoveObjectAt( i ); + } +} + + +// +// Name: GetHighestScoringPackage +// Parameters: None +// Description: Returns the index of the highest scoring package +// +int PackageManager::GetHighestScoringPackage() +{ + BehaviorPackageEntry_t pEntry; + float points; + int packageIndex = -1; + + points = 0; + + for ( int i = 1 ; i <= _BehaviorPackages.NumObjects() ; i++ ) + { + pEntry = _BehaviorPackages.ObjectAt( i ); + + if ( pEntry.currentScore > points ) + { + points = pEntry.currentScore; + packageIndex = pEntry.packageIndex; + } + } + + return packageIndex; +} + + +int PackageManager::GetCurrentFVarIndex() +{ + return _currentFVarIndex; +} + +float PackageManager::GetCurrentFVarLastExecuteTime() +{ + return _currentFVarLastExecuteTime; +} + +// +// Name: EvaluatePackages +// Parameters: FuzzyEngine *fEngine +// Description: Runs the evaluations in the fuzzyengine to score the behavior packages +// +void PackageManager::EvaluatePackages( FuzzyEngine *fEngine ) +{ +/* +Will likely need to pass in the _fuzzyEngine to here. +We then use the packageIndex to index into the globalpackagelist +to get the string name, we then use the string name and the +_fuzzyEngine to get the correct _fuzzyVar. We then fuzzyVar->evaluate() +assigning the points as necessary + + _fuzzyVar->evaluate keeps a running total of all the points accumulated and after + all the evaluations are complete, it returns the total + */ + + FuzzyVar *currentFVar; + BehaviorPackageEntry_t *currentEntry; + BehaviorPackageType_t *package; + + for ( int i = 1 ; i <= _BehaviorPackages.NumObjects() ; i++ ) + { + currentEntry = &_BehaviorPackages.ObjectAt( i ); + + _currentFVarIndex = currentEntry->packageIndex; + _currentFVarLastExecuteTime = currentEntry->lastTimeExecuted; + + package = PackageList.ObjectAt( _currentFVarIndex ); + + + currentFVar = fEngine->FindFuzzyVar( package->packageName.c_str() ); + + if ( currentFVar ) + { + currentEntry->lastScore = currentEntry->currentScore; + currentEntry->currentScore = currentFVar->Evaluate( *act , &act->fuzzy_conditionals ); + } + + + } + +} + + +void PackageManager::SetLastExecutionTime(int packageIndex) +{ + BehaviorPackageEntry_t *currentEntry; + + + for ( int i = 1 ; i <= _BehaviorPackages.NumObjects() ; i++ ) + { + currentEntry = &_BehaviorPackages.ObjectAt( i ); + + if ( currentEntry->packageIndex == packageIndex ) + { + currentEntry->lastTimeExecuted = level.time; + return; + } + + } +} + +void PackageManager::UpdateCurrentPackageIndex( int packageIndex ) +{ + if ( _currentPackageIndex > -1 ) + SetLastExecutionTime( _currentPackageIndex ); + + _currentPackageIndex = packageIndex; +} + + +//-------------------------------------------------------------- +// Name: GetCurrentPackageName() +// Class: PackageManager +// +// Description: Gets the name of the current behavior package +// +// Parameters: None +// +// Returns: str +//-------------------------------------------------------------- +str PackageManager::GetCurrentPackageName() +{ + BehaviorPackageType_t* package; + package = PackageList.ObjectAt( _currentPackageIndex ); + + return package->packageName; +} + +//-------------------------------------------------------------- +// Name: GetPackageIndex() +// Class: PackageManager +// +// Description: Returns the package index for the packageName. +// We iterate through our list of registered behavior +// packages because: First, if the packages are registered +// we know that the actor can use them. Secondly, by only +// having to be concerned about the packages we have specifically +// registered we can cut way down on string compares. +// +// Parameters: const str &packageName +// +// Returns: int +//-------------------------------------------------------------- +int PackageManager::GetPackageIndex( const str &packageName ) +{ + BehaviorPackageEntry_t pEntry; + BehaviorPackageType_t *package; + + // We do this so that we only have to do str compares on behavior + // packages we have registered -- Not the whole Package List + for ( int i = 1 ; i <= _BehaviorPackages.NumObjects() ; i++ ) + { + pEntry = _BehaviorPackages.ObjectAt( i ); + package = PackageList.ObjectAt( pEntry.packageIndex ); + + if ( !stricmp( package->packageName.c_str() , packageName.c_str() ) ) + return pEntry.packageIndex; + } + + return -1; +} + +// +// Name: DoArchive +// Parameters: Archiver &arc +// Actor *actor +// Description: Sets the Actor pointer during archiving +// +void PackageManager::DoArchive( Archiver &arc , Actor *actor ) +{ + int i; + BehaviorPackageEntry_t *pEntry; + BehaviorPackageType_t *package; + int numPackages; + str packageName; + + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "PackageManager::DoArchive -- actor is NULL" ); + + if ( arc.Loading() ) + { + arc.ArchiveInteger( &numPackages ); + + for ( i = 1 ; i <= numPackages ; i++ ) + { + arc.ArchiveString( &packageName ); + + RegisterPackage( packageName ); + + // The package we just added should always be the last one + + pEntry = &_BehaviorPackages.ObjectAt( _BehaviorPackages.NumObjects() ); + + arc.ArchiveFloat( &pEntry->currentScore ); + arc.ArchiveFloat( &pEntry->lastScore ); + arc.ArchiveFloat( &pEntry->lastTimeExecuted ); + arc.ArchiveFloat( &pEntry->priority ); + } + + } + else + { + numPackages = _BehaviorPackages.NumObjects(); + arc.ArchiveInteger( &numPackages ); + + for ( i = 1 ; i <= _BehaviorPackages.NumObjects() ; i++ ) + { + pEntry = &_BehaviorPackages.ObjectAt( i ); + + package = PackageList.ObjectAt( pEntry->packageIndex ); + + arc.ArchiveString( &package->packageName ); + + arc.ArchiveFloat( &pEntry->currentScore ); + arc.ArchiveFloat( &pEntry->lastScore ); + arc.ArchiveFloat( &pEntry->lastTimeExecuted ); + arc.ArchiveFloat( &pEntry->priority ); + } + } + + arc.ArchiveInteger( &_currentFVarIndex ); + arc.ArchiveFloat( &_currentFVarLastExecuteTime ); + arc.ArchiveInteger( &_currentPackageIndex ); +} + + + +//================================================================================= +// Personality Implementation +//================================================================================= + + +// +// Name: Personality() +// Parameters: None +// Description: Constructor +// +Personality::Personality() +{ + // Should always use other constructor + gi.Error( ERR_FATAL, "Personality::Personality -- Default Constructor Called" ); +} + + +// +// Name: Personality() +// Parameters: Actor *actor +// Description: Constructor +// +Personality::Personality( Actor *actor ) +{ + //Initialize our Actor + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "Personality::Personality -- actor is NULL" ); + + _aggressiveness = 0.0f; + _talkiness = 0.0f; + +} + + +// +// Name: ~Personality() +// Parameters: None +// Description: Destructor +// +Personality::~Personality() +{ + +} + + + +void Personality::SetAggressiveness( float aggressiveness ) +{ + _aggressiveness = _clampValue( aggressiveness ); +} + +float Personality::GetAggressiveness() +{ + return _aggressiveness; +} + + +void Personality::SetTalkiness( float talkiness ) +{ + _talkiness = _clampValue( talkiness ); +} + +float Personality::GetTalkiness() +{ + return _talkiness; +} + +float Personality::_clampValue( float value ) +{ + if ( value > 1.0f ) + value = 1.0f; + + if ( value < 0.0f ) + value = 0.0f; + + return value; +} + +float Personality::GetTendency( const str &tendencyName ) +{ + Tendency_t tendency; + + // First Make sure we don't already have this tendency + for ( int i = 1 ; i <= _Tendencies.NumObjects() ; i++ ) + { + tendency = _Tendencies.ObjectAt( i ); + + if ( !Q_stricmp( tendencyName.c_str() , tendency.tendencyName.c_str() ) ) + { + return tendency.tendencyValue; + } + + } + + // gi.WDPrintf( "Actor %s, Entnum %d , has no tendency named %s", act->TargetName(), act->entnum , tendencyName.c_str() ); + return 0.0f; +} + +// +// Name: DoArchive +// Parameters: Archiver &arc +// Actor *actor +// Description: Sets the Actor pointer during archiving +// +void Personality::DoArchive( Archiver &arc , Actor *actor ) +{ + Archive( arc ); + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "Personality::DoArchive -- actor is NULL" ); + +} + +void Personality::Archive( Archiver &arc ) +{ + int i; + int j; + int num; + str packageName; + BehaviorPackageType_t *package; + PackageTendency_t *tendency; + PackageTendency_t tempTendency; + + Tendency_t tempGeneralTendency; + Tendency_t *generalTendency; + + tempTendency.packageIndex = 0; + tempTendency.tendency = 0.0f; + tempTendency.lastTendencyCheck = 0.0f; + + arc.ArchiveFloat( &_aggressiveness ); + arc.ArchiveFloat( &_talkiness ); + + arc.ArchiveFloat( &_anger ); + arc.ArchiveFloat( &_fear ); + + if ( arc.Saving() ) + { + num = _PackageTendencies.NumObjects(); + + arc.ArchiveInteger( &num ); + + for ( i = 1 ; i <= num ; i++ ) + { + tendency = &_PackageTendencies.ObjectAt( i ); + + package = PackageList.ObjectAt( tendency->packageIndex ); + + arc.ArchiveString( &package->packageName ); + arc.ArchiveFloat( &tendency->tendency ); + arc.ArchiveFloat( &tendency->lastTendencyCheck ); + + } + } + else + { + arc.ArchiveInteger( &num ); + + _PackageTendencies.ClearObjectList(); + _PackageTendencies.Resize( num ); + + for ( i = 1 ; i <= num ; i++ ) + { + _PackageTendencies.AddObject( tempTendency ); + + tendency = &_PackageTendencies.ObjectAt( i ); + + arc.ArchiveString( &packageName ); + + // Find index + + tendency->packageIndex = -1; + + for ( j = 1 ; j <= PackageList.NumObjects() ; j++ ) + { + package = PackageList.ObjectAt( j ); + + if ( stricmp( packageName.c_str() , package->packageName.c_str() ) == 0 ) + { + tendency->packageIndex = j; + break; + } + } + + assert( tendency->packageIndex != -1 ); + + arc.ArchiveFloat( &tendency->tendency ); + arc.ArchiveFloat( &tendency->lastTendencyCheck ); + } + } + + + if ( arc.Saving() ) + { + num = _Tendencies.NumObjects(); + + arc.ArchiveInteger( &num ); + + for ( i = 1 ; i <= num ; i++ ) + { + generalTendency = &_Tendencies.ObjectAt( i ); + + arc.ArchiveString( &generalTendency->tendencyName ); + arc.ArchiveFloat( &generalTendency->tendencyValue ); + + } + } + else + { + arc.ArchiveInteger( &num ); + + _Tendencies.ClearObjectList(); + _Tendencies.Resize( num ); + + for ( i = 1 ; i <= num ; i++ ) + { + _Tendencies.AddObject( tempGeneralTendency ); + + generalTendency = &_Tendencies.ObjectAt( i ); + + arc.ArchiveString( &generalTendency->tendencyName ); + arc.ArchiveFloat( &generalTendency->tendencyValue ); + } + } +} + + +void Personality::SetBehaviorTendency( const str &packageName , float tendency ) +{ + PackageTendency_t pEntry; + BehaviorPackageType_t *package; + PackageTendency_t *checkEntry; + + for ( int i = 1 ; i <= PackageList.NumObjects() ; i++ ) + { + package = PackageList.ObjectAt( i ); + + if ( !Q_stricmp( packageName.c_str() , package->packageName.c_str() ) ) + { + // We have a match, let's check to make sure we're not doubling up + for ( int j = 1 ; j <= _PackageTendencies.NumObjects() ; j++ ) + { + checkEntry = &_PackageTendencies.ObjectAt( j ); + + if ( checkEntry->packageIndex == i ) + { + checkEntry->tendency = tendency; + return; + } + + } + + // We don't have a match, so lets add the package + pEntry.lastTendencyCheck = 0.0f; + pEntry.tendency = tendency; + pEntry.packageIndex = i; + + _PackageTendencies.AddObject( pEntry ); + return; + } + } +} + +void Personality::SetTendency( const str &tendencyName , float tendencyValue ) +{ + Tendency_t *currentTendency; + Tendency_t tendency; + + // First Make sure we don't already have this tendency + for ( int i = 1 ; i <= _Tendencies.NumObjects() ; i++ ) + { + currentTendency = &_Tendencies.ObjectAt( i ); + + if ( !Q_stricmp( tendencyName.c_str() , currentTendency->tendencyName.c_str() ) ) + { + currentTendency->tendencyValue = tendencyValue; + return; + } + + } + + + tendency.tendencyName = tendencyName; + tendency.tendencyValue = tendencyValue; + + _Tendencies.AddObject( tendency ); +} + +qboolean Personality::WantsToExecuteCurrentPackage(float interval) +{ + int currentIndex; + PackageTendency_t *checkEntry; + + currentIndex = act->packageManager->GetCurrentFVarIndex(); + + for ( int i = 1 ; i <= _PackageTendencies.NumObjects() ; i++ ) + { + checkEntry = &_PackageTendencies.ObjectAt( i ); + + if ( checkEntry->packageIndex == currentIndex ) + { + if ( level.time > checkEntry->lastTendencyCheck + interval ) + return _wantsToExecutePackage(checkEntry); + } + } + + return false; +} + +qboolean Personality::_wantsToExecutePackage(PackageTendency_t *tendency ) +{ + float percent_chance; + float chance; + + tendency->lastTendencyCheck = level.time; + + percent_chance = tendency->tendency; + chance = G_Random(); + + return ( chance < percent_chance ); + +} + +qboolean Personality::ExecutedPackageInLastTimeFrame(float interval) +{ + float lastExecutionTime; + + lastExecutionTime = act->packageManager->GetCurrentFVarLastExecuteTime(); + + if ( level.time <= lastExecutionTime + interval ) + return true; + + return false; +} + +//====================================== +// Global Functions +//====================================== +void FillBehaviorPackageList( void ) +{ + Script script; + const char *token; + char *buf; + BehaviorPackageType_t *packageType; + + // Check if we even HAVE a BehaviorPackages.txt file + if ( gi.FS_ReadFile( "global/BehaviorPackages.txt", ( void ** )&buf, true ) == -1 ) + return; + + script.LoadFile( "global/BehaviorPackages.txt" ); + + if ( script.length < 0 ) + return; + + + while ( script.TokenAvailable ( true ) ) + { + token = script.GetToken(false); + + if (!Q_stricmp( token , "Package" ) ) + { + packageType = 0; + packageType = new BehaviorPackageType_t; + + if ( !packageType ) + gi.Error( ERR_FATAL, "FillBehaviorPackageList -- could not create packageType" ); + + //Set the name and file + packageType->packageName = script.GetToken(false); + packageType->stateFile = script.GetToken(false); + + PackageList.AddObject( packageType ); + + } + + } + + script.Close(); +} + +void ClearBehaviorPackageList( void ) +{ + for ( int i = PackageList.NumObjects() ; i > 0 ; i-- ) + { + delete PackageList.ObjectAt( i ); + PackageList.RemoveObjectAt( i ); + } + + //Check that everything is clear + if ( PackageList.NumObjects() > 0 ) + gi.Error( ERR_FATAL, "ClearBehaviorPackageList -- PackageList is NOT empty." ); +} + + diff --git a/dlls/game/RageAI.h b/dlls/game/RageAI.h new file mode 100644 index 0000000..e1dc993 --- /dev/null +++ b/dlls/game/RageAI.h @@ -0,0 +1,245 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/RageAI.h $ +// $Revision:: 30 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// The Rage AI System, will house several components that will make the AI function +// such as the Strategos, Personality, etc... +// + +//============================ +// Forward Declarations +//============================ +class Strategos; +class DefaultStrategos; +class PackageManager; +class Personality; + +#ifndef __RAGE_AI_H__ +#define __RAGE_AI_H__ + +#include "actor.h" +#include "actorincludes.h" +#include "characterstate.h" +#include "script.h" + +//============================ +// Global Functions +//============================ +void FillBehaviorPackageList( void ); +void ClearBehaviorPackageList( void ); + +//============================ +// Class Strategos +//============================ +// +// Handles all strategic thinking for the actor +// This will get more complicated as the AI gets more +// sophisticated +// +class Strategos + { + public: + Strategos() { } + Strategos( Actor *act ) { } + virtual ~Strategos() { } + + virtual void Evaluate() { } + virtual void NotifySightStatusChanged ( Entity *enemy , qboolean canSee ) { } + virtual void NotifyDamageChanged( Entity *enemy , float damage ) { } + + virtual void Attack ( Entity *enemy ) { } + virtual void DoArchive( Archiver &arc , Actor *act ); + /* virtual */ void Archive( Archiver &arc ); + virtual void SetBehaviorPackage( const str &packageName ) { } + + virtual float GetCheckYawMin() { assert( 0 ); return 0; } + virtual float GetCheckYawMax() { assert( 0 ); return 0; } + virtual float GetCheckInConeDistMax() { assert( 0 ); return 0; } + virtual void SetCheckInConeDistMax( float distance ) { assert( 0 ); } + + //Accessors and Mutators + void SetEvaluateInterval( float interval ) { _evaluateInterval = interval; } + float GetEvaluateInterval() { return _evaluateInterval; } + + void SetNextEvaluateTime( float time ) { _nextEvaluateTime = time; } + float GetNextEvaluateTime() { return _nextEvaluateTime; } + + void SetSightBasedHate( float hate ) { _sightBasedHate = hate; } + float GetSightBasedHate() { return _sightBasedHate; } + + + private: + float _sightBasedHate; + float _nextEvaluateTime; + float _evaluateInterval; + + + + + }; + + +//============================ +// Class DefaultStrategos +//============================ +// +// The Strategos supplied by default. All actors get this +// in construction. As additional subclasses are added +// events will be put into place in Actor that will allow +// the specification of the strategos to use from the TIKI file +// +class DefaultStrategos : public Strategos +{ +public: + + DefaultStrategos(); + DefaultStrategos( Actor *act ); + ~DefaultStrategos(); + + void Evaluate(); + void NotifySightStatusChanged ( Entity *enemy , qboolean canSee ); + void NotifyDamageChanged( Entity *enemy, float damage ); + void SetBehaviorPackage( const str &packageName ); + + float GetCheckYawMin(); + float GetCheckYawMax(); + float GetCheckInConeDistMax(); + + void SetCheckInConeDistMax( float distance ); + + void Attack ( Entity *enemy ); + void DoArchive ( Archiver &arc, Actor *actor ); + /* virtual */ void Archive( Archiver &arc ); + +private: // Functions + + void _EvaluateEnemies(); + void _EvaluatePackages(); + void _EvaluateWorld(); + void _CheckForInTheWay(); + void _CheckForInConeOfFire(); + +private: // Member Variables + + Actor *act; + float _checkYawMin; + float _checkYawMax; + float _checkInConeDistMax; + + + +}; + + + +//============================ +// PackageManager +//============================ +// +// Handles behavior packages. It should be noted that, if an +// actor has a statemap, but no fuzzyengine, then it will just execute +// the specified statemap. A fuzzyengine MUST be supplied if the actor +// is going to take advantage of the PackageManager. +// + +class PackageManager + { + public: + PackageManager(); + PackageManager( Actor *actor ); + ~PackageManager(); + + void RegisterPackage( const str &packageName ); + void UnregisterPackage( const str &packageName ); + + void EvaluatePackages( FuzzyEngine *fEngine ); + int GetHighestScoringPackage(); + int GetCurrentFVarIndex(); + float GetCurrentFVarLastExecuteTime(); + void SetLastExecutionTime(int packageIndex); + void UpdateCurrentPackageIndex( int packageIndex ); + int GetPackageIndex( const str &packageName ); + str GetCurrentPackageName(); + + void DoArchive( Archiver &arc , Actor *actor ); + + private: // Member Variables + Actor *act; + Container _BehaviorPackages; + + int _currentFVarIndex; + float _currentFVarLastExecuteTime; + int _currentPackageIndex; + }; + + +//============================ +// Class Personality +//============================ +// +// Stores Personality Measures. Now, you maybe asking why am I making +// a full blown class if all I'm doing is storing data. Well, I'm setting +// this up for future expansion, so when I realize I need this class do something +// to the data its storing, it won't be a huge pain to make that happen +// + +class Personality + { + public: + Personality(); + Personality( Actor *actor ); + ~Personality(); + + void SetBehaviorTendency( const str& packageName , float tendency ); + void SetTendency ( const str& tendencyName , float tendencyValue ); + + qboolean WantsToExecuteCurrentPackage(float interval); + qboolean ExecutedPackageInLastTimeFrame(float interval); + + void SetAggressiveness( float aggressiveness ); + float GetAggressiveness(); + + void SetTalkiness( float talkiness ); + float GetTalkiness(); + + float GetTendency( const str& tendencyName ); + + virtual void Archive( Archiver &arc ); + void DoArchive ( Archiver &arc, Actor *actor ); + + protected: // Member Functions + float _clampValue( float value ); + qboolean _wantsToExecutePackage(PackageTendency_t *tendency); + + + private: // Emotions and Tendencies + + float _aggressiveness; + float _talkiness; + + float _anger; + float _fear; + + + // Package Tendencies + Container _PackageTendencies; + Container _Tendencies; + + private: // Member Variables + Actor *act; + + }; + + +#endif /* __RAGE_AI_H__ */ diff --git a/dlls/game/UseData.cpp b/dlls/game/UseData.cpp new file mode 100644 index 0000000..9a49c2f --- /dev/null +++ b/dlls/game/UseData.cpp @@ -0,0 +1,43 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/UseData.cpp $ +// $Revision:: 4 $ +// $Author:: Steven $ +// $Date:: 5/20/02 3:53p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// UseDataObject +// + +#include "_pch_cpp.h" +//#include "g_local.h" +#include "UseData.h" + + +//-------------------------------------------------------------- +// +// Name: useMe +// Class: UseData +// +// Description: Local notification that this entity has been +// used. Needed to decrement use counter. +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +void UseData::useMe() +{ + if ( _useCount == -1 ) + return; + + _useCount--; +} diff --git a/dlls/game/UseData.h b/dlls/game/UseData.h new file mode 100644 index 0000000..6930f31 --- /dev/null +++ b/dlls/game/UseData.h @@ -0,0 +1,96 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/UseData.h $ +// $Revision:: 5 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// UseDataObject +// + + + +// UseData.h: interface for the UseData class. +// +////////////////////////////////////////////////////////////////////// + +class UseData; + +#ifndef __USEDATA_H__ +#define __USEDATA_H__ + +#include "g_local.h" + + +//------------------------- CLASS ------------------------------ +// +// Name: UseData +// Base Class: Class +// +// Description: Stores information about how to use this entity +// +// Method of Use: There is a pointer to this class in Entity, +// NULL by default. If this class is allocated, +// it is assumed that this entity can be "used" +// When used, it plays _useAnim on the player, +// calls _useThread in the script. Displays +// _useType icon in the HUD. +//-------------------------------------------------------------- +class UseData : public Class +{ + private: + str _useAnim; + str _useThread; + str _useType; + + float _useMaxDist; + int _useCount; + + public: + UseData::UseData() + :_useAnim(""), + _useType(""), + _useThread(""), + _useMaxDist(64.0f), + _useCount(-1) + { } + + ~UseData() { } + + const str& getUseAnim() { return _useAnim; } + const str& getUseThread() { return _useThread; } + const str& getUseType() { return _useType; } + float getUseMaxDist() { return _useMaxDist; } + int getUseCount() { return _useCount; } + + void setUseAnim(const str& newanim) { _useAnim = newanim; } + void setUseThread(const str& newthread) { _useThread = newthread; } + void setUseType(const str& newtype) { _useType = newtype; } + void setUseMaxDist(float newdist) { _useMaxDist = newdist; } + void setUseCount(float newcount) { _useCount = (int)newcount; } + + void useMe(); + + void Archive(Archiver &arc); +}; + +inline void UseData::Archive(Archiver &arc) +{ + Class::Archive( arc ); + + arc.ArchiveString( &_useAnim ); + arc.ArchiveString( &_useThread ); + arc.ArchiveString( &_useType ); + arc.ArchiveFloat( &_useMaxDist ); + arc.ArchiveInteger( &_useCount ); +} + +#endif // __USEDATA_H__ diff --git a/dlls/game/WeaponDualWield.cpp b/dlls/game/WeaponDualWield.cpp new file mode 100644 index 0000000..97ed337 --- /dev/null +++ b/dlls/game/WeaponDualWield.cpp @@ -0,0 +1,145 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/WeaponDualWield.cpp $ +// $Revision:: 6 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// + + +#include "WeaponDualWield.h" +#include + +CLASS_DECLARATION( Weapon, WeaponDualWield, NULL ) +{ + { &EV_Anim, &WeaponDualWield::PassToAnimate }, + { &EV_NewAnim, &WeaponDualWield::PassToAnimate }, + + { NULL, NULL } +}; + +WeaponDualWield::WeaponDualWield() + : _leftweapon(0), + _rightweapon(0) +{ +} + +WeaponDualWield::~WeaponDualWield() +{ + +} + +//-------------------------------------------------------------- +// +// Name: AttachToOwner +// Class: WeaponDualWield +// +// Description: Attach to the owner. +// +// Parameters: weaponhand_t hand -- hand +// +// Returns: None +// +//-------------------------------------------------------------- +void WeaponDualWield::AttachToOwner( weaponhand_t hand ) +{ + _leftweapon->SetOwner(owner); + _rightweapon->SetOwner(owner); + _leftweapon->AttachGun( WEAPON_LEFT ); + _rightweapon->AttachGun( WEAPON_RIGHT ); + _rightweapon->ForceIdle(); + _leftweapon->ForceIdle(); +} + +//-------------------------------------------------------------- +// +// Name: processGameplayData +// Class: WeaponDualWield +// +// Description: Process gameplay data +// +// Parameters: Event *ev -- not used +// +// Returns: None +// +//-------------------------------------------------------------- +void WeaponDualWield::processGameplayData( Event *ev ) +{ + ClassDef *cls; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(getArchetype()) ) + return; + + str leftmodel = gpm->getStringValue(getArchetype(), "leftmodel"); + str rightmodel = gpm->getStringValue(getArchetype(), "rightmodel"); + + if ( !_leftweapon ) + { + cls = getClass( leftmodel ); + if ( !cls ) + { + SpawnArgs args; + args.setArg( "model", leftmodel ); + cls = args.getClassDef(); + if ( !cls ) + return; + } + _leftweapon = ( Weapon * )cls->newInstance(); + _leftweapon->setModel( leftmodel ); + _leftweapon->ProcessPendingEvents(); + _leftweapon->hideModel(); + } + + if ( !_rightweapon ) + { + cls = getClass( rightmodel ); + if ( !cls ) + { + SpawnArgs args; + args.setArg( "model", rightmodel ); + cls = args.getClassDef(); + if ( !cls ) + return; + } + _rightweapon = ( Weapon * )cls->newInstance(); + _rightweapon->setModel( rightmodel ); + _rightweapon->ProcessPendingEvents(); + _rightweapon->hideModel(); + } + + // Process gameplay data on myself. + Weapon::processGameplayData( NULL ); +} + +//=============================================================== +// Name: PassToAnimate +// Class: WeaponDualWield +// +// Description: Passes animation events on to both the left +// and right weapons. +// +// Parameters: Event* -- the event to pass on. +// +// Returns: None +// +//=============================================================== +void WeaponDualWield::PassToAnimate( Event *ev ) +{ + if ( _leftweapon ) + { + Event *leftEvent = new Event( ev ); + _leftweapon->ProcessEvent( leftEvent ); + } + if ( _rightweapon ) + { + Event *rightEvent = new Event( ev ); + _rightweapon->ProcessEvent( rightEvent ); + } +} \ No newline at end of file diff --git a/dlls/game/WeaponDualWield.h b/dlls/game/WeaponDualWield.h new file mode 100644 index 0000000..4d7c479 --- /dev/null +++ b/dlls/game/WeaponDualWield.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/WeaponDualWield.h $ +// $Revision:: 5 $ +// $Author:: Steven $ +// $Date:: 10/11/02 4:05a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// + +class WeaponDualWield; + +#ifndef __WEAPONDUALWIELD_H__ +#define __WEAPONDUALWIELD_H__ + +#include "g_local.h" +#include "item.h" +#include "ammo.h" +#include "sentient.h" +#include "weapon.h" + +class WeaponDualWield : public Weapon +{ +private: + Weapon *_leftweapon; + Weapon *_rightweapon; + +protected: + void PassToAnimate( Event *ev ); + +public: + CLASS_PROTOTYPE( WeaponDualWield ); + + WeaponDualWield(); + ~WeaponDualWield(); + + Weapon* getRightWeapon() { return _rightweapon; } + Weapon* getLeftWeapon() { return _leftweapon; } + + void processGameplayData( Event *ev ); + + virtual void AttachToOwner( weaponhand_t hand ); + void Archive( Archiver &arc ); +}; + +inline void WeaponDualWield::Archive( Archiver &arc ) +{ + Weapon::Archive( arc ); + + arc.ArchiveObjectPointer( (Class **)&_leftweapon ); + arc.ArchiveObjectPointer( (Class **)&_rightweapon ); +} + +#endif diff --git a/dlls/game/_pch_cpp.cpp b/dlls/game/_pch_cpp.cpp new file mode 100644 index 0000000..6831bcf --- /dev/null +++ b/dlls/game/_pch_cpp.cpp @@ -0,0 +1,22 @@ + +//--------------------------------------------------------------------------- +// _pch_cpp.cpp +// +// Precompiled header prototype source file for Game module. +// +// NOTE: Precompiled headers may not mix C with C++ files; this +// is the CPP version for the module. +// +// In this module's Project Settings -> C++ -> Precompiled Headers, +// the file _PCH_CPP.C should be set to: +// Create precompiled header file (.pch) +// Through header: _pch_cpp.h +// +// All other C++ files in the project should be set to: +// Use precompiled header file (.pch) +// Through header: _pch_cpp.h +//--------------------------------------------------------------------------- + +#include "_pch_cpp.h" + + diff --git a/dlls/game/_pch_cpp.h b/dlls/game/_pch_cpp.h new file mode 100644 index 0000000..0eddc50 --- /dev/null +++ b/dlls/game/_pch_cpp.h @@ -0,0 +1,25 @@ + +//--------------------------------------------------------------------------- +// _pch_cpp.h +// +// Precompiled header for Game module. +// +// NOTE: Precompiled headers may not mix C with C++ files; this +// is the CPP version for the module. +// +// In this module's Project Settings -> C++ -> Precompiled Headers, +// the file _PCH_CPP.C should be set to: +// Create precompiled header file (.pch) +// Through header: _pch_cpp.h +// +// All other C++ files in the project should be set to: +// Use precompiled header file (.pch) +// Through header: _pch_cpp.h +//--------------------------------------------------------------------------- + +#include "g_local.h" +#include "actor.h" +#include "player.h" +#include "entity.h" +#include "object.h" + diff --git a/dlls/game/actor.cpp b/dlls/game/actor.cpp new file mode 100644 index 0000000..95f2156 --- /dev/null +++ b/dlls/game/actor.cpp @@ -0,0 +1,19685 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actor.cpp $ +// $Revision:: 557 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:43a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// Base class for character AI. +// + +#include "_pch_cpp.h" +//#include "g_local.h" +#include "actor.h" +#include "behavior.h" +#include "behaviors_general.h" +#include "behaviors_specific.h" +#include "scriptmaster.h" +#include "doors.h" +#include "gibs.h" +#include "misc.h" +#include "specialfx.h" +#include "object.h" +#include "scriptslave.h" +#include "explosion.h" +#include "misc.h" +#include "PlayerStart.h" +#include "characterstate.h" +#include "weaputils.h" +#include "player.h" +#include "armor.h" +#include "groupcoordinator.hpp" +#include +#include "teammateroster.hpp" +#include "talk.hpp" +#include "equipment.h" + +Container SleepList; //All actors in the level that are asleep +Container ActiveList; //All actors in the level that are active +Container TeamMateList; //Global list of all teammates +Container PackageList; //Global list of all behavior packages ( in BehaviorPackages.txt ) + +extern Container SpecialPathNodes; + +Event EV_Actor_SetSelfDetonateModel + ( + "selfdetonatemodel", + EV_TIKIONLY, + "s", + "modelname", + "Set the modelname of the explosion to be spawned when an actor self-detonates" + ); + +Event EV_Actor_BlindlyFollowPath + ( + "blindlyfollowpath", + EV_SCRIPTONLY, + "sFS", + "anim_name offset pathnode", + "Actor walks to specified path node without avoidance or collision" + ); + +Event EV_Actor_SetSimplifiedThink + ( + "setsimplifiedthink", + EV_DEFAULT, + "B", + "boolean", + "change actor to SimplifiedThink think strategy" + ); + +Event EV_Actor_SetActorToActorDamageModifier + ( + "actortoactordamage", + EV_DEFAULT, + "f", + "modifier", + "Amount to modifiy damage by 1 is full damage, 0 would be no damage" + ); +Event EV_Actor_OnUse + ( + "onuse", + EV_SCRIPTONLY, + "s", + "thread_name", + "Sets the thread to call" + ); + +Event EV_Actor_NoUse + ( + "nouse", + EV_SCRIPTONLY, + NULL, + NULL, + "Clears the on use thread" + ); + +Event EV_Actor_ClearCurrentEnemy + ( + "clearCurrentEnemy", + EV_DEFAULT, + NULL, + NULL, + "Sets Current Enemy to Null" + ); + +Event EV_Actor_SetTargetType + ( + "setTargetType", + EV_DEFAULT, + "i", + "set_target_type", + "Set Type of Target (0) Any, (1) Player Only, (2) Actors Only (3) Scripted Only (4) Level_Interaction Triggers Only " + ); +Event EV_Actor_Sleep + ( + "sleep", + EV_DEFAULT, + NULL, + NULL, + "Put the actor to sleep." + ); +Event EV_Actor_Wakeup + ( + "wakeup", + EV_SCRIPTONLY, + NULL, + NULL, + "Wake up the actor." + ); +Event EV_Actor_Fov + ( + "fov", + EV_CONSOLE, + "f", + "fov", + "Sets the actor's field of view (fov)." + ); +Event EV_Actor_VisionDistance + ( + "visiondistance", + EV_DEFAULT, + "f", + "vision_distance", + "Sets the distance the actor can see." + ); +Event EV_Actor_Start + ( + "start", + EV_DEFAULT, + NULL, + NULL, + "Initializes the actor a little, " + "it is not meant to be called from script." + ); +Event EV_Actor_Dead + ( + "dead", + EV_CODEONLY, + NULL, + NULL, + "Does everything necessary when an actor dies, " + "it is not meant to be called from script." + ); + +Event EV_Actor_SetEnemyType + ( + "enemytype", + EV_DEFAULT, + "s", + "enemytype", + "Sets the name of this actor's enemy type." + ); +Event EV_Actor_Swim + ( + "swim", + EV_DEFAULT, + NULL, + NULL, + "Specifies actor as being able to swim." + ); +Event EV_Actor_Fly + ( + "fly", + EV_DEFAULT, + "B", + "fly_bool", + "Specifies actor as being able to fly (optional bool can turn fly on or off)." + ); +Event EV_Actor_NotLand + ( + "noland", + EV_DEFAULT, + NULL, + NULL, + "Specifies actor as not being able to walk on land." + ); + +Event EV_Actor_RunThread + ( + "runthread", + EV_CODEONLY, + "s", + "label", + "Runs the specified thread." + ); + +Event EV_Actor_Statemap + ( + "statemap", + EV_DEFAULT, + "sS", + "statemap_name state_name", + "Sets which statemap file to use and optionally what the first state to go to." + ); + +Event EV_Actor_MasterStateMap + ( + "masterstatemap", + EV_DEFAULT, + "sS", + "statemap_name state_name", + "Sets which masterstatemap file to use and optionally what the first state to go to." + ); + +Event EV_Actor_FuzzyEngine + ( + "fuzzyengine", + EV_DEFAULT, + "s", + "fuzzyengine_name", + "Sets which fuzzy engine file to use" + ); + +Event EV_Actor_SetBehaviorPackage + ( + "setbehaviorpackage", + EV_DEFAULT, + "s", + "package_name", + "sets the actor to use the specified behavior package AND sets the masterstate" + ); + +Event EV_Actor_UseBehaviorPackage + ( + "usebehaviorpackage", + EV_DEFAULT, + "s", + "package_name", + "sets the actor to use the specified behavior package but does NOT set the master state" + ); + +Event EV_Actor_ChildUseBehaviorPackage + ( + "childusebehaviorpackage", + EV_DEFAULT, + "ss", + "childname package_name", + "sets the child to use the specified behavior package but does NOT set the master state" + ); + +Event EV_Actor_ChildSetAnim + ( + "childsetanim", + EV_DEFAULT, + "ss", + "childname anim_name", + "sets the child to play the anim specified" + ); + +Event EV_Actor_ChildSuicide + ( + "childsuicide", + EV_DEFAULT, + "s", + "childname", + "sets the child to kill itself" + ); + +Event EV_Actor_IfEnemyVisible + ( + "ifenemyvisible", + EV_SCRIPTONLY, + "SSSSSS", + "token1 token2 token3 token4 token5 token6", + "Process the following command if enemy is visible" + ); +Event EV_Actor_IfNear + ( + "ifnear", + EV_SCRIPTONLY, + "sfSSSSSS", + "name distance token1 token2 token3 token4 token5 token6", + "Process the following command if enemy is within specified distance" + ); +Event EV_Actor_ForwardSpeed + ( + "forwardspeed", + EV_DEFAULT, + "f", + "forwardspeed", + "Sets the actor's forward speed." + ); +Event EV_Actor_Idle + ( + "idlestate", + EV_SCRIPTONLY, + "S", + "state_name", + "Tells the actor to go into idle mode." + ); +Event EV_Actor_LookAt + ( + "lookat", + EV_SCRIPTONLY, + "e", + "ent", + "Specifies an entity to look at." + ); +Event EV_Actor_TurnTo + ( + "turntoangle", + EV_SCRIPTONLY, + "f", + "direction", + "Specifies the direction to look in." + ); +Event EV_Actor_HeadWatch + ( + "headwatch", + EV_SCRIPTONLY, + "eF", + "entity_to_watch max_speed", + "Actor watches the specified entity by turning his head." + ); + +Event EV_Actor_HeadAndEyeWatch + ( + "headandeyewatch", + EV_SCRIPTONLY, + "eF", + "entity_to_watch max_speed", + "Actor watches the specified entity by turning his eyes,then head." + ); + +Event EV_Actor_ResetHead + ( + "resethead", + EV_DEFAULT, + "F", + "max_speed", + "Actor resets its head back to looking forwards." + ); +Event EV_Actor_EyeWatch + ( + "eyewatch", + EV_SCRIPTONLY, + "eF", + "entity_to_watch max_speed", + "Actor watches the specified entity by turning his eyes." + ); +Event EV_Actor_ResetEye + ( + "reseteyes", + EV_DEFAULT, + "F", + "max_speed", + "Actor resets its eyes back to looking forwards." + ); + +Event EV_Actor_ResetTorso + ( + "resettorso", + EV_DEFAULT, + "f", + "max_speed", + "Actor resets its torso to looking forwards" + ); + +Event EV_Actor_BehaviorFinished + ( + "behaviorfinished", + EV_CODEONLY, + "iS", + "behaviorReturnCode behaviorFailureReason", + "The last behavior finished with the specified " + "return code and optionally a failure reason." + "This is sent to controllers of the actor." + ); + +Event EV_Actor_ControlLost + ( + "controlost", + EV_CODEONLY, + NULL, + NULL, + "Sent to a controller when it loses control." + ); + +Event EV_Actor_EndBehavior + ( + "endbehavior", + EV_CODEONLY, + NULL, + NULL, + "Ends the current behavior, " + "it is not meant to be called from script." + ); + +Event EV_Actor_EndHeadBehavior + ( + "endheadbehavior", + EV_CODEONLY, + NULL, + NULL, + "Ends the current head behavior " + "it is not meant to be called from script." + ); + +Event EV_Actor_EndEyeBehavior + ( + "endeyebehavior", + EV_CODEONLY, + NULL, + NULL, + "Ends the current eye behavior " + "it is not meant to be called from script." + ); + +Event EV_Actor_EndTorsoBehavior + ( + "endtorsobehavior", + EV_CODEONLY, + NULL, + NULL, + "Ends the current torso behavior " + "it is not meant to be called from script." + ); + +Event EV_Actor_NotifyBehavior + ( + "notifybehavior", + EV_CODEONLY, + NULL, + NULL, + "Notifies the current behavior of an event," + "it is not meant to be called from script." + ); +Event EV_Actor_NotifyHeadBehavior + ( + "notifyheadbehavior", + EV_CODEONLY, + NULL, + NULL, + "Notifies the current head behavior of an event" + "it is not meant to be called from script." + ); +Event EV_Actor_NotifyEyeBehavior + ( + "notifyeyebehavior", + EV_CODEONLY, + NULL, + NULL, + "Notifies the current eye behavior of an event" + "it is not meant to be called from script." + ); + +Event EV_Actor_NotifyTorsoBehavior + ( + "notifytorsobehavior", + EV_CODEONLY, + NULL, + NULL, + "Notifies the current torso behavior of an event" + "it is not meant to be called from script." + ); +Event EV_Actor_FallToDeath + ( + "falltodeath", + EV_SCRIPTONLY, + "fffsssF", + "forwardmove sidemove speed startanim fallanim deathanim anim_delay", + "makes an actor fall to his death" + ); + +Event EV_Actor_WalkTo + ( + "walkto", + EV_DEFAULT, + "sSFF", + "pathnode anim_name force maxfailures", + "Actor walks to specified path node" + ); + +Event EV_Actor_WalkWatch + ( + "walkwatch", + EV_SCRIPTONLY, + "seS", + "pathnode entity anim_name", + "Actor walks to specified path node and watches the specified entity" + ); + +Event EV_Actor_WarpTo + ( + "warpto", + EV_SCRIPTONLY, + "s", + "node_name", + "Warps the actor to the specified node" + ); +Event EV_Actor_JumpTo + ( + "jumpto", + EV_SCRIPTONLY, + "sFF", + "pathnode_or_entity launchAngle dummy_arg", + "Actor jumps to specified path node" + ); +Event EV_Actor_PickupEnt + ( + "pickupent", + EV_DEFAULT, + "es", + "entity_to_pickup pickup_anim_name", + "Makes actor pick up the specified entity" + ); +Event EV_Actor_ThrowEnt + ( + "throwent", + EV_DEFAULT, + "s", + "throw_anim_name", + "Makes actor throw the entity in hands" + ); +Event EV_Actor_Anim + ( + "anim", + EV_DEFAULT, + "s", + "anim_name", + "Starts the PlayAnim behavior." + ); +Event EV_Actor_SetAnim + ( + "setanim", + EV_DEFAULT, + "sF", + "anim_name animationRate", + "Sets the animation directly." + ); +Event EV_Actor_Attack + ( + "attack", + EV_SCRIPTONLY, + "eB", + "ent force", + "Makes the actor attack the specified entity." + ); +Event EV_Actor_AttackPlayer + ( + "attackplayer", + EV_SCRIPTONLY, + NULL, + NULL, + "Makes enemies of all the players." + ); +Event EV_Actor_ReserveNode + ( + "reservenode", + EV_CODEONLY, + "vf", + "pos time", + "Reserves a path node for the specified amount of time." + ); +Event EV_Actor_ReleaseNode + ( + "releasenode", + EV_CODEONLY, + "v", + "pos", + "Releases a path node from being reserved." + ); +Event EV_Actor_IfCanHideAt + ( + "ifcanhideat", + EV_SCRIPTONLY, + "vSSSSSS", + "pos token1 token2 token3 token4 token5 token6", + "Processes command if actor can hide at specified position." + ); +Event EV_Actor_IfEnemyWithin + ( + "ifenemywithin", + EV_SCRIPTONLY, + "fSSSSSS", + "distance token1 token2 token3 token4 token5 token6", + "Processes command if actor is within distance of its current enemy." + ); +Event EV_Actor_Remove + ( + "remove_useless", + EV_CODEONLY, + NULL, + NULL, + "Removes a useless dead body from the game." + ); +Event EV_Actor_Melee + ( + "melee", + EV_DEFAULT, + "FSSVFIF", + "damage tag_name means_of_death attack_vector knockback use_pitch_to_enemy attack_min_height", + "Makes the actor do a melee attack. " + "attack_vector = width length height" + ); +Event EV_Actor_PainThreshold + ( + "painthreshold", + EV_TIKIONLY, + "f", + "pain_threshold", + "Sets the actor's pain threshold." + ); +Event EV_Actor_SetKillThread + ( + "killthread", + EV_SCRIPTONLY, + "s", + "kill_thread", + "Sets the actor's kill thread." + ); +Event EV_Actor_EyePositionOffset + ( + "eyeoffset", + EV_TIKIONLY, + "v", + "eyeoffset", + "Sets the actor's eye position." + ); +Event EV_Actor_DeathFade + ( + "deathfade", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor fade when dead." + ); +Event EV_Actor_DeathEffect +( + "deathEffect", + EV_DEFAULT, + "s", + "deathEffectName", + "Displays a display effect instead of fading, shrinking, etc." +); +Event EV_Actor_DeathShrink + ( + "deathshrink", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor shrink when dead." + ); +Event EV_Actor_DeathSink + ( + "deathsink", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor sink into the ground when dead." + ); +Event EV_Actor_StaySolid + ( + "staysolid", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor stay solid after death." + ); +Event EV_Actor_NoChatter + ( + "nochatter", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor not chatter." + ); +Event EV_Actor_TurnSpeed + ( + "turnspeed", + EV_DEFAULT, + "f", + "turnspeed", + "Sets the actor's turnspeed." + ); +Event EV_Actor_SetActorFlag + ( + "setactorflag", + EV_DEFAULT, + "sB", + "flag_name flag_bool", + "Sets an Actor's flag" + ); + +Event EV_Actor_SetNotifyFlag + ( + "setnotifyflag", + EV_DEFAULT, + "sB", + "flag_name flag_bool", + "Sets an Actor's Notify Flag" + ); + +Event EV_Actor_SetVar + ( + "setvar", + EV_DEFAULT, + "ss", + "var_name var_value", + "Sets a variable" + ); + +Event EV_Actor_PersistData + ( + "persistData", + EV_CODEONLY, + "ss", + "var_name var_value", + "Sets a persistant variable" + ); + +Event EV_Actor_SetVarTime + ( + "setvartime", + EV_CODEONLY, + "s", + "var_name", + "Sets the variable name to the current level time" + ); + +Event EV_Anim_Done + ( + "anim_done", + EV_CODEONLY, + NULL, + NULL, + "Called when the actor's animation is done, " + "it is not meant to be called from script." + ); +Event EV_Torso_Anim_Done + ( + "torso_anim_done", + EV_CODEONLY, + NULL, + NULL, + "Called when actor's torso anim is done, " + "If you call this from script, I will hunt you down, and end you" + ); +Event EV_Posture_Anim_Done + ( + "posture_anim_done", + EV_CODEONLY, + NULL, + NULL, + "Called when a posture animation is done" + ); + +Event EV_Actor_ProjAttack + ( + "proj", + EV_DEFAULT, + "ssIBFFBF", + "tag_name projectile_name number_of_tags arc_bool speed offset lead spread", + "Fires a projectile from the actor towards the current enemy." + ); +Event EV_Actor_BulletAttack + ( + "bullet", + EV_DEFAULT, + "sbffsvF", + "tag_name use_current_pitch damage knockback means_of_death spread range", + "Fires a bullet from the actor from the specified tag towards the current enemy." + ); +Event EV_Actor_RadiusAttack + ( + "radiusattack", + EV_DEFAULT, + "ssfffb", + "tag_name means_of_death damage radius knockback constant_damage", + "Does a radius attack from the tag name" + ); + +Event EV_Actor_Active + ( + "active", + EV_SCRIPTONLY, + "i", + "active_flag", + "Specifies whether the actor's is active or not." + ); +Event EV_Actor_SpawnGib + ( + "spawngib", + EV_DEFAULT, + "vffssSSSSSSSS", + "offset final_pitch width cap_name surface_name1 surface_name2 surface_name3 surface_name4 surface_name5 surface_name6 surface_name7 surface_name8 surface_name9" , + "Spawns a body part." + ); +Event EV_Actor_SpawnGibAtTag + ( + "spawngibattag", + EV_DEFAULT, + "sffssSSSSSSSS", + "tag_name final_pitch width cap_name surface_name1 surface_name2 surface_name3 surface_name4 surface_name5 surface_name6 surface_name7 surface_name8 surface_name9" , + "Spawns a body part." + ); +Event EV_Actor_SpawnNamedGib + ( + "spawnnamedgib", + EV_DEFAULT, + "ssff", + "gib_name tag_name final_pitch width", + "Spawns a body named gib." + ); +Event EV_Actor_SpawnBlood + ( + "spawnblood", + EV_DEFAULT, + "ssB", + "blood_name tag_name use_last_spawn_result" , + "Spawns blood at the specified tag." + ); +Event EV_Actor_AIOn + ( + "ai_on", + EV_SCRIPTONLY, + NULL, + NULL, + "Turns the AI on for this actor." + ); +Event EV_Actor_AIOff + ( + "ai_off", + EV_SCRIPTONLY, + NULL, + NULL, + "Turns the AI off for this actor." + ); +Event EV_Actor_RespondTo + ( + "respondto", + EV_DEFAULT, + "sb", + "stimuli respond", + "sets AI response to stimuli" + ); + +Event EV_Actor_PermanentlyRespondTo + ( + "permanentrespondto", + EV_TIKIONLY, + "sb", + "stimuli respond", + "sets AI response to stimuli" + ); + +Event EV_Actor_SetIdleThread + ( + "setidlethread", + EV_SCRIPTONLY, + "s", + "thread", + "Sets the thread that will be run if this actor gets back to the idle state again." + ); +Event EV_Actor_SetMaxInactiveTime + ( + "max_inactive_time", + EV_DEFAULT, + "f", + "max_inactive_time", + "Sets the maximum amount of time an actor will stay idle before going to sleep.\n" + "Also sepecifies the maximum amount of time an actor will keep looking for an\n" + "enemy that the actor can no longer see." + ); +Event EV_ActorRegisterParts + ( + "register_parts", + EV_CODEONLY, + "ei", + "entity forward", + "Registers the passed in part as another part of this actor and specifies\n" + "whether or not to forward this message to the other parts." + ); +Event EV_ActorRegisterSelf + ( + "register_self", + EV_CODEONLY, + NULL, + NULL, + "Starts registration process for multi-entity actors" + ); +Event EV_ActorName + ( + "name", + EV_DEFAULT, + "s", + "name", + "Specifies the name of this actor type." + ); +Event EV_ActorPartName + ( + "part_name", + EV_TIKIONLY, + "s", + "part_name", + "Specifies the name of this part (implying that this is a multi-part creature." + ); +Event EV_ActorSetupTriggerField + ( + "trigger_field", + EV_TIKIONLY, + "vv", + "min max", + "Specifies to create a trigger field around the actor of the specified size." + ); +Event EV_ActorTriggerTouched + ( + "trigger_touched", + EV_CODEONLY, + "e", + "ent", + "Notifies the actor that its trigger field has been touched." + ); + +Event EV_ActorIncomingProjectile + ( + "incoming_proj", + EV_CODEONLY, + "e", + "ent", + "Notifies the actor of an incoming projectile." + ); +Event EV_ActorSpawnActor + ( + "spawnactor", + EV_DEFAULT, + "ssibffFBF", + "model_name tag_name how_many attack width height spawn_offset force add_height", + "Spawns the specified number of actors." + ); +Event EV_ActorSpawnActorAboveEnemy + ( + "spawnactoraboveenemy", + EV_DEFAULT, + "sibfff", + "model_name how_many attack width height how_far", + "Spawns actors above current enemy" + ); + +Event EV_ActorSpawnActorAtLocation + ( + "spawnactoratlocation", + EV_DEFAULT, + "ssibff", + "model_name pathnode_name how_many_path_nodes attack width height", + "Spawns the specified actor at the specified pathnode." + ); +Event EV_Actor_AddDialog + ( + "dialog", + EV_DEFAULT, + "sSSSSSS", + "alias token1 token2 token3 token4 token5 token6", + "Add a dialog to this sentient." + ); + +Event EV_Actor_DialogDone + ( + "dialogdone", + EV_CODEONLY, + NULL, + NULL, + "Called when the sentient's dialog is done, " + "it is not meant to be called from script." + ); + +Event EV_Actor_PlayDialog + ( + "playdialog", + EV_DEFAULT, + "SFFBBSE", + "sound_file volume min_dist headDisplay do_talk state_name user", + "Plays a dialog." + ); +Event EV_Actor_StopDialog + ( + "stopdialog", + EV_SCRIPTONLY, + NULL, + NULL, + "Stops the actor's dialog." + ); + +Event EV_Actor_BroadcastDialog + ( + "broadcastdialog", + EV_CODEONLY, + "s", + "context", + "Broadcasts a context dialog" + ); + +Event EV_Actor_BranchDialog + ( + "branchdialog", + EV_DEFAULT, + "s", + "dialogName", + "Presents a branch dialog to the player." + ); +Event EV_Actor_AllowTalk + ( + "allowtalk", + EV_DEFAULT, + "i", + "allow_bool", + "Sets whether or not the actor will bother to talk to the player." + ); +Event EV_Actor_AllowHangBack + ( + "allowhangback", + EV_DEFAULT, + "i", + "allow_bool", + "Sets whether or not the actor will bother to hang back." + ); +Event EV_Actor_SolidMask + ( + "solidmask", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor use a solid mask." + ); +Event EV_Actor_NotSolidMask + ( + "notsolidmask", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor use a nonsolid mask." + ); +Event EV_Actor_NoMask + ( + "nomask", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor use a mask of 0." + ); +Event EV_Actor_SetMask + ( + "setmask", + EV_DEFAULT, + "s", + "mask_name", + "Sets the actor's mask to the specified mask." + ); +Event EV_Actor_Pickup + ( + "actor_pickup", + EV_TIKIONLY, + "s", + "tag_name", + "Makes the actor pickup current pickup_ent (should only be called from a tiki)." + ); +Event EV_Actor_Throw + ( + "actor_throw", + EV_TIKIONLY, + "s", + "tag_name", + "Makes the actor throw whatever is in its hand (should only be called from a tiki)." + ); +Event EV_Actor_DamageOnceStart + ( + "damage_once_start", + EV_TIKIONLY, + NULL, + NULL, + "Makes the actor only do melee damage at most once during this attack." + ); +Event EV_Actor_DamageOnceStop + ( + "damage_once_stop", + EV_TIKIONLY, + NULL, + NULL, + "Specifies that the actor is done with the damage once event." + ); +Event EV_Actor_DamageEnemy + ( + "damageenemy", + EV_DEFAULT, + "fS", + "damage model", + "Damages the current enemy by the specified amount." + ); +Event EV_Actor_DamageSelf + ( + "damageself", + EV_DEFAULT, + "fs", + "damage means_of_death", + "Damages Self" + ); +Event EV_Actor_TurnTowardsEnemy + ( + "turntowardsenemy", + EV_SCRIPTONLY, + "f", + "angle", + "Turns the actor towards the current enemy." + ); +Event EV_Actor_TurnTowardsPlayer + ( + "turntowardsplayer", + EV_SCRIPTONLY, + NULL, + NULL, + "Turns the actor towards the player." + ); + +Event EV_Actor_TurnTowardsEntity + ( + "turntowardsentity", + EV_SCRIPTONLY, + "e", + "entity", + "Turns the actor towards the entity" + ); + +Event EV_Actor_Suicide + ( + "suicide", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor commit suicide." + ); +Event EV_Actor_GotoNextStage + ( + "gotonextstage", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor goto his next stage." + ); +Event EV_Actor_GotoPrevStage + ( + "gotoprevstage", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor goto his previous stage." + ); +Event EV_Actor_GotoStage + ( + "gotostage", + EV_DEFAULT, + "i", + "stage_number", + "Makes the actor goto the specified stage." + ); +Event EV_Actor_GetStage + ( + "getstage", + EV_SCRIPTONLY, + "@f", + "Result", + "Returns this actors current stage." + ); +Event EV_Actor_NotifyOthersAtDeath + ( + "notifyothersatdeath", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor notify other actors of the same type when killed." + ); +Event EV_Actor_SetBounceOff + ( + "bounceoff", + EV_DEFAULT, + NULL, + NULL, + "Makes projectiles bounce off of actor (if they can't damage actor)." + ); +Event EV_Actor_SetHaveThing + ( + "havething", + EV_DEFAULT, + "ib", + "thing_number have_bool", + "Sets whether or not the actor has this thing number." + ); + +Event EV_Actor_SetUseGravity + ( + "usegravity", + EV_DEFAULT, + "b", + "use_gravity", + "Tells the actor whether or not to use gravity for this animation." + ); +Event EV_Actor_SetDeathSize + ( + "deathsize", + EV_TIKIONLY, + "vv", + "min max", + "Sets the actors new size for death." + ); +Event EV_Actor_Fade + ( + "actorfade", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor fade out." + ); +Event EV_Actor_AttackMode + ( + "attackmode", + EV_SCRIPTONLY, + "b", + "attack_bool", + "Makes the actor go directly into attacking the player if bool is true." + ); +Event EV_Actor_BounceOff + ( + "bounceoffevent", + EV_CODEONLY, + "v", + "object_origin", + "Lets the actor know something just bounces off of it." + ); +Event EV_Actor_SetBounceOffEffect + ( + "bounceoffeffect", + EV_DEFAULT, + "s", + "bounce_off_effect_name", + "Sets the name of the effect to play when something bounces off the actor." + ); +Event EV_Actor_AddSpawnItem + ( + "spawnitem", + EV_DEFAULT, + "s", + "spawn_item_name", + "Adds this names item to what will be spawned when this actor is killed." + ); +Event EV_Actor_SetSpawnChance + ( + "spawnchance", + EV_DEFAULT, + "f", + "spawn_chance", + "Sets the chance that this actor will spawn something when killed." + ); +Event EV_Actor_ClearSpawnItems + ( + "clearspawnitems", + EV_DEFAULT, + NULL, + NULL, + "Clears the list of items to spawn when this actor is killed." + ); +Event EV_Actor_SetAllowFall + ( + "allowfall", + EV_DEFAULT, + "B", + "allow_fall_bool", + "Makes the actor ignore falls when trying to move." + ); +Event EV_Actor_SetCanBeFinishedBy + ( + "canbefinishedby", + EV_TIKIONLY, + "sSSSSS", + "mod1 mod2 mod3 mod4 mod5 mod6", + "Adds to the can be finished by list for this actor." + ); +Event EV_Actor_SetFeetWidth + ( + "feetwidth", + EV_TIKIONLY, + "f", + "feet_width", + "Sets the width of the feet for this actor if different than the bounding box size." + ); +Event EV_Actor_SetCanWalkOnOthers + ( + "canwalkonothers", + EV_DEFAULT, + NULL, + NULL, + "Allows the actor to walk on top of others." + ); +Event EV_Actor_Push + ( + "push", + EV_CODEONLY, + "v", + "dir", + "Pushes the actor in the specified direction." + ); +Event EV_Actor_Pushable + ( + "pushable", + EV_DEFAULT, + "B", + "flag", + "Sets whether or not an actor can be pushed out of the way." + ); +Event EV_Actor_ChargeWater + ( + "chargewater", + EV_DEFAULT, + "ff", + "damage range", + "Does a charge water attack." + ); +Event EV_Actor_SendCommand + ( + "sendcommand", + EV_CODEONLY, + "ss", + "command part_name", + "Sends a command to another one of its parts." + ); + +Event EV_Actor_SetTargetable + ( + "targetable", + EV_DEFAULT, + "b", + "should_target", + "Sets whether or not this actor should be targetable by the player." + ); +Event EV_Actor_ChangeType + ( + "changetype", + EV_DEFAULT, + "s", + "new_model_name", + "Changes the actor to the specified new type of actor." + ); +Event EV_Actor_IgnoreMonsterClip + ( + "ignoremonsterclip", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor ignore monster clip brushes." + ); +Event EV_Actor_MinimumMeleeHeight + ( + "minmeleeheight", + EV_DEFAULT, + "f", + "minimum_height", + "Sets the minimum height a melee attack has to be to hurt the actor." + ); +Event EV_Actor_SetDamageAngles + ( + "damageangles", + EV_DEFAULT, + "f", + "damage_angles", + "Sets the the angles where the actor can be hurt (like fov)." + ); +Event EV_Actor_Immortal + ( + "immortal", + EV_DEFAULT, + "b", + "immortal_bool", + "Sets whether or not the actor is immortal or not." + ); +Event EV_Actor_SetDieCompletely + ( + "diecompletely", + EV_DEFAULT, + "b", + "die_bool", + "Sets whether or not the actor dies completely (if he doesn't he mostly just" + " runs his kill_thread)." + ); +Event EV_Actor_SetBleedAfterDeath + ( + "bleed_after_death", + EV_DEFAULT, + "b", + "bleed_bool", + "Sets whether or not the actor will bleed after dying." + ); +Event EV_Actor_IgnorePlacementWarning + ( + "ignore_placement_warning", + EV_DEFAULT, + "s", + "warning_string", + "Makes the specified placement warning not get printed for this actor." + ); +Event EV_Actor_SetIdleStateName + ( + "set_idle_state_name", + EV_SCRIPTONLY, + "s", + "new_idle_state_name", + "Sets the actor's new idle state name." + ); +Event EV_Actor_SetNotAllowedToKill + ( + "not_allowed_to_kill", + EV_DEFAULT, + NULL, + NULL, + "Player fails the level if he kills an actor with this set." + ); + +Event EV_Actor_IgnoreWater + ( + "ignorewater", + EV_DEFAULT, + "b", + "ignore_water_bool", + "Sets whether or not this actor will ignore water when moving." + ); + +Event EV_Actor_SimplePathfinding + ( + "simplepathfinding", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor use simplier path finding." + ); +Event EV_Actor_NoPainSounds + ( + "nopainsounds", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor not broadcast sounds (AI stimuli) when taking pain or killed." + ); +Event EV_Actor_BroadcastAlert + ( + "alertevent", + EV_DEFAULT, + "F", + "soundRadius", + "Alerts Entities within the radius of the enemy's location." + ); + +Event EV_Actor_UpdateBossHealth + ( + "updatebosshealth", + EV_DEFAULT, + "BB", + "updateFlag forceOn", + "Tells the actor to update the bosshealth cvar each time it thinks." + ); +Event EV_Actor_SetMaxBossHealth + ( + "maxbosshealth", + EV_DEFAULT, + "f", + "max_boss_health", + "Sets the actor's max boss health." + ); +Event EV_Actor_IgnorePainFromActors + ( + "ignorepainfromactors", + EV_DEFAULT, + NULL, + NULL, + "Makes this actor ignore pain from other actors." + ); +Event EV_Actor_DamageAllowed + ( + "damageallowed", + EV_DEFAULT, + "b", + "damage_allowed", + "Turns melee damage on and off." + ); +Event EV_Actor_SetEmotion + ( + "emotion", + EV_DEFAULT, + "S", + "expression_name", + "Sets the actors current emotion." + ); +Event EV_Actor_ReturnProjectile + ( + "returnproj", + EV_TIKIONLY, + NULL, + NULL, + "Returns a projectile to the current enemy" + ); +Event EV_Actor_SetRadiusDialogRange + ( + "setradiusdialogrange", + EV_DEFAULT, + "f", + "range", + "Sets the range for playing radius dialog" + ); + +Event EV_Actor_SetDialogMode + ( + "setdialogmode", + EV_DEFAULT, + "s", + "mode_type", + "Sets the Dialog Mode for the actor, valid values are 'anxious', 'normal', or 'ignore'" + ); + +Event EV_Actor_DialogAnimDone + ( + "dialoganimdone", + EV_CODEONLY, + NULL, + NULL, + "Not meant to be called from script -- So DONT FREAKIN DO IT!!!!" + ); + +Event EV_Actor_SetEyeAngleConstraints + ( + "seteyeangleconstraints", + EV_TIKIONLY, + "ffff", + "min_eye_yaw_angle max_eye_yaw_angle min_eye_pitch_angle max_eye_pitch_angle", + "Sets the constraints on eye movement" + ); + +Event EV_Actor_SetActivateThread + ( + "setactivatethread", + EV_SCRIPTONLY, + "s", + "thread_name", + "Sets the thread to call when the AI Activates" + ); + +Event EV_Actor_SetValidTarget + ( + "setvalidtarget", + EV_DEFAULT, + "b", + "valid_target", + "Sets whether or not actor is valid target in actor to actor confrontations" + ); + +Event EV_Actor_SetAlertThread + ( + "setalertthread", + EV_SCRIPTONLY, + "s", + "thread_name", + "sets a thread to be called when AI goes to alert" + ); + +Event EV_Actor_RunAlertThread + ( + "runalertthread", + EV_CODEONLY, + NULL, + NULL, + "runs an actors alert thread - NOT MEANT TO BE CALLED FROM SCRIPT" + ); + +Event EV_Actor_CheckActorDead + ( + "checkactordead", + EV_SCRIPTONLY, + "@ie", + "dead_bool entity_to_check", + "checks if an actor is dead" + ); + +Event EV_Actor_EnemyActorFlag + ( + "setenemyactorflag", + EV_SCRIPTONLY, + "sB", + "flag_name flag_bool", + "Sets an Actor's flag" + ); + +Event EV_Actor_EnemyAIOn + ( + "enemyaion", + EV_CODEONLY, + NULL, + NULL, + "turns on the current enemy AI" + ); + +Event EV_Actor_EnemyAIOff + ( + "enemyaioff", + EV_CODEONLY, + NULL, + NULL, + "turns off the current enemy AI" + ); + +Event EV_Actor_AttachCurrentEnemy + ( + "attachcurrentenemy", + EV_CODEONLY, + "s", + "bone", + "attach current enemy to the given bone" + ); + +Event EV_Actor_AttachActor + ( + "attachactor", + EV_DEFAULT, + "sss", + "model targetname bone", + "attach actor to the given bone" + ); + +Event EV_Actor_SetEnemyAttached + ( + "setenemyattached", + EV_DEFAULT, + "b", + "attached", + "sets whether or not the current enemy is attached -- Quetzal Specific" + ); + +Event EV_Actor_PickUpThrowObject + ( + "pickupthrowobject", + EV_SCRIPTONLY, + "s", + "bone", + "bone to attach object to" + ); + +Event EV_Actor_TossThrowObject + ( + "tossthrowobject", + EV_DEFAULT, + "ff", + "speed gravity", + "throws a throw object" + ); + +Event EV_Actor_SetTurretMode + ( + "setturretmode", + EV_DEFAULT, + "b", + "on_off", + "sets turret mode on or off" + ); + +Event EV_Actor_SetHitscanResponseChance + ( + "sethitscanresponse", + EV_DEFAULT, + "f", + "chance", + "sets chance an actor will respond to hitscan attacks" + ); + +Event EV_Actor_SetWeaponReady + ( + "setweaponready", + EV_DEFAULT, + "b", + "ready", + "sets if the actor has its weapon ready or not" + ); + +Event EV_Actor_SetOnDamageThread + ( + "actorondamage", + EV_SCRIPTONLY, + "sI", + "thread_name damage_threshold", + "sets the thread that is called when actor is damaged" + ); + +Event EV_Actor_SetTimeBetweenSleepChecks + ( + "timebetweensleepchecks", + EV_SCRIPTONLY, + "f", + "delay", + "sets the time between tests to see if the actor should sleep" + ); + +Event EV_Actor_SetAimLeadFactors + ( + "setaimleadfactors", + EV_DEFAULT, + "ff", + "minLeadFactor maxLeadFactor", + "sets the lead factor for projectile aiming; 0 = don't lead, 1 = perfect lead" + ); + +Event EV_Actor_SetActorType + ( + "actortype", + EV_DEFAULT, + "s", + "actor_type", + "sets the actortype" + ); + +Event EV_Actor_RegisterBehaviorPackage + ( + "registerpackage", + EV_TIKIONLY, + "s", + "package_name", + "registers a behavior package" + ); + +Event EV_Actor_UnregisterBehaviorPackage + ( + "unregisterpackage", + EV_TIKIONLY, + "s", + "package_name", + "unregisters a behavior package" + ); + +Event EV_Actor_SetBehaviorPackageTendency + ( + "setpackagetendency", + EV_DEFAULT, + "sf", + "package_name tendency", + "sets the tendency to execute the behavior package" + ); + +Event EV_Actor_SetAbsoluteMaxRange + ( + "setabsolutemaxrange", + EV_DEFAULT, + "f", + "absolute_max_range", + "sets the absolute maximum range the actor will get from an entity" + ); + +Event EV_Actor_SetAbsoluteMinRange + ( + "setabsoluteminrange", + EV_DEFAULT, + "f", + "absolute_min_range", + "sets the absolute minimum range the actor will get from an entity" + ); + +Event EV_Actor_SetPreferredMaxRange + ( + "setpreferredmaxrange", + EV_DEFAULT, + "f", + "preferred_max_range", + "sets the preferred maximum range the actor would like to be from an entity" + ); + +Event EV_Actor_SetPreferredMinRange + ( + "setpreferredminrange", + EV_DEFAULT, + "f", + "preferred_min_range", + "sets the preferred minimum range the actor would like to be from an entity" + ); + +Event EV_Actor_DebugStates + ( + "debugstates", + EV_CODEONLY, + "i", + "debug_state", + "sets debug level for actor statemachine" + ); + +Event EV_Actor_SetHeadWatchTarget + ( + "headwatchtarget", + EV_SCRIPTONLY, + "sf", + "target speed", + "sets the headwatch target... currently to enemy or none" + ); +Event EV_Actor_SetHeadTwitch + ( + "headTwitch", + EV_SCRIPTONLY, + "b", + "bool", + "Sets whether or not the head should twitch." + ); + +Event EV_Actor_SetFuzzyEngineActive + ( + "fuzzyengineactive", + EV_DEFAULT, + "b", + "active", + "sets the fuzzy engine active or not" + ); + +// +// Waypoint stuff +// +Event EV_Actor_FollowWayPoints + ( + "followwaypoints", + EV_SCRIPTONLY, + "sS", + "waypointnode_name starting_anim_name", + "Makes an actor follow a waypoint path starting at , starting at the start_point" + ); + + +Event EV_Actor_Disable + ( + "disable", + EV_DEFAULT, + "i", + "disable_flag", + "disable actor ( 1 ) or not disable actor ( 0 )" + ); + +Event EV_Actor_Cripple + ( + "cripple", + EV_DEFAULT, + "i", + "cripple_flag", + "cripple actor ( 1 ) or not cripple actor ( 0 )" + ); + +Event EV_Actor_In_Alcove + ( + "in_alcove", + EV_DEFAULT, + "i", + "in_alcove_flat", + "in alcove ( 1 ) or not in alcove ( 0 )" + ); + +// +// Weapon stuff +// +Event EV_Actor_GiveActorWeapon + ( + "giveactorweapon", + EV_DEFAULT, + "sI", + "weapon amount", + "Gives a weapon to an actor" + ); + +Event EV_Actor_RemoveActorWeapon + ( + "removeactorweapon", + EV_DEFAULT, + "s", + "weapon", + "removes an actors weapon" + ); + +Event EV_Actor_UseWeapon + ( + "useactorweapon", + EV_TIKIONLY, + "sS", + "weapon hand", + "Makes the specified weapon active for the actor \n" + "If they have the weapon" + ); + +Event EV_Actor_SetMovementMode + ( + "movementmode", + EV_DEFAULT, + "s", + "movment_mode", + "sets the movment mode of the actor" + ); + +Event EV_Actor_ResetMoveDir + ( + "resetmovedir", + EV_CODEONLY, + NULL, + NULL, + "Resets and resyncs movedir with animdir" + ); + +Event EV_Actor_SetNodeID + ( + "setnodeid", + EV_SCRIPTONLY, + "i", + "id_number", + "Sets the ID number of the helper nodes that this actor can use" + ); + +// +// Personality Stuff +// + +Event EV_Actor_SetAggressiveness + ( + "aggressive", + EV_DEFAULT, + "f", + "aggressiveness", + "sets the aggressiveness of the actor... valid range between 0 and 1" + ); + +Event EV_Actor_SetTalkiness + ( + "talkiness", + EV_DEFAULT, + "f", + "talkiness", + "sets the talkiness of the actor... valid range between 0 and 1" + ); + +Event EV_Actor_SetTendency + ( + "settendency", + EV_DEFAULT, + "sf", + "name value", + "Sets a tendency for the actor" + ); + +Event EV_Actor_SetFloatProperty + ( + "setfloatproperty", + EV_DEFAULT, + "sf", + "name value", + "Sets a float property on the actor" + ); + +Event EV_Actor_SetGroupNumber + ( + "groupnumber", + EV_DEFAULT, + "i", + "group_number", + "sets the group number of the actor" + ); + +Event EV_Actor_Blink + ( + "blink", + EV_DEFAULT, + "b", + "shouldBlink", + "Sets whether or not the actor should blink" + ); + +Event EV_Actor_ClearArmorAdapations + ( + "cleararmoradaptions", + EV_SCRIPTONLY, + NULL, + NULL, + "clears armor adaptions" + ); + +Event EV_Actor_SetFollowTarget + ( + "followtarget", + EV_SCRIPTONLY, + "e", + "entity_to_follow", + "Sets the following target for the actor" + ); + +Event EV_Actor_SetFollowRange + ( + "followrange", + EV_SCRIPTONLY, + "f", + "maxRange", + "Sets a range that the target considers _close enough_ while following" + ); + +Event EV_Actor_SetFollowRangeMin + ( + "followrangemin", + EV_SCRIPTONLY, + "f", + "minRange", + "Sets a minimum range for following" + ); + +Event EV_Actor_SetCombatFollowRange + ( + "followcombatrange", + EV_SCRIPTONLY, + "f", + "maxRange", + "Sets a range that the target considers _close enough_ while following in a combat situation" + ); + +Event EV_Actor_SetCombatFollowRangeMin + ( + "followcombatrangemin", + EV_SCRIPTONLY, + "f", + "minRange", + "Sets a minimum range for following in a combat situation" + ); + +Event EV_Actor_SetSteeringDirectionPreference + ( + "steeringdirectionpreference", + EV_SCRIPTONLY, + "s", + "preference", + "Sends a string to define the way actors will turn when avoiding obstacles" + ); + +Event EV_Actor_SetStickToGround + ( + "setsticktoground", + EV_DEFAULT, + "b", + "stick", + "Sets a bool that determines whether the actor ground follows" + ); + +Event EV_Actor_SetDialogMorphMult + ( + "dialogMorphMult", + EV_DEFAULT, + "f", + "dialogMorphMult", + "Sets the multiplier for all dialog morphs for this actor." + ); + +// Context Dialog Context Events +Event EV_ContextDialog_InContext + ( + "incontext", + EV_CODEONLY, + "s", + "context", + "Used to start a context dialog" + ); + +Event EV_ContextDialog_IgnoreNextContext + ( + "ignorenextcontext", + EV_CODEONLY, + "bs", + "flag context", + "Makes the actor ignore the next context event it receives" + ); + +Event EV_Actor_ForceSetClip + ( + "forcesetclip", + EV_CODEONLY, + NULL, + NULL, + "Makes the actor set his contents to setclip" + ); + +Event EV_Actor_WhatAreYouDoing + ( + "whatareyoudoing", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor print a bunch of debug state info to the console" + ); + +Event EV_Actor_WhatsWrong + ( + "whatswrong", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor print the current behaviors failure reaon to the console" + ); + +Event EV_Actor_PutawayWeapon + ( + "putawayweapon", + EV_DEFAULT, + "S", + "hand", + "Deactivate the weapon in the specified hand." + ); + +Event EV_Actor_SetCombatTraceInterval + ( + "combattraceinterval", + EV_DEFAULT, + "f", + "interval", + "Determines how often an actor will re-trace when doing can-attack types of checks" + ); + +Event EV_Actor_UseWeaponDamage + ( + "useweapondamage", + EV_TIKIONLY, + "SB", + "hand setflag", + "Makes the melee event reference the damage of the weapon in the specified hand." + ); + +Event EV_Actor_StrictlyFollowPath + ( + "strictlyfollowpath", + EV_DEFAULT, + "b", + "boolean", + "Lets the actor know if he should follow paths exactly or if he can go directly to his goal." + ); +Event EV_Actor_EvaluateEnemies + ( + "evaluateenemies", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor evaluate his enemy list" + ); + +Event EV_Actor_ForgetEnemies + ( + "forgetenemies", + EV_DEFAULT, + NULL, + NULL, + "Makes the actor forget about all enemies for one frame" + ); + +Event EV_Actor_SetMaxHeadYaw + ( + "maxheadyaw", + EV_DEFAULT, + "f", + "maxyaw", + "Sets the max yaw the headwatcher can turn the head" + ); + +Event EV_Actor_SetMaxHeadPitch + ( + "maxheadpitch", + EV_DEFAULT, + "f", + "maxpitch", + "Sets the max pitch the headwatcher can turn the head" + ); + +Event EV_Actor_SetPostureStateMap + ( + "posturestatemap", + EV_DEFAULT, + "sB", + "statemap loadingFlag", + "Sets the state machine for the posture controller" + ); + +Event EV_Actor_SendEventToGroup + ( + "sendeventtogroup", + EV_DEFAULT, + "sSSSSS", + "event parm parm parm parm parm", + "sends the specified event to the entire group" + ); + +Event EV_Actor_GroupAttack + ( + "groupattack", + EV_DEFAULT, + NULL, + NULL, + "Sends and attack event to the whole group with this actors current enemy" + ); + +Event EV_Actor_GroupActorType + ( + "groupactortype", + EV_DEFAULT, + "s", + "actortype", + "Sends an actortype event to the whole group" + ); + +Event EV_Actor_SetMasterState + ( + "setmasterstate", + EV_DEFAULT, + "s", + "state_name", + "Sets the master state" + ); + +Event EV_Actor_PrintDebugMessage + ( + "printmessage", + EV_DEFAULT, + "s", + "message", + "Prints a warning message to the console" + ); + +Event EV_Actor_SelectNextEnemy + ( + "SelectNextEnemy", + EV_DEFAULT, + NULL, + NULL, + "Sets the actor's current enemy to be the next enemy in it's hate list, assuming there is one." + ); + +Event EV_Actor_SelectClosestEnemy + ( + "selectclosestenemy", + EV_DEFAULT, + NULL, + NULL, + "Sets the actor's current enemy to be the closest enemy in it's hate list " + ); + +Event EV_Actor_SetGroupDeathThread + ( + "groupdeaththread", + EV_DEFAULT, + "s", + "thread_name", + "Sets a thread to call when all the members of a group have been killed" + ); + +Event EV_Actor_SetAnimSet + ( + "useanimset", + EV_DEFAULT, + "s", + "anim_set", + "Sets the AnimSet... Valid Set Names are AnimSet1 , AnimSet2 " + ); + +Event EV_Actor_SetPostureState + ( + "setposturestate", + EV_DEFAULT, + "ss", + "currentState requestedState", + "Sets the Posture State" + ); + +Event EV_Actor_SetTalkWatchMode + ( + "settalkwatchmode", + EV_DEFAULT, + "sB", + "mode useConvAnim", + "Sets the talk watch mode -- valid entries are turnto, headwatchonly, and ignore" + ); + +Event EV_Actor_PrepareMissionFailure + ( + "failmission", + EV_DEFAULT, + "fS", + "time reason", + "Fails the mission in the time specified" + ); + +Event EV_Actor_FailMission + ( + "domissionfailure", + EV_DEFAULT, + "S", + "reason", + "Fails the mission" + ); + +Event EV_Actor_DebugEvent + ( + "debugevent", + EV_DEFAULT, + NULL, + NULL, + "Called for Debug Purposes from state machine" + ); + +Event EV_Actor_UnreserveCurrentHelperNode + ( + "unreservecurrenthelpernode", + EV_CODEONLY, + NULL, + NULL, + "Unreserves the current helper node" + ); + +Event EV_Actor_ProjectileClose + ( + "projectileclose", + EV_CODEONLY, + "e", + "owener", + "Informs Actor that a projectile is close" + ); + +Event EV_Actor_SaveOffLastHitBone + ( + "saveofflasthitbone", + EV_CODEONLY, + NULL, + NULL, + "Saves off the last hit_bone" + ); + +Event EV_Actor_ClearTorsoAnim + ( + "cleartorsoanim", + EV_CODEONLY, + NULL, + NULL, + "clears the f'ng torso anim" + ); + +Event EV_Actor_SetPlayPainSoundInterval + ( + "setplaypainsoundinterval", + EV_DEFAULT, + "f", + "interval", + "Sets the pain sound interval" + ); + +Event EV_Actor_SetContextSoundInterval + ( + "setcontextsoundinterval", + EV_DEFAULT, + "f", + "interval", + "Sets the context sound ( dialog ) interval " + ); + +Event EV_Actor_SetMinPainTime + ( + "setminpaintime", + EV_DEFAULT, + "f", + "time", + "Sets the minimum pain time" + ); + +Event EV_Actor_SetEnemyTargeted + ( + "setenemytargeted", + EV_DEFAULT, + "b", + "targeted", + "Sets whether or not the enemy should display targeted shader" + ); + +Event EV_Actor_SetActivationDelay + ( + "setactivationdelay", + EV_DEFAULT, + "f", + "delay", + "If set up to use it, actors will delay action for the specifed delay time" + ); + +Event EV_Actor_SetActivationStart + ( + "startactivationtimer", + EV_DEFAULT, + NULL, + NULL, + "Sets the activationStart time to the current level time" + ); + +Event EV_Actor_SetCheckConeOfFireDistance + ( + "SetCheckConeOfFireDistance", + EV_DEFAULT, + "f", + "distance", + "Sets how close an actor must be for IN_CONE_OF_FIRE checks from a state machine." + ); + +Event EV_Actor_AddCustomThread + ( + "addcustomthread", + EV_DEFAULT, + "ss", + "threadType threadName", + "Adds a custom thread to the actor. The thread type is the specific type to call, the name is the thread to call" + ); + +Event EV_Actor_SetHeadWatchMaxDistance + ( + "setheadwatchmaxdistance", + EV_DEFAULT, + "f", + "maxdistance", + "Sets the max distance for the headwatcher" + ); + +Event EV_Actor_AnimateOnce + ( + "animateonce", + EV_DEFAULT, + "s", + "anim_name", + "Runs the specified animation one time, then holds the last frame" + ); + +Event EV_Actor_SetDeathKnockbackValues + ( + "setdeathknockbackvalues", + EV_DEFAULT, + "ff", + "vertical_value horiz_value", + "Sets Death Knockback Values" + ); + +char actor_flag_strings[ ACTOR_FLAG_MAX ][ 32 ] = + { + "noiseheard", + "investigating", + "deathgib", + "deathfade", + "nochatter", + "inactive", + "animdone", + "statedonetimevalid", + "masterstatedonetimevalid", + "AIon", + "lastcanseeenemy", + "lastcanseeenemynofov", + "dialogplaying", + "radiusdialogplaying", + "allowtalk", + "damageonceon", + "damageoncedamaged", + "bounceoff", + "notifiyothersatdeath", + "hasthing1", + "hasthing2", + "hasthing3", + "hasthing4", + "lastattackhit", + "started", + "allowhangback", + "usegravity", + "spawnfailed", + "fadingout", + "deathshrink", + "deathsink", + "staysolid", + "stunned", + "allowfall", + "finished", + "inlimbo", + "canwalkonothers", + "pushable", + "lasttrytalk", + "targetable", + "immortal", + "turninghead", + "movingeyes", + "diecompletely", + "bleedafterdeath", + "ignorestuckwarning", + "ignoreoffgroundwarning", + "allowedtokill", + "touchtriggers", + "ignorewater", + "neverignoresounds", + "simplepathfinding", + "havemoved", + "nopainsounds", + "updatebosshealth", + "ignorepainfromactors", + "damageallowed", + "atcovernode", + "waitfornewenemy", + "takedamage", + "usedamageskins", + "captured", + "turretmode", + "incominghitscan", + "respondingtohitscan", + "meleehitworld", + "torsoanimdone", + "weaponready", + "disabled", + "inalcove", + "inconeoffire", + "inplayerconeoffire", + "playerincallvolume", + "incallvolume", + "outoftorsorange", + "ducked", + "prone", + "shouldblink", + "crippled", + "retreating", + "hidden", + "followinginformation", + "displayingfailureFX", + "groupmemberinjured", + "canhealother", + "strictlyfollowpath", + "postureanimdone", + "attackingenemy", + "updatehatebasedonattackers", + "lastcanseeplayer", + "lastcanseeplayernofov", + "meleeallowed", + "playingdialoganim", + "usinghud", + "forcelifebar", + "updateactionlevel", + "canchangeanim", + "usefollowrangefornodes", + "immediateactivate", + "cannotdisintegrate", + "cannotuse", + "cannotfreeze" + + + }; + +char actor_notify_strings[ ACTOR_FLAG_MAX ][ 32 ] = + { + "on_damage", + "on_killed", + "on_spottedenemy" + }; + +CLASS_DECLARATION( Sentient, Actor, "monster_generic" ) + { + { &EV_Activate, &Actor::ActivateEvent }, + { &EV_Actor_BlindlyFollowPath, &Actor::BlindlyFollowPath }, + { &EV_Actor_SetSimplifiedThink, &Actor::SetSimplifiedThink }, + { &EV_Actor_SetActorToActorDamageModifier, &Actor::SetActorToActorDamageModifier }, + { &EV_Use, &Actor::UseEvent }, + { &EV_Actor_OnUse, &Actor::SetOnUseThread }, + { &EV_Actor_NoUse, &Actor::ClearOnUseThread }, + { &EV_Actor_Sleep, &Actor::Sleep }, + { &EV_Actor_Wakeup, &Actor::Wakeup }, + { &EV_Actor_SetTargetType, &Actor::SetTargetType }, + { &EV_Actor_Start, &Actor::Start }, + { &EV_Pain, &Actor::Pain }, + { &EV_Killed, &Actor::Killed }, + { &EV_Actor_Dead, &Actor::Dead }, + { &EV_Actor_Suicide, &Actor::Suicide }, + { &EV_Actor_ForwardSpeed, &Actor::ForwardSpeedEvent }, + { &EV_Actor_Fov, &Actor::SetFOV }, + { &EV_Actor_VisionDistance, &Actor::SetVisionDistance }, + { &EV_Actor_SetEnemyType, &Actor::SetEnemyType }, + { &EV_Actor_ClearCurrentEnemy, &Actor::ClearCurrentEnemy }, + { &EV_Actor_Swim, &Actor::SwimEvent }, + { &EV_Actor_Fly, &Actor::FlyEvent }, + { &EV_Actor_NotLand, &Actor::NotLandEvent }, + { &EV_Actor_SetDialogMode, &Actor::SetDialogMode }, + { &EV_Actor_DialogAnimDone, &Actor::DialogAnimDone }, + { &EV_Actor_RunThread, &Actor::RunThread }, + { &EV_Actor_Statemap, &Actor::LoadStateMap }, + { &EV_Actor_MasterStateMap, &Actor::LoadMasterStateMap }, + { &EV_Actor_SetBehaviorPackage, &Actor::SetBehaviorPackage }, + { &EV_Actor_UseBehaviorPackage, &Actor::UseBehaviorPackage }, + { &EV_Actor_ChildUseBehaviorPackage, &Actor::ChildUseBehaviorPackage }, + { &EV_Actor_ChildSetAnim, &Actor::ChildSetAnim }, + { &EV_Actor_ChildSuicide, &Actor::ChildSuicide }, + { &EV_Actor_IfEnemyVisible, &Actor::IfEnemyVisibleEvent }, + { &EV_Actor_IfNear, &Actor::IfNearEvent }, + { &EV_Actor_Idle, &Actor::GoIdle }, + { &EV_Actor_LookAt, &Actor::LookAt }, + { &EV_Actor_TurnTo, &Actor::TurnToEvent }, + { &EV_Actor_HeadWatch, &Actor::HeadWatchEvent }, + { &EV_Actor_ResetHead, &Actor::ResetHeadEvent }, + { &EV_Actor_EyeWatch, &Actor::EyeWatchEvent }, + { &EV_Actor_ResetEye, &Actor::ResetEyeEvent }, + { &EV_Actor_ResetTorso, &Actor::ResetTorsoEvent }, + { &EV_Actor_HeadAndEyeWatch, &Actor::HeadAndEyeWatchEvent }, + { &EV_Actor_EndBehavior, &Actor::EndBehaviorEvent }, + { &EV_Actor_EndHeadBehavior, &Actor::EndHeadBehaviorEvent }, + { &EV_Actor_EndEyeBehavior, &Actor::EndEyeBehaviorEvent }, + { &EV_Actor_EndTorsoBehavior, &Actor::EndTorsoBehaviorEvent }, + { &EV_Actor_NotifyBehavior, &Actor::NotifyBehavior }, + { &EV_Actor_NotifyHeadBehavior, &Actor::NotifyHeadBehavior }, + { &EV_Actor_NotifyEyeBehavior, &Actor::NotifyEyeBehavior }, + { &EV_Actor_NotifyTorsoBehavior, &Actor::NotifyTorsoBehavior }, + { &EV_Actor_FallToDeath, &Actor::FallToDeathEvent }, + { &EV_Actor_WalkTo, &Actor::WalkTo }, + { &EV_Actor_WalkWatch, &Actor::WalkWatch }, + { &EV_Actor_JumpTo, &Actor::JumpToEvent }, + { &EV_Actor_WarpTo, &Actor::WarpTo }, + { &EV_Actor_Anim, &Actor::Anim }, + { &EV_Actor_SetAnim, &Actor::SetAnim }, + { &EV_Actor_Attack, &Actor::AttackEntity }, + { &EV_Actor_AttackPlayer, &Actor::AttackPlayer }, + { &EV_Actor_Remove, &Actor::RemoveUselessBody }, + { &EV_Actor_ReserveNode, &Actor::ReserveNodeEvent }, + { &EV_Actor_ReleaseNode, &Actor::ReleaseNodeEvent }, + { &EV_Actor_IfCanHideAt, &Actor::IfCanHideAtEvent }, + { &EV_Actor_IfEnemyWithin, &Actor::IfEnemyWithinEvent }, + { &EV_HeardSound, &Actor::HeardSound }, + { &EV_Actor_BroadcastAlert, &Actor::BroadcastAlert }, + { &EV_Actor_Melee, &Actor::MeleeEvent }, + { &EV_Actor_PainThreshold, &Actor::SetPainThresholdEvent }, + { &EV_Actor_SetKillThread, &Actor::SetKillThreadEvent }, + { &EV_SetHealth, &Actor::SetHealth }, + { &EV_SetMaxHealth, &Actor::SetMaxHealth }, + { &EV_Actor_EyePositionOffset, &Actor::EyeOffset }, + { &EV_Actor_DeathFade, &Actor::DeathFadeEvent }, + { &EV_Actor_DeathEffect, &Actor::setDeathEffect }, + { &EV_Actor_DeathShrink, &Actor::DeathShrinkEvent }, + { &EV_Actor_DeathSink, &Actor::DeathSinkEvent }, + { &EV_Actor_StaySolid, &Actor::StaySolidEvent }, + { &EV_Actor_NoChatter, &Actor::NoChatterEvent }, + { &EV_Actor_TurnSpeed, &Actor::SetTurnSpeed }, + { &EV_Actor_SetActorFlag, &Actor::SetActorFlag }, + { &EV_Actor_SetNotifyFlag, &Actor::SetNotifyFlag }, + { &EV_Actor_SetVar, &Actor::SetVar }, + { &EV_Actor_PersistData, &Actor::SetVar }, + { &EV_Actor_SetVarTime, &Actor::SetVarTime }, + { &EV_Actor_SetMaxInactiveTime, &Actor::SetMaxInactiveTime }, + { &EV_Anim_Done, &Actor::AnimDone }, + { &EV_Torso_Anim_Done, &Actor::TorsoAnimDone }, + { &EV_Actor_ProjAttack, &Actor::FireProjectile }, + { &EV_Actor_BulletAttack, &Actor::FireBullet }, + { &EV_Actor_RadiusAttack, &Actor::FireRadiusAttack }, + { &EV_Actor_Active, &Actor::Active }, + { &EV_Actor_SpawnGib, &Actor::SpawnGib }, + { &EV_Actor_SpawnGibAtTag, &Actor::SpawnGibAtTag }, + { &EV_Actor_SpawnNamedGib, &Actor::SpawnNamedGib }, + { &EV_Actor_SpawnBlood, &Actor::SpawnBlood }, + { &EV_Actor_AIOn, &Actor::TurnAIOn }, + { &EV_Actor_AIOff, &Actor::TurnAIOff }, + { &EV_Actor_SetIdleThread, &Actor::SetIdleThread }, + { &EV_ActorRegisterParts, &Actor::RegisterParts }, + { &EV_ActorRegisterSelf, &Actor::RegisterSelf }, + { &EV_ActorName, &Actor::Name }, + { &EV_ActorPartName, &Actor::PartName }, + { &EV_Actor_SendCommand, &Actor::SendCommand }, + { &EV_ActorSetupTriggerField, &Actor::SetupTriggerField }, + { &EV_ActorTriggerTouched, &Actor::TriggerTouched }, + { &EV_ActorIncomingProjectile, &Actor::IncomingProjectile }, + { &EV_ActorSpawnActor, &Actor::SpawnActorAtTag }, + { &EV_ActorSpawnActorAtLocation, &Actor::SpawnActorAtLocation }, + { &EV_ActorSpawnActorAboveEnemy, &Actor::SpawnActorAboveEnemy }, + { &EV_Actor_AddDialog, &Actor::AddDialog }, + { &EV_Actor_DialogDone, &Actor::DialogDone }, + { &EV_Actor_PlayDialog, &Actor::PlayDialog }, + { &EV_Actor_StopDialog, &Actor::StopDialog }, + { &EV_Actor_BranchDialog, &Actor::BranchDialog }, + { &EV_Sentient_SetMouthAngle, &Actor::SetMouthAngle }, + { &EV_Actor_AllowTalk, &Actor::AllowTalk }, + { &EV_Actor_AllowHangBack, &Actor::AllowHangBack }, + { &EV_Actor_SolidMask, &Actor::SolidMask }, + { &EV_Actor_IgnoreMonsterClip, &Actor::IgnoreMonsterClip }, + { &EV_Actor_NotSolidMask, &Actor::NotSolidMask }, + { &EV_Actor_NoMask, &Actor::NoMask }, + { &EV_Actor_SetMask, &Actor::SetMask }, + { &EV_Actor_PickupEnt, &Actor::PickupEnt }, + { &EV_Actor_ThrowEnt, &Actor::ThrowEnt }, + { &EV_Actor_Pickup, &Actor::Pickup }, + { &EV_Actor_Throw, &Actor::Throw }, + { &EV_Actor_DamageOnceStart, &Actor::DamageOnceStart }, + { &EV_Actor_DamageOnceStop, &Actor::DamageOnceStop }, + { &EV_Actor_DamageEnemy, &Actor::DamageEnemy }, + { &EV_Actor_DamageSelf, &Actor::DamageSelf }, + { &EV_Actor_TurnTowardsEnemy, &Actor::TurnTowardsEnemy }, + { &EV_Actor_TurnTowardsPlayer, &Actor::TurnTowardsPlayer }, + { &EV_Actor_TurnTowardsEntity, &Actor::TurnTowardsEntity }, + { &EV_Actor_GotoNextStage, &Actor::GotoNextStage }, + { &EV_Actor_GotoPrevStage, &Actor::GotoPrevStage }, + { &EV_Actor_GotoStage, &Actor::GotoStage }, + { &EV_Actor_GetStage, &Actor::GetStage }, + { &EV_Actor_NotifyOthersAtDeath, &Actor::NotifyOthersAtDeath }, + { &EV_Actor_SetBounceOff, &Actor::SetBounceOff }, + { &EV_Actor_BounceOff, &Actor::BounceOffEvent }, + { &EV_Actor_SetBounceOffEffect, &Actor::SetBounceOffEffect }, + { &EV_Actor_SetHaveThing, &Actor::SetHaveThing }, + { &EV_Actor_SetUseGravity, &Actor::SetUseGravity }, + { &EV_Actor_SetAllowFall, &Actor::SetAllowFall }, + { &EV_Actor_SetDeathSize, &Actor::SetDeathSize }, + { &EV_Actor_Fade, &Actor::FadeEvent }, + { &EV_Stun, &Actor::StunEvent }, + { &EV_Actor_AddSpawnItem, &Actor::AddSpawnItem }, + { &EV_Actor_SetSpawnChance, &Actor::SetSpawnChance }, + { &EV_Actor_ClearSpawnItems, &Actor::ClearSpawnItems }, + { &EV_Actor_SetCanBeFinishedBy, &Actor::SetCanBeFinishedBy }, + { &EV_Actor_SetFeetWidth, &Actor::SetFeetWidth }, + { &EV_Actor_SetCanWalkOnOthers, &Actor::SetCanWalkOnOthers }, + { &EV_Actor_Push, &Actor::Push }, + { &EV_Actor_Pushable, &Actor::Pushable }, + { &EV_Actor_ChargeWater, &Actor::ChargeWater }, + { &EV_Actor_SetTargetable, &Actor::SetTargetable }, + { &EV_Actor_ChangeType, &Actor::ChangeType }, + { &EV_Actor_MinimumMeleeHeight, &Actor::SetMinimumMeleeHeight }, + { &EV_Actor_SetDamageAngles, &Actor::SetDamageAngles }, + { &EV_Actor_Immortal, &Actor::SetImmortal }, + { &EV_Actor_SetDieCompletely, &Actor::SetDieCompletely }, + { &EV_Actor_SetBleedAfterDeath, &Actor::SetBleedAfterDeath }, + { &EV_Actor_IgnorePlacementWarning, &Actor::IgnorePlacementWarning }, + { &EV_Actor_SetIdleStateName, &Actor::SetIdleStateName }, + { &EV_Actor_SetNotAllowedToKill, &Actor::SetNotAllowedToKill }, + { &EV_TouchTriggers, &Actor::TouchTriggers }, + { &EV_Actor_IgnoreWater, &Actor::IgnoreWater }, + { &EV_Actor_SimplePathfinding, &Actor::SimplePathfinding }, + { &EV_Actor_NoPainSounds, &Actor::NoPainSounds }, + { &EV_Actor_UpdateBossHealth, &Actor::UpdateBossHealth }, + { &EV_Actor_SetMaxBossHealth, &Actor::SetMaxBossHealth }, + { &EV_Actor_IgnorePainFromActors, &Actor::IgnorePainFromActors }, + { &EV_Actor_DamageAllowed, &Actor::DamageAllowed }, + { &EV_Touch, &Actor::Touched }, + { &EV_Actor_SetEmotion, &Actor::SetEmotion }, + { &EV_Actor_ReturnProjectile, &Actor::ReturnProjectile }, + { &EV_Actor_SetRadiusDialogRange, &Actor::SetRadiusDialogRange }, + { &EV_Actor_SetEyeAngleConstraints, &Actor::SetEyeAngles }, + { &EV_Actor_SetActivateThread, &Actor::SetActivateThread }, + { &EV_Actor_SetValidTarget, &Actor::SetValidTarget }, + { &EV_Actor_SetAlertThread, &Actor::SetAlertThread }, + { &EV_Actor_RunAlertThread, &Actor::RunAlertThread }, + { &EV_Actor_CheckActorDead, &Actor::checkActorDead }, + { &EV_Actor_EnemyActorFlag, &Actor::SetFlagOnEnemy }, + { &EV_Actor_EnemyAIOn, &Actor::TurnOnEnemyAI }, + { &EV_Actor_EnemyAIOff, &Actor::TurnOffEnemyAI }, + { &EV_Actor_AttachCurrentEnemy, &Actor::AttachCurrentEnemy }, + { &EV_Actor_AttachActor, &Actor::AttachActor }, + { &EV_Actor_SetEnemyAttached, &Actor::SetEnemyAttached }, + { &EV_Actor_PickUpThrowObject, &Actor::PickupThrowObject }, + { &EV_Actor_TossThrowObject, &Actor::TossThrowObject }, + { &EV_Actor_SetTurretMode, &Actor::SetTurretMode }, + { &EV_Actor_SetHitscanResponseChance, &Actor::SetHitscanResponse }, + { &EV_Actor_SetWeaponReady, &Actor::SetWeaponReady }, + { &EV_Actor_GiveActorWeapon, &Actor::GiveActorWeapon }, + { &EV_Actor_RemoveActorWeapon, &Actor::RemoveActorWeapon }, + { &EV_Actor_SetOnDamageThread, &Actor::SetOnDamageThread }, + { &EV_Actor_SetTimeBetweenSleepChecks, &Actor::SetTimeBetweenSleepChecks }, + { &EV_Actor_FollowWayPoints, &Actor::FollowWayPoints }, + { &EV_Actor_SetAimLeadFactors, &Actor::SetAimLeadFactors }, + { &EV_Actor_SetActorType, &Actor::SetActorType }, + { &EV_Actor_RespondTo, &Actor::RespondTo }, + { &EV_Actor_PermanentlyRespondTo, &Actor::PermanentlyRespondTo }, + { &EV_Actor_RegisterBehaviorPackage, &Actor::RegisterBehaviorPackage }, + { &EV_Actor_UnregisterBehaviorPackage, &Actor::UnRegisterBehaviorPackage }, + { &EV_Actor_SetBehaviorPackageTendency, &Actor::SetPackageTendency }, + { &EV_Actor_FuzzyEngine, &Actor::LoadFuzzyEngine }, + { &EV_Actor_SetAbsoluteMaxRange, &Actor::SetAbsoluteMax }, + { &EV_Actor_SetAbsoluteMinRange, &Actor::SetAbsoluteMin }, + { &EV_Actor_SetPreferredMaxRange, &Actor::SetPreferredMax }, + { &EV_Actor_SetPreferredMinRange, &Actor::SetPreferredMin }, + + { &EV_Actor_UseWeapon, &Actor::UseActorWeapon }, + { &EV_Sentient_Attack, &Actor::FireWeapon }, + { &EV_Sentient_StopFire, &Actor::StopFireWeapon }, + { &EV_Actor_DebugStates, &Actor::DebugStates }, + { &EV_Actor_Disable, &Actor::SetDisabled }, + { &EV_Actor_Cripple, &Actor::SetCrippled }, + + { &EV_Actor_In_Alcove, &Actor::SetInAlcove }, + { &EV_Actor_ResetMoveDir, &Actor::ResetMoveDir }, + + { &EV_Actor_SetMovementMode, &Actor::SetMovementMode }, + { &EV_Actor_SetHeadWatchTarget, &Actor::SetHeadWatchTarget }, + { &EV_Actor_SetHeadTwitch, &Actor::setHeadTwitch }, + { &EV_Actor_SetFuzzyEngineActive, &Actor::SetFuzzyEngineActive }, + { &EV_Actor_SetNodeID, &Actor::SetNodeID }, + { &EV_Actor_WhatAreYouDoing, &Actor::WhatAreYouDoing }, + { &EV_Actor_WhatsWrong, &Actor::WhatsWrong }, + + // Personality Stuff + { &EV_Actor_SetAggressiveness, &Actor::SetAggressiveness }, + { &EV_Actor_SetTalkiness, &Actor::SetTalkiness }, + { &EV_Actor_SetGroupNumber, &Actor::SetGroupNumber }, + { &EV_Actor_SetTendency, &Actor::SetTendency }, + { &EV_Actor_SetFloatProperty, &Actor::SetTendency }, + + { &EV_Actor_Blink, &Actor::SetBlink }, + { &EV_Actor_ClearArmorAdapations, &Actor::ClearArmorAdaptions }, + { &EV_Actor_SetFollowTarget, &Actor::SetFollowTarget }, + { &EV_Actor_SetFollowRange, &Actor::SetFollowRange }, + { &EV_Actor_SetFollowRangeMin, &Actor::SetFollowRangeMin }, + { &EV_Actor_SetCombatFollowRange, &Actor::SetFollowCombatRange }, + { &EV_Actor_SetCombatFollowRangeMin, &Actor::SetFollowCombatRangeMin }, + { &EV_Actor_SetSteeringDirectionPreference, &Actor::SetSteeringDirectionPreference }, + { &EV_Actor_SetStickToGround, &Actor::SetStickToGround }, + + { &EV_Actor_SetDialogMorphMult, &Actor::setDialogMorphMult }, + + // Context Dialog Stuff + { &EV_ContextDialog_InContext, &Actor::InContext }, + { &EV_ContextDialog_IgnoreNextContext, &Actor::SetIgnoreNextContext }, + { &EV_Actor_BroadcastDialog, &Actor::BroadcastDialog }, + + { &EV_Actor_ForceSetClip, &Actor::ForceSetClip }, + { &EV_Actor_SetCombatTraceInterval, &Actor::SetCombatTraceInterval }, + + { &EV_Actor_PutawayWeapon, &Actor::PutawayWeapon }, + { &EV_Actor_UseWeaponDamage, &Actor::UseWeaponDamage }, + { &EV_Actor_StrictlyFollowPath, &Actor::StrictlyFollowPath }, + { &EV_HelperNodeCommand, &Actor::HelperNodeCommand }, + { &EV_Actor_SetMaxHeadYaw, &Actor::SetMaxHeadYaw }, + { &EV_Actor_SetMaxHeadPitch, &Actor::SetMaxHeadPitch }, + { &EV_Actor_SetPostureState, &Actor::SetPostureState }, + { &EV_Actor_SaveOffLastHitBone, &Actor::SaveOffLastHitBone }, + { &EV_Actor_SetPlayPainSoundInterval, &Actor::SetPlayPainSoundInterval }, + + + { &EV_Sentient_GroupMemberInjured, &Actor::GroupMemberInjured }, + { &EV_Actor_EvaluateEnemies, &Actor::EvaluateEnemies }, + { &EV_Actor_ForgetEnemies, &Actor::ForgetEnemies }, + { &EV_Actor_SetPostureStateMap, &Actor::LoadPostureStateMachine }, + { &EV_Posture_Anim_Done, &Actor::PostureAnimDone }, + { &EV_Actor_SendEventToGroup, &Actor::SendEventToGroup }, + { &EV_Actor_GroupAttack, &Actor::GroupAttack }, + { &EV_Actor_GroupActorType, &Actor::GroupActorType }, + { &EV_Actor_SetMasterState, &Actor::SetMasterState }, + { &EV_Actor_PrintDebugMessage, &Actor::PrintDebugMessage }, + { &EV_ProcessGameplayData, &Actor::processGameplayData }, + { &EV_Actor_SelectNextEnemy, &Actor::SelectNextEnemy }, + { &EV_Actor_SelectClosestEnemy, &Actor::SelectClosestEnemy }, + { &EV_Actor_SetGroupDeathThread, &Actor::SetGroupDeathThread }, + { &EV_Actor_SetAnimSet, &Actor::SetAnimSet }, + { &EV_Actor_SetSelfDetonateModel, &Actor::SetSelfDetonateModel }, + { &EV_Actor_SetTalkWatchMode, &Actor::SetTalkWatchMode }, + { &EV_Actor_PrepareMissionFailure, &Actor::PrepareToFailMission }, + { &EV_Actor_FailMission, &Actor::FailMission }, + { &EV_Actor_DebugEvent, &Actor::DebugEvent }, + { &EV_Actor_UnreserveCurrentHelperNode, &Actor::UnreserveCurrentHelperNode }, + { &EV_Actor_ProjectileClose, &Actor::ProjectileClose }, + { &EV_Actor_ClearTorsoAnim, &Actor::ClearTorsoAnim }, + { &EV_Actor_SetContextSoundInterval, &Actor::SetContextInterval }, + { &EV_Actor_SetMinPainTime, &Actor::SetMinPainTime }, + { &EV_Actor_SetEnemyTargeted, &Actor::SetEnemyTargeted }, + { &EV_Actor_SetActivationDelay, &Actor::SetActivationDelay }, + { &EV_Actor_SetActivationStart, &Actor::SetActivationStart }, + { &EV_Actor_SetCheckConeOfFireDistance, &Actor::SetCheckConeOfFireDistance }, + { &EV_Actor_AddCustomThread, &Actor::AddCustomThread }, + { &EV_Actor_SetHeadWatchMaxDistance, &Actor::SetHeadWatchMaxDistance }, + { &EV_Actor_AnimateOnce, &Actor::AnimateOnce }, + { &EV_Actor_SetDeathKnockbackValues, &Actor::SetDeathKnockbackValues }, + + //Game Specific Events + { NULL, NULL } + }; + +//=================================================================================== +// Construction and Destruction +//================================================================================== + +// +// Name: Actor() +// Parameters: None +// Description: Constructor +// +Actor::Actor() + { + Event *immunity_event; + + forcedEnemy = NULL; + + // + // make sure this is a modelanim entity + // + edict->s.eType = ET_MODELANIM; + edict->clipmask = MASK_MONSTERSOLID; + edict->svflags |= SVF_MONSTER; + edict->ownerNum = ENTITYNUM_NONE; + + + // Set ActorType and TargetType + actortype = IS_ENEMY; + targetType = ATTACK_ANY; + + //Set SolidType and MoveType + setSolidType( SOLID_BBOX ); + setMoveType( MOVETYPE_STEP ); + + // Clear All Flags + actor_flags1 = 0; + actor_flags2 = 0; + actor_flags3 = 0; + actor_flags4 = 0; + + + // Set default AnimSet + animset = "AnimSet1"; + + // Clear Notify Flags; + notify_flags1 = 0; + + // Clear All Behaviors + behavior = NULL; + headBehavior = NULL; + eyeBehavior = NULL; + torsoBehavior = NULL; + + // Set up Statemap + statemap = NULL; + currentState = NULL; + globalState = NULL; + lastState = NULL; + state_time = level.time; + times_done = 0; + + masterstatemap = NULL; + currentMasterState = NULL; + lastMasterState = NULL; + masterstate_time = level.time; + masterstate_times_done = 0; + + // Set up FuzzyEngine + fuzzyEngine = NULL; + fuzzyEngine_active = false; + + // Threads + onuse_thread_name = ""; + escape_thread = ""; + captured_thread = ""; + activate_thread = ""; + ondamage_thread = ""; + alert_thread = ""; + + //Death KnockBack + deathKnockbackVerticalValue = 300; + deathKnockbackHorizontalValue = 500; + + + // Ranges + absoluteMin = 20.0f; + absoluteMax = 175.0f; // Was 300 + preferredMin = 50.0f; + preferredMax = 175.0f; // Was 375 + //preferredMax = 95.0f; // Was 375 + + // Lead Factors for projectile aiming + minLeadFactor = 0.0f; + maxLeadFactor = 2.0f; + + //SomeDialog Defaults + radiusDialogRange = 125.0f; + DialogMode = DIALOG_MODE_NORMAL; + dialog_list = NULL; + dialog_done_time = 0; + _ignoreNextContext = false; + _nextContextTime = 0.0f; + _contextInterval = 15.0f; + + saved_bone_hit = -9999; + + //Eye Angle Defaults + minEyeYawAngle = -30.0f; + maxEyeYawAngle = 30.0f; + minEyePitchAngle = -30.0f; + maxEyePitchAngle = 30.0f; + + //Group Number + //groupnumber = 0; + + //Do We Bleed? + if ( com_blood->integer ) + { + flags |= FL_BLOOD; + flags |= FL_DIE_GIBS; + } + + // don't talk all at once initially + chattime = G_Random( 20.0f ); + nextsoundtime = 0; + + // All actors start immune to falling damage + immunity_event = new Event( EV_Sentient_AddImmunity ); + immunity_event->AddString( "falling" ); + ProcessEvent( immunity_event ); + + // ThrowObject Stuff + haveThrowObject = false; + + useConvAnims = true; + + // Set our default size + setSize( Vector( "-16 -16 0" ), Vector( "16 16 116" ) ); + + // Health + health = 100; + max_health = health; + + // General Data + takedamage = DAMAGE_AIM; + deadflag = DEAD_NO; + mode = ACTOR_MODE_AI; //ACTOR_MODE_IDLE; + max_inactive_time = MAX_INACTIVE_TIME; + newanimevent = NULL; + newTorsoAnimEvent = NULL; + groundentity = NULL; + saved_mode = ACTOR_MODE_NONE; + showStates = DEBUG_NONE; + talkMode = TALK_TURNTO; + + mass = 200; + stage = 1; + newanimnum = -1; + newTorsoAnimNum = -1; + pain_threshold = 20; + minimum_melee_height = -100; + + air_finished = level.time + 5.0f; + + globalState = NULL; + global_state_name = "GLOBAL_MAIN"; + + + actorrange_time = 0; + canseeenemy_time = 0; + canseeplayer_time = 0; + last_time_active = 0; + next_player_near = 0; + last_used_time = 0; + bullet_hits = 0; + state_flags = 0; + num_of_spawns = 0; + next_drown_time = 0; + next_pain_time = 0; + min_pain_time = 1.0; + next_forced_pain_time = 0; + max_pain_time = 2.5; + + last_jump_time = 0; + spawn_chance = 0; + feet_width = 0; + next_find_enemy_time = 0; + damage_angles = 0; + real_head_pitch = 0; + next_pain_sound_time = 0; + max_boss_health = 0; + next_blink_time = 0; + shotsFired = 0; + ondamage_threshold = 0; + timeBetweenSleepChecks = 10.0f; + currentSplineTime = 0; + _nextCheckForWorkNodeTime = 0.0f; + _nextCheckForHibernateNodeTime = 0.0f; + _nextCheckForEnemyPath = 0.0f; + _havePathToEnemy = false; + _nextPathDistanceToFollowTargetCheck = 0.0f; + + saved_anim_event_name = ""; + newanim = ""; + newTorsoAnim = ""; + last_anim_event_name = ""; + last_torso_anim_event_name = ""; + emotion = "none"; + + hitscan_response_chance = 0.0f; + actor_to_actor_damage_modifier = 1.0f; + + gunoffset = Vector(0, 0, 44); + eyeoffset = Vector(0, 0, 0); + eyeposition = Vector(0, 0, 64); + velocity = Vector(0, 0, -20); + + incoming_bullet = false; + validTarget = true; + haveAttached = false; + + showStates = DEBUG_NONE; + _useWeaponDamage = WEAPON_ERROR; + + _checkedChance = false; + _dialogMorphMult = 1.0f; + + _nextPlayPainSoundTime = 0.0f; + _playPainSoundInterval = 2.0f; //was 0.5 + + activationDelay = 0.0f; + activationStart = 0.0f; + + //1st playable hack + lastPathCheck_Work = 0.0f; + lastPathCheck_Flee = 0.0f; + lastPathCheck_Patrol = 0.0f; + testing = false; + _levelAIOff = false; + + // CurrentHelperNode + currentHelperNode.node = NULL; + currentHelperNode.mask = 0; + currentHelperNode.nodeID = 0; + + ignoreHelperNode.node = NULL; + ignoreHelperNode.mask = 0; + ignoreHelperNode.nodeID = 0; + + // FollowTarget + followTarget.currentFollowTarget = NULL; + followTarget.specifiedFollowTarget = NULL; + followTarget.maxRangeIdle = 384.0f; + followTarget.minRangeIdle = 256.0; + followTarget.maxRangeCombat = 512.0f; + followTarget.minRangeCombat = 256.0f; + + _steeringDirectionPreference = STEER_RIGHT_ALWAYS; + //set initial move dir; + //angles.AngleVectors( &movedir ); + + bounce_off_velocity = -0.5f; + + // Set all the flags + SetActorFlag( ACTOR_FLAG_TAKE_DAMAGE, true ); + SetActorFlag( ACTOR_FLAG_USE_DAMAGESKINS, true ); + SetActorFlag( ACTOR_FLAG_AI_ON, true ); + SetActorFlag( ACTOR_FLAG_ALLOW_TALK, true ); + SetActorFlag( ACTOR_FLAG_ALLOW_HANGBACK, true ); + SetActorFlag( ACTOR_FLAG_LAST_ATTACK_HIT, true ); + SetActorFlag( ACTOR_FLAG_USE_GRAVITY, true ); + + SetActorFlag( ACTOR_FLAG_TARGETABLE, true ); + SetActorFlag( ACTOR_FLAG_ALLOWED_TO_KILL, true ); + SetActorFlag( ACTOR_FLAG_DIE_COMPLETELY, true ); + SetActorFlag( ACTOR_FLAG_BLEED_AFTER_DEATH, true ); + SetActorFlag( ACTOR_FLAG_TOUCH_TRIGGERS, true ); + SetActorFlag( ACTOR_FLAG_DAMAGE_ALLOWED, true ); + SetActorFlag( ACTOR_FLAG_SHOULD_BLINK, true ); + SetActorFlag( ACTOR_FLAG_MELEE_ALLOWED, true ); + + SetActorFlag( ACTOR_FLAG_LAST_CANSEEENEMY, false ); + SetActorFlag( ACTOR_FLAG_LAST_CANSEEENEMY_NOFOV, false ); + SetActorFlag( ACTOR_FLAG_CAPTURED, false ); + SetActorFlag( ACTOR_FLAG_TURRET_MODE, false ); + SetActorFlag( ACTOR_FLAG_INCOMING_HITSCAN, false ); + SetActorFlag( ACTOR_FLAG_RESPONDING_TO_HITSCAN, false ); + SetActorFlag( ACTOR_FLAG_MELEE_HIT_WORLD, false ); + SetActorFlag( ACTOR_FLAG_INACTIVE, false ); + SetActorFlag( ACTOR_FLAG_ANIM_DONE, false ); + SetActorFlag( ACTOR_FLAG_STATE_DONE_TIME_VALID, false ); + SetActorFlag( ACTOR_FLAG_MASTER_STATE_DONE_TIME_VALID , false ); + SetActorFlag( ACTOR_FLAG_NOISE_HEARD, false ); + SetActorFlag( ACTOR_FLAG_INVESTIGATING, false ); + SetActorFlag( ACTOR_FLAG_DIALOG_PLAYING, false ); + SetActorFlag( ACTOR_FLAG_RADIUS_DIALOG_PLAYING, false ); + SetActorFlag( ACTOR_FLAG_NOTIFY_OTHERS_AT_DEATH, false ); + SetActorFlag( ACTOR_FLAG_DAMAGE_ONCE_ON, false ); + SetActorFlag( ACTOR_FLAG_BOUNCE_OFF, false ); + SetActorFlag( ACTOR_FLAG_HAS_THING1, false ); + SetActorFlag( ACTOR_FLAG_HAS_THING2, false ); + SetActorFlag( ACTOR_FLAG_HAS_THING3, false ); + SetActorFlag( ACTOR_FLAG_HAS_THING4, false ); + SetActorFlag( ACTOR_FLAG_SPAWN_FAILED, false ); + SetActorFlag( ACTOR_FLAG_FADING_OUT, false ); + SetActorFlag( ACTOR_FLAG_ALLOW_FALL, false ); + SetActorFlag( ACTOR_FLAG_STUNNED, false ); + SetActorFlag( ACTOR_FLAG_PUSHABLE, false ); + SetActorFlag( ACTOR_FLAG_WAIT_FOR_NEW_ENEMY, false ); + SetActorFlag( ACTOR_FLAG_DEATHFADE, false ); + SetActorFlag( ACTOR_FLAG_DEATHSHRINK, false ); + SetActorFlag( ACTOR_FLAG_DEATHSINK, false ); + SetActorFlag( ACTOR_FLAG_NOCHATTER, false ); + SetActorFlag( ACTOR_FLAG_STAYSOLID, false ); + SetActorFlag( ACTOR_FLAG_FINISHED, false ); + SetActorFlag( ACTOR_FLAG_IN_LIMBO, false ); + SetActorFlag( ACTOR_FLAG_CAN_WALK_ON_OTHERS, false ); + SetActorFlag( ACTOR_FLAG_LAST_TRY_TALK, false ); + SetActorFlag( ACTOR_FLAG_IMMORTAL, false ); + SetActorFlag( ACTOR_FLAG_AT_COVER_NODE, false ); + SetActorFlag( ACTOR_FLAG_TURNING_HEAD, false ); + SetActorFlag( ACTOR_FLAG_IGNORE_STUCK_WARNING, false ); + SetActorFlag( ACTOR_FLAG_IGNORE_OFF_GROUND_WARNING, false ); + SetActorFlag( ACTOR_FLAG_IGNORE_WATER, false ); + SetActorFlag( ACTOR_FLAG_NEVER_IGNORE_SOUNDS, false ); + SetActorFlag( ACTOR_FLAG_SIMPLE_PATHFINDING, false ); + SetActorFlag( ACTOR_FLAG_NO_PAIN_SOUNDS, false ); + SetActorFlag( ACTOR_FLAG_UPDATE_BOSS_HEALTH, false ); + SetActorFlag( ACTOR_FLAG_IGNORE_PAIN_FROM_ACTORS, false ); + SetActorFlag( ACTOR_FLAG_STARTED, false ); + SetActorFlag( ACTOR_FLAG_DEATHGIB, false ); + SetActorFlag( ACTOR_FLAG_WEAPON_READY, false ); + SetActorFlag( ACTOR_FLAG_DISABLED, false ); + SetActorFlag( ACTOR_FLAG_CRIPPLED, false ); + SetActorFlag( ACTOR_FLAG_IN_ALCOVE, false ); + SetActorFlag( ACTOR_FLAG_IN_CONE_OF_FIRE, false ); + SetActorFlag( ACTOR_FLAG_IN_PLAYER_CONE_OF_FIRE, false ); + SetActorFlag( ACTOR_FLAG_PLAYER_IN_CALL_VOLUME, false ); + SetActorFlag( ACTOR_FLAG_IN_CALL_VOLUME, false ); + SetActorFlag( ACTOR_FLAG_OUT_OF_TORSO_RANGE, false ); + SetActorFlag( ACTOR_FLAG_DUCKED, false ); + SetActorFlag( ACTOR_FLAG_PRONE, false ); + SetActorFlag( ACTOR_FLAG_RETREATING, false ); + SetActorFlag( ACTOR_FLAG_HIDDEN, false ); + SetActorFlag( ACTOR_FLAG_FOLLOWING_IN_FORMATION, false ); + SetActorFlag( ACTOR_FLAG_DISPLAYING_FAILURE_FX, false ); + SetActorFlag( ACTOR_FLAG_GROUPMEMBER_INJURED, false ); + SetActorFlag( ACTOR_FLAG_CAN_HEAL_OTHER, false ); + SetActorFlag( ACTOR_FLAG_STRICTLY_FOLLOW_PATHS, false ); + SetActorFlag( ACTOR_FLAG_ATTACKING_ENEMY, false ); + SetActorFlag( ACTOR_FLAG_UPDATE_HATE_WITH_ATTACKERS, false ); + + SetActorFlag( ACTOR_FLAG_LAST_CANSEEPLAYER, false ); + SetActorFlag( ACTOR_FLAG_LAST_CANSEEPLAYER_NOFOV, false ); + SetActorFlag( ACTOR_FLAG_PLAYING_DIALOG_ANIM, false ); + SetActorFlag( ACTOR_FLAG_USING_HUD, false ); + SetActorFlag( ACTOR_FLAG_FORCE_LIFEBAR, false ); + SetActorFlag( ACTOR_FLAG_UPDATE_ACTION_LEVEL, true ); + SetActorFlag( ACTOR_FLAG_CAN_CHANGE_ANIM, true ); + SetActorFlag( ACTOR_FLAG_USE_FOLLOWRANGE_FOR_NODES, false ); + SetActorFlag( ACTOR_FLAG_IMMEDIATE_ACTIVATE, false ); + SetActorFlag( ACTOR_FLAG_CANNOT_USE, false ); + SetActorFlag( ACTOR_FLAG_CANNOT_FREEZE, false ); + + + SetNotifyFlag( NOTIFY_FLAG_DAMAGED, false ); + SetNotifyFlag( NOTIFY_FLAG_KILLED, false ); + SetNotifyFlag( NOTIFY_FLAG_SPOTTED_ENEMY, false ); + + + // Init Helper Classes + InitThinkStrategy(); + InitGameComponent(); + InitSensoryPerception(); + InitEnemyManager(); + InitStrategos(); + InitPackageManager(); + InitMovementSubsystem(); + InitPersonality(); + InitCombatSubsystem(); + InitHeadWatcher(); + InitPostureController(); + + + // Call ShowModel + showModel(); + + // + // The following is here for reference + // + /* + ObjectProgram = new Program; + ObjectProgram->Load( "ai/test.scr" ); + + program2.Load("ai/test.scr"); + CThread *gamescript = 0; + + gamescript = Director.CreateThread( "obj_main" , ObjectProgram ); + gamescript->DelayedStart( 0 ); + */ + + // Make sure our animdir and movedir are the same; + setAngles( angles ); + movementSubsystem->setAnimDir( orientation[0] ); + movementSubsystem->setMoveDir( orientation[0] ); + + if ( !LoadingSavegame ) + PostEvent( EV_Actor_Start, EV_POSTSPAWN ); + + } + + +// +// Name: ~Actor() +// Parameters: None +// Description: Destructor +// +Actor::~Actor() + { + Actor *actor; + actor = this; + int i; + + if ( groupcoordinator ) + groupcoordinator->RemoveEntityFromGroup( this , GetGroupID() ); + + if ( GetActorFlag( ACTOR_FLAG_DIALOG_PLAYING ) ) + { + StopDialog(); + } + + if ( SleepList.ObjectInList( actor ) ) + { + SleepList.RemoveObject( actor ); + } + + if ( ActiveList.ObjectInList( actor ) ) + { + ActiveList.RemoveObject( actor ); + } + + if ( TeamMateList.ObjectInList( actor ) ) + { + TeamMateList.RemoveObject( actor ); + } + + for( i=stateVarList.NumObjects(); i>0; i-- ) + { + StateVar *var = stateVarList.ObjectAt( i ); + delete var; + } + + stateVarList.FreeObjectList(); + + for ( i = threadList.NumObjects(); i > 0; i-- ) + { + threadlist_t *threadEntry = threadList.ObjectAt( i ); + delete threadEntry; + } + + threadList.FreeObjectList(); + + if ( behavior ) + { + delete behavior; + behavior = NULL; + } + + if ( headBehavior ) + { + delete headBehavior; + headBehavior = NULL; + } + + if ( eyeBehavior ) + { + delete eyeBehavior; + eyeBehavior = NULL; + } + + if ( torsoBehavior ) + { + delete torsoBehavior; + torsoBehavior = NULL; + } + + if ( thinkStrategy ) + { + delete thinkStrategy; + thinkStrategy = NULL; + } + + if ( gameComponent ) + { + delete gameComponent; + gameComponent = NULL; + } + + if ( sensoryPerception ) + { + delete sensoryPerception; + sensoryPerception = NULL; + } + + if ( strategos ) + { + delete strategos; + strategos = NULL; + } + + if ( enemyManager ) + { + delete enemyManager; + enemyManager = NULL; + } + + if ( packageManager ) + { + delete packageManager; + packageManager = NULL; + } + + if ( movementSubsystem ) + { + delete movementSubsystem; + movementSubsystem = NULL; + } + + if ( personality ) + { + delete personality; + personality = NULL; + } + + if ( combatSubsystem ) + { + delete combatSubsystem; + combatSubsystem = NULL; + } + + if ( headWatcher ) + { + delete headWatcher; + headWatcher = NULL; + } + + if ( postureController ) + { + delete postureController; + postureController = NULL; + } + + freeConditionals( conditionals ); + freeConditionals( master_conditionals ); + freeConditionals( fuzzy_conditionals ); + + FreeDialogList(); + } + +//=================================================================================== +// Activity Level +//=================================================================================== + +// +// Name: Sleep() +// Parameters: Event *ev +// Description: Event Interface for Sleep() +// +void Actor::Sleep( Event *ev ) + { + Sleep(); + } + + +// +// Name: Sleep() +// Parameters: None +// Description: Puts the actor in a sleep state +// +void Actor::Sleep( void ) + { + Actor *actor; + + // inanimate actors don't target enemies + if ( actortype == IS_INANIMATE ) + return; + + actor = this; + + if ( !SleepList.ObjectInList( actor ) ) + SleepList.AddObject( actor ); + + if ( ActiveList.ObjectInList( actor ) ) + ActiveList.RemoveObject( actor ); + + enemyManager->ClearCurrentEnemy(); + turnThinkOff(); + } + + +// +// Name: Wakeup +// Parameters: Event *ev +// Description: Event interface for Wakeup() +// +void Actor::Wakeup( Event *ev ) + { + Wakeup(); + } + + +// +// Name: Wakeup() +// Parameters: None +// Description: Puts the actor in an awake state +// +void Actor::Wakeup( void ) + { + Actor *actor; + + // See if already awake + if ( isThinkOn() && !LoadingSavegame ) + return; + + // inanimate actors don't target enemies + if ( actortype == IS_INANIMATE ) + return; + + actor = this; + + if ( SleepList.ObjectInList( actor ) ) + SleepList.RemoveObject( actor ); + + if ( !ActiveList.ObjectInList( actor ) ) + ActiveList.AddObject( actor ); + + turnThinkOn(); + + // here the last_time_active must be updated! + // otherwise, a sleeping, script-controlled entity with no sensory stimulus will immediately + // be put back to sleep in TrySleep on the next frame! + last_time_active = level.time; + + } + + +// +// Name: Start() +// Parameters: Event *ev +// Description: Handles some initialization of the Actor +// +void Actor::Start( Event *ev ) + { + // Register with other parts of self if there are any + if ( target.length() > 0 ) + PostEvent( EV_ActorRegisterSelf, FRAMETIME ); + + // add them to the active list (they will be removed by sleep). + //if ( !ActiveList.ObjectInList( ( Actor * )this ) ) + // ActiveList.AddObject( ( Actor * )this ); + + // Align our movement and anim dirs + Vector animDir; + angles.AngleVectors( &animDir ); + + movementSubsystem->setMoveDir( animDir ); + movementSubsystem->setAnimDir( animDir ); + + _dropActorToGround(); + + //if ( (!behavior || ( currentBehavior == "Idle" )) && max_inactive_time > 0.0f ) + // Sleep(); + //else + // Wakeup(); + + //Let's go ahead and wakeup, + //but let's try to fall asleep immediately after so we don't burn a lot of + //cpu waiting for our inactivetime to kick in + Wakeup(); + enemyManager->TrySleep(); + + SetActorFlag( ACTOR_FLAG_STARTED, true ); + + // Make sure the actor is in the idle animation of it has no state machine + if ( !statemap ) + SetAnim( "idle" ); + + // Override starting health if we're using the GameplayManager + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + GameplayFormulaData fd(this); + if ( gpm->hasObject(getArchetype()) && gpm->hasFormula("Health")) + health = gpm->calculate("Health", fd); + } + + + + +//=================================================================================== +// Stimuli Control +//=================================================================================== +void Actor::RespondTo( Event *ev ) + { + str stimuli = ev->GetString( 1 ); + qboolean respond = ev->GetBoolean( 2 ); + + sensoryPerception->RespondTo( stimuli , respond ); + } + +void Actor::PermanentlyRespondTo( Event *ev ) + { + str stimuli = ev->GetString( 1 ); + qboolean respond = ev->GetBoolean( 2 ); + + sensoryPerception->PermanentlyRespondTo( stimuli , respond ); + + } + + + +//=================================================================================== +// Event Handlers -- Actions +// -- These Events are Primarily called from state machines, inside entry or +// exit commands. The Events in this section are general, and not really +// directly related to any kind of overriding concept, like Combat or Behavior +// management. +//=================================================================================== + +// +// Name: TurnTowardsEnemy() +// Parameters: Event *ev +// Description: Immediately turns the actor towards its enemy +// +void Actor::TurnTowardsEnemy( Event *ev ) + { + float extraYaw; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy || (currentEnemy->flags & FL_NOTARGET )) + return; + + //See if we want to add any additional yaw + if (ev->NumArgs() > 0 ) + extraYaw = ev->GetInteger( 1 ); + else + extraYaw = 0; + + turnTowardsEntity( currentEnemy, extraYaw ); + + } + + +// +// Name: TurnTowardsPlayer() +// Parameters: Event *ev +// Description: Immediately turns the actor towards the player +// +void Actor::TurnTowardsPlayer( Event *ev ) + { + float extraYaw; + + Player* player; + player = GetPlayer(0); + + // don't target while player is not in the game or he's in notarget + if( !player || ( player->flags & FL_NOTARGET ) ) + return; + + //See if we want to add any additional yaw + if (ev->NumArgs() > 0 ) + extraYaw = ev->GetInteger( 1 ); + else + extraYaw = 0; + + turnTowardsEntity( player, extraYaw ); + } + + +void Actor::TurnTowardsEntity( Event *ev ) + { + /* + float extraYaw; + + Entity *ent; + ent = ev->GetEntity( 1 ); + + if ( !ent ) + return; + + if (ev->NumArgs() > 1 ) + extraYaw = ev->GetInteger( 2 ); + else + extraYaw = 0; + + + turnTowardsEntity( ent, extraYaw ); + */ + TurnTo *turnTo; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_TurnTo ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + turnTo = new TurnTo; + turnTo->SetTarget ( ev->GetEntity( 1 ) ); + + SetBehavior( turnTo, NULL, ev->GetThread() ); + + + } + +// +// Name: GoIdle() +// Parameters: Event *ev +// Description: Forces the Actor into Idle State and Idle Mode +// +void Actor::GoIdle ( Event *ev ) + { + const char *state_name; + + if ( !ModeAllowed( ACTOR_MODE_IDLE ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_Idle ); + return; + } + + if ( ev->NumArgs() > 0 ) + state_name = ev->GetString( 1 ); + else + state_name = idle_state_name; + + SetState( state_name ); + + StartMode( ACTOR_MODE_IDLE ); + } + + + +//=================================================================================== +// Event Handlers -- Script Commands +// -- These events are Primarily called from scripts, and are not often called +// from within a statemachine +//=================================================================================== + +// +// Name: LookAt +// Parameters: Event *ev +// Description: Sets the behavior to TurnTo +// +void Actor::LookAt( Event *ev ) + { + Entity *ent; + TurnTo *turnTo; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_LookAt ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + ent = ev->GetEntity( 1 ); + if ( ent && ( ent != world ) ) + { + turnTo = new TurnTo; + turnTo->SetTarget( ent ); + SetBehavior( turnTo, NULL, ev->GetThread() ); + } + } + + +// +// Name: TurnToEvent +// Parameters: Event *ev +// Description: Turns the Actor in a specified direction +// +void Actor::TurnToEvent( Event *ev ) + { + TurnTo *turnTo; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_TurnTo ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + turnTo = new TurnTo; + turnTo->SetDirection( ev->GetFloat( 1 ) ); + + if ( ev->NumArgs() > 1 ) + turnTo->useAnims( ev->GetBoolean( 2 ) ); + + SetBehavior( turnTo, NULL, ev->GetThread() ); + } + + +// +// Name: HeadAndEyeWatchEvent() +// Parameters: Event *ev +// Description: Sets the Actor to Head and Eye Watch the +// Specified Entity +// +void Actor::HeadAndEyeWatchEvent( Event *ev ) + { + Event *event; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_HeadWatch ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + event = new Event( EV_Behavior_Args ); + event->AddEntity( ev->GetEntity( 1 ) ); + + if ( ev->NumArgs() > 1 ) + event->AddFloat( ev->GetFloat( 2 ) ); + + SetHeadBehavior( new HeadAndEyeWatch, event, ev->GetThread() ); + } + + +// +// Name: HeadWatchEvent() +// Parameters: Event *ev +// Description: Sets the Actor to Head Watch the +// Specified Entity +// +void Actor::HeadWatchEvent ( Event *ev ) + { + //Event *event; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_HeadWatch ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + /*event = new Event( EV_Behavior_Args ); + event->AddEntity( ev->GetEntity( 1 ) ); + + if ( ev->NumArgs() > 1 ) + event->AddFloat( ev->GetFloat( 2 ) ); + */ + + Entity* entToWatch; + entToWatch = ev->GetEntity( 1 ); + + if ( !entToWatch ) return; + + headWatcher->SetWatchTarget( entToWatch ); + if ( ev->NumArgs() > 1 ) + headWatcher->SetWatchSpeed( ev->GetFloat(2) ); + + //SetHeadBehavior( new HeadWatch, event, ev->GetThread() ); + } + + +// +// Name: ResetHeadEvent +// Parameters: Event *ev +// Description: Resets the head +// +void Actor::ResetHeadEvent ( Event *ev ) + { + //Event *event; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_ResetHead ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + /* + event = new Event( EV_Behavior_Args ); + event->AddEntity( NULL ); + + if ( ev->NumArgs() > 0 ) + event->AddFloat( ev->GetFloat( 1 ) ); + */ + + headWatcher->SetWatchTarget( NULL ); + //SetHeadBehavior( new HeadWatch, event, ev->GetThread() ); + } + + +// +// Name: EyeWatchEvent() +// Parameters: Event *ev +// Description: Sets the actor to Eye Watch +// The Specified Entity +// +void Actor::EyeWatchEvent ( Event *ev ) + { + Event *event; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_EyeWatch ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + event = new Event( EV_Behavior_Args ); + event->AddEntity( ev->GetEntity( 1 ) ); + + if ( ev->NumArgs() > 1 ) + event->AddFloat( ev->GetFloat( 2 ) ); + + SetEyeBehavior( new EyeWatch, event, ev->GetThread() ); + } + + +// +// Name: ResetEyeEvent() +// Parameters: Event *ev +// Description: Resets the eyes +// +void Actor::ResetEyeEvent( Event *ev ) + { + Event *event; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_ResetEye ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + event = new Event( EV_Behavior_Args ); + event->AddEntity( NULL ); + + if ( ev->NumArgs() > 0 ) + event->AddFloat( ev->GetFloat( 1 ) ); + + SetEyeBehavior( new EyeWatch, event, ev->GetThread() ); + } + + +// +// Name: ResetTorsoEvent() +// Parameters: Event *ev +// Description: Resets the torso +// +void Actor::ResetTorsoEvent( Event *ev ) + { + /* + Event *event; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_ResetTorso ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + event = new Event( EV_Behavior_Args ); + event->AddEntity( NULL ); + + event->AddInteger( 0 ); + event->AddInteger( 30 ); + event->AddInteger( 0 ); + + SetEyeBehavior( new TorsoTurn, event, ev->GetThread() ); + */ + + SetControllerTag( ACTOR_TORSO_TAG, gi.Tag_NumForName( edict->s.modelindex, "Bip01 Spine1" ) ); + SetControllerAngles( ACTOR_TORSO_TAG, vec_zero ); + } + + +// +// Name: FallToDeathEvent() +// Parameters: Event *ev +// Description: Allows the Actor to play a sequence of animations +// So that it will fall from a position and die on +// impact +// +void Actor::FallToDeathEvent( Event *ev ) + { + Event *e; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_FallToDeath ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + e = new Event( EV_Behavior_Args ); + + e->SetSource( ev->GetSource() ); + + if ( ev->GetSource() == EV_FROM_SCRIPT ) + { + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + } + + e->AddFloat( ev->GetFloat( 1 ) ); + e->AddFloat( ev->GetFloat( 2 ) ); + e->AddFloat( ev->GetFloat( 3 ) ); + e->AddString( ev->GetString( 4 ) ); + e->AddString( ev->GetString( 5 ) ); + e->AddString( ev->GetString( 6 ) ); + if (ev->NumArgs() > 6 ) + e->AddFloat( ev->GetFloat( 7 ) ); + + SetBehavior( new FallToDeath, e, ev->GetThread() ); + + } + +// +// Name: WalkTo() +// Parameters: Event *ev +// Description: Makes an Actor WalkTo a location +// +void Actor::WalkTo ( Event *ev ) + { + Event *e; + //HelperNode *node; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_WalkTo ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + e = new Event( EV_Behavior_Args ); + + e->SetSource( ev->GetSource() ); + + if ( ev->GetSource() == EV_FROM_SCRIPT ) + { + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + } + + if ( ev->NumArgs() > 1 ) + e->AddString( ev->GetString( 2 ) ); + else + e->AddString( "walk" ); + + + if ( ev->NumArgs() > 0 ) + { + //If we have a helper node, let's use its origin + //node = HelperNode::FindClosestHelperNode( *this , ev->GetString( 1 ) ); + //if ( node ) + // e->AddVector( node->origin ); + //else + e->AddToken( ev->GetToken( 1 ) ); + } + + if ( ev->NumArgs() > 2 ) + { + e->AddInteger( 0 ); + e->AddInteger( ev->GetInteger( 3 ) ); + } + + if ( ev->NumArgs() > 3 ) + { + e->AddInteger( ev->GetInteger( 4 ) ); + } + + //SetBehavior( new GotoPathNode, e, ev->GetThread() ); + SetBehavior( new GotoSpecified, e, ev->GetThread() ); + } +// +// Name: BlindlyFollowPath() +// Parameters: Event *ev +// Description: Makes an Actor blindly follow a helper node path +// +void Actor::BlindlyFollowPath ( Event *ev ) +{ + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + { + RepostEvent( ev, EV_Actor_BlindlyFollowPath ); + } + else if ( mode == ACTOR_MODE_AI ) + { + SendMoveDone( ev->GetThread() ); + } + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + Event *e = new Event( EV_Behavior_Args ); + + e->SetSource( ev->GetSource() ); + + if ( ev->GetSource() == EV_FROM_SCRIPT ) + { + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + } + + if ( ev->NumArgs() > 0 ) + { + e->AddString( ev->GetString( 1 ) ); + } + else + { + e->AddString( "walk" ); + } + + if ( ev->NumArgs() > 1 ) + { + e->AddFloat( ev->GetFloat( 2 ) ); + } + + if ( ev->NumArgs() > 2 ) + { + e->AddString( ev->GetString( 3 ) ); + } + + SetBehavior( new FollowPathBlindly(), e, ev->GetThread() ); +} + + + +// +// Name: FollowWayPoints() +// Parameters: Event *ev +// Description: Makes an Actor Follow a Waypoint Chain +// +void Actor::FollowWayPoints ( Event *ev ) + { + Event *e; + + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_WalkTo ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + e = new Event( EV_Behavior_Args ); + + e->SetSource( ev->GetSource() ); + + if ( ev->GetSource() == EV_FROM_SCRIPT ) + { + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + } + + //PathName + e->AddString( ev->GetString( 1 ) ); + + //Animation + if ( ev->NumArgs() > 1 ) + e->AddString( ev->GetString( 2 ) ); + else + e->AddString( "walk" ); + + //StartPoint + if ( ev->NumArgs() > 2 ) + e->AddString( ev->GetString( 3 ) ); + + SetBehavior( new GotoWayPoint, e, ev->GetThread() ); + } + + +// +// Name: WalkWatch +// Parameters: Event *ev +// Description: Makes an Actor walk to a specified location while +// watching a Specified Entity +// +void Actor::WalkWatch ( Event *ev ) + { + Event *e; + + + if ( ev->NumArgs() < 2 ) + return; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_WalkWatch ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + e = new Event( EV_Behavior_Args ); + + e->SetSource( ev->GetSource() ); + + if ( ev->GetSource() == EV_FROM_SCRIPT ) + { + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + } + + // Get animation name + + if ( ev->NumArgs() > 2 ) + e->AddString( ev->GetString( 3 ) ); + else + e->AddString( "walk" ); + + // Get the node to walk to + + e->AddToken( ev->GetToken( 1 ) ); + + // Get the entity to watch + + e->AddEntity( ev->GetEntity( 2 ) ); + + SetBehavior( new GotoPathNode, e, ev->GetThread() ); + } + +// +// Name: WarpTo() +// Parameters: Event *ev +// Description: Makes an actor warp to a specified location +// +void Actor::WarpTo ( Event *ev ) + { + PathNode *goal_node; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_WarpTo ); + return; + } + + if ( ev->NumArgs() > 0 ) + { + goal_node = thePathManager.FindNode( ev->GetString( 1 ) ); + + if ( goal_node ) + { + origin = goal_node->origin; + setOrigin( origin ); + + angles = goal_node->angles; + setAngles( angles ); + + NoLerpThisFrame(); + } + else + { + gi.WDPrintf( "Warpto failed : couldn't find goal node %s\n", ev->GetString( 1 ) ); + } + } + } + + +// +// Name: PickupEnt() +// Parameters: Event *ev +// Description: Makes the Actor Pickup the specified entity +// +void Actor::PickupEnt( Event *ev ) + { + Event *e; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_PickupEnt ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + e = new Event( EV_Behavior_Args ); + + e->SetSource( ev->GetSource() ); + + if ( ev->GetSource() == EV_FROM_SCRIPT ) + { + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + } + + e->AddEntity( ev->GetEntity( 1 ) ); + e->AddString( ev->GetString( 2 ) ); + + SetBehavior( new PickupEntity, e, ev->GetThread() ); + } + + +// +// Name: ThrowEnt() +// Parameters: Event *ev +// Description: Makes the actor throw the specified entity +// +void Actor::ThrowEnt( Event *ev ) + { + Event *e; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + RepostEvent( ev, EV_Actor_ThrowEnt ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + e = new Event( EV_Behavior_Args ); + + e->SetSource( ev->GetSource() ); + + if ( ev->GetSource() == EV_FROM_SCRIPT ) + { + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + } + + e->AddString( ev->GetString( 1 ) ); + + SetBehavior( new ThrowEntity, e, ev->GetThread() ); + } + + +// +// Name: AttackEntity() +// Parameters: Event *ev +// Description: Makes the actor attack the specified entity +// +void Actor::AttackEntity( Event *ev ) + { + Entity *target = ev->GetEntity( 1 ); + + bool forceEnemy = true; + + if ( !GetActorFlag( ACTOR_FLAG_STARTED ) ) + { + PostEvent( *ev, FRAMETIME ); + return; + } + + if ( ev->NumArgs() > 1 ) + forceEnemy = ev->GetBoolean( 2 ); + + if ( forceEnemy ) + forcedEnemy = target; + + + if ( forceEnemy ) + { + enemyManager->SetCurrentEnemy( target ); + enemyManager->LockOnCurrentEnemy( true ); + return; + } + + sensoryPerception->Stimuli( STIMULI_SIGHT, target ); + + if ( enemyManager->IsInHateList( target ) ) + { + enemyManager->SetCurrentEnemy( target ); + } + } + + +// +// Name: AttackPlayer() +// Parameters: Event *ev +// Description: Makes the Actor attack the Player +// +void Actor::AttackPlayer( Event *ev ) + { + if ( !GetActorFlag( ACTOR_FLAG_STARTED ) ) + { + PostEvent( *ev, FRAMETIME ); + return; + } + + + int i; + gentity_t *ent; + + for( i = 0; i < maxclients->integer; i++ ) + { + ent = &g_entities[ i ]; + if ( !ent->inuse || !ent->client || !ent->entity ) + { + continue; + } + + strategos->Attack( ent->entity ); + } + } + + +// +// Name: JumpToEvent() +// Parameters: Event *ev +// Description: Makes the Actor Jump to a specified location +// +void Actor::JumpToEvent( Event *ev ) + { + Event *e; + int i; + int n; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + PostEvent( *ev, FRAMETIME ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + StartMode( ACTOR_MODE_SCRIPT ); + + e = new Event( EV_Behavior_Args ); + + e->SetSource( ev->GetSource() ); + + if ( ev->GetSource() == EV_FROM_SCRIPT ) + { + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + } + + n = ev->NumArgs(); + + for( i = 1; i <= n; i++ ) + { + e->AddToken( ev->GetToken( i ) ); + } + + SetBehavior( new JumpToPathNode, e, ev->GetThread() ); + } + + +// +// Name: RepostEvent() +// Parameters: Event *ev, +// Event &event_type +// Description: Reposts the passed in event +// +void Actor::RepostEvent( Event *ev, const Event &event_type ) + { + Event *event; + int i; + str token; + + event = new Event( event_type ); + + for( i = 1 ; i <= ev->NumArgs() ; i++ ) + { + token = ev->GetString( i ); + event->AddString( token.c_str() ); + } + + event->SetThread( ev->GetThread() ); + + PostEvent( event, FRAMETIME ); + } + + +//=================================================================================== +// Event Handlers -- Data Manipulation +// -- These Events are meant to set data the actor uses, such as TargetType or +// EyeHeight. These events are called from script, statemachines, and sometimes +// from code. Right now there are too many events in this section. Much like +// the Actions section above, the events here should be general and not part +// of an overriding concept. As I continue to refine, I will be working toward +// that. +//=================================================================================== + +// +// Name: SetMinimumMeleeHeight() +// Parameters: Event *ev +// Description: Sets minimum_melee_height +// +void Actor::SetMinimumMeleeHeight ( Event *ev ) + { + minimum_melee_height = ev->GetFloat( 1 ); + } + + +// +// Name: SetDamageAngles() +// Parameters: Event *ev +// Description: Sets damage_angles +// +void Actor::SetDamageAngles ( Event *ev ) + { + damage_angles = ev->GetFloat( 1 ); + } + + +// +// Name: SetImmortal() +// Parameters: Event *ev +// Description: Sets the ACTOR_FLAG_IMMORTAL +// +void Actor::SetImmortal ( Event *ev ) + { + qboolean isImmortal = true; + + if ( ev->NumArgs() > 0 ) + isImmortal = ev->GetBoolean( 1 ); + + SetActorFlag( ACTOR_FLAG_IMMORTAL, isImmortal ); + } + + +// +// Name: SetTargetType() +// Parameters: Event *ev +// Description: Sets targetType -- Needs to be removed +// +void Actor::SetTargetType ( Event *ev ) + { + targetType = (targetType_t)ev->GetInteger( 1 ); + } + + +// +// Name: SetEyeAngles() +// Parameters: Event *ev +// Description: Sets the eyeangles; +// +void Actor::SetEyeAngles ( Event *ev ) + + { + minEyeYawAngle = ev->GetFloat( 1 ); + maxEyeYawAngle = ev->GetFloat( 2 ); + minEyePitchAngle = ev->GetFloat( 3 ); + maxEyePitchAngle = ev->GetFloat( 4 ); + + if ( minEyeYawAngle < -180.0f ) minEyeYawAngle = -180.0f; + if ( maxEyeYawAngle > 180.0f ) maxEyeYawAngle = 180.0f; + if ( minEyePitchAngle < -180.0f ) minEyePitchAngle = -180.0f; + if ( maxEyePitchAngle > 180.0f ) maxEyePitchAngle = 180.0f; + } + + +// +// Name: SetTakeDamage() +// Parameters: Event *ev +// Description: Sets the ACTOR_FLAG_TAKE_DAMAGE +// +void Actor::SetTakeDamage ( Event *ev ) + { + qboolean damage = true; + + if ( ev->NumArgs() > 0 ) + damage = ev->GetBoolean( 1 ); + + SetActorFlag( ACTOR_FLAG_TAKE_DAMAGE, damage ); + } + + +// +// Name: SetIdleStateName() +// Parameters: Event *ev +// Description: Sets the idle_state_name +// +void Actor::SetIdleStateName ( Event *ev ) + { + idle_state_name = ev->GetString( 1 ); + } + + +// +// Name: SetActivateThread() +// Parameters: Event *ev +// Description: Sets the activate_thread +// +void Actor::SetActivateThread ( Event *ev ) + { + activate_thread = ev->GetString( 1 ); + } + + +// +// Name: SetValidTarget() +// Parameters: Event *ev +// Description: Sets validTarget Boolean -- Needs to go away, I believe +// We should find a better way to do this +// +void Actor::SetValidTarget ( Event *ev ) + { + Event *flagEvent; + qboolean valid; + + valid = ev->GetBoolean( 1 ); + + flagEvent = new Event(EV_EntityFlags); + + if( valid ) + flagEvent->AddString( "-notarget" ); + else + flagEvent->AddString( "+notarget" ); + + ProcessEvent(flagEvent); + + } + + +// +// Name: SetAlertThread() +// Parameters: Event *ev +// Description: Sets the alert_thread +// +void Actor::SetAlertThread ( Event *ev ) + { + alert_thread = ev->GetString( 1 ); + } + + +// +// Name: SetEnemyType() +// Parameters: Event *ev +// Description: Sets our enemytype +// +void Actor::SetEnemyType ( Event *ev ) + { + enemytype = ev->GetString( 1 ); + } + + +// +// Name: SetWeaponReady() +// Parameters: Event *ev +// Description: Sets the ACTOR_FLAG_WEAPON_READY +// +void Actor::SetWeaponReady ( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_WEAPON_READY , ev->GetBoolean( 1 ) ); + } + + + +//=================================================================================== +// StateMachine And FuzzyEngine Functions +// -- These Functions deal with managing the Actor's State Machine +// and Fuzzy Engine +//=================================================================================== + +// +// Name: LoadStateMap() +// Parameters: Event *ev +// Description: Loads a StateMap into memory +// +void Actor::LoadStateMap( Event *ev ) + { + str anim_name; + qboolean loading; + qboolean packageChange; + + // Load the new state map + + statemap_name = ev->GetString( 1 ); + + freeConditionals( conditionals ); + //conditionals.FreeObjectList(); + statemap = GetStatemap( statemap_name, ( Condition * )Conditions, &conditionals, false ); + + // Check if we're changing behavior packages + packageChange = false; + if ( ev->NumArgs() > 3 ) + { + packageChange = ev->GetBoolean( 4 ); + } + + + // Set the first state + if ( ev->NumArgs() > 1 ) + idle_state_name = ev->GetString( 2 ); + else if ( fuzzyEngine ) + idle_state_name = "START"; + else + idle_state_name = "IDLE"; + + if ( ev->NumArgs() > 2 ) + loading = ev->GetBoolean( 3 ); + else + loading = false; + + // Initialize the actors first animation + + if ( !loading ) + { + SetState( idle_state_name.c_str() ); + SetGlobalState(global_state_name.c_str() ); + + if ( currentState ) + anim_name = currentState->getLegAnim( *this, &conditionals ); + + if ( anim_name == "" && !newanim.length() ) + anim_name = "idle"; + + + SetAnim( anim_name.c_str(), EV_Anim_Done ); + ChangeAnim(); + + } + } + + +//-------------------------------------------------------------- +// Name: LoadMasterStateMap() +// Class: Actor +// +// Description: Loads the Master State Map for the Actor +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::LoadMasterStateMap( Event *ev ) + { + qboolean loading; + + // Load the new state map + masterstatemap_name = ev->GetString( 1 ); + + freeConditionals( master_conditionals ); + masterstatemap = GetStatemap( masterstatemap_name, ( Condition * )Conditions, &master_conditionals, false ); + + // Set the first state + master_idle_state_name = "START"; + + if ( ev->NumArgs() > 2 ) + loading = ev->GetBoolean( 3 ); + else + loading = false; + + // Initialize the actors first animation + + if ( !loading ) + { + SetMasterState( master_idle_state_name.c_str() ); + } + } + +// +// Name: SetGlobalState() +// Parameters: const char *state_name +// Description: Sets the Current Global State to state_name +// +void Actor::SetGlobalState( const char *state_name ) + { + if ( !statemap ) + return; + + if ( deadflag ) + return; + + if ( globalState ) + globalState->ProcessExitCommands( this ); + + globalState = statemap->FindGlobalState( state_name ); + } + + +// +// Name: SetState() +// Parameters: const char *state_name +// Description: Sets the Current State to state_name +// +void Actor::SetState( const char *state_name ) + { + ClassDef *cls; + int i; + Event *e; + + if ( !statemap ) + return; + + if ( deadflag ) + return; + + if ( currentState ) + currentState->ProcessExitCommands( this ); + + currentState = statemap->FindState( state_name ); + state_time = level.time; + + // Set the behavior + + if ( currentState ) + { + cls = getClass( currentState->getBehaviorName() ); + + if ( cls ) + { + if ( currentState->numBehaviorParms() ) + { + e = new Event( EV_Behavior_Args ); + + for ( i = 1 ; i <= currentState->numBehaviorParms() ; i++ ) + e->AddString( currentState->getBehaviorParm( i ) ); + + SetBehavior( ( Behavior * )cls->newInstance(), e ); + } + else + { + SetBehavior( ( Behavior * )cls->newInstance() ); + } + } + + cls = getClass( currentState->getHeadBehaviorName() ); + + if ( cls ) + { + if ( currentState->numHeadBehaviorParms() ) + { + e = new Event( EV_Behavior_Args ); + + for ( i = 1; i <= currentState->numHeadBehaviorParms() ; i++ ) + e->AddString( currentState->getHeadBehaviorParm( i ) ); + + SetHeadBehavior( ( Behavior * )cls->newInstance(), e ); + } + else + { + SetHeadBehavior( ( Behavior * )cls->newInstance() ); + } + } + + cls = getClass( currentState->getEyeBehaviorName() ); + + if ( cls ) + { + if ( currentState->numEyeBehaviorParms() ) + { + e = new Event ( EV_Behavior_Args ); + + for ( i = 1; i <= currentState->numEyeBehaviorParms() ; i++ ) + e->AddString( currentState->getEyeBehaviorParm( i ) ); + + SetEyeBehavior( ( Behavior * )cls->newInstance(), e ); + } + else + { + SetEyeBehavior( ( Behavior * )cls->newInstance() ); + } + } + + cls = getClass( currentState->getTorsoBehaviorName() ); + + if ( cls ) + { + if ( currentState->numTorsoBehaviorParms() ) + { + e = new Event ( EV_Behavior_Args ); + + for ( i = 1; i <= currentState->numTorsoBehaviorParms(); i++ ) + e->AddString( currentState->getTorsoBehaviorParm( i ) ); + + SetTorsoBehavior( (Behavior *)cls->newInstance(), e ); + } + else + { + SetTorsoBehavior( (Behavior *)cls->newInstance() ); + } + } + + InitState(); + currentState->ProcessEntryCommands( this ); + } + else + { + gi.WDPrintf( "State %s not found\n", state_name ); + } + } + +void Actor::resetStateMachine( void ) +{ + SetState( idle_state_name ); +} + + +void Actor::SetMasterState( Event *ev ) +{ + SetMasterState(ev->GetString( 1 ) ); +} + +//-------------------------------------------------------------- +// Name: SetMasterState() +// Class: Actor +// +// Description: Sets the Current Master State to state_name +// +// Parameters: const str &state_name +// +// Returns: None +//-------------------------------------------------------------- +void Actor::SetMasterState( const str &state_name ) + { + if ( !masterstatemap ) + return; + + if ( deadflag ) + return; + + if ( currentMasterState ) + currentMasterState->ProcessExitCommands( this ); + + currentMasterState = masterstatemap->FindState( state_name ); + masterstate_time = level.time; + + // Masterstates don't have behaviors -- They are for transitioning + // the regular statemaps that DO have behaviors. + // Set the behavior + if ( currentMasterState ) + { + InitMasterState(); + currentMasterState->ProcessEntryCommands( this ); + } + else + { + gi.WDPrintf( "Master State %s not found\n", state_name.c_str() ); + } + } + +// +// Name: InitState() +// Parameters: None +// Description: Initializes a state +// +void Actor::InitState( void ) + { + float min_time; + float max_time; + + if ( !currentState ) + return; + + min_time = currentState->getMinTime(); + max_time = currentState->getMaxTime(); + + if ( ( min_time != -1.0f ) && ( max_time != -1.0f ) ) + { + SetActorFlag( ACTOR_FLAG_STATE_DONE_TIME_VALID, true ); + + state_done_time = level.time + min_time + G_Random( max_time - min_time ); + } + else + { + SetActorFlag( ACTOR_FLAG_STATE_DONE_TIME_VALID, false ); + } + + state_time = level.time; + times_done = 0; + + ClearStateFlags(); + command = ""; + + SetActorFlag( ACTOR_FLAG_ANIM_DONE, false ); + SetActorFlag( ACTOR_FLAG_NOISE_HEARD, false ); + + + } + + +//-------------------------------------------------------------- +// Name: InitMasterState() +// Class: Actor +// +// Description: Initializes a master state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Actor::InitMasterState( void ) + { + float min_time; + float max_time; + + if ( !currentMasterState ) + return; + + min_time = currentMasterState->getMinTime(); + max_time = currentMasterState->getMaxTime(); + + if ( ( min_time != -1.0f ) && ( max_time != -1.0f ) ) + { + SetActorFlag( ACTOR_FLAG_MASTER_STATE_DONE_TIME_VALID, true ); + + masterstate_done_time = level.time + min_time + G_Random( max_time - min_time ); + } + else + { + SetActorFlag( ACTOR_FLAG_MASTER_STATE_DONE_TIME_VALID, false ); + } + + masterstate_time = level.time; + masterstate_times_done = 0; + } + +// +// Name: RegisterBehaviorPackage() +// Parameters: Event *ev +// Description: Registers a BehaviorPackage with the packageManager +// +void Actor::RegisterBehaviorPackage( Event *ev ) + { + packageManager->RegisterPackage(ev->GetString( 1 ) ); + } + + +// +// Name: UnRegisterBehaviorPackage() +// Parameters: Event *ev +// Description: UnRegisters a BehaviorPackage with the packageManager +// +void Actor::UnRegisterBehaviorPackage( Event *ev ) + { + packageManager->UnregisterPackage(ev->GetString( 1 ) ); + } + + +void Actor::SetPackageTendency( Event *ev ) + { + str packageName = ev->GetString( 1 ); + float tendency = ev->GetFloat( 2 ); + + personality->SetBehaviorTendency( packageName, tendency ); + } + +// +// Name: LoadFuzzyEngine() +// Parameters: Event *ev +// Description: Loads a Fuzzy Engine into memory +// +void Actor::LoadFuzzyEngine( Event *ev ) + { + // Load the new fuzzy engine + + fuzzyengine_name = ev->GetString( 1 ); + //fuzzy_conditionals.FreeObjectList(); + freeConditionals( fuzzy_conditionals ); + fuzzyEngine = GetFuzzyEngine( fuzzyengine_name, ( Condition * )Conditions, &fuzzy_conditionals, false ); + fuzzyEngine_active = true; + + } + + +//-------------------------------------------------------------- +// Name: ProcessActorStateMachine() +// Class: Actor +// +// Description: Evaluates our state map file +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Actor::ProcessActorStateMachine( void ) +{ + int count; + str behavior_arg; + str behavior_name; + str headBehavior_name; + str eyeBehavior_name; + str torsoBehavior_name; + str currentTorsoAnim; + str currentanim; + str stateLegAnim; + str stateTorsoAnim; + ClassDef *cls = NULL; + State *laststate = NULL; + + stateLegAnim = animname; + stateTorsoAnim = TorsoAnimName; + + count = 0; + + if ( deadflag || !currentState ) + return; + + if ( ( mode != ACTOR_MODE_AI ) && ( mode != ACTOR_MODE_IDLE ) ) + return; + + do + { + // Since we could get into an infinite loop here, do a check to make sure we don't. + count++; + if ( count > 10 ) + { + gi.WDPrintf( "Possible infinite loop in state '%s'\n", currentState->getName() ); + if ( count > 50 ) + { + gi.Error( ERR_DROP, "Stopping due to possible infinite state loop\n" ); + break; + } + } + + // Determine the next state to go to + laststate = currentState; + + + //Attempt to evaluate our global state. + if ( !currentState->IgnoreGlobalStates() && globalState ) + { + currentState = globalState->Evaluate( *this , &conditionals ); + + //If, after the evaluation, our state is still the global state, + //that means nothing in the global state required a transition, + //so we can safely evaluate our main state + if ( currentState == globalState ) + { + currentState = laststate; + } + else + { + ClearStateFlags(); + + // Process exit commands of the last state + laststate->ProcessExitCommands( this ); + // Process entry commands of the new state + currentState->ProcessEntryCommands( this ); + } + } + + //Evaluate our current state + currentState = currentState->Evaluate( *this, &conditionals ); + + if ( !currentState ) + return; + + // Change the behavior if the state has changed + if ( laststate != currentState ) + { + // Clear our _checkedChance flag + _checkedChance = false; + + // Process exit commands of the last state + laststate->ProcessExitCommands( this ); + lastState = laststate; + + // Process entry commands of the new state + currentState->ProcessEntryCommands( this ); + + + // End Old Behaviors + if ( behavior ) + { + EndBehavior(); + //behavior->End(*this); + //behavior = NULL; + } + + if ( torsoBehavior ) + { + EndTorsoBehavior(); + //torsoBehavior->End(*this); + //torsoBehavior = NULL; + } + + // Setup the new behavior + behavior_name = currentState->getBehaviorName(); + + // Check if our behavior is set up in the GPD + if ( !behavior_name.length() ) + { + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str stateName = currentState->getName(); + str objname = getArchetype(); + + stateName = objname + "." + stateName; + if ( gpm->hasObject( stateName ) ) + { + if ( gpm->hasProperty( stateName , "behavior" ) ) + { + behavior_name = gpm->getStringValue( stateName , "behavior" ); + } + } + + } + + if ( behavior_name.length() ) + { + cls = getClass( currentState->getBehaviorName() ); + } + //else if ( behavior ) + // { + // behavior->End(*this); + // behavior = NULL; + // } + + + if ( cls ) + { + if ( currentState->numBehaviorParms() ) + { + int i; + Event *e = new Event( EV_Behavior_Args ); + + for ( i = 1 ; i <= currentState->numBehaviorParms() ; i++ ) + e->AddString( currentState->getBehaviorParm( i ) ); + + SetBehavior( ( Behavior * )cls->newInstance(), e ); + } + else + { + SetBehavior( ( Behavior * )cls->newInstance() ); + } + } + else if ( behavior_name.length() ) + { + gi.WDPrintf( "Invalid behavior name %s\n", behavior_name.c_str() ); + } + + + // Setup the new torso behavior + torsoBehavior_name = currentState->getTorsoBehaviorName(); + cls = 0; + + if ( torsoBehavior_name.length() ) + cls = getClass( currentState->getTorsoBehaviorName() ); + //else if ( torsoBehavior ) + // { + // torsoBehavior->End(*this); + // torsoBehavior = NULL; + // } + + if ( cls ) + { + if ( currentState->numTorsoBehaviorParms() ) + { + int i; + Event *e = new Event( EV_Behavior_Args ); + + for ( i = 1 ; i <= currentState->numTorsoBehaviorParms() ; i++ ) + e->AddString( currentState->getTorsoBehaviorParm( i ) ); + + SetTorsoBehavior( ( Behavior * )cls->newInstance(), e ); + } + else + { + SetTorsoBehavior( ( Behavior * )cls->newInstance() ); + } + + } + else if ( torsoBehavior_name.length() ) + { + gi.WDPrintf( "Invalid torso behavior name %s\n", torsoBehavior_name.c_str() ); + } + // Initialize some stuff for changing states + + + + InitState(); + } +/* + // Setup the new head behavior + headBehavior_name = currentState->getHeadBehaviorName(); + cls = 0; + + if ( headBehavior_name.length() ) + cls = getClass( currentState->getHeadBehaviorName() ); + else if ( headBehavior ) + { + headBehavior->End(*this); + headBehavior = NULL; + } + + if ( cls ) + { + if ( currentState->numHeadBehaviorParms() ) + { + int i; + Event *e = new Event( EV_Behavior_Args ); + + for ( i = 1 ; i <= currentState->numHeadBehaviorParms() ; i++ ) + e->AddString( currentState->getHeadBehaviorParm( i ) ); + + SetHeadBehavior( ( Behavior * )cls->newInstance(), e ); + } + else + { + SetHeadBehavior( ( Behavior * )cls->newInstance() ); + } + + } + else if ( headBehavior_name.length() ) + { + gi.WDPrintf( "Invalid head behavior name %s\n", headBehavior_name.c_str() ); + } + + // Setup the new eye behavior + eyeBehavior_name = currentState->getEyeBehaviorName(); + cls = 0; + + if ( eyeBehavior_name.length() ) + cls = getClass( currentState->getEyeBehaviorName() ); + else if ( eyeBehavior ) + { + eyeBehavior->End(*this); + eyeBehavior = NULL; + } + + if ( cls ) + { + if ( currentState->numEyeBehaviorParms() ) + { + int i; + Event *e = new Event( EV_Behavior_Args ); + + for ( i = 1 ; i <= currentState->numEyeBehaviorParms() ; i++ ) + e->AddString( currentState->getEyeBehaviorParm( i ) ); + + SetEyeBehavior( ( Behavior * )cls->newInstance(), e ); + } + else + { + SetEyeBehavior( ( Behavior * )cls->newInstance() ); + } + + } + else if ( eyeBehavior_name.length() ) + { + gi.WDPrintf( "Invalid eye behavior name %s\n", eyeBehavior_name.c_str() ); + } + + + // Setup the new torso behavior + torsoBehavior_name = currentState->getTorsoBehaviorName(); + cls = 0; + + if ( torsoBehavior_name.length() ) + cls = getClass( currentState->getTorsoBehaviorName() ); + else if ( torsoBehavior ) + { + torsoBehavior->End(*this); + torsoBehavior = NULL; + } + + if ( cls ) + { + if ( currentState->numTorsoBehaviorParms() ) + { + int i; + Event *e = new Event( EV_Behavior_Args ); + + for ( i = 1 ; i <= currentState->numTorsoBehaviorParms() ; i++ ) + e->AddString( currentState->getTorsoBehaviorParm( i ) ); + + SetTorsoBehavior( ( Behavior * )cls->newInstance(), e ); + } + else + { + SetTorsoBehavior( ( Behavior * )cls->newInstance() ); + } + + } + else if ( torsoBehavior_name.length() ) + { + gi.WDPrintf( "Invalid torso behavior name %s\n", torsoBehavior_name.c_str() ); + } + // Initialize some stuff for changing states + + InitState(); + } +*/ + + // See if we've SOMEHOW managed to get in a state with no behavior, + // yet we're still playing a behavior. WTF. Break out of the behavior + // immediately if this is the case. + behavior_name = currentState->getBehaviorName(); + if ( !behavior_name.length() ) + { + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str stateName = currentState->getName(); + str objname = getArchetype(); + + stateName = objname + "." + stateName; + if ( gpm->hasObject( stateName ) ) + { + if ( gpm->hasProperty( stateName , "behavior" ) ) + { + behavior_name = gpm->getStringValue( stateName , "behavior" ); + } + } + } + + if ( behavior && !behavior_name.length() ) + { + EndBehavior(); + //behavior->End(*this); + //behavior = NULL; + } + + + + // Change the animation if it has changed + currentanim = currentState->getLegAnim( *this, &conditionals ); + currentTorsoAnim = currentState->getTorsoAnim( *this, &conditionals ); + + // We need to store the stateAnimation in local variables because we won't actually change + // the animation until the next frame update. If we don't store them here, we'll reset them + // every time we loop -- I tried moving this section outside of the while loop, but that didn't + // work well either. + if ( deadflag ) return; + + if ( !GetActorFlag(ACTOR_FLAG_PLAYING_DIALOG_ANIM) ) + { + if ( currentanim.length() && ( strcmp( stateLegAnim , currentanim.c_str() ) != 0 ) ) + { + SetAnim( currentanim, EV_Anim_Done, legs ); + stateLegAnim = currentanim; + } + + if ( currentTorsoAnim.length() && ( strcmp( stateTorsoAnim , currentTorsoAnim.c_str() ) != 0 ) ) + { + SetAnim( currentTorsoAnim, EV_Torso_Anim_Done, torso ); + stateTorsoAnim = currentTorsoAnim; + } + } + + /* + if ( (laststate != currentState) && !currentTorsoAnim.length() ) + { + TorsoAnimName = ""; + animate->ClearTorsoAnim(); + } + */ + + if ( showStates != DEBUG_NONE && laststate != currentState ) + _printDebugInfo(laststate->getName() , currentState->getName() , currentanim , currentTorsoAnim ); + + } + while( laststate != currentState ); +} + + + +//-------------------------------------------------------------- +// Name: ProcessMasterStateMachine() +// Class: Actor +// +// Description: Processes our Master State Machine. This file +// is used to specify which state machine we will +// actually run. It CANNOT run behaviors or set +// animations... It is only in place to set which +// main state machine will run +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Actor::ProcessMasterStateMachine( void ) + { + int count; + State *laststate = NULL; + + + if ( !masterstatemap ) + return; + + if ( deadflag || !currentMasterState ) + return; + + if ( ( mode != ACTOR_MODE_AI ) && ( mode != ACTOR_MODE_IDLE ) ) + return; + + + count = 0; + do + { + // Since we could get into an infinite loop here, do a check to make sure we don't. + + count++; + if ( count > 10 ) + { + gi.WDPrintf( "Possible infinite loop in Master State '%s'\n", currentMasterState->getName() ); + if ( count > 50 ) + { + gi.Error( ERR_DROP, "Stopping due to possible infinite state loop\n" ); + break; + } + } + + // Determine the next state to go to + laststate = currentMasterState; + + + currentMasterState = currentMasterState->Evaluate( *this, &master_conditionals ); + if ( !currentMasterState ) + return; + + + if ( laststate != currentMasterState ) + { + laststate->ProcessExitCommands( this ); + lastMasterState = laststate; + + // Process entry commands of the new state + currentMasterState->ProcessEntryCommands( this ); + InitMasterState(); + } + + } while( laststate != currentMasterState ); + + } + +//=================================================================================== +// Behavior Management Functions +// -- These Functions deal with managing the Actor's Behaviors +//=================================================================================== + +// +// Name: SendMoveDone() +// Parameters: CThread *script_thread +// Description: Notifies the script that the behavior is finished +// +void Actor::SendMoveDone( CThread *script_thread ) + { + Event *event; + + if ( script_thread ) + { + event = new Event( EV_MoveDone ); + event->AddEntity( this ); + script_thread->PostEvent( event, FRAMETIME ); + } + } + + +// +// Name: EndBehavior() +// Parameters: None +// Description: Ends the current behavior +// +void Actor::EndBehavior( void ) + { + Event *event; + if ( behavior ) + { + behavior->End( *this ); + // If we are controlled, notify our controller of the behavior's ending. + if ( _controller && (_controller == behavior->GetController()) ) + { + event = new Event ( EV_Actor_BehaviorFinished ); + event->AddInteger( behaviorCode ); + event->AddString( behaviorFailureReason ); + _controller->ProcessEvent( event ); + } + + delete behavior; + behavior = NULL; + } + + + + // Required so that script threads will get the waitfor notification + if ( scriptthread ) + { + CThread *t = scriptthread; + scriptthread = NULL; + + if ( t ) + { + event = new Event( EV_MoveDone ); + event->AddEntity( this ); + t->ProcessEvent( event ); + } + } + + + // Prevent previous behaviors from stopping the next behavior + CancelEventsOfType( EV_Actor_EndBehavior ); + } + + +// +// Name: EndHeadBehavior() +// Parameters: None +// Description: Ends the current head behavior +// +void Actor::EndHeadBehavior( void ) + { + + if ( headBehavior ) + { + headBehavior->End( *this ); + delete headBehavior; + headBehavior = NULL; + } + + // Prevent previous behaviors from stopping the next behavior + CancelEventsOfType( EV_Actor_EndHeadBehavior ); + } + + +// +// Name: EndEyeBehavior() +// Parameters: None +// Description: Ends the current eye behavior +// +void Actor::EndEyeBehavior( void ) + { + if ( eyeBehavior ) + { + eyeBehavior->End( *this ); + delete eyeBehavior; + eyeBehavior = NULL; + } + + // Prevent previous behaviors from stopping the next behavior + CancelEventsOfType( EV_Actor_EndEyeBehavior ); + } + + +// +// Name: EndTorsoBehavior() +// Parameters: None +// Description: Ends the current torso behavior +// +void Actor::EndTorsoBehavior( void ) + { + if ( torsoBehavior ) + { + torsoBehavior->End( *this ); + delete torsoBehavior; + torsoBehavior = NULL; + } + + // Prevent previous behaviors from stopping the next behavior + CancelEventsOfType( EV_Actor_EndTorsoBehavior ); + } + + +// +// Name: EndAllBehaviors() +// Parameters: None +// Description: Ends all current behaviors +// +void Actor::EndAllBehaviors( void ) + { + EndBehavior(); + EndHeadBehavior(); + EndEyeBehavior(); + EndTorsoBehavior(); + } + + +// +// Name: SetBehavior() +// Parameters: Behavior *newbehavior +// Event *startevent +// CThread *newthread +// Description: Sets the current behavior +// +void Actor::SetBehavior ( Behavior *newbehavior, Event *startevent, CThread *newthread ) + { + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + // Delete the unused stuff + + if ( newbehavior ) + delete newbehavior; + if ( startevent ) + delete startevent; + + return; + } + + // End any previous behavior, but don't call EV_MoveDone if we're using the same thread, + // or we'll end THIS behavior + if ( scriptthread == newthread ) + { + scriptthread = NULL; + } + EndBehavior(); + + behavior = newbehavior; + behaviorCode = BEHAVIOR_EVALUATING; + if ( behavior ) + { + Wakeup(); + + if ( startevent ) + { + behavior->ProcessEvent( startevent ); + } + currentBehavior = behavior->getClassname(); + if ( _controller ) behavior->SetController( _controller ); + behavior->SetSelf( this ); + behavior->Begin( *this ); + scriptthread = newthread; + } + } + + +// +// Name: SetHeadBehavior() +// Parameters: Behavior *newbehavior +// Event *startevent +// CThread *newthread +// Description: Sets the current head behavior +// +void Actor::SetHeadBehavior( Behavior *newHeadBehavior, Event *startevent, CThread *newthread ) + { + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + // Delete the unused stuff + + if ( newHeadBehavior ) + delete newHeadBehavior; + if ( startevent ) + delete startevent; + + return; + } + + // End any previous behavior, but don't call EV_MoveDone if we're using the same thread, + // or we'll end THIS behavior + if ( scriptthread == newthread ) + { + scriptthread = NULL; + } + + if ( headBehavior ) + { + currentHeadBehavior = headBehavior->getClassname(); + str newHeadBehaviorName = newHeadBehavior->getClassname(); + + if ( currentHeadBehavior == newHeadBehaviorName ) + { + if ( startevent ) + { + headBehavior->ProcessEvent( startevent ); + } + + return; + } + } + + + EndHeadBehavior(); + + headBehavior = newHeadBehavior; + if ( headBehavior ) + { + Wakeup(); + + if ( startevent ) + { + headBehavior->ProcessEvent( startevent ); + } + currentHeadBehavior = headBehavior->getClassname(); + headBehavior->Begin( *this ); + scriptthread = newthread; + } + } + + +// +// Name: SetEyeBehavior() +// Parameters: Behavior *newbehavior +// Event *startevent +// CThread *newthread +// Description: Sets the current eye behavior +// +void Actor::SetEyeBehavior ( Behavior *newEyeBehavior, Event *startevent, CThread *newthread ) + { + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + // Delete the unused stuff + + if ( newEyeBehavior ) + delete newEyeBehavior; + if ( startevent ) + delete startevent; + + return; + } + + // End any previous behavior, but don't call EV_MoveDone if we're using the same thread, + // or we'll end THIS behavior + if ( scriptthread == newthread ) + { + scriptthread = NULL; + } + EndEyeBehavior(); + + eyeBehavior = newEyeBehavior; + if ( eyeBehavior ) + { + Wakeup(); + + if ( startevent ) + { + eyeBehavior->ProcessEvent( startevent ); + } + currentEyeBehavior = eyeBehavior->getClassname(); + eyeBehavior->Begin( *this ); + scriptthread = newthread; + } + } + + +// +// Name: SetTorsoBehavior() +// Parameters: Behavior *newbehavior +// Event *startevent +// CThread *newthread +// Description: Sets the current torso behavior +// +void Actor::SetTorsoBehavior( Behavior *newTorsoBehavior, Event *startevent, CThread *newthread ) + { + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + // Delete the unused stuff + + if ( newTorsoBehavior ) + delete newTorsoBehavior; + if ( startevent ) + delete startevent; + + return; + } + + // End any previous behavior, but don't call EV_MoveDone if we're using the same thread, + // or we'll end THIS behavior + if ( scriptthread == newthread ) + { + scriptthread = NULL; + } + EndTorsoBehavior(); + + torsoBehavior = newTorsoBehavior; + torsoBehaviorCode = BEHAVIOR_EVALUATING; + if ( torsoBehavior ) + { + //turnThinkOn(); + Wakeup(); + + if ( startevent ) + { + torsoBehavior->ProcessEvent( startevent ); + } + currentTorsoBehavior = torsoBehavior->getClassname(); + torsoBehavior->Begin( *this ); + scriptthread = newthread; + } + } + + +// +// Name: EndBehaviorEvent +// Parameters: Event *ev +// Description: Calls EndBehavior +// +void Actor::EndBehaviorEvent( Event *ev ) + { + EndBehavior(); + } + + +// +// Name: EndHeadBehaviorEvent +// Parameters: Event *ev +// Description: Calls EndHeadBehavior +// +void Actor::EndHeadBehaviorEvent( Event *ev ) + { + EndHeadBehavior(); + } + + +// +// Name: EndEyeBehaviorEvent +// Parameters: Event *ev +// Description: Calls EndEyeBehavior +// +void Actor::EndEyeBehaviorEvent( Event *ev ) + { + EndEyeBehavior(); + } + + +// +// Name: EndTorsoBehaviorEvent +// Parameters: Event *ev +// Description: Calls EndTorsoBehavior +// +void Actor::EndTorsoBehaviorEvent( Event *ev ) + { + EndTorsoBehavior(); + } + + +// +// Name: NotifyBehavior() +// Parameters: Event *ev +// Description: Tells the current behavior that the anim is done +// +void Actor::NotifyBehavior( Event *ev ) + { + if ( behavior ) + { + behavior->ProcessEvent( EV_Behavior_AnimDone ); + SetActorFlag( ACTOR_FLAG_ANIM_DONE, true ); + } + } + + +// +// Name: NotifyHeadBehavior() +// Parameters: Event *ev +// Description: Tells the current head behavior that the anim is done +// +void Actor::NotifyHeadBehavior( Event *ev ) + { + if ( headBehavior ) + { + headBehavior->ProcessEvent( EV_Behavior_AnimDone ); + SetActorFlag( ACTOR_FLAG_ANIM_DONE, true ); + } + } + + +// +// Name: NotifyEyeBehavior() +// Parameters: Event *ev +// Description: Tells the current eye behavior that the anim is done +// +void Actor::NotifyEyeBehavior( Event *ev ) + { + if ( eyeBehavior ) + { + eyeBehavior->ProcessEvent( EV_Behavior_AnimDone ); + SetActorFlag( ACTOR_FLAG_ANIM_DONE, true ); + } + } + + +// +// Name: NotifyTorsoBehavior() +// Parameters: Event *ev +// Description: Tells the current torso behavior that the anim is done +// +void Actor::NotifyTorsoBehavior( Event *ev ) + { + if ( torsoBehavior ) + { + torsoBehavior->ProcessEvent( EV_Behavior_AnimDone ); + SetActorFlag( ACTOR_FLAG_TORSO_ANIM_DONE, true ); + } + } + + + + +//*********************************************************************************************** +// +// Actor type script commands +// +//*********************************************************************************************** + +void Actor::SetDialogMode( Event *ev ) + { + str modeType = ev->GetString(1); + if(stricmp(modeType,"anxious") == 0 ) + { + DialogMode = DIALOG_MODE_ANXIOUS; + return; + } + + if(stricmp(modeType,"normal") == 0 ) + { + DialogMode = DIALOG_MODE_NORMAL; + return; + } + + if(stricmp(modeType,"ignore") == 0) + { + DialogMode = DIALOG_MODE_IGNORE; + SetActorFlag( ACTOR_FLAG_ALLOW_TALK, 0 ); + return; + } + + gi.WDPrintf( "SetDialogMode has an unknown dialog type - - Valid strings are 'anxious', 'normal', or 'ignore'\n"); + gi.WDPrintf( "Defaulting to type 'normal'"); + + DialogMode = DIALOG_MODE_NORMAL; + } + + + +void Actor::RunAlertThread ( Event *ev ) + { + if (alert_thread.length() ) + RunThread(alert_thread); + } + +void Actor::RunDamageThread ( void ) + { + if ( ondamage_threshold > 0 ) + { + if ( ( health <= ondamage_threshold ) && ondamage_thread.length() ) + RunThread( ondamage_thread ); + return; + } + + if ( ondamage_thread.length() ) + RunThread( ondamage_thread ); + + } + + + + + +//*********************************************************************************************** +// +// Targeting functions +// +//*********************************************************************************************** + +qboolean Actor::CloseToEnemy ( const Vector &pos, float howclose ) + { + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + if ( !IsEntityAlive( currentEnemy ) ) + { + return false; + } + + if ( WithinDistance( currentEnemy, howclose ) ) + { + return true; + } + + return false; + } + +void Actor::EyeOffset ( Event *ev ) + { + eyeposition -= eyeoffset; + eyeoffset = ev->GetVector( 1 ); + eyeposition += eyeoffset; + } + +qboolean Actor::EntityInRange ( Entity *ent, float range, float min_height, float max_height , bool XYOnly ) + { + float r; + Vector delta , cent , enemyCent; + float height_diff; + + + // Make sure the entity is alive + + if ( !IsEntityAlive( ent ) ) + { + return false; + } + + cent = centroid; + enemyCent = ent->centroid; + + // See if the entity is in range + if ( XYOnly ) + { + cent.z = 0.0f; + enemyCent.z = 0.0f; + } + + delta = enemyCent - cent; + + if ( ( max_height != 0 ) || ( min_height != 0 ) ) + { + height_diff = delta[ 2 ]; + + if ( ( height_diff < min_height ) || ( height_diff > max_height ) ) + { + return false; + } + + delta[ 2 ] = 0; + } + + r = delta * delta; + + return ( r < ( range * range ) ); + } + + + +//*********************************************************************************************** +// +// Thread based script commands +// +//*********************************************************************************************** + +void Actor::RunThread( Event *ev ) + { + str thread_name; + thread_name = ev->GetString( 1 ); + RunThread(thread_name); + } + +void Actor::RunThread( const str &thread_name ) + { + if ( thread_name.length() <= 0 ) + return; + + ExecuteThread(thread_name,true,this); + + //jhefty: scripting getcurrententity will now work, leaving old code commented + //in case something magical is happening with CreateThread/DelayedStart + + /*CThread *thread; + + thread = Director.CreateThread( thread_name ); + + if ( thread ) + thread->DelayedStart( 0.0f );*/ + } + + + +//*********************************************************************************************** +// +// Path and node management +// +//*********************************************************************************************** + +PathNode *Actor::NearestNodeInPVS ( const Vector &pos ) + { + Vector delta; + PathNode *node; + PathNode *bestnode; + float bestdist; + float dist; + int number_nodes; + int i; + MapCell *cell; + Vector min; + Vector max; + + + cell = thePathManager.GetMapCell( pos ); + + if ( !cell ) + return NULL; + + number_nodes = cell->NumNodes(); + + bestnode = NULL; + bestdist = 999999999.0f; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + + for( i = 0; i < number_nodes; i++ ) + { + node = ( PathNode * )cell->GetNode( i ); + + if ( !node ) + continue; + + delta = node->origin - pos; + + // get the distance squared (faster than getting real distance) + dist = delta * delta; + + if ( ( dist < bestdist ) && gi.inPVS( node->origin, (Vector)pos ) ) + { + bestnode = node; + bestdist = dist; + + // if we're close enough, early exit + if ( dist < 16.0f ) + break; + } + } + + return bestnode; + } + +void Actor::SetPath( Path *newpath ) + { + movementSubsystem->setPath( newpath ); + } + +void Actor::ReserveNodeEvent( Event *ev ) + { + PathNode *node; + Vector pos; + + pos = ev->GetVector( 1 ); + node = thePathManager.NearestNode( pos, this ); + + if ( node && ( !node->entnum || ( node->entnum == entnum ) || ( node->occupiedTime < level.time ) ) ) + { + // Mark node as occupied for a short time + node->occupiedTime = level.time + ev->GetFloat( 2 ); + node->entnum = entnum; + } + } + +void Actor::ReleaseNodeEvent( Event *ev ) + { + PathNode *node; + Vector pos; + + pos = ev->GetVector( 1 ); + node = thePathManager.NearestNode( pos, this ); + + if ( node && ( node->entnum == entnum ) ) + { + node->occupiedTime = 0; + node->entnum = 0; + } + } + +trace_t Actor::Trace( const Vector &end, const char *reason ) const + { + return Trace( origin, end, reason ); + } + +trace_t Actor::Trace( const float distance, const char *reason ) const + { + Vector endPoint(orientation[0][0], orientation[0][1], orientation[0][2]); + endPoint.normalize(); + endPoint *= distance; + endPoint += origin; + return Trace(endPoint, reason ); + } + +trace_t Actor::Trace( const float angle, const float distance, const char *reason ) const + { + vec3_t end; + RotatePointAroundVector( end, orientation[2], orientation[0], angle ); + Vector endPoint(end); + endPoint.normalize(); + endPoint *= distance; + endPoint += origin; + return Trace(endPoint, reason ); + } + +trace_t Actor::Trace( const Vector &begin, const Vector &end, const char *reason ) const + { + return Trace(begin, end, edict->clipmask, reason ); + } + +trace_t Actor::Trace( const Vector &begin, const Vector &end, const int contentMask, const char *reason ) const +{ + Vector beginPoint; + Vector endPoint; + trace_t fullStepUpTrace; + trace_t halfStepUpTrace; + trace_t straightTrace; + trace_t *bestTrace; + float bestFraction; + bool betterTrace; + + + // Try the normal trace first + + beginPoint = begin + movementSubsystem->getStep(); + endPoint = end + movementSubsystem->getStep(); + + fullStepUpTrace = G_Trace( beginPoint, mins , maxs , endPoint, this, contentMask, false, reason ); + + bestTrace = &fullStepUpTrace; + bestFraction = fullStepUpTrace.fraction; + + // If the first trace didn't work very well try it again but only step up half way + + if ( bestFraction < 1.0f || bestTrace->startsolid ) + { + beginPoint = begin + ( movementSubsystem->getStep() / 2.0f ); + endPoint = end + ( movementSubsystem->getStep() / 2.0f ); + + halfStepUpTrace = G_Trace( beginPoint, mins , maxs , endPoint, this, contentMask, false, reason ); + + // See if this trace is the best one + + if ( halfStepUpTrace.fraction > bestFraction && ( !halfStepUpTrace.startsolid || bestTrace->startsolid ) ) + betterTrace = true; + else if ( !halfStepUpTrace.startsolid && bestTrace->startsolid ) + betterTrace = true; + else + betterTrace = false; + + if ( betterTrace ) + { + bestTrace = &halfStepUpTrace; + bestFraction = halfStepUpTrace.fraction; + } + } + + // If the second trace didn't work very well try it again but don't step up at all + + if ( bestFraction < 1.0f || bestTrace->startsolid ) + { + beginPoint = begin; + endPoint = end; + + straightTrace = G_Trace( beginPoint, mins , maxs , endPoint, this, contentMask, false, reason ); + + // See if this trace is the best one + + if ( straightTrace.fraction > bestFraction && ( !straightTrace.startsolid || bestTrace->startsolid ) ) + betterTrace = true; + else if ( !straightTrace.startsolid && bestTrace->startsolid ) + betterTrace = true; + else + betterTrace = false; + + if ( betterTrace ) + { + bestTrace = &straightTrace; + bestFraction = straightTrace.fraction; + } + } + + if ( g_showactortrace->integer ) + { + G_DebugLine( bestTrace->endpos, endPoint, 1.0f, 0.0f, 0.0f, 1.0f ); + G_DebugLine( beginPoint, bestTrace->endpos , 0.0f, 0.0f, 1.0f, 1.0f ); + G_DebugLine( beginPoint, endPoint, 0.0f, 1.0f, 0.0f, 1.0f ); + } + + return *bestTrace; +} + + + +//*********************************************************************************************** +// +// Animation control functions +// +//*********************************************************************************************** + +void Actor::RemoveAnimDoneEvent( void ) + { + animate->SetAnimDoneEvent( NULL ); + + if ( newanimevent ) + { + delete newanimevent; + newanimevent = NULL; + } + + last_anim_event_name = ""; + } + +void Actor::ChangeAnim( void ) + { + //float time; + Vector totalmove; + + if ( ( newanimnum == -1 ) && ( newTorsoAnimNum == -1 ) ) + { + return; + } + + if ( newTorsoAnimNum != -1 ) + { + // If we're changing to the same anim, don't restart the animation + if ( ( newTorsoAnimNum == CurrentAnim(torso) ) && ( TorsoAnimName == newTorsoAnim ) && !( edict->s.torso_frame & FRAME_EXPLICIT ) ) + { + animate->SetAnimDoneEvent( newTorsoAnimEvent , torso ); + } + else + { + TorsoAnimName = newTorsoAnim; + animate->NewAnim( newTorsoAnimNum, newTorsoAnimEvent, torso ); + } + + // clear the new anim variables + newTorsoAnimNum = -1; + newTorsoAnim = ""; + newTorsoAnimEvent = NULL; + } + + if ( newanimnum != -1 ) + { + // If we're changing to the same anim, don't restart the animation + if ( ( newanimnum == CurrentAnim(legs) ) && ( animname == newanim ) && !( edict->s.frame & FRAME_EXPLICIT ) ) + { + animate->SetAnimDoneEvent( newanimevent ); + } + else + { + animname = newanim; + animate->NewAnim( newanimnum, newanimevent, legs ); + //time = gi.Anim_Time( edict->s.modelindex, newanimnum ); + } + + // clear the new anim variables + newanimnum = -1; + newanim = ""; + newanimevent = NULL; + + } + + } + +void Actor::AnimDone( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_ANIM_DONE, true ); + } + +void Actor::TorsoAnimDone( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_TORSO_ANIM_DONE, true ); + } + +qboolean Actor::SetAnim( const str &anim, Event *ev, bodypart_t part, const float animationRate ) + { + int num; + if ( !anim.length() ) + return false; + + if ( !GetActorFlag( ACTOR_FLAG_CAN_CHANGE_ANIM ) ) return false; + + num = gi.Anim_Random( edict->s.modelindex, anim.c_str() ); + + if ( num != -1 ) + { + if ( part == legs ) + { + newanim = anim; + newanimnum = num; + animnum = newanimnum; + + if ( newanimevent ) + delete newanimevent; + + newanimevent = ev; + + if ( newanimevent ) + last_anim_event_name = newanimevent->getName(); + else + last_anim_event_name = ""; + + } + + if ( part == torso ) + { + newTorsoAnim = anim; + newTorsoAnimNum = num; + + if ( newTorsoAnimEvent ) + delete newTorsoAnimEvent; + + newTorsoAnimEvent = ev; + + if ( newTorsoAnimEvent ) + last_torso_anim_event_name = newTorsoAnimEvent->getName(); + else + last_torso_anim_event_name = ""; + } + + if ( actortype == IS_INANIMATE ) + { + // inanimate objects change anims immediately + ChangeAnim(); + } + + edict->s.animationRate = animationRate; + return true; + + } + + warning( "Actor::SetAnim", "Actor \"%s\" has no anim named \"%s\"\n", model.c_str(), anim.c_str() ); + + // kill the event + if ( ev ) + { + delete ev; + } + + return false; + } + +void Actor::AnimateOnce( Event *ev ) +{ + animate->RandomAnimate( ev->GetString( 1 ), EV_StopAnimating ); +} + +qboolean Actor::SetAnim( const str &anim, Event &ev, bodypart_t part, const float animationRate ) + { + Event * event; + + event = new Event( ev ); + assert(event); + + return SetAnim( anim, event, part, animationRate ); + } + +void Actor::SetAnim( Event *ev ) +{ + + if ( ev->NumArgs() > 1 ) + { + SetAnim( ev->GetString( 1 ), NULL, legs, ev->GetFloat(2) ); + } + else + { + SetAnim( ev->GetString( 1 ) ); + } + ChangeAnim(); +} + +void Actor::Anim( Event *ev ) + { + Event *e; + + if ( !ModeAllowed( ACTOR_MODE_SCRIPT ) ) + { + if ( mode == ACTOR_MODE_TALK ) + PostEvent( *ev, FRAMETIME ); + else if ( mode == ACTOR_MODE_AI ) + SendMoveDone( ev->GetThread() ); + return; + } + + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + e = new Event( EV_Behavior_Args ); + + e->SetSource( ev->GetSource() ); + + if ( ev->GetSource() == EV_FROM_SCRIPT ) + { + StartMode( ACTOR_MODE_SCRIPT ); + + e->SetThread( ev->GetThread() ); + e->SetLineNumber( ev->GetLineNumber() ); + } + + e->AddToken( ev->GetToken( 1 ) ); + + SetBehavior( new PlayAnim, e, ev->GetThread() ); + } + + + +//*********************************************************************************************** +// +// Script conditionals +// +//*********************************************************************************************** + +void Actor::IfEnemyVisibleEvent( Event *ev ) + { + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + + if ( currentEnemy ) + ev->ReturnInteger( sensoryPerception->CanSeeEntity( this , currentEnemy, true, true ) ); + else + ev->ReturnInteger( false ); + + + } + +void Actor::IfNearEvent( Event *ev ) + { + CThread *thread; + Entity *ent; + Entity *bestent; + float bestdist; + float dist; + str name; + Vector delta; + float distance; + TargetList *tlist; + int n; + int i; + + thread = ev->GetThread(); + assert( thread ); + if ( !thread ) + { + return; + } + + name = ev->GetString( 1 ); + distance = ev->GetFloat( 2 ); + + if ( name[ 0 ] == '*' ) + { + ent = ev->GetEntity( 1 ); + ev->ReturnInteger( WithinDistance( ent, distance ) ); + } + else if ( name[ 0 ] == '$' ) + { + bestent = NULL; + bestdist = distance * distance; + + tlist = world->GetTargetList( str( &name[ 1 ] ) ); + n = tlist->list.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ent = tlist->list.ObjectAt( i ); + delta = centroid - ent->centroid; + dist = delta * delta; + if ( dist <= bestdist ) + { + bestent = ent; + bestdist = dist; + } + } + + ev->ReturnInteger( ( bestent != NULL ) ); + } + else + { + bestent = NULL; + bestdist = distance * distance; + + ent = NULL; + + for( ent = findradius( ent, origin, distance ) ; ent ; ent = findradius( ent, origin, distance ) ) + { + if ( ent->inheritsFrom( name.c_str() ) ) + { + delta = centroid - ent->centroid; + dist = delta * delta; + if ( dist <= bestdist ) + { + bestent = ent; + bestdist = dist; + } + } + } + + ev->ReturnInteger( bestent != NULL ); + } + } + +void Actor::IfCanHideAtEvent( Event *ev ) + { + PathNode *node; + Vector pos; + CThread *thread; + bool result; + + thread = ev->GetThread(); + assert( thread ); + if ( !thread ) + { + return; + } + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + pos = ev->GetVector( 1 ); + node = thePathManager.NearestNode( pos, this ); + + result = false; + if ( sensoryPerception ) + { + if ( node && ( node->nodeflags & ( AI_DUCK | AI_COVER ) ) && + !sensoryPerception->CanSeeEntity( pos , currentEnemy , true , true ) ) + { + if ( !node->entnum || ( node->entnum == entnum ) || ( node->occupiedTime < level.time ) ) + { + result = true; + } + } + } + + ev->ReturnInteger( result ); + } + +void Actor::IfEnemyWithinEvent( Event *ev ) + { + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + ev->ReturnInteger( false ); + return; + } + + ev->ReturnInteger( WithinDistance( currentEnemy, ev->GetFloat( 1 ) ) ); + } + +//*********************************************************************************************** +// +// Sound reaction functions +// +//*********************************************************************************************** + +void Actor::NoPainSounds( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_NO_PAIN_SOUNDS, true ); + } + +void Actor::HeardSound( Event *ev ) + { + Vector location; + int soundType = SOUNDTYPE_GENERAL; + + location = ev->GetVector( 2 ); + + if (ev->NumArgs() > 2) + { + soundType = ev->GetInteger( 3 ); + } + + Entity *soundEnt; + Entity *theEntity; + soundEnt = ev->GetEntity( 1 ); + + theEntity = soundEnt; + if ( soundEnt && soundEnt->isSubclassOf( Weapon ) ) + { + Weapon *soundWeapon; + soundWeapon = ( Weapon*)soundEnt; + theEntity = soundWeapon->GetOwner(); + } + + + sensoryPerception->Stimuli( STIMULI_SOUND, location, soundType ); + if( GetActorFlag( ACTOR_FLAG_NOISE_HEARD ) ) + { + enemyManager->TryToAddToHateList( theEntity ); + } + + //HeardDialog( soundType ); + + } + + + + + +void Actor::BroadcastAlert( float rad ) + { + Vector enemypos; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + if ( !( this->flags & FL_NOTARGET ) ) + { + enemypos = currentEnemy->centroid; + G_BroadcastAlert( this, centroid, enemypos, rad ); + } + } + +void Actor::BroadcastAlert( float rad, int soundtype ) + { + Vector enemypos; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + if ( !( this->flags & FL_NOTARGET ) ) + { + enemypos = currentEnemy->centroid; + G_BroadcastSound( this, centroid, rad, soundtype ); + } + } + +void Actor::BroadcastAlert( Event *ev ) + { + float rad = ev->GetFloat( 1 ); + BroadcastAlert( rad , SOUNDTYPE_ALERT ); + } + +//*********************************************************************************************** +// +// Pain and death related functions +// +//*********************************************************************************************** + +void Actor::Pain( Event *ev ) + { + float damage; + Entity *ent; + int mod; + Vector direction; + Vector position; + Vector dir; + State *tempState; + bool showPain; + + + damage = ev->GetFloat( 1 ); + ent = ev->GetEntity( 2 ); + mod = ev->GetInteger( 3 ); + position = ev->GetVector( 4 ); + direction = ev->GetVector( 5 ); + + if ( ev->NumArgs() > 5 ) + { + showPain = ev->GetBoolean( 6 ); + } + else + showPain = false; + + + // Add to the players action level and (hit) count + + /* if ( damage && !deadflag && ent && ent->isSubclassOf( Player ) ) + { + Player *player = (Player *)ent; + + //player->IncreaseActionLevel( damage / 4.0f ); + if ( player->p_heuristics ) + player->p_heuristics->IncrementShotsHit(); + } */ + + if ( deadflag ) + { + // Do gib stuff + + if ( statemap ) + { + tempState = statemap->FindState( "GIB" ); + + if ( tempState ) + tempState = tempState->Evaluate( *this, &conditionals ); + + if ( tempState ) + { + tempState->ProcessEntryCommands( this ); + } + } + return; + } + + if ( ( damage > 0.0f ) && ( next_pain_sound_time <= level.time ) && !GetActorFlag( ACTOR_FLAG_NO_PAIN_SOUNDS ) ) + { + next_pain_sound_time = level.time + 0.4f + G_Random( 0.2f ); + BroadcastSound(); + } + + + if ( level.time >= _nextPlayPainSoundTime && !GetActorFlag( ACTOR_FLAG_NO_PAIN_SOUNDS )) + { + _nextPlayPainSoundTime = level.time + _playPainSoundInterval; + Sound( "snd_generalpain" ); + } + + if ( ( mod == MOD_BULLET ) && behavior && ( currentBehavior == "Pain" ) ) + { + bullet_hits++; + } + + // Determine which pain flags to set + + AddStateFlag( STATE_FLAG_SMALL_PAIN ); + + if ( damage < pain_threshold ) + { + if ( G_Random( 1.0f ) < ( damage / pain_threshold ) ) + { + if ( ( means_of_death == MOD_SWORD ) || ( means_of_death == MOD_AXE ) || ( means_of_death == MOD_FIRESWORD ) || + ( means_of_death == MOD_ELECTRICSWORD ) ) + pain_type = PAIN_BIG; + else + pain_type = PAIN_SMALL; + AddStateFlag( STATE_FLAG_IN_PAIN ); + } + } + else + { + pain_type = PAIN_BIG; + AddStateFlag( STATE_FLAG_IN_PAIN ); + } + + //------------------------------------------------------------------- + // Pain Based on Damage Modification System: + // -- Because I don't want to screw anyone over, I'm leaving + // the old BIG_PAIN/SMALL_PAIN stuff in place, and it will + // work like it always has, so nobody should be screwed by the + // changes I'm making + // + // However, in the future, we want to get away from this system + // and use the damage modification system. Basically, we have + // a boolean flag in this function, which is coming from + // the DMS. If it's true, then I'm setting the SHOW_PAIN state flag + // to true. To show pain, from the state machine, use the SHOW_PAIN + // conditional + //-------------------------------------------------------------------- + + // Check for GPM Pain Override Here + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( gpm->hasObject(getArchetype()) ) + { + str propName = MOD_NumToName(mod); + float painChanceForActor = 0.0f; + + if ( propName.length() ) + { + str objName; + objName = getArchetype(); + objName += ".PainChance"; + + if ( gpm->hasObject( objName ) ) + { + if ( gpm->hasProperty( objName , propName ) ) + painChanceForActor = gpm->getFloatValue( objName, propName ); + } + } + + if ( G_Random() <= painChanceForActor ) + showPain = true; + else + showPain = false; + } + + if ( ( level.time >= next_forced_pain_time ) || ( level.time >= next_pain_time && showPain ) ) + { + next_pain_time = level.time + min_pain_time; + next_forced_pain_time = level.time + max_pain_time; + AddStateFlag( STATE_FLAG_SHOW_PAIN ); + } + + + // Determine pain angles + + if ( Vector( position - centroid ).length() > 1.0f ) + dir = centroid - position; + else + dir = direction; + + dir = dir * -1.0f; + + pain_angles = dir.toAngles(); + + pain_angles[YAW] = AngleNormalize180( angles[YAW] - pain_angles[YAW] ); + pain_angles[PITCH] = AngleNormalize180( angles[PITCH] - pain_angles[PITCH] ); + } + +void Actor::StunEvent( Event *ev ) + { + float time; + + time = ev->GetFloat( 1 ); + + SetActorFlag( ACTOR_FLAG_STUNNED, true ); + + stunned_end_time = level.time + time; + } + +void Actor::CheckStun( void ) + { + if ( GetActorFlag( ACTOR_FLAG_STUNNED ) && ( stunned_end_time <= level.time ) ) + SetActorFlag( ACTOR_FLAG_STUNNED, false ); + } + +void Actor::Dead( Event *ev ) + { + Vector min, max; + + // stop animating legs + animate->StopAnimatingAtEnd(); + + // Make sure we can walk through this guys corpse + edict->clipmask = MASK_DEADSOLID; + if ( edict->solid != SOLID_NOT ) + setContents( CONTENTS_CORPSE ); + + if ( edict->s.torso_anim & ANIM_BLEND ) + animate->StopAnimatingAtEnd( torso ); + + edict->s.eFlags |= EF_DONT_PROCESS_COMMANDS; + + if ( !groundentity && ( velocity != vec_zero ) && ( movetype != MOVETYPE_STATIONARY ) ) + { + // wait until we hit the ground + PostEvent( EV_Actor_Dead, FRAMETIME ); + return; + } + + // don't allow them to fly, think, or swim anymore + flags &= ~( FL_SWIM | FL_FLY ); + turnThinkOff(); + + deadflag = DEAD_DEAD; + setMoveType( MOVETYPE_NONE ); + setOrigin( origin ); + + if ( trigger ) + { + trigger->ProcessEvent( EV_Remove ); + } + + if ( spawnparent ) + PostEvent( EV_Actor_Fade, .5f ); + else + PostEvent( EV_Actor_Fade, 5.0f ); + } + +void Actor::KilledEffects( Entity *attacker ) + { + Entity *ent; + Event *event; + str target_name; + + if ( g_debugtargets->integer ) + { + G_DebugTargets( this, "Actor::KilledEffects" ); + } + + // + // kill the killtargets + // + target_name = KillTarget(); + if ( target_name && strcmp( target_name, "" ) ) + { + ent = NULL; + do + { + ent = G_FindTarget( ent, target_name ); + if ( !ent ) + { + break; + } + ent->PostEvent( EV_Remove, 0.0f ); + } + while ( 1 ); + } + + // + // fire targets + // + target_name = Target(); + if ( target_name && strcmp( target_name, "" ) ) + { + ent = NULL; + do + { + ent = G_FindTarget( ent, target_name ); + if ( !ent ) + { + break; + } + + event = new Event( EV_Activate ); + event->AddEntity( attacker ); + ent->PostEvent( event, 0.0f ); + } + while ( 1 ); + } + // + // see if we have a kill_thread + // + if ( kill_thread.length() > 1 ) + { + CThread * thread; + // + // create the thread, but don't start it yet + // + thread = ExecuteThread( kill_thread, false, this ); + if ( !thread ) + warning( "Killed", "could not process kill_thread" ); + + } + } + +void Actor::Killed( Event *ev ) + { + Vector position; + ClassDef *cls; + str deathanim; + str newdeathanim; + Entity *attacker; + float damage; + qboolean fallingDeath; + Weapon *weapon = 0; + + attacker = ev->GetEntity( 1 ); + damage = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 4 ) + fallingDeath = ev->GetBoolean( 5 ); + else + fallingDeath = false; + + if ( ev->NumArgs() > 5 ) + weapon = (Weapon *)ev->GetEntity( 6 ); + + // Update boss health if necessary + + if ( GetActorFlag( ACTOR_FLAG_UPDATE_BOSS_HEALTH ) && max_boss_health ) + { + char bosshealth_string[20]; + sprintf( bosshealth_string, "%.5f", health / max_boss_health ); + gi.cvar_set( "bosshealth", bosshealth_string ); + } + + //if the actor is a teammate, update the teammates killed #. + //We dont who the attacker is. + if(actortype == IS_TEAMMATE) + { + Player* player; + player = GetPlayer(0); + + if(player->p_heuristics) + { + player->p_heuristics->IncrementTeammatesKilled(); + } + if( player->client ) + { + ++player->client->ps.stats[ STAT_TEAMMATES_KILLED ]; + } + } + else + { + UnreserveCurrentHelperNode(); + } + + // Add to the players action level + + if ( damage && attacker && attacker->isSubclassOf( Player ) ) + { + Player *player = (Player *)attacker; + + //player->IncreaseActionLevel( damage / 4.0f ); + + // Calculate the number of points for this kill (0, if we're not using, + // the gameplay system). + float points = 0.0f; + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + GameplayFormulaData fd(player, this, weapon, player->getAttackType()); + if ( gpm->hasObject(player->getArchetype()) && gpm->hasFormula("Points")) + points = gpm->calculate("Points", fd); + player->AwardPoints((int)points); + + if(actortype == IS_ENEMY) + { + if(player->p_heuristics) + { + player->p_heuristics->IncrementEnemiesKilled(); + } + if( player->client ) + { + ++player->client->ps.stats[ STAT_ENEMIES_KILLED ]; + } + } + } + + if ( damage && attacker && attacker->isSubclassOf( Actor ) ) + { + Actor *actor = (Actor*)attacker; + + actor->InContext( "killedenemy" , 0 ); + } + + // If we have a behavior going for some reason, kill it now. +// if ( behavior ) +// { +// behavior->End(*this); +// behavior = NULL; +// } + + if ( attacker ) + KilledEffects( attacker ); + + if ( next_pain_sound_time <= level.time && !GetActorFlag( ACTOR_FLAG_NO_PAIN_SOUNDS ) ) + { + next_pain_sound_time = level.time + 0.4f + G_Random( 0.2f ); + BroadcastSound(); + } + + if ( !GetActorFlag( ACTOR_FLAG_DIE_COMPLETELY ) ) + return; + + // See if means of death should be bumped up from MOD_BULLET to MOD_FAST_BULLET + + if ( means_of_death == MOD_BULLET ) + { + if ( bullet_hits < 5 ) + bullet_hits = 0; + + if ( (int)G_Random( 100 ) < ( bullet_hits * 10 ) ) + means_of_death = MOD_FAST_BULLET; + } + + if ( means_of_death == MOD_ELECTRIC ) + { + Event *event; + + event = new Event( EV_DisplayEffect ); + event->AddString( "electric" ); + ProcessEvent( event ); + + event = new Event( EV_DisplayEffect ); + event->AddString( "noelectric" ); + PostEvent( event, 3.0f + G_Random( 2.0f ) ); + } + + if ( + means_of_death == MOD_EXPLOSION || + means_of_death == MOD_SMALL_EXPLOSION || + means_of_death == MOD_PLASMASHOTGUN + ) + { + float chance = G_Random(); + if ( chance >= .45 ) + { + Vector attackerToSelf = origin - attacker->origin; + attackerToSelf.z = deathKnockbackVerticalValue; + attackerToSelf.normalize(); + attackerToSelf *= deathKnockbackHorizontalValue; + velocity = attackerToSelf; + } + } + + // Make sure all bones are put back in their normal positions + + if ( edict->s.bone_tag[ ACTOR_MOUTH_TAG ] != -1 ) + SetControllerAngles( ACTOR_MOUTH_TAG, vec_zero ); + + if ( edict->s.bone_tag[ ACTOR_HEAD_TAG ] != -1 ) + SetControllerAngles( ACTOR_HEAD_TAG, vec_zero ); + + if ( edict->s.bone_tag[ ACTOR_TORSO_TAG ] != -1 ) + SetControllerAngles( ACTOR_TORSO_TAG, vec_zero ); + + if ( edict->s.bone_tag[ ACTOR_LEYE_TAG ] != -1 ) + SetControllerAngles( ACTOR_LEYE_TAG, vec_zero ); + + if ( edict->s.bone_tag[ ACTOR_REYE_TAG ] != -1 ) + SetControllerAngles( ACTOR_REYE_TAG, vec_zero ); + + + + if ( !fallingDeath ) + { + // Stop behavior + cls = getClass( "idle" ); + SetBehavior( ( Behavior * )cls->newInstance() ); + } + + //If the MissionFailed keeps coming up, make sure the tiki file is set correctly, + if ( !GetActorFlag( ACTOR_FLAG_ALLOWED_TO_KILL ) && attacker && attacker->isSubclassOf( Player ) ) + G_MissionFailed( "CivilianKilled" ); + + // don't allow them to fly or swim anymore + flags &= ~( FL_SWIM | FL_FLY ); + + deadflag = DEAD_DYING; + + groundentity = NULL; + + edict->svflags |= SVF_DEADMONSTER; + + stopStasis(); + + if ( !GetActorFlag( ACTOR_FLAG_STAYSOLID ) ) + { + edict->clipmask = MASK_DEADSOLID; + + if ( edict->solid != SOLID_NOT ) + setContents( CONTENTS_CORPSE ); + } + + // Stop the actor from talking + + if ( GetActorFlag( ACTOR_FLAG_DIALOG_PLAYING ) ) + { + CancelEventsOfType( EV_SetControllerAngles ); + StopSound( CHAN_DIALOG ); + } + + // Remove the actor from it's group + //groupcoordinator->RemoveEntityFromGroup( this , GetGroupID() ); + groupcoordinator->MemberDied( this , GetGroupID() ); + + if ( !fallingDeath ) + { + deathanim = "death"; + + if ( GetActorFlag( ACTOR_FLAG_IN_ALCOVE ) ) + deathanim = "death_alcove"; + + // See if this actor has a death state in its state machine + + if ( statemap ) + { + int count = 0; + State *temp_state; + State *last_temp_state = NULL; + str temp_anim; + + if ( behavior ) + { + behavior->End( *this ); + delete behavior; + behavior = NULL; + } + + if ( torsoBehavior ) + { + torsoBehavior->End( *this ); + delete torsoBehavior; + torsoBehavior = NULL; + } + + temp_state = statemap->FindState( "DEATH" ); + + if ( temp_state ) + { + do + { + count++; + if ( count > 50 ) + { + gi.Error( ERR_DROP, "Stopping due to possible infinite state loop in death state\n" ); + break; + } + + // Process the current state + if ( last_temp_state != temp_state ) + { + // Get the new animation name + + temp_anim = temp_state->getLegAnim( *this, &conditionals ); + + if ( temp_anim.length() ) + newdeathanim = temp_anim; + + // Process exit commands of the last state + + if ( last_temp_state ) + last_temp_state->ProcessExitCommands( this ); + + // Process the entry commands of the new state + + temp_state->ProcessEntryCommands( this ); + } + + // Determine the next state to go to + + last_temp_state = temp_state; + temp_state = temp_state->Evaluate( *this, &conditionals ); + } + while ( last_temp_state != temp_state ); + } + + if ( newdeathanim.length() > 0 ) + deathanim = newdeathanim; + } + + // Play the death animation + + if ( animate ) + { + animate->ClearTorsoAnim(); + animate->ClearLegsAnim(); + } + SetAnim( deathanim, EV_Actor_Dead , legs); + + ChangeAnim(); + } + + // See if we were spawned by another actor + + if ( spawnparent ) + { + spawnparent->num_of_spawns--; + } + + // See if we should notify others + + if ( GetActorFlag( ACTOR_FLAG_NOTIFY_OTHERS_AT_DEATH ) ) + NotifyOthersOfDeath(); + + // Do Game Specific Death Stuff + if ( gameComponent && attacker ) + gameComponent->HandleDeath( attacker ); + + SpawnItems(); + DropItemsOnDeath(); + + if ( means_of_death == MOD_GIB ) + { + Event *event; + int i; + + if ( blood_model.length() == 0 ) + blood_model = "fx/fx_bspurt.tik"; + + if ( max_gibs == 0 ) + max_gibs = 4; + + for( i = 0 ; i < 4 ; i++ ) + { + event = new Event( EV_Sentient_SpawnBloodyGibs ); + event->AddInteger( max_gibs ); + ProcessEvent( event ); + } + + PostEvent( EV_Remove, 0.0f ); + } + + //Gotta Make Sure that this guy dies if it's from a falling death + if ( fallingDeath ) + { + PostEvent ( EV_Actor_Dead, 0.0f ); + PostEvent ( EV_Remove , 5.0f ); + } + } + +qboolean Actor::RespondToHitscan( void ) + { + //First Check our response chance value, we may not need to do anything at all + if( G_Random() > hitscan_response_chance ) + { + SetActorFlag( ACTOR_FLAG_INCOMING_HITSCAN , false ); + return false; + } + + + //Let the Statemachine tell us what to do + if ( statemap ) + { + + State *temp_state; + temp_state = statemap->FindState( "INCOMING_HITSCAN" ); + + if ( temp_state ) + { + SetActorFlag( ACTOR_FLAG_RESPONDING_TO_HITSCAN, true ); + currentState = temp_state; + ProcessActorStateMachine(); + } + else + { + SetActorFlag( ACTOR_FLAG_INCOMING_HITSCAN , false ); + return false; + } + } + + //Lastly clear our flag + SetActorFlag( ACTOR_FLAG_INCOMING_HITSCAN , false ); + SetActorFlag( ACTOR_FLAG_RESPONDING_TO_HITSCAN, false ); + + return true; + } + +void Actor::HandleGameSpecificEvent( Event *ev ) + { + if ( gameComponent ) + gameComponent->HandleEvent( ev ); + } + +void Actor::SetHitscanResponse( Event *ev ) + { + hitscan_response_chance = ev->GetFloat ( 1 ); + + if (hitscan_response_chance > 1.0f ) + hitscan_response_chance = 1.0f; + + if (hitscan_response_chance < 0.0f ) + hitscan_response_chance = 0.0f; + } + +void Actor::SetDieCompletely( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_DIE_COMPLETELY, ev->GetBoolean( 1 ) ); + } + +void Actor::SetBleedAfterDeath( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_BLEED_AFTER_DEATH, ev->GetBoolean( 1 ) ); + } + +void Actor::SpawnGib( Event *ev ) + { + RealSpawnGib( false, ev ); + } + +void Actor::SpawnGibAtTag( Event *ev ) + { + RealSpawnGib( true, ev ); + } + +void Actor::RealSpawnGib( qboolean use_tag, Event *ev ) + { + str tag_name; + Gib *gib; + Entity *ent = NULL; + Vector final_gib_offset; + Vector orig; + Vector dir; + int current_arg; + float final_pitch; + float vel; + trace_t trace; + float time; + float pitch_change; + float pitch_vel; + str attach_tag_name; + Vector offset; + str cap_name; + float width; + const char *current_arg_str; + int current_surface; + const char *current_surface_name; + qboolean use_blood; + str blood_name; + str blood_splat_name; + str blood_model_name; + float blood_splat_size; + Vector gib_mins; + Vector gib_maxs; + float m; + qboolean at_least_one_visible_surface = false; + int surface_length; + int tagnum; + orientation_t orn; + float raw_offset; + Vector raw_offset_dir; + Vector real_tag_pos; + Vector real_tag_dir; + Vector real_tag_angles; + + SetActorFlag( ACTOR_FLAG_SPAWN_FAILED, true ); + + if ( !com_blood->integer ) + return; + + if ( GetActorFlag( ACTOR_FLAG_FADING_OUT ) ) + return; + + if ( ev->NumArgs() < 5 ) + return; + + if ( use_tag ) + { + attach_tag_name = ev->GetString( 1 ); + raw_offset = ev->GetFloat( 2 ); + width = ev->GetFloat( 3 ); + + // Get all the tag information + + tagnum = gi.Tag_NumForName( edict->s.modelindex, attach_tag_name.c_str() ); + + if ( tagnum == -1 ) + return; + + GetRawTag( tagnum, &orn ); + GetTag( tagnum, &real_tag_pos, &real_tag_dir ); + + real_tag_angles = real_tag_dir.toAngles(); + + // Determine the final pitch of the gib + + final_pitch = AngleNormalize180( angles[PITCH] - real_tag_angles[PITCH] ); + + // Determine the offset of the gib + + raw_offset_dir = orn.axis[0]; + + offset = orn.origin; + offset += raw_offset * raw_offset_dir; + MatrixTransformVector( offset, orientation, orig ); + orig += origin; + } + else + { + offset = ev->GetVector( 1 ); + final_pitch = ev->GetFloat( 2 ); + width = ev->GetFloat( 3 ); + + MatrixTransformVector( offset, orientation, orig ); + orig += origin; + } + + // Determine the mass + + m = mass; + + if ( m < 50.0f ) + m = 50.0f; + else if ( m > 250.0f ) + m = 250.0f; + + // Determine which blood spurt & splat to use for the gib + + blood_name = GetBloodSpurtName(); + blood_splat_name = GetBloodSplatName(); + blood_splat_size = GetBloodSplatSize(); + + if ( blood_name.length() && blood_splat_name.length() ) + use_blood = true; + else + use_blood = false; + + if ( GetActorFlag( ACTOR_FLAG_BLEED_AFTER_DEATH ) ) + blood_model_name = blood_model; + + // Get the mins and maxs for this gib + + gib_mins = Vector( -width, -width, -width ); + gib_maxs = Vector( width, width, width ); + + // Make sure we can spawn in a gib here + + trace = G_Trace( orig, gib_mins, gib_maxs, orig, NULL, MASK_DEADSOLID, false, "spawngib" ); + + if ( trace.allsolid || trace.startsolid ) + return; + + // Make sure at least one of the surfaces is not hidden + + for( current_arg = 5 ; current_arg <= ev->NumArgs() ; current_arg++ ) + { + current_arg_str = ev->GetString( current_arg ); + + for( current_surface = 0 ; current_surface < numsurfaces ; current_surface++ ) + { + current_surface_name = gi.Surface_NumToName( edict->s.modelindex, current_surface ); + + if ( current_arg_str[ strlen( current_arg_str ) - 1 ] == '*' ) + surface_length = strlen( current_arg_str ) - 1; + else + surface_length = strlen( current_arg_str ); + + if ( Q_stricmpn( current_surface_name, current_arg_str, surface_length ) == 0 ) + { + if ( !( edict->s.surfaces[ current_surface ] & MDL_SURFACE_NODRAW ) ) + at_least_one_visible_surface = true; + } + } + } + + if ( !at_least_one_visible_surface ) + return; + + // Determine time till it hits the ground + + vel = 100.0f + G_Random( 200.0f * ( 2.0f - ( ( m - 50.0f ) / 200.0f ) ) ); + + time = SpawnGetTime( vel , orig, gib_mins, gib_maxs ); + + // Flip final pitch 180 degrees? + + if ( G_Random() > .5f ) + { + final_pitch += 180.0f; + final_pitch = AngleNormalize360( final_pitch ); + } + + // Calculate the pitch change and velocity + + pitch_change = AngleNormalize180( final_pitch - angles[PITCH] ); + pitch_vel = pitch_change / time; + + // Spawn in the hidden gib + + gib = new Gib( "", use_blood, blood_name, blood_model_name, blood_splat_name, blood_splat_size, final_pitch ); + + gib->setOrigin( orig ); + + gib->angles[PITCH] = angles[PITCH]; + gib->angles[ROLL] = 0; + + if ( use_tag ) + gib->angles[YAW] = real_tag_angles[YAW]; + else + gib->angles[YAW] = angles[YAW]; + + gib->setAngles( gib->angles ); + + gib->velocity = Vector( G_CRandom( 150.0f * ( 2.0f - ( ( m - 50.0f ) / 200.0f ) ) ), + G_CRandom( 150.0f * ( 2.0f - ( ( m - 50.0f ) / 200.0f ) ) ), vel ); + gib->avelocity = Vector( pitch_vel, G_CRandom( 300.0f ), 0.0f ); + + gib->setSize( gib_mins, gib_maxs ); + + gib->edict->svflags |= SVF_DEADMONSTER; + gib->edict->clipmask = MASK_DEADSOLID; + + gib->setSolidType( SOLID_BBOX ); + gib->setContents( CONTENTS_SHOOTABLE_ONLY ); + gib->link(); + + // Spawn in the visible gib + + ent = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + ent->setModel( model ); + + // Make sure the init stuff in the tiki get processed now + + //ent->ProcessPendingEvents(); + ent->CancelPendingEvents(); + + // Make sure no client side commands are processed + + ent->edict->s.eFlags |= EF_DONT_PROCESS_COMMANDS; + + // Set the animation to the current anim & frame of the actor + ent->animate->RandomAnimate( animate->AnimName() ); + ent->animate->SetFrame( CurrentFrame() ); + + ent->setAngles( angles ); + ent->bind( gib, true ); + ent->gravity = 0; + + final_gib_offset = offset * -1.0f; + ent->setOrigin( final_gib_offset ); + + ent->setMoveType( MOVETYPE_BOUNCE ); + ent->setSolidType( SOLID_NOT ); + ent->showModel(); + + // Hide all of the surfaces on the gib + + ent->SurfaceCommand( "all", "+nodraw" ); + + cap_name = ev->GetString( 4 ); + + // Show the cap surface on the gib and the actor + + if ( cap_name.length() ) + { + ent->SurfaceCommand( cap_name.c_str(), "-nodraw" ); + + SurfaceCommand( cap_name.c_str(), "-nodraw" ); + } + + // Show and hide all of the rest of the necessary surfaces + + for ( current_arg = 5 ; current_arg <= ev->NumArgs() ; current_arg++ ) + { + current_arg_str = ev->GetString( current_arg ); + + if ( current_arg_str[ strlen( current_arg_str ) - 1 ] == '*' ) + surface_length = strlen( current_arg_str ) - 1; + else + surface_length = strlen( current_arg_str ); + + for( current_surface = 0 ; current_surface < numsurfaces ; current_surface++ ) + { + current_surface_name = gi.Surface_NumToName( edict->s.modelindex, current_surface ); + + if ( Q_stricmpn( current_surface_name, current_arg_str, surface_length ) == 0 ) + { + if ( !( edict->s.surfaces[ current_surface ] & MDL_SURFACE_NODRAW ) ) + { + // Show this surface on the gib + ent->SurfaceCommand( current_surface_name, "-nodraw" ); + + // Hide this surface on the actor + SurfaceCommand( current_surface_name, "+nodraw" ); + } + } + } + } + + // Make sure the gibs go away after a while + + if ( GetActorFlag( ACTOR_FLAG_DEATHFADE ) ) + { + ent->PostEvent( EV_Fade, 10.0f ); + gib->PostEvent( EV_Fade, 10.5f ); + } + else if ( GetActorFlag( ACTOR_FLAG_DEATHSHRINK ) ) + { + ent->PostEvent( EV_FadeOut, 10.0f ); + gib->PostEvent( EV_FadeOut, 10.5f ); + } + else + { + ent->PostEvent( EV_Unbind, 10.0f ); + ent->PostEvent( EV_DeathSinkStart, 12.0f ); + gib->PostEvent( EV_Remove, 10.5f ); + } + + // Mark the spawn as being successful + + SetActorFlag( ACTOR_FLAG_SPAWN_FAILED, false ); + + // Play the severed sound + + ent->Sound( "impact_sever", CHAN_BODY ); + } + +void Actor::SpawnNamedGib( Event *ev ) + { + str gib_name; + str tag_name; + Gib *gib; + Vector orig; + float final_pitch; + float vel; + trace_t trace; + float time; + float pitch_change; + float pitch_vel; + float width; + Vector gib_mins; + Vector gib_maxs; + + + SetActorFlag( ACTOR_FLAG_SPAWN_FAILED, true ); + + /* + if ( !com_blood->integer ) + return; + */ + + if ( GetActorFlag( ACTOR_FLAG_FADING_OUT ) ) + return; + + // Get all of the parameters + + gib_name = ev->GetString( 1 ); + tag_name = ev->GetString( 2 ); + final_pitch = ev->GetFloat( 3 ); + width = ev->GetFloat( 4 ); + + // Get the tag position + + GetTag( tag_name, &orig ); + + // Get the mins and maxs for this gib + + gib_mins = Vector( -width, -width, -width ); + gib_maxs = Vector( width, width, width ); + + // Make sure we can spawn in a gib here + + trace = G_Trace( orig, gib_mins, gib_maxs, orig, NULL, MASK_DEADSOLID, false, "spawnnamedgib1" ); + + if ( trace.allsolid || trace.startsolid ) + SetActorFlag( ACTOR_FLAG_SPAWN_FAILED, true ); + + // Determine time till it hits the ground + + vel = 400.0f + G_Random( 400.0f ); + + time = SpawnGetTime( vel , orig, gib_mins, gib_maxs ); + + pitch_change = AngleNormalize180( final_pitch - angles[PITCH] ); + pitch_vel = pitch_change / time; + + // Spawn the gib + + gib = new Gib( gib_name, false, "", "", "", final_pitch ); + + gib->setOrigin( orig ); + gib->setAngles( angles ); + + gib->velocity = Vector( G_CRandom( 200.0f ), G_CRandom( 200.0f ), vel ); + gib->avelocity = Vector( pitch_vel, G_CRandom( 300.0f ), 0.0f ); + + gib->edict->svflags |= SVF_DEADMONSTER; + gib->edict->clipmask = MASK_DEADSOLID; + + gib->setSolidType( SOLID_BBOX ); + gib->setContents( CONTENTS_CORPSE ); + + // Make sure the gib goes away after a while + + if ( GetActorFlag( ACTOR_FLAG_DEATHFADE ) ) + gib->PostEvent( EV_Fade, 10.0f ); + else if ( GetActorFlag( ACTOR_FLAG_DEATHSHRINK ) ) + gib->PostEvent( EV_FadeOut, 10.0f ); + else + gib->PostEvent( EV_DeathSinkStart, 10.0f ); + + // Mark the spawn as being successful + + SetActorFlag( ACTOR_FLAG_SPAWN_FAILED, false ); + } + +float Actor::SpawnGetTime( float vel, const Vector &orig, const Vector &gib_mins, const Vector &gib_maxs ) + { + float grav; + Vector end_pos; + Vector dir; + float time; + float other; + trace_t trace; + float dist; + + grav = -sv_currentGravity->value; + + end_pos = orig; + end_pos[2] = -10000.0f; + + trace = G_Trace( orig, gib_mins, gib_maxs, end_pos, NULL, MASK_DEADSOLID, false, "SpawnGetTime" ); + + end_pos = trace.endpos; + + dir = end_pos - orig; + dist = dir.length(); + + time = ( ( grav / -20.0f ) - vel ) / grav; + + other = sqrt( ( grav / 20.0f + vel ) * ( grav / 20.0f + vel ) - ( 2.0f * grav * dist ) ); + + time = time - ( other / grav ); + + return time; + } + +void Actor::SpawnBlood( Event *ev ) + { + str blood_name; + str tag_name; + qboolean use_last_result = false; + Event *attach_event; + + + if ( !com_blood->integer ) + return; + + blood_name = ev->GetString( 1 ); + tag_name = ev->GetString( 2 ); + + // See if we care about the last spawn working or not + + if ( ev->NumArgs() > 2 ) + use_last_result = ev->GetBoolean( 3 ); + + if ( use_last_result && GetActorFlag( ACTOR_FLAG_SPAWN_FAILED ) ) + return; + + // Spawn the blood + + attach_event = (Event *)new Event( EV_AttachModel ); + + attach_event->AddString( blood_name ); + attach_event->AddString( tag_name ); + attach_event->AddInteger( 1 ); + attach_event->AddString( "" ); + attach_event->AddInteger( 0 ); + attach_event->AddInteger( 5 ); + PostEvent( attach_event, 0.0f ); + } + +void Actor::RemoveUselessBody( Event *ev ) + { + + PostEvent( EV_FadeOut, 5.0f ); + } + +void Actor::SetPainThresholdEvent( Event *ev ) + { + pain_threshold = ( ev->GetFloat( 1 ) ); + } + +void Actor::SetKillThreadEvent( Event *ev ) + { + kill_thread = ev->GetString( 1 ); + } + +void Actor::DeathFadeEvent( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_DEATHFADE, true ); + } + +void Actor::setDeathEffect( Event *ev ) +{ + _deathEffect = ev->GetString( 1 ); +} + +void Actor::DeathShrinkEvent( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_DEATHSHRINK, true ); + } + +void Actor::DeathSinkEvent( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_DEATHSINK, true ); + } + +void Actor::StaySolidEvent( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_STAYSOLID, true ); + } + +void Actor::Suicide( Event *ev ) + { + Event *event; + qboolean use_last_mod; + + health = 0; + + if ( ev->NumArgs() > 0 ) + use_last_mod = ev->GetBoolean( 1 ); + else + use_last_mod = false; + + if ( !use_last_mod ) + means_of_death = MOD_SUICIDE; + + event = new Event( EV_Killed ); + event->AddEntity( this ); + event->AddInteger( 0 ); + event->AddEntity( this ); + event->AddInteger( MOD_SUICIDE ); + ProcessEvent( event ); + } + +void Actor::SetDeathSize( Event *ev ) + { + Vector death_min; + Vector death_max; + trace_t trace; + + death_min = ev->GetVector( 1 ); + death_max = ev->GetVector( 2 ); + + // Make sure actor will not be stuck if we change the bounding box + + trace = G_Trace( origin, death_min, death_max, origin, this, edict->clipmask, false, "Actor::SetDeathSize" ); + + if ( !trace.startsolid ) + { + setSize( death_min, death_max ); + return; + } + + // Try again, calculate a smaller death bounding box + + death_min = (death_min + mins) * .5f; + death_max = (death_max + maxs) * .5f; + + trace = G_Trace( origin, death_min, death_max, origin, this, edict->clipmask, false, "Actor::SetDeathSize2" ); + + if ( !trace.startsolid ) + setSize( death_min, death_max ); + } + +void Actor::FadeEvent( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_FADING_OUT, true ); + + if ( GetActorFlag( ACTOR_FLAG_DEATHFADE ) ) + { + ProcessEvent( EV_ForceAlpha ); + ProcessEvent( EV_Fade ); + } + else if ( GetActorFlag( ACTOR_FLAG_DEATHSHRINK ) ) + ProcessEvent( EV_FadeOut ); + else if ( GetActorFlag( ACTOR_FLAG_DEATHSINK ) ) + ProcessEvent( EV_DeathSinkStart ); + else if ( _deathEffect.length() > 0 ) + { + Event *newEvent; + + newEvent = new Event( EV_DisplayEffect ); + newEvent->AddString( _deathEffect ); + ProcessEvent( newEvent ); + + PostEvent( EV_Remove, 5.0f ); + } + else + SetActorFlag( ACTOR_FLAG_FADING_OUT, false ); + } + +//*********************************************************************************************** +// +// Movement functions +// +//*********************************************************************************************** + +void Actor::SimplePathfinding( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_SIMPLE_PATHFINDING, true ); + } + +void Actor::SetCanWalkOnOthers( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_CAN_WALK_ON_OTHERS, true ); + } + +void Actor::SetFeetWidth( Event *ev ) + { + feet_width = ev->GetFloat( 1 ); + } + +void Actor::ForwardSpeedEvent( Event *ev ) + { + movementSubsystem->setForwardSpeed( ev->GetFloat( 1 ) ); + } + +void Actor::SwimEvent( Event *ev ) + { + flags &= ~FL_FLY; + flags |= FL_SWIM; + } + +void Actor::FlyEvent( Event *ev ) + { + if ( ev->NumArgs() == 0 ) + { + // Turn flying on + flags &= ~FL_SWIM; + flags |= FL_FLY; + } + else + { + if ( ev->GetBoolean( 1 ) ) + { + // Turn flying on + flags &= ~FL_SWIM; + flags |= FL_FLY; + } + else + { + // Turn flying off + flags &= ~FL_FLY; + } + } + } + +void Actor::NotLandEvent( Event *ev ) + { + flags &= FL_SWIM | FL_FLY; + } + +void Actor::Push( Event *ev ) + { + movementSubsystem->Push(ev->GetVector( 1 ) ); + } + +void Actor::Push(const Vector &dir) + { + movementSubsystem->Push(dir ); + } + +void Actor::Pushable( Event *ev ) + { + bool flag; + + if ( ev->NumArgs() ) + flag = ev->GetBoolean( 1 ); + else + flag = true; + + SetActorFlag( ACTOR_FLAG_PUSHABLE, flag ); + } + +//*********************************************************************************************** +// +// Debug functions +// +//*********************************************************************************************** + +void Actor::ShowInfo(void) +{ + gi.Printf( "\nEntity # : %d\n", entnum ); + gi.Printf( "Class ID : %s\n", getClassID() ); + gi.Printf( "Classname : %s\n", getClassname() ); + gi.Printf( "Name : %s\n", name.c_str() ); + + if ( part_name.length() > 0 ) + gi.Printf( "Part name : %s\n", part_name.c_str() ); + + gi.Printf( "\n" ); + + + gi.Printf( "Targetname : %s\n", TargetName() ); + gi.Printf( "Origin : ( %f, %f, %f )\n", origin.x, origin.y, origin.z ); + gi.Printf( "Bounds : Mins( %.2f, %.2f, %.2f ) Maxs( %.2f, %.2f, %.2f )\n", mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z ); + + gi.Printf( "\n" ); + + + if ( behavior ) + gi.Printf( "Behavior : %s\n", behavior->getClassname() ); + else + gi.Printf( "Behavior : NULL -- was '%s'\n", currentBehavior.c_str() ); + + if ( headBehavior ) + gi.Printf( "Head Behavior : %s\n", headBehavior->getClassname() ); + else + gi.Printf( "Head Behavior : NULL -- was '%s'\n", currentHeadBehavior.c_str() ); + + if ( eyeBehavior ) + gi.Printf( "Eye Behavior : %s\n", eyeBehavior->getClassname() ); + else + gi.Printf( "Eye Behavior : NULL -- was '%s'\n", currentEyeBehavior.c_str() ); + + if ( torsoBehavior ) + gi.Printf( "Torso Behavior : %s\n", torsoBehavior->getClassname() ); + else + gi.Printf( "Torso Behavior : NULL -- was '%s'\n", currentTorsoBehavior.c_str() ); + + if ( currentState ) + gi.Printf( "State : %s\n", currentState->getName() ); + else + gi.Printf( "State : NONE\n" ); + + if ( GetActorFlag( ACTOR_FLAG_AI_ON ) ) + gi.Printf( "AI is ON\n" ); + else + gi.Printf( "AI is OFF\n" ); + + if ( sensoryPerception ) + { + sensoryPerception->ShowInfo(); + } + + if ( isThinkOn() ) + gi.Printf( "Think is ON\n" ); + else + gi.Printf( "Think is OFF\n" ); + + if ( mode == ACTOR_MODE_IDLE ) + gi.Printf( "Mode : IDLE\n" ); + else if ( mode == ACTOR_MODE_AI ) + gi.Printf( "Mode : AI\n" ); + else if ( mode == ACTOR_MODE_SCRIPT ) + gi.Printf( "Mode : SCRIPT\n" ); + else if ( mode == ACTOR_MODE_TALK ) + gi.Printf( "Mode : TALK\n" ); + + gi.Printf( "\n" ); + + gi.Printf( "Actortype : %d\n", actortype ); + + gi.Printf( "Model : %s\n", model.c_str() ); + gi.Printf( "Anim : %s\n", animname.c_str() ); + gi.Printf( "Health : %f\n", health ); + + gi.Printf( "\ncurrentEnemy: " ); + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( currentEnemy ) + { + gi.Printf( "%d : '%s'\n", currentEnemy->entnum, currentEnemy->targetname.c_str() ); + } + else + { + gi.Printf( "None\n" ); + } + + gi.Printf( "actortype: %d\n", actortype ); + + switch( deadflag ) + { + case DEAD_NO : + gi.Printf( "deadflag: NO\n" ); + break; + case DEAD_DYING : + gi.Printf( "deadflag: DYING\n" ); + break; + case DEAD_DEAD : + gi.Printf( "deadflag: DEAD\n" ); + break; + case DEAD_RESPAWNABLE : + gi.Printf( "deadflag: RESPAWNABLE\n" ); + break; + + } + + gi.Printf( "\n" ); + if ( behavior ) + { + gi.Printf( "Behavior Info:\n" ); + gi.Printf( "Game time: %f\n", level.time ); + behavior->ShowInfo( *this ); + gi.Printf( "\n" ); + } + + if ( headBehavior ) + { + gi.Printf( "Head Behavior Info:\n" ); + gi.Printf( "Game time: %f\n", level.time ); + headBehavior->ShowInfo( *this ); + gi.Printf( "\n" ); + } + + if ( eyeBehavior ) + { + gi.Printf( "Eye Behavior Info:\n" ); + gi.Printf( "Game time: %f\n", level.time ); + eyeBehavior->ShowInfo( *this ); + gi.Printf( "\n" ); + } + + if ( torsoBehavior ) + { + gi.Printf( "Torso Behavior Info:\n" ); + gi.Printf( "Game time: %f\n", level.time ); + torsoBehavior->ShowInfo( *this ); + gi.Printf( "\n" ); + } + +} + +//*********************************************************************************************** +// +// Stimuli functions +// +//*********************************************************************************************** + +void Actor::TurnAIOn(Event *ev) + { + TurnAIOn(); + } + +void Actor::TurnAIOn( void ) + { + if ( GetActorFlag( ACTOR_FLAG_AI_ON ) ) + return; + + SetActorFlag( ACTOR_FLAG_AI_ON, true ); + + if ( sensoryPerception ) + sensoryPerception->RespondTo( STIMULI_ALL, true ); + + EndMode(); + mode = ACTOR_MODE_AI; + Wakeup(); + + } + +void Actor::TurnAIOff( Event *ev ) + { + TurnAIOff(); + } + +void Actor::TurnAIOff( void ) + { + SetActorFlag( ACTOR_FLAG_AI_ON, false ); + if ( sensoryPerception ) + sensoryPerception->RespondTo( STIMULI_NONE, true ); + + if ( mode == ACTOR_MODE_AI ) + { + // Ai is currently on, get out of AI mode + //gi.WDPrintf( "Forcing an actor (#%d, %s) out of AI mode, this can be dangerous.\n", entnum, name.c_str() ); + enemyManager->SetCurrentEnemy( NULL ); + enemyManager->LockOnCurrentEnemy( false ); + EndMode(); + } + } + + + +void Actor::ActivateAI( void ) + { + if ( !statemap && !fuzzyEngine && !masterstatemap) + return; + + last_time_active = level.time; + + if ( (mode == ACTOR_MODE_AI) || mode == ACTOR_MODE_TALK ) + return; + + StartMode( ACTOR_MODE_AI ); + + if ( fuzzyEngine ) + SetState( "START" ); + + if ( sensoryPerception ) + sensoryPerception->RespondTo(STIMULI_ALL , true ); + + if (activate_thread.length() ) + RunThread(activate_thread); + } + +void Actor::SetIdleThread( Event *ev ) + { + idle_thread = ev->GetString( 1 ); + } + +//*********************************************************************************************** +// +// Targeting functions +// +//*********************************************************************************************** + +void AI_SenseEnemies ( void ) + { + Actor *actor; + Player *player; + + player = GetPlayer( 0 ); + + // process the list in reverse order in case SleepList is changed + for( int i = SleepList.NumObjects(); i > 0; i-- ) + { + actor = SleepList.ObjectAt( i ); + + /* + //First Check if the player is in PVS -- If so we want to wake up anyway + //Assuming, of course, the scriptor did not turn the AI off explicitly + if ( player ) + { + if ( actor->GetActorFlag(ACTOR_FLAG_AI_ON) && gi.inPVS( actor->centroid , player->centroid ) ) + { + actor->Wakeup(); + actor->ActivateAI(); + } + } + */ + + if ( actor ) + actor->sensoryPerception->SenseEnemies(); + } + + } + +//********************************************************************************************* +// +// GetPlayer +// +//********************************************************************************************* + +Player *GetPlayer( int index ) + { + gentity_t *ed; + + if( index > game.maxclients ) + return 0; + + ed = &g_entities[ index ]; + + if ( !ed->inuse || !ed->entity ) + return 0; + + return ( Player * )g_entities[index].entity; + + } + +//*********************************************************************************************** +// +// Actor checks +// +//*********************************************************************************************** + + +// Temporary +qboolean Actor::checkInAIMode( Conditional &condition ) + { + if ( mode == ACTOR_MODE_AI ) + return true; + + return false; + } + + +void Actor::checkActorDead( Event *ev ) + { + + Actor* act = (Actor*)ev->GetEntity( 1 ); + + if ( act ) + ev->ReturnInteger( act->checkActorDead() ); + else + ev->ReturnInteger( false ); + } + +qboolean Actor::checkActorDead( ) + { + if ( deadflag || ( health <= 0 ) ) + return true; + + return false; + } + +qboolean Actor::checkanimname( Conditional &condition ) + { + str anim_name_test; + int use_length; + int result; + + anim_name_test = condition.getParm( 1 ); + + if ( ( animname.length() == 0 ) || ( anim_name_test.length() == 0 ) ) + return false; + + if ( condition.numParms() > 1 ) + use_length = atoi( condition.getParm( 2 ) ); + else + use_length = false; + + if ( use_length ) + result = strncmp( animname.c_str(), anim_name_test.c_str(), anim_name_test.length() ); + else + result = strcmp( animname.c_str(), anim_name_test.c_str() ); + + return (result == 0); + } + +qboolean Actor::checkActorFlag( Conditional &condition ) + { + str flagName = condition.getParm( 1 ); + + return GetActorFlag( flagName ); + + } + +qboolean Actor::checkinactive( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_INACTIVE ); + } + +qboolean Actor::checkanimdone( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_ANIM_DONE ); + } + +qboolean Actor::checktorsoanimdone( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_TORSO_ANIM_DONE ); + } + +qboolean Actor::checkdead( Conditional &condition ) + { + return deadflag != 0; + } + +qboolean Actor::checkhaveenemy( Conditional &condition ) + { + // Get our current enemy + Entity *currentEnemy; + //enemyManager->FindHighestHateEnemy(); + //currentEnemy = enemyManager->GetCurrentEnemy(); + //if ( !currentEnemy ) + // return false; + + currentEnemy = enemyManager->GetCurrentEnemy(); + + if ( currentEnemy && IsEntityAlive( currentEnemy ) ) + return true; + + if ( enemyManager->getEnemyCount() ) + return true; + + return false; + } + +qboolean Actor::checkenemydead( Conditional &condition ) + { + return checkenemydead(); + } + +qboolean Actor::checkenemydead( void ) + { + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + if ( currentEnemy->deadflag || ( currentEnemy->health <= 0 ) ) + return true; + + return false; + } + +qboolean Actor::checkenemynoclip( Conditional &condition ) + { + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + if ( currentEnemy->movetype == MOVETYPE_NOCLIP ) + return true; + + return false; + } + +qboolean Actor::checkcanseeenemy( Conditional &condition ) + { + qboolean use_fov = true; + qboolean can_see; + qboolean in_fov; + qboolean real_can_see; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + enemyManager->FindHighestHateEnemy(); + + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + if ( condition.numParms() > 0 ) + use_fov = atoi( condition.getParm( 1 ) ); + + // See if we should check again + + if ( canseeenemy_time > level.time ) + { + if ( use_fov ) + return GetActorFlag( ACTOR_FLAG_LAST_CANSEEENEMY ); + else + return GetActorFlag( ACTOR_FLAG_LAST_CANSEEENEMY_NOFOV ); + } + + can_see = true; + in_fov = true; + + // Check to see if we can see enemy + + if ( !IsEntityAlive( currentEnemy ) ) + { + can_see = false; + in_fov = false; + } + + if ( sensoryPerception ) + { + if ( can_see && !sensoryPerception->CanSeeEntity( this , currentEnemy , true , true ) ) + can_see = false; + } + + // Save can see info + + SetActorFlag( ACTOR_FLAG_LAST_CANSEEENEMY, can_see && in_fov ); + SetActorFlag( ACTOR_FLAG_LAST_CANSEEENEMY_NOFOV, can_see ); + + canseeenemy_time = level.time + 0.2f + G_Random( 0.1f ); + + if ( use_fov ) + real_can_see = GetActorFlag( ACTOR_FLAG_LAST_CANSEEENEMY ); + else + real_can_see = GetActorFlag( ACTOR_FLAG_LAST_CANSEEENEMY_NOFOV ); + + // Save the last known position of our enemy + + if ( real_can_see ) + last_known_enemy_pos = currentEnemy->centroid; + + return real_can_see; + } + +qboolean Actor::checkcanseeplayer( Conditional &condition ) + { + qboolean use_fov = true; + qboolean can_see; + qboolean in_fov; + qboolean real_can_see; + + // Get our current enemy + Entity *player; + player = GetPlayer ( 0 ); + + if ( !player ) + return false; + + if ( condition.numParms() > 0 ) + use_fov = atoi( condition.getParm( 1 ) ); + + // See if we should check again + + if ( canseeplayer_time > level.time ) + { + if ( use_fov ) + return GetActorFlag( ACTOR_FLAG_LAST_CANSEEPLAYER ); + else + return GetActorFlag( ACTOR_FLAG_LAST_CANSEEPLAYER_NOFOV ); + } + + can_see = true; + in_fov = sensoryPerception->InFOV( player ); + + // Check to see if we can see enemy + + if ( !IsEntityAlive( player ) ) + { + can_see = false; + in_fov = false; + } + + if ( sensoryPerception ) + { + if ( can_see && !sensoryPerception->CanSeeEntity( this , player , true , true ) ) + can_see = false; + } + + // Save can see info + + SetActorFlag( ACTOR_FLAG_LAST_CANSEEPLAYER, can_see && in_fov ); + SetActorFlag( ACTOR_FLAG_LAST_CANSEEPLAYER_NOFOV, can_see ); + + canseeplayer_time = level.time + 0.2f + G_Random( 0.1f ); + + if ( use_fov ) + real_can_see = GetActorFlag( ACTOR_FLAG_LAST_CANSEEPLAYER ); + else + real_can_see = GetActorFlag( ACTOR_FLAG_LAST_CANSEEPLAYER_NOFOV ); + + // Save the last known position of our enemy + + if ( real_can_see ) + last_known_player_pos = player->origin; + + return real_can_see; + } +qboolean Actor::checkcanshootenemy( Conditional &condition ) + { + str tag_name; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + /* + if ( condition.numParms() > 1 ) + tag_name = condition.getParm( 2 ); + + return ( checkcanseeenemy( condition ) && TestAttack( tag_name ) ); + */ + + return combatSubsystem->CanAttackTarget( currentEnemy ); + } + +qboolean Actor::checkCanAttackAnyEnemy ( Conditional &condition ) + { + return enemyManager->CanAttackAnyEnemy(); + } + +qboolean Actor::checkenemyinfov( Conditional &condition ) + { + float check_fov; + float check_fovdot; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + if ( !sensoryPerception ) + return false; + + if ( condition.numParms() > 0 ) + { + check_fov = (float)atof( condition.getParm( 1 ) ); + check_fovdot = (float) cos( check_fov * 0.5 * M_PI / 180.0 ); + + return sensoryPerception->InFOV( currentEnemy->centroid, check_fov, check_fovdot ); + } + else + { + return sensoryPerception->InFOV( currentEnemy ); + } + } + +qboolean Actor::checkenemyonground( Conditional &condition ) + { + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + if ( currentEnemy->groundentity ) + return true; + else + return false; + } + +qboolean Actor::checkenemyrelativeyaw( Conditional &condition ) + { + Vector dir; + Vector dir_angles; + float relative_yaw; + float check_yaw_min; + float check_yaw_max; + Vector temp_angles; + qboolean use_range; + + // Get our current enemy + Entity *currentEnemy; + //currentEnemy = enemyManager->GetCurrentEnemy(); + //if ( !currentEnemy ) + // return false; + + currentEnemy = GetPlayer(0); + + check_yaw_min = (float)atof( condition.getParm( 1 ) ); + + if ( condition.numParms() > 1 ) + { + check_yaw_max = atof( condition.getParm( 2 ) ); + use_range = true; + } + else + { + check_yaw_max = 0; + use_range = false; + } + + dir = origin - currentEnemy->origin; + + dir_angles = dir.toAngles(); + + temp_angles = currentEnemy->angles; + + relative_yaw = AngleNormalize180( currentEnemy->angles[YAW] - dir_angles[YAW] ); + + if (use_range) + { + //Special Case Check + if ( check_yaw_max < check_yaw_min ) + { + if ( ( relative_yaw < check_yaw_max ) && ( relative_yaw >= -180 ) ) + return true; + if ( ( relative_yaw > check_yaw_min ) && ( relative_yaw <= 180 ) ) + return true; + + return false; + } + + if ( ( relative_yaw >= check_yaw_min ) && ( relative_yaw <= check_yaw_max ) ) + return true; + else + return false; + } + + if ( relative_yaw < check_yaw_min ) + return true; + else + return false; + } + +qboolean Actor::checkenemyyawrange ( Conditional &condition ) + { + //This function will return true if the the currentEnemy is within the + //angles passed in. + Vector dir; + float check_yaw_min; + float check_yaw_max; + float dirYaw; + float originYaw; + float angleCheck; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + check_yaw_min = (float)atof( condition.getParm( 1 ) ); + check_yaw_max = (float)atof( condition.getParm( 2 ) ); + + //Normalize the Angles + check_yaw_min = AngleNormalize180(check_yaw_min); + check_yaw_max = AngleNormalize180(check_yaw_max); + + dir = currentEnemy->origin - origin; + dirYaw = dir.toYaw(); + + originYaw = angles[YAW]; + + angleCheck = AngleNormalize180(dirYaw - originYaw); + + //Special Case Check for the 180 Problem + if ( check_yaw_min > check_yaw_max ) + { + if ( ( angleCheck < 180.0f ) && ( angleCheck > check_yaw_min ) ) + return true; + + if ( ( angleCheck > -180.0f ) && ( angleCheck < check_yaw_max ) ) + return true; + } + + if ( ( angleCheck >= check_yaw_min ) && ( angleCheck <= check_yaw_max ) ) + return true; + else + return false; + + } + +qboolean Actor::checkcanjumptoenemy( Conditional &condition ) + { + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + return ( movementSubsystem->CanWalkTo( currentEnemy->origin, 0.0f, currentEnemy->entnum ) ); + } + +qboolean Actor::checkcanflytoenemy( Conditional &condition ) + { + trace_t trace; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + trace = G_Trace( origin, mins, maxs, currentEnemy->centroid, this, edict->clipmask, false, "Actor::checkcanflytoenemy" ); + + if ( trace.startsolid || trace.allsolid ) + return false; + + if ( trace.entityNum == currentEnemy->entnum ) + return true; + + return false; + } + +qboolean Actor::checkinpain( Conditional &condition ) + { + return ( state_flags & STATE_FLAG_IN_PAIN ); + } + +qboolean Actor::checksmallpain( Conditional &condition ) + { + return ( state_flags & STATE_FLAG_SMALL_PAIN ); + } + +qboolean Actor::checkpainyaw( Conditional &condition ) + { + float check_yaw; + + check_yaw = (float)atof( condition.getParm( 1 ) ); + + if ( pain_angles[YAW] <= check_yaw ) + return true; + else + return false; + } + +qboolean Actor::checkpainpitch( Conditional &condition ) + { + float check_pitch; + + check_pitch = (float)atof( condition.getParm( 1 ) ); + + if ( pain_angles[PITCH] <= check_pitch ) + return true; + else + return false; + } + +qboolean Actor::checkstunned( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_STUNNED ); + } + +qboolean Actor::checkfinished( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_FINISHED ); + } + +qboolean Actor::checkmeleehit( Conditional &condition ) + { + return ( state_flags & STATE_FLAG_MELEE_HIT ); + } + +qboolean Actor::checkblockedhit( Conditional &condition ) + { + return ( state_flags & STATE_FLAG_BLOCKED_HIT ); + } + +qboolean Actor::checkblocked( Conditional &condition ) + { + if ( attack_blocked && ( attack_blocked_time + .75 > level.time ) ) + { + attack_blocked = false; + return true; + } + else + return false; + } + +qboolean Actor::checkonfire( Conditional &condition ) + { + return on_fire; + } + +qboolean Actor::checkotherdied( Conditional &condition ) + { + return ( state_flags & STATE_FLAG_OTHER_DIED ); + } + +qboolean Actor::checkstuck( Conditional &condition ) + { + return ( state_flags & STATE_FLAG_STUCK ); + } + +qboolean Actor::checknopath( Conditional &condition ) + { + return ( state_flags & STATE_FLAG_NO_PATH ); + } + +qboolean Actor::checkbehaviordone( Conditional &condition ) + { + return ( behavior == NULL ); + } + +qboolean Actor::checkheadbehaviordone( Conditional &condition ) + { + return ( headBehavior == NULL ); + } + +qboolean Actor::checkeyebehaviordone( Conditional &condition ) + { + return ( eyeBehavior == NULL ); + } + +qboolean Actor::checktorsobehaviordone( Conditional &condition ) + { + return ( torsoBehavior == NULL ); + } + +qboolean Actor::checktorsobehaviorfailed( Conditional &condition ) +{ + return ( + (torsoBehaviorCode != BEHAVIOR_SUCCESS) + && (torsoBehaviorCode != BEHAVIOR_EVALUATING ) + ); +} + +qboolean Actor::checktorsobehaviorsuccess( Conditional &condition ) +{ + return (torsoBehaviorCode == BEHAVIOR_SUCCESS); +} + +qboolean Actor::checktimedone( Conditional &condition ) + { + if ( GetActorFlag( ACTOR_FLAG_STATE_DONE_TIME_VALID ) ) + { + return ( state_done_time < level.time ); + } + + return false; + } + +qboolean Actor::checkdone ( Conditional &condition ) + { + return ( checkbehaviordone( condition ) || checktimedone( condition ) ); + } + +qboolean Actor::checkenemyrange ( Conditional &condition ) + { + float range; + float min_height; + float max_height; + int XYOnly; + bool onlyXY; + + // Get our current enemy + Entity *currentEnemy; + + currentEnemy = enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + enemyManager->FindHighestHateEnemy(); + + currentEnemy = enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + enemyManager->FindHighestHateEnemy(); + + if ( !currentEnemy ) + return false; + + range = (float)atof( condition.getParm( 1 ) ); + + if ( condition.numParms() > 1 ) + max_height = (float)atof( condition.getParm( 2 ) ); + else + max_height = 0; + + if ( condition.numParms() > 2 ) + min_height = (float)atof( condition.getParm( 3 ) ); + else + min_height = -max_height; + + if ( condition.numParms() > 3 ) + XYOnly = atoi( condition.getParm( 4 ) ); + else + XYOnly = 0; + + // Stupid compiler warning complaints about forcing an int to bool + // So that's why this BS is here + if ( XYOnly ) + onlyXY = true; + else + onlyXY = false; + + + /* + Vector temp; + temp = currentEnemy->origin - origin; + float templen = temp.length(); + */ + + + return EntityInRange( currentEnemy, range, min_height, max_height , onlyXY ); + + } + +qboolean Actor::checkEnemyAttached( Conditional &condition ) + { + return haveAttached; + } + +qboolean Actor::checkparentrange( Conditional &condition ) + { + float range; + float height; + Actor *act; + + if ( !spawnparent ) return false; + + + range = (float)atof( condition.getParm( 1 ) ); + + if ( condition.numParms() == 2 ) + height = (float)atof( condition.getParm( 2 ) ); + else + height = 0.0f; + + act = (Actor*)(Entity*)spawnparent; + + if( EntityInRange( act, range, -height, height ) ) + { + return true; + } + + + return false; + } + + + +qboolean Actor::checkplayerrange( Conditional &condition ) + { + float range; + float height; + bool XYOnly; + + Player *player; + + range = (float)atof( condition.getParm( 1 ) ); + + if ( condition.numParms() == 2 ) + height = (float)atof( condition.getParm( 2 ) ); + else + height = 0.0f; + + XYOnly = false; + if ( condition.numParms() == 3 ) + { + int XYCheck = atoi(condition.getParm( 3 ) ); + if ( XYCheck ) + XYOnly = true; + } + + for(int i = 0; i < game.maxclients; i++) + { + player = GetPlayer(i); + + if( EntityInRange( player, range, -height, height, XYOnly ) ) + { + return true; + } + } + + return false; + } + +qboolean Actor::checkplayerrange( float range, float height ) + { + Player *player; + + for(int i = 0; i < game.maxclients; i++) + { + player = GetPlayer(i); + + if( EntityInRange( player, range, -height, height ) ) + { + return true; + } + } + + return false; + } + +qboolean Actor::checkmovingactorrange( Conditional &condition ) + { + float range; + float height = 0; + float height_diff; + Entity *ent_in_range; + int i; + Vector delta; + float r2; + gentity_t *ed; + float smallest_dist2; + float dist2; + + + // Get distances + range = (float)atof( condition.getParm( 1 ) ); + + if ( condition.numParms() == 2 ) + { + height = (float)atof( condition.getParm( 2 ) ); + } + + r2 = range * range; + + if ( ( actorrange_time > level.time ) && ( height == last_height ) ) + { + ent_in_range = last_ent; + + if ( IsEntityAlive( ent_in_range ) ) + { + delta = origin - ent_in_range->centroid; + + if ( height ) + { + height_diff = delta[ 2 ]; + delta[ 2 ] = 0; + + if ( ( height_diff < -height ) || ( height_diff > height ) ) + return false; + } + + if ( sensoryPerception ) + { + // dot product returns length squared + if ( ( ( delta * delta ) <= r2 ) && sensoryPerception->CanSeeEntity( this, ent_in_range, true, true )) + return true; + + } + } + + return false; + } + + actorrange_time = level.time + 0.2f + G_Random( 0.1f ); + + last_height = height; + last_ent = NULL; + + smallest_dist2 = 99999999; + + // See if any clients are in range + for( i = 0 ; i < game.maxclients; i++ ) + { + ed = &g_entities[ i ]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + ent_in_range = ed->entity; + + if ( IsEntityAlive( ent_in_range ) ) + { + delta = origin - ent_in_range->centroid; + + if ( height > 0.0f ) + { + height_diff = delta[ 2 ]; + + if ( ( height_diff < -height ) || ( height_diff > height ) ) + { + continue; + } + + delta[ 2 ] = 0; + } + + // dot product returns length squared + + dist2 = delta * delta; + + if ( sensoryPerception ) + { + if ( ( dist2 <= r2 ) && sensoryPerception->CanSeeEntity( this , ent_in_range, true, true ) ) + { + if ( dist2 < smallest_dist2 ) + { + smallest_dist2 = dist2; + last_ent = ent_in_range; + } + } + + } + } + } + + // See if any actors are in range + for( i = 1; i <= ActiveList.NumObjects(); i++ ) + { + ent_in_range = ActiveList.ObjectAt( i ); + + if ( + ( ent_in_range->movetype != MOVETYPE_NONE ) && + ( ent_in_range->movetype != MOVETYPE_STATIONARY ) && + ( this != ent_in_range ) && + ( ent_in_range->health > 0 ) && + !( ent_in_range->flags & FL_NOTARGET ) + ) + { + delta = origin - ent_in_range->centroid; + + if ( height > 0.0f ) + { + height_diff = delta[ 2 ]; + + if ( ( height_diff < -height ) || ( height_diff > height ) ) + { + continue; + } + + delta[ 2 ] = 0.0f; + } + + // dot product returns length squared + + dist2 = delta * delta; + + if ( sensoryPerception ) + { + if ( ( dist2 <= r2 ) && sensoryPerception->CanSeeEntity( this, ent_in_range , true , true ) ) + { + if ( dist2 < smallest_dist2 ) + { + smallest_dist2 = dist2; + last_ent = ent_in_range; + } + } + + } + } + } + + if ( last_ent ) + return true; + + return false; + } + +qboolean Actor::checkchance( Conditional &condition ) + { + float percent_chance; + bool checkedChance = false; + + percent_chance = (float)atof( condition.getParm( 1 ) ); + + //Stupid crazy conversion here, not because I am stupid, but becaus"e the + //compiler is... + if ( condition.numParms() > 1 ) + { + int value = atoi( condition.getParm( 2 ) ); + if ( value > 0 ) + checkedChance = true; + } + + + if ( checkedChance && _checkedChance ) + return false; + + + if ( checkedChance && !_checkedChance ) + _checkedChance = true; + + + return ( G_Random() < percent_chance ); + } + +qboolean Actor::checkstatetime( Conditional &condition ) + { + float time_to_wait; + + time_to_wait = (float)atof( condition.getParm( 1 ) ); + + return ( state_time + time_to_wait < level.time ); + } + +qboolean Actor::checktimesdone( Conditional &condition ) + { + return ( times_done == atoi( condition.getParm( 1 ) ) ); + } + +qboolean Actor::checkmeansofdeath( Conditional &condition ) + { + int mod; + + mod = MOD_NameToNum( condition.getParm( 1 ) ); + + return ( mod == means_of_death ); + } + +qboolean Actor::checknoiseheard( Conditional &condition ) + { + + str soundTypeStr; + int soundTypeIdx; + + if ( !sensoryPerception ) + return false; + + if (condition.numParms() > 0 ) + { + soundTypeStr = condition.getParm( 1 ); + soundTypeIdx = Soundtype_string_to_int( soundTypeStr ); + if ( soundTypeIdx == sensoryPerception->GetLastSoundType()) + { + //Clear our soundtype + sensoryPerception->SetLastSoundType( SOUNDTYPE_NONE ); + + return true; + } + else + { + return false; + } + } + + return GetActorFlag( ACTOR_FLAG_NOISE_HEARD ); + } + +qboolean Actor::checkpartstate( Conditional &condition ) + { + str part_name; + str state_name; + Actor *part; + + part_name = condition.getParm( 1 ); + state_name = condition.getParm( 2 ); + + part = FindPartActor( part_name ); + + return ( part && part->currentState && ( strnicmp( part->currentState->getName(), state_name.c_str(), strlen( state_name.c_str() ) ) == 0 ) ); + } + +qboolean Actor::checkpartflag( Conditional &condition ) + { + str part_name; + str flag_name; + unsigned int flag; + int current_part; + part_t *part; + Entity *partent; + Actor *partact; + + part_name = condition.getParm( 1 ); + flag_name = condition.getParm( 2 ); + + if ( stricmp( flag_name, "pain" ) == 0 ) + { + flag = STATE_FLAG_IN_PAIN; + } + else if ( stricmp( flag_name, "small_pain" ) == 0 ) + { + flag = STATE_FLAG_SMALL_PAIN; + } + else if ( stricmp( flag_name, "melee_hit" ) == 0 ) + { + flag = STATE_FLAG_MELEE_HIT; + } + else if ( stricmp( flag_name, "touched" ) == 0 ) + { + flag = STATE_FLAG_TOUCHED; + } + else if ( stricmp( flag_name, "activated" ) == 0 ) + { + flag = STATE_FLAG_ACTIVATED; + } + else if ( stricmp( flag_name, "used" ) == 0 ) + { + flag = STATE_FLAG_USED; + } + else if ( stricmp( flag_name, "twitch" ) == 0 ) + { + flag = STATE_FLAG_TWITCH; + } + else + { + gi.WDPrintf( "Unknown flag name (%s) in checkpartflag.", flag_name.c_str() ); + flag = 0; + } + + for( current_part = 1; current_part <= parts.NumObjects(); current_part++ ) + { + part = &parts.ObjectAt( current_part ); + + partent = part->ent; + partact = (Actor *)partent; + + if ( partact && ( partact->part_name == part_name ) ) + { + if ( part->state_flags & flag ) + { + return true; + } + } + } + + return false; + } + +qboolean Actor::checkpartdead( Conditional &condition ) + { + str part_name; + str state_name; + Actor *part; + + part_name = condition.getParm( 1 ); + + part = FindPartActor( part_name ); + + if ( !part ) + return false; + + return ( part->deadflag || ( part->health <= 0 ) ); + } + +qboolean Actor::checknumspawns( Conditional &condition ) + { + int check_num; + + check_num = atoi( condition.getParm( 1 ) ); + + return ( num_of_spawns < check_num ); + } + +qboolean Actor::checkcommand( Conditional &condition ) + { + return ( command == condition.getParm( 1 ) ); + } + +qboolean Actor::checktouched( Conditional &condition ) + { + return state_flags & STATE_FLAG_TOUCHED; + } + +qboolean Actor::checktouchedbyplayer ( Conditional &condition ) + { + return checktouchedbyplayer(); + } + +qboolean Actor::checktouchedbyplayer() + { + return state_flags & STATE_FLAG_TOUCHED_BY_PLAYER; + } + +qboolean Actor::checkInTheWay( Conditional &condition ) +{ + return checkInTheWay(); +} + +qboolean Actor::checkInTheWay() +{ + if ( state_flags & STATE_FLAG_IN_THE_WAY ) + return true; + + if ( state_flags & STATE_FLAG_TOUCHED_BY_PLAYER ) + return true; + + return false; + +} + +qboolean Actor::checkactivated( Conditional &condition ) + { + return state_flags & STATE_FLAG_ACTIVATED; + } + +qboolean Actor::checkused( Conditional &condition ) + { + return ( state_flags & STATE_FLAG_USED ); + } + +qboolean Actor::checktwitch( Conditional &condition ) + { + return ( state_flags & STATE_FLAG_TWITCH ); + } + +qboolean Actor::checkhealth( Conditional &condition ) + { + return ( health < (float)atof( condition.getParm( 1 ) ) ); + } + +qboolean Actor::checkonground( Conditional &condition ) + { + CheckGround(); + return groundentity != NULL; + } + +qboolean Actor::checkinwater( Conditional &condition ) + { + return (waterlevel > 0 ); + } + +qboolean Actor::checkincomingmeleeattack( Conditional &condition ) + { + return checkincomingmeleeattack(); + } + +qboolean Actor::checkincomingmeleeattack() + { + //Entity *enemy_ent; + Sentient *enemy; + trace_t trace; + Vector forward; + Vector end_pos; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + if ( IsEntityAlive( currentEnemy ) ) + { + if ( currentEnemy->isSubclassOf( Sentient ) ) + { + //enemy_ent = ( Entity * )currentEnemy; + enemy = ( Sentient * )( Entity * )currentEnemy; + + if ( enemy->in_melee_attack ) + { + enemy->angles.AngleVectors( &forward ); + end_pos = ( forward * 160.0f ) + enemy->centroid; + trace = G_Trace( enemy->centroid, vec_zero, vec_zero, end_pos, enemy, MASK_SHOT, false, "Actor::checkincomingmeleeattack" ); + + if ( trace.entityNum == entnum ) + { + return true; + } + } + } + } + + return false; + } + +qboolean Actor::checkincomingrangedattack( Conditional &condition ) + { + //Entity *enemy_ent; + Sentient *enemy; + trace_t trace; + Vector forward; + Vector end_pos; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + if ( IsEntityAlive( currentEnemy ) ) + { + if ( currentEnemy->isSubclassOf( Sentient ) ) + { + //enemy_ent = ( Entity * )currentEnemy; + enemy = ( Sentient * )( Entity * )currentEnemy; + + if ( enemy->in_ranged_attack ) + { + enemy->angles.AngleVectors( &forward ); + end_pos = ( forward * 125.0f ) + enemy->centroid; + trace = G_Trace( enemy->centroid, vec_zero, vec_zero, origin, enemy, MASK_SHOT, false, "Actor::checkincomingrangedattack" ); + + if ( trace.entityNum == entnum ) + { + return true; + } + } + } + } + + return false; + } + +qboolean Actor::checkincomingprojectile( Conditional &condition ) + { + trace_t trace; + Vector forward; + Vector end_pos; + float time = 0; + float time_left; + Vector dir; + float dist; + + if ( condition.numParms() == 1 ) + { + time = (float)atof( condition.getParm( 1 ) ); + } + + if ( incoming_proj && ( incoming_time <= level.time ) ) + { + incoming_proj->angles.AngleVectors( &forward ); + end_pos = ( forward * 1000.0f ) + incoming_proj->centroid; + trace = G_Trace( incoming_proj->centroid, vec_zero, vec_zero, end_pos, incoming_proj, MASK_SHOT, false, "Actor::checkincomingprojectile" ); + + if ( trace.entityNum == entnum ) + { + if ( time ) + { + dir = trace.endpos - incoming_proj->centroid; + dist = dir.length(); + time_left = dist / incoming_proj->velocity.length(); + + return ( time_left <= time ); + } + + return true; + } + } + + return false; + } + +qboolean Actor::checkenemystunned( Conditional &condition ) + { + Sentient *enemy; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + if ( IsEntityAlive( currentEnemy ) ) + { + if ( currentEnemy->isSubclassOf( Sentient ) ) + { + enemy = (Sentient *)(Entity *)currentEnemy; + + if ( enemy->in_stun ) + return true; + } + } + + return false; + } + +qboolean Actor::checkenemyinpath( Conditional &condition ) + { + trace_t trace; + Vector end_pos; + int mask; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + if ( IsEntityAlive( currentEnemy ) ) + { + Vector forward( orientation[ 0 ] ); + + forward *= 1000.0f; + end_pos = origin + forward; + + // Pick a reasonable mask (most actors will just use their normal mask, actors that can walk though + // actors use MASK_SHOT) + + if ( edict->clipmask & CONTENTS_BODY ) + mask = edict->clipmask; + else + mask = MASK_SHOT; + + trace = G_Trace( centroid, vec_zero, vec_zero, end_pos, this, mask, false, "Actor::checkenemyinpath" ); + + if ( trace.entityNum == currentEnemy->entnum ) + { + return true; + } + } + + return false; + } + +qboolean Actor::checkstage( Conditional &condition ) + { + return ( stage == (float)atoi( condition.getParm( 1 ) ) ); + } + +qboolean Actor::checkheld( Conditional &condition ) + { + return ( edict->s.parent != ENTITYNUM_NONE ); + } + +qboolean Actor::checkenemymelee( Conditional &condition ) + { + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + return ( EntityHasFireType( currentEnemy, FT_MELEE ) ); + } + +qboolean Actor::checkenemyranged( Conditional &condition ) + { + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + return ( EntityHasFireType( currentEnemy, FT_BULLET ) || EntityHasFireType( currentEnemy, FT_PROJECTILE ) ); + } + +qboolean Actor::checkplayerranged( Conditional &condition ) + { + return checkplayerranged(); + } + +qboolean Actor::checkplayerranged() + { + Player *player; + player = NULL; + player = GetPlayer( 0 ); + + if ( !player ) return false; + + return ( EntityHasFireType( player, FT_BULLET ) || EntityHasFireType( player, FT_PROJECTILE ) ); + } + +qboolean Actor::checkhasthing( Conditional &condition ) + { + int thing_number; + int i; + + + for( i = 1 ; i <= condition.numParms() ; i++ ) + { + thing_number = atoi( condition.getParm( i ) ); + + switch( thing_number ) + { + case 1 : + if ( GetActorFlag( ACTOR_FLAG_HAS_THING1 ) ) + return true; + break; + case 2 : + if ( GetActorFlag( ACTOR_FLAG_HAS_THING2 ) ) + return true; + break; + case 3 : + if ( GetActorFlag( ACTOR_FLAG_HAS_THING3 ) ) + return true; + break; + case 4 : + if ( GetActorFlag( ACTOR_FLAG_HAS_THING4 ) ) + return true; + break; + } + } + + return false; + } + +qboolean Actor::checkatcovernode( Conditional &condtion ) + { + return GetActorFlag( ACTOR_FLAG_AT_COVER_NODE ); + } + +qboolean Actor::checkallowhangback( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_ALLOW_HANGBACK ); + } + +qboolean Actor::checkname( Conditional &condition ) + { + return ( name == condition.getParm( 1 ) ); + } + +qboolean Actor::checkVar( Conditional &condition ) + { + StateVar *checkVar = 0; + str varName = condition.getParm( 1 ); + str varValue = condition.getParm( 2 ); + + for (int i = 1; i <= stateVarList.NumObjects() ; i++ ) + { + checkVar = stateVarList.ObjectAt( i ); + if( !stricmp ( checkVar->varName, varName ) ) + { + if (checkVar->varValue == varValue ) + return true; + else + return false; + } + } + + //Need to throw and exception here, the var in question is not in the list + //gi.WDPrintf( "Var %s is not in stateVarList\n", varName.c_str() ); + return false; + } + + +//-------------------------------------------------------------- +// Name: checkVarTimeDifference +// Class: Actor +// +// Description: Checks if the difference between the statevar's time +// and the current level time is greater than or equal to +// the time specified in the conditional +// +// Parameters: Conditional &condition +// +// Returns: None +//-------------------------------------------------------------- +qboolean Actor::checkVarTimeDifference( Conditional &condition ) +{ + StateVar *checkVar = NULL; + str varName = condition.getParm( 1 ); + float varTime = atof(condition.getParm( 2 )); + + for (int i = 1; i <= stateVarList.NumObjects() ; i++ ) + { + checkVar = stateVarList.ObjectAt( i ); + if( !stricmp ( checkVar->varName, varName ) ) + { + if ( level.time - checkVar->varTime >= varTime ) + return true; + else + return false; + } + } + + //Need to throw and exception here, the var in question is not in the list + //gi.WDPrintf( "Var %s is not in stateVarList\n", varName.c_str() ); + return false; +} + +qboolean Actor::checkNodeExists( Conditional &condition ) + { + str nodeName = condition.getParm( 1 ); + PathNode *testNode = 0; + + + nodeName += "0"; + + testNode = thePathManager.FindNode( nodeName ); + + if( testNode ) + { + return true; + } + + return false; + + } + +qboolean Actor::checkCoverNodes( Conditional &condition ) + { + for ( int i = 1 ; i <= thePathManager.NumberOfSpecialNodes(); i++ ) + { + PathNode *node = thePathManager.GetSpecialNode( i ); + + if ( node && ( node->nodeflags & ( AI_DUCK | AI_COVER ) ) && + ( ( node->occupiedTime <= level.time ) || ( node->entnum == entnum ) ) ) + { + return true; + } + } + + return false; + } + +qboolean Actor::checkSurfaceDamaged( Conditional &condition ) + { + int surface_number; + str surface_name = condition.getParm( 1 ); + + if ( last_surface_hit == -1 ) + return false; + + surface_number = gi.Surface_NameToNum( edict->s.modelindex, surface_name.c_str() ); + + if ( surface_number == last_surface_hit ) + return true; + else + return false; + } + +qboolean Actor::checkBoneDamaged( Conditional &condition ) + { + int bone_number; + str bone_name = condition.getParm( 1 ); + + if ( saved_bone_hit == -9999 ) + return false; + + bone_number = gi.Tag_NumForName( edict->s.modelindex, bone_name.c_str() ); + + if ( bone_number == saved_bone_hit ) + return true; + else + return false; + } + +qboolean Actor::checkRegionDamaged( Conditional &condition ) + { + int region_bit; + str region_name; + + // See if any region has been hit + + if ( last_region_hit == 0 ) + return false; + + // Get the region to test + + region_name = condition.getParm( 1 ); + + // Figure out which bit to check + + region_bit = 0; + + if ( region_name == "back" ) + region_bit = REGIONAL_DAMAGE_BACK; + else if ( region_name == "front" ) + region_bit = REGIONAL_DAMAGE_FRONT; + + // Return whether or not this region has been hit + + return ( last_region_hit & region_bit ); + } + +qboolean Actor::checkCaptured( Conditional &condition ) + { + return ( GetActorFlag(ACTOR_FLAG_CAPTURED) ); + } + +qboolean Actor::checkCanWalkForward( Conditional &condition ) + { + trace_t trace; + Vector endpos; + Vector startpos; + str rangestr; + float range; + + angles.AngleVectors(&endpos); //Get Forward direction + + endpos += origin; + endpos.z += 50.0f; // Pull it off the ground a little + + rangestr = condition.getParm( 1 ); + range = (float)atof( rangestr.c_str() ); + + endpos *= range; + + startpos = origin; + startpos.z += 50.0f; + + trace = G_Trace( startpos, mins, maxs, endpos, this, edict->clipmask, false, "Actor::start" ); + + if (trace.fraction == 1.0f ) + return true; + else + return false; + } + +qboolean Actor::checkHasThrowObject( Conditional &condition ) + { + return haveThrowObject; + } + +qboolean Actor::checkEnemyIsThrowObject( Conditional &condition ) + { + Entity* ent = 0; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + + ent = (Entity*)currentEnemy; + if ( ent->isSubclassOf(ThrowObject) ) + return true; + else + return false; + + } + +qboolean Actor::checkTurretMode( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_TURRET_MODE ); + } + +qboolean Actor::checkGameSpecific( Conditional &condition ) + { + if (!gameComponent) + return false; + + return gameComponent->DoCheck( condition ); + + } + +qboolean Actor::checkWeaponReady( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_WEAPON_READY ); + } + +qboolean Actor::checkMeleeHitWorld( Conditional &condition ) + { + qboolean hit; + if ( GetActorFlag( ACTOR_FLAG_MELEE_HIT_WORLD ) ) + { + hit = true; + SetActorFlag( ACTOR_FLAG_MELEE_HIT_WORLD, false ); + } + else + hit = false; + + return hit; + } + +qboolean Actor::checkPlayerValid( Conditional &condition ) + { + Player *player = GetPlayer(0); + + if ( !player ) + return false; + + if ( EntityIsValidTarget( player ) ) + return true; + + return false; + } + +qboolean Actor::checkInAbsoluteRange( Conditional &condition ) + { + + if ( !Q_stricmp( condition.getParm( 1 ) , "player" ) ) + { + Player *player; + Entity *enemy; + + Vector dist; + float length; + + player = GetPlayer( 0 ); + if ( !player ) + return false; + + dist = origin - player->origin; + length = dist.length(); + + enemy = NULL; + enemy = enemyManager->GetCurrentEnemy(); + + if ( enemy ) + { + if ( ( length < (absoluteMax * 2 ) ) && ( length > absoluteMin ) ) + return true; + } + + if ( ( length < absoluteMax ) && ( length > absoluteMin ) ) + return true; + } + + return false; + + } + +qboolean Actor::checkInPreferredRange( Conditional &condition ) + { + if ( !Q_stricmp( condition.getParm( 1 ) , "player" ) ) + { + Player *player; + Vector dist; + float length; + + player = GetPlayer( 0 ); + if ( !player ) + return false; + + dist = origin - player->origin; + length = dist.length(); + + if ( ( length < preferredMax ) && ( length > preferredMin ) ) + return true; + } + + return false; + } + +qboolean Actor::checkCrippled( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_CRIPPLED ); + } + +qboolean Actor::checkDisabled( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_DISABLED ); + } + +qboolean Actor::checkInAlcove( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_IN_ALCOVE ); + } + +qboolean Actor::checkPlayerInCallVolume( Conditional &condition ) + { + return GetActorFlag(ACTOR_FLAG_PLAYER_IN_CALL_VOLUME); + } + +qboolean Actor::checkInCallVolume( Conditional &condition ) + { + return GetActorFlag(ACTOR_FLAG_IN_CALL_VOLUME); + } + +qboolean Actor::checkUsingWeaponNamed( Conditional &condition ) + { + str weaponName = condition.getParm( 1 ); + return checkUsingWeaponNamed ( weaponName ); + + } + +qboolean Actor::checkUsingWeaponNamed ( const str &name ) + { + return combatSubsystem->UsingWeaponNamed( name ); + } + +qboolean Actor::checkOutOfTorsoRange( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_OUT_OF_TORSO_RANGE ); + } + +qboolean Actor::returntrue( Conditional &condition ) + { + return true; + } + + +//-------------------------------------------------------------- +// +// Name: checkPropChance +// Class: Actor +// +// Description: Check the chance based on the property passed. +// This property should be located in the Actor's +// StateData object in the gameplay database. +// +// Parameters: Conditional &conditional +// - objname -- Name of the object to check +// - propname -- Name of the property to get the chance for. +// +// Returns: qboolean +// +//-------------------------------------------------------------- +qboolean Actor::checkPropChance( Conditional &condition ) + { + str propname; + str objname = condition.getParm( 1 ); + str scopestr; + + if ( condition.numParms() > 1 ) + propname = condition.getParm( 2 ); + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + + if ( !objname.length() ) + scopestr = getArchetype(); + else + scopestr = getArchetype() + "." + objname; + + if ( !gpm->hasObject(scopestr) ) + return false; + + float chance; + if ( propname.length() ) + chance = gpm->getFloatValue(scopestr, propname); + else + chance = gpm->getFloatValue(scopestr, "value"); + + return ( G_Random() <= chance ); + } + +//-------------------------------------------------------------- +// +// Name: checkPropExists +// Class: Actor +// +// Description: Check to see if the property exists +// The Actor's StateData object will be asked +// if it has the property. +// +// Parameters: Conditional &conditional +// - objname -- Name of the object to check +// - propname -- Name of the property to check for +// +// Returns: qboolean +// +//-------------------------------------------------------------- +qboolean Actor::checkPropExists( Conditional &condition ) + { + str objname = condition.getParm( 1 ); + str propname = condition.getParm( 2 ); + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str scopestr = getArchetype() + "." + objname; + if ( !gpm->hasProperty(scopestr, propname) ) + return false; + + return true; + } + +Condition Actor::Conditions[] = + { + { "default", &Actor::returntrue }, + { "INACTIVE", &Actor::checkinactive }, + { "ANIM_DONE", &Actor::checkanimdone }, + { "TORSO_ANIM_DONE", &Actor::checktorsoanimdone }, + { "DEAD", &Actor::checkdead }, + { "ACTOR_FLAG", &Actor::checkActorFlag }, + { "HAVE_ENEMY", &Actor::checkhaveenemy }, + { "ENEMY_DEAD", &Actor::checkenemydead }, + { "ENEMY_NOCLIP", &Actor::checkenemynoclip }, + { "CAN_ATTACK_ENEMY", &Actor::checkCanAttackEnemy }, + { "CAN_SEE_ENEMY", &Actor::checkcanseeenemy }, + { "CAN_SEE_PLAYER", &Actor::checkcanseeplayer }, + { "ENEMY_IN_FOV", &Actor::checkenemyinfov }, + { "ENEMY_RELATIVE_YAW", &Actor::checkenemyrelativeyaw }, + { "CHECK_ENEMY_YAW_RANGE", &Actor::checkenemyyawrange }, + { "CAN_SHOOT_ANY_ENEMY", &Actor::checkCanAttackAnyEnemy }, + { "ENEMY_ON_GROUND", &Actor::checkenemyonground }, + { "CAN_JUMP_TO_ENEMY", &Actor::checkcanjumptoenemy }, + { "CAN_FLY_TO_ENEMY", &Actor::checkcanflytoenemy }, + { "PAIN", &Actor::checkinpain }, + { "SMALL_PAIN", &Actor::checksmallpain }, + { "PAIN_YAW", &Actor::checkpainyaw }, + { "PAIN_PITCH", &Actor::checkpainpitch }, + { "SHOW_PAIN", &Actor::checkShowPain }, + { "STUNNED", &Actor::checkstunned }, + { "FINISHED", &Actor::checkfinished }, + { "MELEE_HIT", &Actor::checkmeleehit }, + { "HIT_WORLD", &Actor::checkMeleeHitWorld }, + { "BLOCKED_HIT", &Actor::checkblockedhit }, + { "BLOCKED", &Actor::checkblocked }, + { "OTHER_DIED", &Actor::checkotherdied }, + { "STUCK", &Actor::checkstuck }, + { "NO_PATH", &Actor::checknopath }, + { "STEERING_FAILED", &Actor::checkSteeringFailed }, + { "HAVE_PATH_TO_ENEMY", &Actor::checkHavePathToEnemy }, + { "ON_FIRE", &Actor::checkonfire }, + { "BEHAVIOR_DONE", &Actor::checkbehaviordone }, + { "BEHAVIOR_SUCCESS", &Actor::checkbehaviorsuccess }, + { "BEHAVIOR_FAILED", &Actor::checkbehaviorfailed }, + { "HEAD_BEHAVIOR_DONE", &Actor::checkheadbehaviordone }, + { "EYE_BEHAVIOR_DONE", &Actor::checkeyebehaviordone }, + { "TORSO_BEHAVIOR_DONE", &Actor::checktorsobehaviordone }, + { "TORSO_BEHAVIOR_FAILED", &Actor::checktorsobehaviorfailed }, + { "TORSO_BEHAVIOR_SUCCESS", &Actor::checktorsobehaviorsuccess }, + { "TIME_DONE", &Actor::checktimedone }, + { "DONE", &Actor::checkdone }, + { "RANGE", &Actor::checkenemyrange }, + { "ENEMY_WITHIN_RANGE", &Actor::checkEnemyWithinRange }, + { "ENEMY_ATTACHED", &Actor::checkEnemyAttached }, + { "PLAYER_RANGE", &Actor::checkplayerrange }, + { "PARENT_RANGE", &Actor::checkparentrange }, + { "CHANCE", &Actor::checkchance }, + { "MOVING_ACTOR_RANGE", &Actor::checkmovingactorrange }, + { "STATE_TIME", &Actor::checkstatetime }, + { "TIMES_DONE", &Actor::checktimesdone }, + { "MOD", &Actor::checkmeansofdeath }, + { "NOISE_HEARD", &Actor::checknoiseheard }, + { "PART_STATE", &Actor::checkpartstate }, + { "PART_DEAD", &Actor::checkpartdead }, + { "PART_FLAG", &Actor::checkpartflag }, + { "NUM_SPAWNS", &Actor::checknumspawns }, + { "COMMAND", &Actor::checkcommand }, + { "TOUCHED", &Actor::checktouched }, + { "TOUCHED_BY_PLAYER", &Actor::checktouchedbyplayer }, + { "ACTIVATED", &Actor::checkactivated }, + { "USED", &Actor::checkused }, + { "TWITCH", &Actor::checktwitch }, + { "HEALTH", &Actor::checkhealth }, + { "HEALTH_PERCENT_LESS_THAN", &Actor::checkhealthpercent }, + { "HEALTH_PERCENT_IN_RANGE", &Actor::checkhealthpercentinrange }, + { "ON_GROUND", &Actor::checkonground }, + { "IN_WATER", &Actor::checkinwater }, + { "INCOMING_MELEE_ATTACK", &Actor::checkincomingmeleeattack }, + { "INCOMING_RANGED_ATTACK", &Actor::checkincomingrangedattack }, + { "INCOMING_PROJECTILE", &Actor::checkincomingprojectile }, + { "ENEMY_STUNNED", &Actor::checkenemystunned }, + { "ENEMY_IN_PATH", &Actor::checkenemyinpath }, + { "STAGE", &Actor::checkstage }, + { "HELD", &Actor::checkheld }, + { "ENEMY_HAS_MELEE", &Actor::checkenemymelee }, + { "ENEMY_HAS_RANGED", &Actor::checkenemyranged }, + { "PLAYER_HAS_WEAPON", &Actor::checkplayerranged }, + { "HAS_THING", &Actor::checkhasthing }, + { "AT_COVER_NODE", &Actor::checkatcovernode }, + { "ALLOW_HANGBACK", &Actor::checkallowhangback }, + { "NAME", &Actor::checkname }, + { "ANIM_NAME", &Actor::checkanimname }, + { "CHECK_VAR", &Actor::checkVar }, + { "CHECK_VAR_TIME_DIFFERENCE", &Actor::checkVarTimeDifference }, + { "NODE_EXISTS", &Actor::checkNodeExists }, + { "COVER_NODES", &Actor::checkCoverNodes }, + { "SURFACE_DAMAGED", &Actor::checkSurfaceDamaged }, + { "BONE_DAMAGED", &Actor::checkBoneDamaged }, + { "REGION_DAMAGED", &Actor::checkRegionDamaged }, + { "CAPTURED", &Actor::checkCaptured }, + { "CAN_WALK_FORWARD", &Actor::checkCanWalkForward }, + { "HAS_THROWOBJECT", &Actor::checkHasThrowObject }, + { "ENEMY_IS_THROWOBJECT", &Actor::checkEnemyIsThrowObject }, + { "TURRET_MODE", &Actor::checkTurretMode }, + { "WEAPON_READY", &Actor::checkWeaponReady }, + { "PLAYER_VALID", &Actor::checkPlayerValid }, + { "IN_PREFERRED_RANGE", &Actor::checkInPreferredRange }, + { "IN_ABSOLUTE_RANGE", &Actor::checkInAbsoluteRange }, + { "IN_AI_MODE", &Actor::checkInAIMode }, + { "DISABLED", &Actor::checkDisabled }, + { "CRIPPLED", &Actor::checkCrippled }, + { "IN_ALCOVE", &Actor::checkInAlcove }, + { "PLAYER_IN_CALL_VOLUME", &Actor::checkPlayerInCallVolume }, + { "IN_CALL_VOLUME", &Actor::checkInCallVolume }, + { "IS_AGGRESSIVE", &Actor::checkIsAggressive }, + { "IN_CONE_OF_FIRE", &Actor::checkInConeOfFire }, + { "IN_PLAYER_CONE_OF_FIRE", &Actor::checkInPlayerConeOfFire }, + { "PATROL_NODE_IN_DISTANCE", &Actor::checkPatrolWaypointNodeInDistance }, + { "PATH_NODE_IN_DISTANCE", &Actor::checkPathNodeTypeInDistance }, + { "WEAPON_NAMED", &Actor::checkUsingWeaponNamed }, + { "OUT_OF_TORSO_RANGE", &Actor::checkOutOfTorsoRange }, + { "WANTS_TO_EXECUTE_PACKAGE", &Actor::checkWantsToExecutePackage }, + { "EXECUTED_IN_LAST", &Actor::checkExecutedPackageInLastTimeFrame }, + { "FORWARD_CLEAR", &Actor::checkForwardDirectionClear }, + { "REAR_CLEAR", &Actor::checkRearDirectionClear }, + { "LEFT_CLEAR", &Actor::checkLeftDirectionClear }, + { "RIGHT_CLEAR", &Actor::checkRightDirectionClear }, + { "LAST_STATE", &Actor::checkLastState }, + { "GROUP_MEMBER_IN_RANGE", &Actor::checkGroupMememberRange }, + { "ACTORTYPE", &Actor::checkActorType }, + { "IS_TEAMMATE", &Actor::checkIsTeammate }, + { "HAVE_ACTIVE_WEAPON", &Actor::checkHaveActiveWeapon }, + { "WEAPON_IS_MELEE", &Actor::checkWeaponIsMelee }, + { "WEAPON_CHANGED", &Actor::checkWeaponChanged }, + { "GROUP_HAS_THIS_NAME_LESS_THAN", &Actor::checkCountOfIdenticalNamesInGroup }, + { "REQUESTED_POSTURE", &Actor::checkRequestedPosture }, + { "POSTURE_ANIM_DONE", &Actor::checkPostureAnimDone }, + { "DAMAGE_THRESHOLD_EXCEEDED", &Actor::checkDamageThresholdExceeded }, + { "ATTACKED", &Actor::checkAttacked }, + { "ATTACKED_BY_PLAYER", &Actor::checkAttackedByPlayer }, + { "HELPERNODE_FLAGGED_IN_RANGED" , &Actor::checkHelperNodeWithFlagInRange }, + { "ENEMY_USING_WEAPON_NAMED", &Actor::checkEnemyWeaponNamed }, + { "PLAYER_USING_WEAPON_NAMED", &Actor::checkPlayerWeaponNamed }, + { "GROUP_HAS_NUMATTACKERS_LESS_THAN", &Actor::checkGroupAttackerCount }, + { "CURRENT_ENEMY_HAS_NUMATTACKERS_LESS_THAN", &Actor::checkCurrentEnemyGroupAttackerCount }, + { "HAVE_BEST_WEAPON" , &Actor::checkHaveBestWeapon }, + { "POSTURE", &Actor::checkPosture }, + { "ANY_ENEMY_IN_RANGE", &Actor::checkAnyEnemyInRange }, + { "VALID_COVER_NODE_IN_RANGE", &Actor::checkValidCoverNodeInRange }, + { "VALID_COMBAT_NODE_IN_RANGE", &Actor::checkValidCombatNodeInRange }, + { "VALID_WORK_NODE_IN_RANGE", &Actor::checkValidWorkNodeInRange }, + { "VALID_HIBERNATE_NODE_IN_RANGE", &Actor::checkValidHibernateNodeInRange }, + { "VALID_PATROL_NODE_IN_RANGE", &Actor::checkValidPatrolNodeInRange }, + { "VALID_CUSTOM_NODE_IN_RANGE", &Actor::checkValidCustomNodeInRange }, + { "VALID_SNIPER_NODE_IN_RANGE", &Actor::checkValidSniperNodeInRange }, + { "ENEMY_CAN_SEE_CURRENT_NODE", &Actor::checkEnemyCanSeeCurrentNode }, + { "FOLLOW_TARGET_OUT_OF_RANGE", &Actor::checkSpecifiedFollowTargetOutOfRange }, + { "WITHIN_FOLLOW_TARGET_MIN_RANGE", &Actor::checkWithinFollowRangeMin }, + { "IN_THE_WAY", &Actor::checkInTheWay }, + { "SHOULD_DO_ACTION", &Actor::checkShouldDoAction }, + { "HAVE_ARMOR", &Actor::checkHaveArmor }, + { "ALLOWED_TO_MELEE_ENEMY", &Actor::checkAllowedToMeleeEnemy }, + { "CURRENT_NODE_COVERTYPE", &Actor::checkCurrentNodeHasThisCoverType }, + { "BLOCKED_BY_ENEMY", &Actor::checkBlockedByEnemy }, + { "ENEMY_PROJECTILE_CLOSE", &Actor::checkEnemyProjectileClose }, + { "ACTIVATION_DELAY_DONE", &Actor::checkActivationDelayTime }, + { "TALKING", &Actor::checkTalking }, + { "ANY_ENEMIES_NEARBY", &Actor::checkEnemiesNearby }, + + + + // + // Property Conditionals for snagging data from the GPD + // + { "PROP_EXISTS", &Actor::checkPropExists }, + { "PROP_CHANCE", &Actor::checkPropChance }, + { "PROP_ENEMY_RANGE", &Actor::checkPropEnemyRange }, + + + // Depreciated Conditionals -- Need to be removed as soon as possible + { "CAN_SHOOT_ENEMY", &Actor::checkcanshootenemy }, + + { NULL, NULL } + }; + +//*********************************************************************************************** +// +// Code for seperate parts +// +//*********************************************************************************************** + +void Actor::RegisterParts( Event *ev ) + { + Entity *targetent; + qboolean forward; + int current_part; + part_t *forward_part; + part_t new_part; + Event *event; + + targetent = ev->GetEntity( 1 ); + forward = ev->GetInteger( 2 ); + + if ( !targetent ) + return; + + // See if we should tell other parts about each other + + if ( forward ) + { + // Tell all old parts about this new part and tell the new part about all of the old ones + + for ( current_part = 1 ; current_part <= parts.NumObjects() ; current_part++ ) + { + forward_part = &parts.ObjectAt( current_part ); + + if ( forward_part ) + { + // Tell old part about new part + + event = new Event( EV_ActorRegisterParts ); + event->AddEntity( targetent ); + event->AddInteger( false ); + forward_part->ent->PostEvent( event, 0.0f ); + + // Tell new part about old part + + event = new Event( EV_ActorRegisterParts ); + event->AddEntity( forward_part->ent ); + event->AddInteger( false ); + targetent->PostEvent( event, 0.0f ); + } + } + } + + // Add this part to our part list + + new_part.ent = targetent; + new_part.state_flags = 0; + + parts.AddObject( new_part ); + } + +void Actor::PartName( Event *ev ) + { + part_name = ev->GetString( 1 ); + } + +void Actor::RegisterSelf( Event *ev ) + { + Entity *targetent; + Actor *targetact; + Event *event; + part_t new_part; + + if ( target.length() > 0 ) + { + // Get the target entity + + targetent = G_FindTarget( this, target.c_str() ); + + if ( !targetent ) + return; + + // See if this target entity is a another part of ourselves + + if ( targetent->isSubclassOf( Actor ) ) + { + targetact = (Actor *)targetent; + + if ( ( name.length() > 0 ) && ( targetact->name == name ) ) + { + // Tell other part about ourselves + + event = new Event( EV_ActorRegisterParts ); + event->AddEntity( this ); + event->AddInteger( true ); + targetent->PostEvent( event, 0.0f ); + + // Add this part to our part list + + new_part.ent = targetent; + new_part.state_flags = 0; + parts.AddObject( new_part ); + } + } + } + } + +Actor *Actor::FindPartActor( const char *name ) + { + int current_part; + part_t *part; + Entity *partent; + Actor *partact; + + for ( current_part = 1 ; current_part <= parts.NumObjects() ; current_part++ ) + { + part = &parts.ObjectAt( current_part ); + + partent = part->ent; + partact = (Actor *)partent; + + if ( partact && ( partact->part_name == name ) ) + return partact; + } + + return NULL; + } + +void Actor::SendCommand( Event *ev ) + { + str command; + str part_to_send_to; + int i; + part_t *part; + Actor *partact; + + command = ev->GetString( 1 ); + part_to_send_to = ev->GetString( 2 ); + + if ( ( command.length() == 0 ) || ( part_to_send_to.length() == 0 ) ) + return; + + for( i = 1 ; i <= parts.NumObjects(); i++ ) + { + part = &parts.ObjectAt( i ); + + partact = ( Actor * )(Entity *)part->ent; + + if ( partact && ( partact->part_name == part_to_send_to ) ) + { + partact->command = command; + } + } + } + +//*********************************************************************************************** +// +// Dialog functions +// +//*********************************************************************************************** + +qboolean Actor::DialogExists( const str &aliasName ) + { + DialogNode_t *dialog_node; + + + dialog_node = dialog_list; + + while(dialog_node != NULL) + { + if ( stricmp(dialog_node->alias_name, aliasName.c_str()) == 0) + return true; + + dialog_node = dialog_node->next; + } + + return false; + } + + + +void Actor::AddDialog( Event *ev ) + { + DialogNode_t *dialog_node; + DialogNode_t *new_node; + + new_node = NewDialogNode(); + + if (new_node != NULL) + { + // Add the alias name to the dialog + strcpy(new_node->alias_name, ev->GetString( 1 )); + + // Add all the other parameters to the dialog + AddDialogParms( new_node, ev ); + + if ( dialog_list == NULL ) + { + // Add the new dialog to this dialog list + new_node->next = NULL; + dialog_list = new_node; + return; + } + + dialog_node = dialog_list; + while ( dialog_node->next != NULL ) + { + dialog_node = dialog_node->next; + } + + // Add the new dialog to this dialog list + dialog_node->next = new_node; + new_node->next = NULL; + return; + } + } + + + +DialogNode_t *Actor::NewDialogNode( void ) + { + DialogNode_t *dialog_node; + + dialog_node = new DialogNode_t; + memset( dialog_node, 0 , sizeof( DialogNode_t ) ); + dialog_node->random_percent = 1.0; + dialog_node->dType = DIALOG_TYPE_NORMAL; + + return dialog_node; + } + + +void Actor::AddDialogParms( DialogNode_t *dialog_node, Event *ev ) + { + const char *token; + int parm_type; + float temp_float; + int current_parm; + int num_parms; + + + if ( dialog_node == NULL ) + return; + + current_parm = 2; + num_parms = ev->NumArgs(); + + // Get all of the parameters + + while( 1 ) + { + if ( current_parm > num_parms ) + break; + + token = ev->GetString( current_parm ); + current_parm++; + + parm_type = DIALOG_PARM_TYPE_NONE; + + if (stricmp(token, "randompick") == 0) + dialog_node->random_flag = true; + else if (stricmp(token, "radiusdialog" ) == 0) + dialog_node->dType = DIALOG_TYPE_RADIUS; + else if (stricmp(token, "greetingdialog" ) == 0) + dialog_node->dType = DIALOG_TYPE_GREETING; + else if (stricmp(token, "combatdialog" ) == 0) + dialog_node->dType = DIALOG_TYPE_COMBAT; + else if (stricmp(token, "playerhas") == 0) + parm_type = DIALOG_PARM_TYPE_PLAYERHAS; + else if (stricmp(token, "playerhasnot") == 0) + parm_type = DIALOG_PARM_TYPE_PLAYERHASNOT; + else if (stricmp(token, "has") == 0) + parm_type = DIALOG_PARM_TYPE_HAS; + else if (stricmp(token, "has_not") == 0) + parm_type = DIALOG_PARM_TYPE_HASNOT; + else if (stricmp(token, "depends") == 0) + parm_type = DIALOG_PARM_TYPE_DEPENDS; + else if (stricmp(token, "dependsnot") == 0) + parm_type = DIALOG_PARM_TYPE_DEPENDSNOT; + else if (stricmp(token, "dependsint") == 0) + parm_type = DIALOG_PARM_TYPE_DEPENDSINT; + else if (stricmp(token, "contextinitiator" ) == 0) + parm_type = DIALOG_PARM_TYPE_CONTEXT_INITIATOR; + else if (stricmp(token, "contextresponse" ) == 0) + parm_type = DIALOG_PARM_TYPE_CONTEXT_RESPONSE; + else if (stricmp(token, "random") == 0) + { + if ( current_parm > num_parms ) + break; + + token = ev->GetString( current_parm ); + current_parm++; + + temp_float = (float)atof(token); + + if ( ( temp_float >= 0.0f ) && ( temp_float <= 1.0f ) ) + dialog_node->random_percent = temp_float; + else + gi.WDPrintf("Random percent out of range for dialog (alias %s)\n", dialog_node->alias_name); + } + else + gi.WDPrintf("Unknown parameter for dialog (alias %s)\n", dialog_node->alias_name); + + if (parm_type != DIALOG_PARM_TYPE_NONE) + { + if ( current_parm > num_parms ) + break; + + token = ev->GetString( current_parm ); + current_parm++; + + if (dialog_node->number_of_parms < MAX_DIALOG_PARMS) + { + strcpy(dialog_node->parms[dialog_node->number_of_parms].parm, token); + dialog_node->parms[dialog_node->number_of_parms].type = parm_type; + + if ( parm_type == DIALOG_PARM_TYPE_DEPENDSINT ) + { + token = ev->GetString( current_parm ); + current_parm++; + strcpy( dialog_node->parms[ dialog_node->number_of_parms ].parm2, token ); + } + + dialog_node->number_of_parms++; + } + else + { + gi.WDPrintf("Too many parms for dialog (alias %s)\n", dialog_node->alias_name); + } + } + } + } + + + + +void Actor::PlayDialog( Event *ev ) + { + Sentient *user = NULL; + const char *dialog_name = NULL; + const char *state_name = NULL; + float volume = DEFAULT_VOL; + float min_dist = DEFAULT_MIN_DIST; + qboolean headDisplay = false; + bool useTalk = false; + + if (ev->NumArgs() > 0) + { + dialog_name = ev->GetString( 1 ); + + if ( strcmp( dialog_name, "" ) == 0 ) + dialog_name = NULL; + } + + if ( ev->NumArgs() > 1 ) + volume = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + { + str minDistString; + + min_dist = ev->GetFloat( 3 ); + + minDistString = ev->GetString( 3 ); + + if ( stricmp( minDistString.c_str(), LEVEL_WIDE_STRING ) == 0 ) + min_dist = LEVEL_WIDE_MIN_DIST; + else + min_dist = ev->GetFloat( 3 ); + + if ( min_dist >= LEVEL_WIDE_MIN_DIST_CUTOFF ) + min_dist = LEVEL_WIDE_MIN_DIST; + } + + if ( ev->NumArgs() > 3 ) + headDisplay = ev->GetBoolean( 4 ); + + if ( ev->NumArgs() > 4 ) + useTalk = ev->GetBoolean( 4 ); + + if ( ev->NumArgs() > 5 ) + { + state_name = ev->GetString( 6 ); + + if ( strcmp( state_name, "" ) == 0 ) + state_name = NULL; + } + + if ( ev->NumArgs() > 6 ) + user = (Sentient *)ev->GetEntity( 7 ); + + //Note: Dialog coming from an event is ALWAYS important, so we want to be able to overide + //current dialog playing + PlayDialog( user, volume, min_dist, dialog_name, state_name, headDisplay, useTalk , true ); + } + +void Actor::PlayDialog( Sentient *user, float volume, float min_dist, const char *dialog_name, const char *state_name, qboolean headDisplay , bool useTalk , bool important ) +{ + str real_dialog; + char *morph_target_name; + const char *dialogAnim; + str dialog_anim; + int frame_number; + int index; + int morph_channel; + float dialog_length; + float morph_time; + float amplitude; + Event *new_event; + bool changedAnim; + + //First, if we are playing dialog, check if our new dialog is important + if ( GetActorFlag( ACTOR_FLAG_DIALOG_PLAYING ) && !important ) + return; + + //If we get here, then our dialog IS important, so let's check again if we're playing + // dialog... If so, then we need to stop our current dialog. + if ( GetActorFlag( ACTOR_FLAG_DIALOG_PLAYING ) ) + StopDialog(); + + SetActorFlag( ACTOR_FLAG_USING_HUD, false ); + + if ( dialog_name ) + real_dialog = dialog_name; + else + real_dialog = FindDialog( user , DIALOG_TYPE_NORMAL ); + + if ( !real_dialog.length()) + return; + + // localize the selected dialog filename + char localizedDialogName[MAX_QPATH]; + gi.LocalizeFilePath( real_dialog, localizedDialogName ); + + //Find the Anim to go along with the dialog, if there is one + dialogAnim = gi.Alias_FindSpecificAnim( edict->s.modelindex , localizedDialogName ); + + if(dialogAnim) + dialog_anim = dialogAnim; + + if ( dialog_anim.length() > 0 ) + { + if (gi.Alias_CheckLoopAnim( edict->s.modelindex, localizedDialogName )) + SetAnim(dialog_anim); + else + SetAnim(dialog_anim, EV_Actor_DialogAnimDone ); + } + + + Sound( str( localizedDialogName ), CHAN_DIALOG, volume, min_dist ); + SetActorFlag( ACTOR_FLAG_DIALOG_PLAYING, true ); + + dialog_length = gi.SoundLength( localizedDialogName ); + + dialog_done_time = level.time + dialog_length; + + // Add dialog to player + + Player *player = GetPlayer( 0 ); + + if ( player ) + { + if ( headDisplay ) + { + player->SetupDialog( this, localizedDialogName ); + SetActorFlag( ACTOR_FLAG_USING_HUD, true ); + } + else + player->SetupDialog( NULL, localizedDialogName ); + } + + if ( dialog_length > 0.0f ) + { + Event *headTwitchEvent; + + if ( state_name != NULL && currentState ) + { + if ( ( mode == ACTOR_MODE_SCRIPT ) || ( mode == ACTOR_MODE_IDLE ) ) + { + dialog_old_state_name = currentState->getName(); + dialog_state_name = state_name; + + Event *idle_event = new Event( EV_Actor_Idle ); + idle_event->AddString( state_name ); + ProcessEvent( idle_event ); + } + } + + index = -1; + changedAnim = false; + + // Start head twitch now + + CancelEventsOfType( EV_Actor_SetHeadTwitch ); + + headTwitchEvent = new Event( EV_Actor_SetHeadTwitch ); + headTwitchEvent->AddInteger( true ); + ProcessEvent( headTwitchEvent ); + + // Stop head twitch when dialog is done + + headTwitchEvent = new Event( EV_Actor_SetHeadTwitch ); + headTwitchEvent->AddInteger( false ); + PostEvent( headTwitchEvent, dialog_length ); + + while( 1 ) + { + morph_target_name = (char *)gi.GetNextMorphTarget( localizedDialogName, &index, &frame_number, &litude ); + + if ( index == -1 || !morph_target_name ) + break; + + // Determine the time we should start this morph + + morph_time = frame_number * ( 1.0f / LIP_SYNC_HZ ); + + // Start 2 frames early + + morph_time -= 2.0f * FRAMETIME; + + if ( morph_time < 0.0f ) + morph_time = 0.0f; + + if ( strnicmp( morph_target_name, "emt_", 4 ) == 0 ) + { + // Set the emotion + + new_event = new Event( EV_Actor_SetEmotion ); + new_event->AddString( morph_target_name + 4 ); + PostEvent( new_event, morph_time ); + } + else if ( strnicmp( morph_target_name, "anm_", 4 ) == 0 ) + { + // Make sure we don't screw things up if we're in TALK_MODE + if (mode != ACTOR_MODE_TALK ) + { + // Change the animation + new_event = new Event( EV_Actor_Anim ); + new_event->AddString( morph_target_name + 4 ); + PostEvent( new_event, morph_time ); + SetActorFlag(ACTOR_FLAG_PLAYING_DIALOG_ANIM , true ); + + changedAnim = true; + } + } + else + { + morph_channel = GetMorphChannel( morph_target_name ); + + new_event = new Event ( EV_Morph ); + + new_event->AddString( morph_target_name ); + + if ( morph_channel == MORPH_CHAN_MOUTH ) + amplitude *= _dialogMorphMult; + + new_event->AddFloat( amplitude ); + + if ( morph_channel == MORPH_CHAN_EYES || morph_channel == MORPH_CHAN_LEFT_LID || morph_channel == MORPH_CHAN_RIGHT_LID ) + new_event->AddFloat( 0.10f ); + else if ( morph_channel == MORPH_CHAN_MOUTH ) + new_event->AddFloat( 0.15f ); + else + new_event->AddFloat( 0.25f ); + + PostEvent( new_event, morph_time, EVENT_DIALOG_ANIM ); + } + } + + // Make sure the mouth shuts after dialog + + new_event = new Event ( EV_Morph ); + + new_event->AddString( "morph_mouth_base" ); + new_event->AddFloat( 0.0f ); + new_event->AddFloat( 0.10f ); + + PostEvent( new_event, dialog_length, EVENT_DIALOG_ANIM ); + + // Send the dialog done event + + PostEvent( EV_Actor_DialogDone, dialog_length ); + + if ( emotion != "none" ) + { + new_event = new Event( EV_Actor_SetEmotion ); + new_event->AddString( emotion.c_str() ); + PostEvent( new_event, dialog_length ); + } + + // Reset the anim if it has changed + + if ( changedAnim ) + { + new_event = new Event( EV_Actor_Anim ); + new_event->AddString( "idle" ); + PostEvent( new_event, dialog_length ); + + Event *clearFlag; + clearFlag = new Event ( EV_Actor_SetActorFlag ); + clearFlag->AddString( "playingdialoganim" ); + clearFlag->AddInteger( 0 ); + PostEvent( clearFlag , dialog_length ); + } + + } + else + { + SetActorFlag( ACTOR_FLAG_DIALOG_PLAYING, false ); + gi.WDPrintf( "Lip file not found for dialog %s\n", localizedDialogName ); + } + + if(useTalk) + StartTalkBehavior(user); +} + + +void Actor::PlayRadiusDialog( Sentient* user ) + { + str real_dialog; + + if ( GetActorFlag( ACTOR_FLAG_RADIUS_DIALOG_PLAYING ) ) + return; + + real_dialog = FindDialog( user, DIALOG_TYPE_RADIUS ); + + if ( !real_dialog.length() ) + return; + + StopDialog(); + SetActorFlag( ACTOR_FLAG_RADIUS_DIALOG_PLAYING, true ); + PlayDialog( user, DEFAULT_VOL, -1.0f, real_dialog.c_str() , NULL ); + } + +void Actor::StopDialog( Event *ev ) + { + StopDialog(); + } + +void Actor::StopDialog() + { + Event *new_event; + + if ( !GetActorFlag( ACTOR_FLAG_DIALOG_PLAYING ) ) + return; + + StopSound( CHAN_DIALOG ); + + // Stop all facial motion + + CancelEventsOfType( EV_Morph ); + CancelEventsOfType( EV_Unmorph ); + + // Undo current morph + + new_event = new Event ( EV_Morph ); + + new_event->AddString( "morph_base" ); + new_event->AddFloat( 100.0f ); + new_event->AddFloat( 0.10f ); + + ProcessEvent( new_event ); + + // Let everything know dialog is done + CancelEventsOfType( EV_Actor_DialogDone ); + CancelEventsOfType( EV_Actor_BroadcastDialog ); + ProcessEvent( EV_Actor_DialogDone ); + + if ( GetActorFlag( ACTOR_FLAG_USING_HUD ) ) + { + Player *player = GetPlayer( 0 ); + + if ( player ) + { + player->ClearDialog(); + } + } +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Actor::setBranchDialog( void ) +{ + Player* player = GetPlayer(0); + str commandString; + if( player ) + { + commandString = "stufftext \"displaybranchdialog "; + commandString += _branchDialogName; + commandString += "\"\n"; + gi.SendServerCommand(player->entnum, commandString); + gi.SendServerCommand(player->entnum, "stufftext \"pushmenu branchdialog\"\n"); + } +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Actor::clearBranchDialog( void ) +{ + _branchDialogName = ""; +} + + +//----------------------------------------------------- +// +// Name: BranchDialog +// Class: Actor +// +// Description: Displays a branch dialog to the player. +// +// Parameters: ev - the branch dialog event. +// +// Returns: +//----------------------------------------------------- +void Actor::BranchDialog(Event* ev) +{ + Player* player = GetPlayer( 0 ); + player->setBranchDialogActor(this); + + _branchDialogName = ev->GetString(1); + setBranchDialog(); +} + + +void Actor::SetEmotion( Event *ev ) + { + Event *new_event; + + // Unmorph the old emotion + + if ( emotion != "none" ) + { + new_event = new Event( EV_Unmorph ); + new_event->AddString( emotion ); + ProcessEvent( new_event ); + } + + if ( ev->NumArgs() > 0 ) + emotion = ev->GetString( 1 ); + + // Morph the new emotion + + if ( emotion != "none" ) + { + new_event = new Event( EV_Morph ); + new_event->AddString( emotion ); + ProcessEvent( new_event ); + } + } + +void Actor::SetBlink( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_SHOULD_BLINK, ev->GetBoolean( 1 ) ); + } + +void Actor::TryBlink( void ) + { + float average_blink_time; + Event *new_event; + + if ( next_blink_time <= level.time ) + { + if ( emotion == "angry" ) + average_blink_time = 6.0f; + else if ( emotion == "nervous" ) + average_blink_time = 2.0f; + else + average_blink_time = 4.0f; + + new_event = new Event( EV_Morph ); + new_event->AddString( "morph_lid-lshut" ); + new_event->AddFloat( 100.0f ); + new_event->AddFloat( .15f ); + new_event->AddInteger( true ); + ProcessEvent( new_event ); + + new_event = new Event( EV_Morph ); + new_event->AddString( "morph_lid-rshut" ); + new_event->AddFloat( 100.0f ); + new_event->AddFloat( .15f ); + new_event->AddInteger( true ); + ProcessEvent( new_event ); + + next_blink_time = level.time + average_blink_time + G_CRandom( average_blink_time / 4.0f ); + } + } + +void Actor::SetRadiusDialogRange( Event *ev ) + { + radiusDialogRange = ev->GetFloat(1); + } + +void Actor::SetSimplifiedThink( Event *ev ) + { + if ( ( ev->NumArgs() > 0 ) && ( ev->GetBoolean( 1 ) == false ) ) + { + if ( thinkStrategy != NULL ) + { + delete thinkStrategy; + thinkStrategy = NULL; + } + + thinkStrategy = new DefaultThink(); + } + else + { + if ( thinkStrategy != NULL ) + { + delete thinkStrategy; + thinkStrategy = NULL; + } + + thinkStrategy = new SimplifiedThink( (Actor *)this ); + } + + if ( !thinkStrategy ) + gi.Error( ERR_FATAL, "Actor Could not create thinkStrategy" ); + + } + +void Actor::SetActorToActorDamageModifier( Event *ev ) + { + float modifier = ev->GetFloat( 1 ); + + if ( modifier > 2.0f ) modifier = 2.0f; //Don't get out of hand + if ( modifier < 0.0f ) modifier = 0.0f; //That's as low as you can go... we don't want them healing each other + + actor_to_actor_damage_modifier = modifier; + + } + +void Actor::ReturnProjectile( Event *ev ) + { + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + + if (!incoming_proj) return; + + Vector dir = currentEnemy->origin - origin; + Vector vel = incoming_proj->velocity; + + Vector Angles = dir.toAngles(); + vel*=-1.0f; + + incoming_proj->setAngles(Angles); + incoming_proj->velocity = vel; + + + } + +float Actor::GetDialogRemainingTime( void ) + { + if ( GetActorFlag( ACTOR_FLAG_DIALOG_PLAYING ) ) + { + return dialog_done_time - level.time; + } + else + { + return 0; + } + } + +void Actor::FreeDialogList( void ) + { + DialogNode_t *dialog_node; + + dialog_node = dialog_list; + + while( dialog_node != NULL ) + { + dialog_list = dialog_node->next; + + delete dialog_node; + + dialog_node = dialog_list; + } + } + + + +void Actor::DialogDone( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_DIALOG_PLAYING, false ); + SetActorFlag( ACTOR_FLAG_RADIUS_DIALOG_PLAYING, false ); + + if ( dialog_state_name ) + { + dialog_state_name = ""; + + if ( ( mode != ACTOR_MODE_AI ) && ( mode != ACTOR_MODE_TALK ) ) + { + if ( dialog_old_state_name.length() ) + SetState( dialog_old_state_name.c_str() ); + } + } + } + + + +void Actor::SetMouthAngle( Event *ev ) + { + int tag_num; + float angle_percent; + Vector mouth_angles; + + + angle_percent = ev->GetFloat( 1 ); + + if ( angle_percent < 0.0f ) + angle_percent = 0.0f; + + if ( angle_percent > 1.0f ) + angle_percent = 1.0f; + + tag_num = gi.Tag_NumForName( edict->s.modelindex, "tag_mouth" ); + + if ( tag_num != -1 ) + { + SetControllerTag( ACTOR_MOUTH_TAG, tag_num ); + + mouth_angles = vec_zero; + mouth_angles[PITCH] = max_mouth_angle * angle_percent; + + SetControllerAngles( ACTOR_MOUTH_TAG, mouth_angles ); + } + } + +void Actor::DialogAnimDone( Event *ev ) + { + SetAnim( "idle" ); + } + +//*********************************************************************************************** +// +// Mode functions +// +//*********************************************************************************************** + + +qboolean Actor::ModeAllowed( int new_mode ) + { + if ( deadflag && ( actortype != IS_INANIMATE ) ) + return false; + + if ( ( new_mode == ACTOR_MODE_SCRIPT ) || ( new_mode == ACTOR_MODE_IDLE ) ) + { + if ( ( mode == ACTOR_MODE_AI ) || ( mode == ACTOR_MODE_TALK ) ) + return false; + } + else if ( new_mode == ACTOR_MODE_TALK ) + { + //Check if we're already speaking + if ( GetActorFlag( ACTOR_FLAG_DIALOG_PLAYING ) ) + return false; + + if ( /*( mode == ACTOR_MODE_AI ) ||*/ ( mode == ACTOR_MODE_TALK ) || ( actortype == IS_ENEMY ) || !GetActorFlag( ACTOR_FLAG_ALLOW_TALK ) || + !dialog_list || level.cinematic ) + return false; + } + + return true; + } + +void Actor::StartMode( int new_mode ) + { + if ( new_mode == ACTOR_MODE_TALK ) + { + SaveMode(); + CancelEventsOfType( EV_Actor_EndBehavior ); + CancelEventsOfType( EV_Actor_EndHeadBehavior ); + CancelEventsOfType( EV_Actor_EndEyeBehavior ); + CancelEventsOfType( EV_Actor_EndTorsoBehavior ); + RemoveAnimDoneEvent(); + } + + mode = new_mode; + } + +void Actor::EndMode( void ) + { + str currentanim; + + if ( mode == ACTOR_MODE_AI ) + { + if ( GetActorFlag( ACTOR_FLAG_UPDATE_BOSS_HEALTH ) && max_boss_health ) + { + char bosshealth_string[20]; + sprintf( bosshealth_string, "%.5f", 0.0 ); + gi.cvar_set( "bosshealth", bosshealth_string ); + } + + mode = ACTOR_MODE_IDLE; + ProcessEvent( EV_Actor_Idle ); + + if ( currentState ) + { + currentanim = currentState->getLegAnim( *this, &conditionals ); + + if ( currentanim.length() && ( currentanim != animname ) ) + SetAnim( currentanim, EV_Anim_Done ); + } + + enemyManager->ClearCurrentEnemy(); + } + else if ( mode == ACTOR_MODE_TALK ) + { + next_player_near = level.time + 5.0f; + RestoreMode(); + } + } + +void Actor::SaveMode( void ) + { + if ( mode == ACTOR_MODE_IDLE ) + { + saved_mode = ACTOR_MODE_IDLE; + + if ( currentState ) + saved_state_name = currentState->getName(); + else + saved_state_name = ""; + } + else if ( mode == ACTOR_MODE_AI ) + { + saved_mode = ACTOR_MODE_AI; + + if ( currentState ) + saved_state_name = currentState->getName(); + else + saved_state_name = ""; + + } + else if ( mode == ACTOR_MODE_SCRIPT ) + { + saved_mode = ACTOR_MODE_SCRIPT; + saved_behavior = behavior; + saved_headBehavior = headBehavior; + saved_eyeBehavior = eyeBehavior; + saved_torsoBehavior = torsoBehavior; + saved_scriptthread = scriptthread; + saved_anim_name = animname; + saved_anim_event_name = last_anim_event_name; + + //Reset Eyes and Head if they're f'd up. + if(headBehavior) + headBehavior->End(*this); + + if(eyeBehavior) + eyeBehavior->End(*this); + + if(torsoBehavior) + torsoBehavior->End(*this); + + behavior = NULL; + headBehavior = NULL; + eyeBehavior = NULL; + torsoBehavior = NULL; + scriptthread = NULL; + } + else + { + gi.WDPrintf( "Can't saved specified mode: %d\n", mode ); + } + } + +void Actor::RestoreMode( void ) + { + Event *idle_event; + + if ( saved_mode == ACTOR_MODE_IDLE ) + { + mode = ACTOR_MODE_IDLE; + + idle_event = new Event( EV_Actor_Idle ); + idle_event->AddString( saved_state_name ); + ProcessEvent( idle_event ); + } + if ( saved_mode == ACTOR_MODE_AI ) + { + mode = ACTOR_MODE_AI; + + SetState( saved_state_name ); + } + else if ( saved_mode == ACTOR_MODE_SCRIPT ) + { + StartMode( ACTOR_MODE_SCRIPT ); + + behavior = saved_behavior; + headBehavior = saved_headBehavior; + eyeBehavior = saved_eyeBehavior; + torsoBehavior = saved_torsoBehavior; + scriptthread = saved_scriptthread; + + if ( saved_behavior ) + currentBehavior = saved_behavior->getClassname(); + else + currentBehavior = ""; +/* + if ( saved_headBehavior ) + currentHeadBehavior = saved_headBehavior->getClassname(); + else + currentHeadBehavior = ""; + + if ( saved_eyeBehavior ) + currentEyeBehavior = saved_eyeBehavior->getClassname(); + else + currentEyeBehavior = ""; +*/ + if ( saved_torsoBehavior ) + currentTorsoBehavior = saved_torsoBehavior->getClassname(); + else + currentTorsoBehavior = ""; + + if ( saved_anim_event_name.length() ) + { + Event *event = new Event( saved_anim_event_name.c_str() ); + SetAnim( saved_anim_name, event ); + } + else + SetAnim( saved_anim_name ); + } + else + { + gi.WDPrintf( "Can't restore specified mode: %d\n", saved_mode ); + } + + saved_mode = ACTOR_MODE_NONE; + } + + +//*********************************************************************************************** +// +// Finishing functions +// +//*********************************************************************************************** + + +qboolean Actor::CanBeFinished( void ) + { + // See if actor can be finished by any means of death + + if ( can_be_finsihed_by_mods.NumObjects() > 0 ) + return true; + else + return false; + } + +qboolean Actor::CanBeFinishedBy( int meansofdeath ) + { + int number_of_mods; + int i; + + // Make sure in limbo + + if ( !InLimbo() ) + return false; + + // Make sure can be finished by this means of death + + number_of_mods = can_be_finsihed_by_mods.NumObjects(); + + for( i = 1 ; i <= number_of_mods ; i++ ) + { + if ( meansofdeath == can_be_finsihed_by_mods.ObjectAt( i ) ) + return true; + } + + return false; + } + +void Actor::SetCanBeFinishedBy( Event *ev ) + { + str mod_string; + int new_mod; + int number_of_mods; + int i; + + + number_of_mods = ev->NumArgs(); + + for ( i = 1 ; i <= number_of_mods ; i++ ) + { + mod_string = ev->GetString( i ); + + new_mod = MOD_NameToNum( mod_string ); + + if ( new_mod != -1 ) + can_be_finsihed_by_mods.AddObject( new_mod ); + } + } + +void Actor::Finish( int meansofdeath ) + { + // Make sure we can be finsihed by this means of death + + if ( CanBeFinishedBy( meansofdeath ) ) + { + // Save that the actor is being finished + + SetActorFlag( ACTOR_FLAG_FINISHED, true ); + + // Kill the actor + + ProcessEvent( EV_Actor_Suicide ); + + // Make sure the correct means of death is set + + means_of_death = meansofdeath; + } + else + { + gi.WDPrintf( "Actor can't be finished by %d means of death\n", meansofdeath ); + } + } + +void Actor::StartLimbo( void ) + { + State *temp_state; + qboolean found_state; + + // Make sure we have a little bit of health + + health = 1; + + // Go to the limbo state + + found_state = false; + + if ( statemap ) + { + temp_state = statemap->FindState( "LIMBO" ); + + if ( temp_state ) + { + currentState = temp_state; + InitState(); + found_state = true; + SetActorFlag( ACTOR_FLAG_IN_LIMBO, true ); + } + } + + if ( !found_state ) + { + // Didn't find a limbo state so just die + + ProcessEvent( EV_Actor_Suicide ); + } + } + +qboolean Actor::InLimbo( void ) + { + return GetActorFlag( ACTOR_FLAG_IN_LIMBO ); + } + + +//*********************************************************************************************** +// +// General functions +// +//*********************************************************************************************** + +void Actor::IgnorePainFromActors( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_IGNORE_PAIN_FROM_ACTORS, true ); + } + +void Actor::UpdateBossHealth( Event *ev ) + { + bool update = true; + bool forceBar = false; + + if ( ev->NumArgs() > 0 ) + update = ev->GetBoolean( 1 ); + + if ( ev->NumArgs() > 1 ) + forceBar = ev->GetBoolean( 2 ); + + if ( !update && GetActorFlag( ACTOR_FLAG_UPDATE_BOSS_HEALTH ) ) + { + char bosshealth_string[20]; + sprintf( bosshealth_string, "%.5f", 0.0 ); + gi.cvar_set( "bosshealth", bosshealth_string ); + } + + SetActorFlag( ACTOR_FLAG_UPDATE_BOSS_HEALTH, update ); + SetActorFlag( ACTOR_FLAG_FORCE_LIFEBAR , forceBar ); + + } + +void Actor::SetMaxBossHealth( Event *ev ) + { + max_boss_health = ev->GetFloat( 1 ); + } + +void Actor::TouchTriggers( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_TOUCH_TRIGGERS, ev->GetBoolean( 1 ) ); + } + +void Actor::IgnoreWater( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_IGNORE_WATER, ev->GetBoolean( 1 ) ); + } + +void Actor::SetNotAllowedToKill( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_ALLOWED_TO_KILL, false ); + } + +void Actor::IgnorePlacementWarning( Event *ev ) + { + str warning; + + warning = ev->GetString( 1 ); + + if ( warning == "stuck" ) + SetActorFlag( ACTOR_FLAG_IGNORE_STUCK_WARNING, true ); + else if ( warning == "off_ground" ) + SetActorFlag( ACTOR_FLAG_IGNORE_OFF_GROUND_WARNING, true ); + } + +void Actor::SetTargetable( Event *ev ) + { + qboolean targetable; + + targetable = ev->GetBoolean( 1 ); + + SetActorFlag( ACTOR_FLAG_TARGETABLE, targetable ); + } + +qboolean Actor::CanTarget( void ) const + { + return GetActorFlag( ACTOR_FLAG_TARGETABLE ); + } + +void Actor::SetSpawnChance( Event *ev ) + { + spawn_chance = ev->GetFloat( 1 ); + } + +void Actor::AddSpawnItem( Event *ev ) + { + str spawn_item_name; + + spawn_item_name = ev->GetString( 1 ); + + spawn_items.AddObject( spawn_item_name ); + } + +void Actor::ClearSpawnItems( Event *ev ) + { + spawn_items.ClearObjectList(); + } + +void Actor::SpawnItems( void ) + { + int number_of_spawn_items; + int i; + qboolean spawn_random = false; + str spawn_item_name; + Player *player; + float health_chance; + //float water_chance; + float plasma_chance; + float bullets_chance; + float player_health; + int player_plasma; + int player_bullets; + + if ( spawn_chance == 0.0f ) return ; + + number_of_spawn_items = spawn_items.NumObjects(); + + // Spawn in all of the items in the spawn_item list + + if ( (number_of_spawn_items) && (G_Random( 100.0f) < spawn_chance) ) + { + for( i = 1 ; i <= number_of_spawn_items ; i++ ) + { + spawn_item_name = spawn_items.ObjectAt( i ); + + if ( spawn_item_name == "random" ) + spawn_random = true; + else + SpawnItem( spawn_item_name ); + } + } + else + { + spawn_random = true; + } + + // See if we should spawn a random item + + if ( spawn_random ) + { + if ( G_Random( 100.0f ) < spawn_chance ) + { + // Set up default chances + + health_chance = 1.0f; + //water_chance = 1.0f; + plasma_chance = 1.0f; + bullets_chance = 1.0f; + + // See what he player needs + + player = (Player *)g_entities[ 0 ].entity; + + player_health = player->health; + player_plasma = player->AmmoCount( "Plasma" ); + player_bullets = player->AmmoCount( "Bullet" ); + + // See if the player is low on health + + if ( player_health <= 50.0f ) + health_chance *= ( 60.0f - player_health ) / 10.0f; + + if ( player_plasma <= 20.0f ) + plasma_chance *= ( 30.0f - player_plasma ) / 10.0f; + + if ( player_bullets <= 50.0f ) + bullets_chance *= ( 60.0f - player_bullets ) / 10.0f; + + } + } + } + +void Actor::SpawnItem( const str &spawn_item_name ) + { + SpawnArgs args; + Entity *ent; + Item *item; + + + args.setArg( "model", spawn_item_name ); + ent = args.Spawn(); + + if ( !ent || !ent->isSubclassOf( Item ) ) + return; + + item = (Item *)ent; + + item->setOrigin( centroid ); + + item->ProcessPendingEvents(); + + item->PlaceItem(); + item->setOrigin( centroid ); + item->velocity = Vector( G_CRandom( 100.0f ), G_CRandom( 100.0f ), 200.0f + G_Random( 200.0f ) ); + item->edict->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + + // Give the new item a targetname + + item->targetname = targetname; + item->targetname += "_item"; + + item->SetTargetName( item->targetname ); + } + +qboolean Actor::CanJump( void ) + { + return ( animate->HasAnim( "jump" ) && animate->HasAnim( "fall" ) && animate->HasAnim( "land" ) ); + } + +void Actor::SetUseGravity( Event *ev ) + { + qboolean use_gravity = (qboolean) gravity ; + + if (ev->NumArgs() > 0 ) + { + use_gravity = ev->GetBoolean( 1 ); + } + else + gi.WDPrintf( "SetUseGravity missing boolean argument. Currently set to %d\n", gravity); + + SetActorFlag( ACTOR_FLAG_USE_GRAVITY, use_gravity ); + + if ( use_gravity ) + gravity = 1; + else + gravity = 0; + } + +void Actor::SetAllowFall( Event *ev ) + { + qboolean allow_fall; + + if ( ev->NumArgs() > 0 ) + allow_fall = ev->GetBoolean( 1 ); + else + allow_fall = true; + + SetActorFlag( ACTOR_FLAG_ALLOW_FALL, allow_fall ); + } + +void Actor::SetHaveThing( Event *ev ) + { + int thing_number; + qboolean thing_bool; + + thing_number = ev->GetInteger( 1 ); + thing_bool = ev->GetBoolean( 2 ); + + SetHaveThing ( thing_number , thing_bool ); + } + +void Actor::SetHaveThing( int thing_number, qboolean thing_bool ) + { + switch( thing_number ) + { + case 1 : + SetActorFlag( ACTOR_FLAG_HAS_THING1, thing_bool ); + break; + case 2 : + SetActorFlag( ACTOR_FLAG_HAS_THING2, thing_bool ); + break; + case 3 : + SetActorFlag( ACTOR_FLAG_HAS_THING3, thing_bool ); + break; + case 4 : + SetActorFlag( ACTOR_FLAG_HAS_THING4, thing_bool ); + break; + default : + gi.WDPrintf( "Has thing %d out of range\n", thing_number ); + return; + } + } + +void Actor::SetStickToGround( const bool stick ) +{ + movementSubsystem->SetStickToGround( stick ); +} + +void Actor::SetStickToGround( Event *ev ) +{ + movementSubsystem->SetStickToGround( ev->GetBoolean( 1 ) ); +} + +const bool Actor::GetStickToGround( void ) const +{ + return movementSubsystem->GetStickToGround(); +} + +void Actor::SetActorFlag( int flag, qboolean flag_value ) + { + unsigned int *flags; + int index; + int bit = 0; + + if ( flag > ACTOR_FLAG_MAX ) + { + gi.WDPrintf( "Actor flag %d out of range\n", flag ); + return; + } + + index = flag / 32; + + switch( index ) + { + case 0 : + flags = &actor_flags1; + bit = flag; + break; + case 1 : + flags = &actor_flags2; + bit = flag - 32; + break; + case 2 : + flags = &actor_flags3; + bit = flag - 64; + break; + case 3 : + flags = &actor_flags4; + bit = flag - 96; + break; + + default : + gi.WDPrintf( "Actor flag %d out of range\n", flag ); + return; + } + + if ( flag_value ) + *flags |= 1 << bit; + else + *flags &= ~( 1 << bit ); + } + +void Actor::SetActorFlag( const str &flag_name , qboolean flag_value ) + { + int flag; + flag = ActorFlag_string_to_int( flag_name ); + + SetActorFlag( flag, flag_value ); + } + +void Actor::SetActorFlag( Event *ev ) + { + str flag_name; + int flag; + qboolean flag_bool; + + flag_name = ev->GetString(1); + flag_bool = ev->GetBoolean(2); + + flag = ActorFlag_string_to_int( flag_name ); + + SetActorFlag( flag, flag_bool ); + + } + +qboolean Actor::GetActorFlag( const str &flag_name ) const + { + int flag; + flag = ActorFlag_string_to_int( flag_name ); + + return GetActorFlag( flag ); + } + +qboolean Actor::GetActorFlag( int flag ) const + { + const unsigned int *flags; + int index; + int bit = 0; + + if ( flag > ACTOR_FLAG_MAX ) + { + gi.WDPrintf( "Actor flag %d out of range\n", flag ); + return false; + } + + index = flag / 32; + + switch( index ) + { + case 0 : + flags = &actor_flags1; + bit = flag; + break; + case 1 : + flags = &actor_flags2; + bit = flag - 32; + break; + case 2 : + flags = &actor_flags3; + bit = flag - 64; + break; + case 3: + flags = &actor_flags4; + bit = flag - 96; + break; + + default : + gi.WDPrintf( "Actor flag %d out of range\n", flag ); + return false; + } + + if ( *flags & ( 1 << bit ) ) + return true; + else + return false; + } + + +void Actor::SetNotifyFlag( int flag, qboolean flag_value ) + { + unsigned int *flags; + int index; + + if ( flag > NOTIFY_FLAG_MAX ) + { + gi.WDPrintf( "Actor flag %d out of range\n", flag ); + return; + } + + index = flag / 32; + + switch( index ) + { + case 0 : + flags = ¬ify_flags1; + break; + + default : + gi.WDPrintf( "Notify flag %d out of range\n", flag ); + return; + } + + if ( flag_value ) + *flags |= 1 << flag; + else + *flags &= ~( 1 << flag ); + } + +void Actor::SetNotifyFlag( const str &flag_name , qboolean flag_value ) + { + int flag; + flag = NotifyFlag_string_to_int( flag_name ); + + SetNotifyFlag( flag, flag_value ); + } + +void Actor::SetNotifyFlag( Event *ev ) + { + str flag_name; + int flag; + qboolean flag_bool; + + flag_name = ev->GetString(1); + flag_bool = ev->GetBoolean(2); + + flag = NotifyFlag_string_to_int( flag_name ); + + SetNotifyFlag( flag, flag_bool ); + + } + +qboolean Actor::GetNotifyFlag( int flag ) + { + unsigned int *flags; + int index; + + if ( flag > NOTIFY_FLAG_MAX ) + { + gi.WDPrintf( "Actor flag %d out of range\n", flag ); + return false; + } + + index = flag / 32; + + switch( index ) + { + case 0 : + flags = ¬ify_flags1; + break; + + default : + gi.WDPrintf( "Notify flag %d out of range\n", flag ); + return false; + } + + if ( *flags & ( 1 << flag ) ) + return true; + else + return false; + } + + + + +void Actor::SetBounceOff( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_BOUNCE_OFF, true ); + } + +void Actor::BounceOffEvent( Event *ev ) + { + Vector object_origin; + Vector object_angles; + + if ( bounce_off_effect.length() ) + { + object_origin = ev->GetVector( 1 ); + + //Calculate Angles as being a Vector from the centroid to point of impact; + object_angles = object_origin - centroid; + object_angles.toAngles(); + + SpawnEffect( bounce_off_effect , object_origin, object_angles , 5.0f ); + } + } + +void Actor::SetBounceOffEffect( Event *ev ) + { + bounce_off_effect = ev->GetString( 1 ); + } + +void Actor::GotoNextStage( Event *ev ) + { + stage++; + } + +void Actor::GotoPrevStage( Event *ev ) + { + stage--; + + if ( stage < 1 ) + stage = 1; + } + +void Actor::GotoStage( Event *ev ) + { + stage = ev->GetInteger( 1 ); + } + + +//-------------------------------------------------------------- +// +// Name: GetStage +// Class: Actor +// +// Description: Added so the scripters can access the current stage +// of this actor. +// +// Parameters: Event *ev -- not used +// +// Returns: None (stage number via event) +// +//-------------------------------------------------------------- +void Actor::GetStage( Event *ev ) + { + ev->ReturnFloat( (float)stage ); + } + +void Actor::NotifyOthersAtDeath( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_NOTIFY_OTHERS_AT_DEATH, true ); + } + +void Actor::NotifyOthersOfDeath( void ) + { + int i; + Actor *act; + + for( i = 1; i <= ActiveList.NumObjects(); i++ ) + { + act = ActiveList.ObjectAt( i ); + + //if ( name.length() && name == act->name && Vector( act->origin - origin ).length() < 1000 ) + if ( name.length() && ( name == act->name ) ) + act->AddStateFlag( STATE_FLAG_OTHER_DIED ); + } + } + +void Actor::Pickup( Event *ev ) + { + str tag_name; + int tag_num; + Vector new_angles; + + tag_name = ev->GetString( 1 ); + tag_num = gi.Tag_NumForName( edict->s.modelindex, tag_name.c_str() ); + + new_angles = Vector(0, 0, 0); + + if ( pickup_ent ) + { + pickup_ent->setAngles( new_angles ); + pickup_ent->attach( entnum, tag_num ); + } + } + +void Actor::Throw( Event *ev ) + { + int i; + int num; + Entity *child; + Vector pos; + Vector forward; + str tag_name; + int tag_num; + + + tag_name = ev->GetString( 1 ); + tag_num = gi.Tag_NumForName( edict->s.modelindex, tag_name.c_str() ); + + + if ( bind_info ) + { + for ( i=0,num = bind_info->numchildren; i < MAX_MODEL_CHILDREN; i++ ) + { + if ( bind_info->children[i] == ENTITYNUM_NONE ) + { + continue; + } + + child = ( Entity * )G_GetEntity( bind_info->children[i] ); + + if ( child->edict->s.tag_num == tag_num ) + { + child->detach(); + + child->setSolidType( SOLID_BBOX ); + + child->setAngles( angles ); + + child->groundentity = NULL; + + tag_num = gi.Tag_NumForName( edict->s.modelindex, tag_name.c_str() ); + GetTag( tag_num, &pos, &forward ); + + child->velocity = orientation[0]; + child->velocity *= 500.0f; + + child->velocity.z = 400.0f; + } + + num--; + + if ( !num ) + break; + } + } + } + +void Actor::SolidMask( Event *ev ) + { + edict->clipmask = MASK_MONSTERSOLID; + } + +void Actor::IgnoreMonsterClip( Event *ev ) + { + edict->clipmask &= ~CONTENTS_MONSTERCLIP; + } + +void Actor::NotSolidMask( Event *ev ) + { + edict->clipmask = MASK_SOLID; + } + +void Actor::NoMask( Event *ev ) + { + edict->clipmask = 0; + } + +void Actor::ResetMoveDir( Event *ev ) + { + Vector newForward; + angles.AngleVectors( &newForward ); + + movementSubsystem->setMoveDir( newForward ); + } + +void Actor::SetMask( Event *ev ) + { + str mask_name; + + mask_name = ev->GetString( 1 ); + + if ( mask_name == "monstersolid" ) + edict->clipmask = MASK_MONSTERSOLID; + else if ( mask_name == "deadsolid" ) + edict->clipmask = MASK_DEADSOLID; + else if ( mask_name == "none" ) + edict->clipmask = 0; + else if ( mask_name == "pathsolid" ) + edict->clipmask = MASK_PATHSOLID; + else + gi.WDPrintf( "Unknown mask name - %s\n", mask_name.c_str() ); + } + +void Actor::setSize( Vector min, Vector max ) + { + Sentient::setSize( min * edict->s.scale, max * edict->s.scale ); + } + +void Actor::SetHealth( Event *ev ) + { + health = ev->GetFloat( 1 ); //* edict->s.scale + + if (max_health < health ) + { + max_health = health; + } + + } + +void Actor::SetMaxHealth( Event *ev ) + { + max_health = ev->GetFloat( 1 ); // * edict->s.scale; + } + +void Actor::SetVar( Event *ev ) + { + StateVar *checkVar = 0; + str varName = ev->GetString(1); + str varValue = ev->GetString(2); + + //First Check if we already have the Var and it just needs + //to have its value updated + for (int i = 1; i <= stateVarList.NumObjects(); i++ ) + { + checkVar = stateVarList.ObjectAt(i); + if( !stricmp( checkVar->varName, varName ) ) + { + checkVar->varValue = varValue; + return; + } + } + + //Didn't find one, so we have to create a new one. + checkVar = new StateVar; + checkVar->varName = varName; + checkVar->varValue = varValue; + + stateVarList.AddObject( checkVar ); + + } + +//-------------------------------------------------------------- +// Name: SetVarTime +// Class: Actor +// +// Description: Sets the level time into a state var +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::SetVarTime( Event *ev ) +{ + StateVar *checkVar = 0; + str varName = ev->GetString(1); + + //First Check if we already have the Var and it just needs + //to have its value updated + for (int i = 1; i <= stateVarList.NumObjects(); i++ ) + { + checkVar = stateVarList.ObjectAt(i); + if( !stricmp( checkVar->varName, varName ) ) + { + checkVar->varTime = level.time; + return; + } + } + + //Didn't find one, so we have to create a new one. + checkVar = new StateVar; + checkVar->varName = varName; + checkVar->varTime = level.time; + + stateVarList.AddObject( checkVar ); +} + +void Actor::SetTurnSpeed( Event *ev ) + { + movementSubsystem->setTurnSpeed(ev->GetFloat( 1 ) ); + } + +void Actor::SetMaxInactiveTime( Event *ev ) + { + max_inactive_time = ev->GetFloat( 1 ); + } + +bool Actor::IsEntityAlive( const Entity *ent ) + { + return ( ent && !ent->deadflag && ( ent->health > 0.0f ) && !(ent->flags & FL_NOTARGET) && level.ai_on ); + } + +void Actor::Name( Event *ev ) + { + name = ev->GetString( 1 ); + } + +void Actor::SetupTriggerField( Event *ev ) + { + Vector min; + Vector max; + TouchField *trig; + + min = ev->GetVector( 1 ); + max = ev->GetVector( 2 ); + + min = min + origin; + max = max + origin; + + trig = new TouchField; + trig->Setup( this, EV_ActorTriggerTouched, min, max, TRIGGER_PLAYERS | TRIGGER_MONSTERS ); + trigger = trig; + } + +void Actor::TriggerTouched( Event *ev ) + { + Entity *other; + + other = ev->GetEntity( 1 ); + + if ( + ( other->movetype != MOVETYPE_NONE ) && + ( other->movetype != MOVETYPE_STATIONARY ) && + ( IsEntityAlive( other ) ) + ) + AddStateFlag( STATE_FLAG_TOUCHED ); + } + +void Actor::AddStateFlag( unsigned int flag ) + { + int current_other_part; + part_t *other_part; + Entity *other_ent; + Actor *other_act; + int current_part; + part_t *part; + + + // Update my state flags + + state_flags |= flag; + + // Update all the other parts of my state flags + + for ( current_other_part = 1 ; current_other_part <= parts.NumObjects() ; current_other_part++ ) + { + other_part = &parts.ObjectAt( current_other_part ); + + other_ent = other_part->ent; + other_act = (Actor *)other_ent; + + // Look for ourselves in this part's part list + + for ( current_part = 1 ; current_part <= other_act->parts.NumObjects() ; current_part++ ) + { + part = &other_act->parts.ObjectAt( current_part ); + + if ( part->ent == this ) + { + // Found ourselves, update state flags + + part->state_flags |= flag; + } + } + } + } + +void Actor::ClearStateFlags( void ) + { + int current_part; + part_t *part; + + + // Clear my state flags + + state_flags = 0; + + // Clear all the other parts state flags + + for ( current_part = 1 ; current_part <= parts.NumObjects() ; current_part++ ) + { + part = &parts.ObjectAt( current_part ); + + part->state_flags = 0; + } + + movementSubsystem->clearBlockingEntity(); + } + +void Actor::NoChatterEvent( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_NOCHATTER, true ); + } + +void Actor::Chatter( const char *snd, float chance, float volume, int channel ) + { + str realname; + + if ( GetActorFlag( ACTOR_FLAG_NOCHATTER ) || ( chattime > level.time ) ) + { + return; + } + + if ( G_Random( 10.0f ) > chance ) + { + chattime = level.time + 1.0f + G_Random( 2.0f ); + return; + } + + realname = GetRandomAlias( snd ); + if ( realname.length() > 1 ) + { + float delay; + + delay = gi.SoundLength( realname.c_str() ); + + if ( delay < 0.0f ) + gi.WDPrintf( "Lip file not found for dialog %s\n", realname.c_str() ); + + chattime = level.time + delay + 4.0f + G_Random( 5.0f ); + Sound( realname, channel, volume ); + } + else + { + // set it into the future, so we don't check it again right away + chattime = level.time + 1.0f; + } + } + +void Actor::ActivateEvent( Event *ev ) + { + if ( ( deadflag ) && ( actortype != IS_INANIMATE ) ) + { + return; + } + + ProcessEvent( EV_Actor_AttackPlayer ); + + AddStateFlag( STATE_FLAG_ACTIVATED ); + } + +void Actor::UseEvent( Event *ev ) +{ + Entity *entity; + + // Can only be used once every 1/4 second + if ( last_used_time + 0.25f >= level.time ) + return; + + if ( GetActorFlag(ACTOR_FLAG_CANNOT_USE) ) + return; + + //Can't switch to talk mode if a cinematic is going on + if ( level.cinematic ) + return; + + // Actors can't be used by equipment + + entity = ev->GetEntity( 1 ); + + if ( entity->isSubclassOf( Equipment ) ) + return; + + last_used_time = level.time; + + AddStateFlag( STATE_FLAG_USED ); + + if ( onuse_thread_name.length() > 0 ) + { + RunThread(onuse_thread_name); + } + + if ( entity->isSubclassOf( Sentient ) && getSolidType() == SOLID_BBOX && !hidden() ) + { + Sentient *user; + user = (Sentient *)entity; + + StartTalkBehavior( user ); + } +} + +void Actor::StartTalkBehavior(Sentient *user) +{ + Talk *talk; + + if ( !ModeAllowed( ACTOR_MODE_TALK ) ) + return; + + StartMode( ACTOR_MODE_TALK ); + + talk = new Talk; + talk->SetUser( user ); + SetBehavior( talk ); +} + +void Actor::SetOnUseThread( Event *ev ) + { + onuse_thread_name = ev->GetString( 1 ); + } + +void Actor::ClearOnUseThread( Event *ev ) + { + onuse_thread_name = ""; + } + +void Actor::Think( void ) + { + if ( !Director.PlayerReady() ) + { + last_time_active = level.time; + return; + } + + if ( thinkStrategy ) + thinkStrategy->Think( *this ); + + if ( !level.ai_on ) + LevelAIOff(); + else + LevelAIOn(); + + /* + Vector BS( 0 , 0 , 100 ); + + + G_DrawDebugNumber( origin + BS , currentHelperNode.nodeID , 1 , 0 , 1, 0 ); + if ( currentHelperNode.node ) + { + if ( currentHelperNode.node->isReserved() ) + G_DebugLine( centroid, currentHelperNode.node->origin, 1.0f, 1.0f, 1.0f, 1.0f ); + } + */ + + + +/* + str pState; + switch ( movementSubsystem->getPostureState() ) + { + case POSTURE_TRANSITION_STAND_TO_CROUCH: + pState = "POSTURE_TRANSITION_STAND_TO_CROUCH\n"; + break; + + case POSTURE_TRANSITION_STAND_TO_PRONE: + pState = "POSTURE_TRANSITION_STAND_TO_PRONE\n"; + break; + + case POSTURE_TRANSITION_STAND_TO_LEAN_LEFT_DUAL: + pState = "POSTURE_TRANSITION_STAND_TO_LEAN_LEFT_DUAL\n"; + break; + + case POSTURE_TRANSITION_STAND_TO_LEAN_RIGHT_DUAL: + pState = "POSTURE_TRANSITION_STAND_TO_LEAN_RIGHT_DUAL\n"; + break; + + case POSTURE_TRANSITION_CROUCH_TO_STAND: + pState = "POSTURE_TRANSITION_CROUCH_TO_STAND\n"; + break; + + case POSTURE_TRANSITION_CROUCH_TO_PRONE: + pState = "POSTURE_TRANSITION_CROUCH_TO_PRONE\n"; + break; + + case POSTURE_TRANSITION_PRONE_TO_CROUCH: + pState = "POSTURE_TRANSITION_PRONE_TO_CROUCH\n"; + break; + + case POSTURE_TRANSITION_PRONE_TO_STAND: + pState = "POSTURE_TRANSITION_PRONE_TO_STAND\n"; + break; + + case POSTURE_TRANSITION_LEAN_LEFT_DUAL_TO_STAND: + pState = "POSTURE_TRANSITION_LEAN_LEFT_DUAL_TO_STAND\n"; + break; + + case POSTURE_TRANSITION_LEAN_RIGHT_DUAL_TO_STAND: + pState = "POSTURE_TRANSITION_LEAN_RIGHT_DUAL_TO_STAND\n"; + break; + + case POSTURE_STAND: + pState = "POSTURE_STAND\n"; + break; + + case POSTURE_CROUCH: + pState = "POSTURE_CROUCH\n"; + break; + + case POSTURE_PRONE: + pState = "POSTURE_PRONE\n"; + break; + + case POSTURE_LEAN_LEFT_DUAL: + pState = "POSTURE_LEAN_LEFT_DUAL\n"; + break; + + case POSTURE_LEAN_RIGHT_DUAL: + pState = "POSTURE_LEAN_RIGHT_DUAL\n"; + break; + } + + gi.Printf( pState.c_str() ); + */ + } + +qboolean Actor::GetClosestTag( const str &tag_name, int number_of_tags, const Vector &target, Vector *orig ) + { + str temp_tag_name; + Vector temp_orig; + Vector diff; + float dist; + float best_dist = -1; + qboolean found = false; + int i; + char number[5]; + + if ( number_of_tags == 1 ) + { + return GetTag( tag_name.c_str(), orig ); + } + + for( i = 1 ; i <= number_of_tags ; i++ ) + { + sprintf( number, "%d", i ); + + temp_tag_name = tag_name + str( number ); + + if ( GetTag( temp_tag_name.c_str(), &temp_orig ) ) + { + diff = target - temp_orig; + dist = diff * diff; + + if ( ( dist < best_dist ) || ( best_dist < 0 ) ) + { + best_dist = dist; + found = true; + *orig = temp_orig; + } + } + } + + return found; + } + +void Actor::Active( Event *ev ) + { + int active_flag; + + if ( ev->NumArgs() > 0 ) + { + active_flag = ev->GetInteger( 1 ); + + if ( active_flag ) + SetActorFlag( ACTOR_FLAG_INACTIVE, false ); + else + SetActorFlag( ACTOR_FLAG_INACTIVE, true ); + } + } + +void Actor::SpawnActorAboveEnemy( Event *ev ) + { + str model_name; + int how_many; + qboolean attack; + float width; + float height; + float how_far; + trace_t trace; + + Vector spawn_mins; + Vector spawn_maxs; + Vector new_orig; + Vector new_dir; + Vector Enemy_orig; + Vector Enemy_dir; + + model_name = ev->GetString( 1 ); + how_many = ev->GetInteger( 2 ); + attack = ev->GetBoolean( 3 ); + width = ev->GetFloat( 4 ); + height = ev->GetFloat( 5 ); + how_far = ev->GetFloat( 6 ); + + //Set Spawn Thing Origin and Angles to make it above the player + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + if ( currentEnemy->isSubclassOf (Player ) ) + { + Player* player; + player = (Player*)(Entity*)currentEnemy; + Enemy_orig = player->origin; + Enemy_dir = player->angles; + } + else + { + Actor* act; + act = (Actor*)(Entity*)currentEnemy; + Enemy_orig = act->origin; + Enemy_dir = act->angles; + } + + new_orig = Enemy_orig; + new_orig.z += how_far; + + new_dir = Enemy_dir; + + spawn_mins.x = -width; + spawn_mins.y = -width; + spawn_mins.z = 0; + + spawn_maxs.x = width; + spawn_maxs.y = width; + spawn_maxs.z = height; + + trace = G_Trace( Enemy_orig, spawn_mins, spawn_maxs, new_orig, NULL, MASK_MONSTERSOLID, false, "SpawnActorAbovePlayer" ); + + SpawnActor( model_name, trace.endpos, new_dir, how_many, attack, width, height, true); + } + +void Actor::SpawnActorAtLocation( Event *ev ) + { + str model_name; + str pathnode_name; + qboolean attack; + Vector orig; + Vector ang; + float width; + float height; + PathNode *goal; + int number_of_pathnodes; + Entity *effect; + trace_t trace; + Vector spawn_mins; + Vector spawn_maxs; + + + model_name = ev->GetString( 1 ); + pathnode_name = ev->GetString( 2 ); + number_of_pathnodes = ev->GetInteger( 3 ); + attack = ev->GetBoolean( 4 ); + width = ev->GetFloat( 5 ); + height = ev->GetFloat( 6 ); + + // Get the pathnode name to spawn in to + + pathnode_name += (int)G_Random( (float)number_of_pathnodes ) + 1; + + // Find the path node + + goal = thePathManager.FindNode( pathnode_name ); + + if ( !goal ) + { + gi.WPrintf( "Can't find position %s\n", pathnode_name.c_str() ); + return; + } + + // Set the spawn in position/direction + + orig = goal->origin; + ang = goal->angles; + + spawn_mins.x = -width; + spawn_mins.y = -width; + spawn_mins.z = 0; + + spawn_maxs.x = width; + spawn_maxs.y = width; + spawn_maxs.z = height; + + trace = G_Trace( orig + Vector( "0 0 64" ), spawn_mins, spawn_maxs, orig - Vector( "0 0 128" ), NULL, MASK_MONSTERSOLID, false, "SpawnActorAtLocation" ); + + if ( trace.allsolid ) + return; + + orig = trace.endpos; + + SpawnActor( model_name, orig, ang, 1, attack, width, height ); + + // Spawn in teleport effect + + effect = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + effect->setModel( "fx_teleport3.tik" ); + effect->setOrigin( orig ); + effect->setSolidType( SOLID_NOT ); + effect->animate->RandomAnimate( "idle", EV_Remove ); + effect->Sound( "snd_teleport" ); + } + +void Actor::SpawnActorAtTag( Event *ev ) + { + str model_name; + str tag_name; + int how_many; + qboolean attack; + float spawn_offset = 0; + Vector tag_orig; + Vector tag_dir; + Vector new_orig; + Vector new_dir; + float width; + float height; + qboolean force; + float addHeight; + + model_name = ev->GetString( 1 ); + tag_name = ev->GetString( 2 ); + how_many = ev->GetInteger( 3 ); + attack = ev->GetBoolean( 4 ); + width = ev->GetFloat( 5 ); + height = ev->GetFloat( 6 ); + + if ( ev->NumArgs() > 6 ) + spawn_offset = ev->GetFloat( 7 ); + else + spawn_offset = 0.0f; + + if ( spawn_offset < 0.0f ) spawn_offset = 0.0f; + + if ( ev->NumArgs() > 7 ) + force = ev->GetBoolean( 8 ); + else + force = false; + + if ( ev->NumArgs() > 8 ) + addHeight = ev->GetFloat( 9 ); + else + addHeight = 0; + + + // Calculate a good origin/angles + + + GetTag( tag_name.c_str(), &tag_orig, &tag_dir ); + + if(spawn_offset) + { + new_orig = tag_orig + tag_dir * spawn_offset; + new_orig[2] = new_orig[2] + addHeight; + + new_dir = tag_dir * -1.0f; + + SpawnActor( model_name, new_orig, new_dir, how_many, attack, width, height, force ); + return; + } + + float offset = 250.0f; + for ( int i = 0; i < how_many; i++) + { + Vector side; + origin.AngleVectors(NULL,&side,NULL); + side.normalize(); + + new_orig = side*offset+origin; + + if ( i%2 == 0 ) + new_orig*=-1.0f; + else + offset*=2.25f; + + new_dir = tag_dir * -1.0f; + SpawnActor( model_name, new_orig, new_dir, how_many, attack, width, height, force ); + } + } + +void Actor::SpawnActor( const str &model_name, const Vector &orig, const Vector &ang, int how_many, + qboolean attack, float width, float height, qboolean force ) + { + Actor *new_actor; + int current_actor; + trace_t trace; + Vector spawn_mins; + Vector spawn_maxs; + + + // Make sure this origin is reasonable + spawn_mins[0] = -width; + spawn_mins[1] = -width; + spawn_mins[2] = 0; + + spawn_maxs[0] = width; + spawn_maxs[1] = width; + spawn_maxs[2] = height; + + trace = G_Trace( orig, spawn_mins, spawn_maxs, orig, NULL, MASK_MONSTERSOLID, false, "Actor::SpawnActor" ); + + + if ( (trace.fraction != 1.0f || trace.allsolid) && !force ) + return; + + + // Spawn in all new actors + + for( current_actor = 0 ; current_actor < how_many ; current_actor++ ) + { + new_actor = new Actor; + new_actor->setModel( model_name ); + + new_actor->setOrigin( orig ); + new_actor->setAngles( ang ); + + // Make new actor attack player if requested + + if ( attack ) + new_actor->PostEvent( EV_Actor_AttackPlayer, 0.0f ); + + // Update number of spawns + + num_of_spawns++; + + // Save our parent + + new_actor->spawnparent = this; + + // Give the new actor a targetname + + new_actor->targetname = targetname; + new_actor->targetname += "_spawned"; + + new_actor->SetTargetName( new_actor->targetname ); + } + } + +void Actor::TryTalkToPlayer( void ) + { + int player_near = false; + Entity *ent_in_range; + int i; + Vector delta; + gentity_t *ed; + float dist2; + Sentient *user = NULL; + Talk *talk; + + + // See if we should even bother looking for players + + if ( level.cinematic ) + next_player_near = level.time + 5.0f; + + if ( deadflag || actortype != IS_FRIEND || next_player_near > level.time || !ModeAllowed( ACTOR_MODE_TALK ) ) + return; + + next_player_near = level.time + .2f + G_Random( .1f ); + + // See if we are near the player + + for( i = 0 ; i < game.maxclients; i++ ) + { + ed = &g_entities[ i ]; + + if ( !ed->inuse || !ed->entity ) + continue; + + ent_in_range = ed->entity; + + if ( EntityHasFireType( ent_in_range, FT_MELEE ) || EntityHasFireType( ent_in_range, FT_BULLET ) || + EntityHasFireType( ent_in_range, FT_PROJECTILE ) ) + continue; + + if ( IsEntityAlive( ent_in_range ) && ( ent_in_range->velocity == vec_zero ) && sensoryPerception) + { + delta = centroid - ent_in_range->centroid; + + // dot product returns length squared + + dist2 = delta * delta; + + if ( ( dist2 <= 100.0f * 100.0f ) && sensoryPerception->CanSeeEntity( this , ent_in_range , true , true ) ) + { + player_near = true; + user = (Sentient *)ent_in_range; + } + } + } + + if ( !player_near ) + { + SetActorFlag( ACTOR_FLAG_LAST_TRY_TALK, false ); + return; + } + + if ( !GetActorFlag( ACTOR_FLAG_LAST_TRY_TALK ) ) + { + SetActorFlag( ACTOR_FLAG_LAST_TRY_TALK, true ); + return; + } + + SetActorFlag( ACTOR_FLAG_LAST_TRY_TALK, false ); + + // Go to talk mode + + StartMode( ACTOR_MODE_TALK ); + + talk = new Talk; + talk->SetUser( user ); + SetBehavior( talk ); + } + +void Actor::AllowTalk( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_ALLOW_TALK, ev->GetBoolean( 1 ) ); + } + +void Actor::AllowHangBack( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_ALLOW_HANGBACK, ev->GetBoolean( 1 ) ); + } + +qboolean Actor::CheckBottom( void ) + { + //Vector test_mins, test_maxs; + Vector start, stop; + trace_t trace; + int x, y; + int corners_ok; + int middle_ok; + float width; + int mask; + + + width = maxs[0]; + + // First see if the origin is on a solid (this is the really simple test) + + start = origin - Vector( 0.0f, 0.0f, 1.0f ); + + if ( gi.pointcontents( start, 0 ) == CONTENTS_SOLID ) + return true; + + // Next see if at least 3 corners are on a solid (this is the simple test) + + corners_ok = 0; + + start[ 2 ] = absmin[ 2 ] - 1.0f; + + for( x = 0; x <= 1; x++ ) + { + for( y = 0; y <= 1; y++ ) + { + start[ 0 ] = x ? absmax[ 0 ] : absmin[ 0 ]; + start[ 1 ] = y ? absmax[ 1 ] : absmin[ 1 ]; + + if ( gi.pointcontents( start, 0 ) == CONTENTS_SOLID ) + corners_ok++; + } + } + + if ( corners_ok >= 3 ) + return true; + + // Next do the hard test + + corners_ok = 0; + middle_ok = 0; + + // Test the origin (if it is close to ground is a plus) + + start = origin; + stop = start - Vector( 0.0f, 0.0f, width ); // Test down as far as the actor is wide + + // Build the correct mask (the actor's normal mask without body) + + mask = edict->clipmask & ~CONTENTS_BODY; + + trace = G_Trace( start, vec_zero, vec_zero, stop, this, mask, false, "CheckBottom 1" ); + + if ( trace.fraction < 1.0f && trace.plane.normal[2] > .7f ) + middle_ok = 1; + + // Test all of the corners + + for( x = 0; x <= 1; x++ ) + { + for( y = 0; y <= 1; y++ ) + { + start[ 0 ] = x ? absmax[ 0 ] : absmin[ 0 ]; + start[ 1 ] = y ? absmax[ 1 ] : absmin[ 1 ]; + start[ 2 ] = origin[ 2 ]; + + stop = start - Vector( 0.0f, 0.0f, 2.0f * width ); + + trace = G_Trace( start, vec_zero, vec_zero, stop, this, mask, false, "CheckBottom 2" ); + + if ( ( trace.fraction < 1.0f ) && ( trace.plane.normal[2] > .7f ) ) + corners_ok++; + + if ( ( middle_ok && corners_ok >= 1 ) || ( corners_ok >= 3 ) ) + return true; + } + } + + return false; + } + +void Actor::ChangeType( Event *ev ) + { + velocity = vec_zero; + setModel( ev->GetString( 1 ) ); + PostEvent( EV_Actor_Wakeup, FRAMETIME ); + NoLerpThisFrame(); + } + +void Actor::GetStateAnims( Container *c ) + { + if ( statemap ) + statemap->GetAllAnims( c ); + } + +void Actor::Touched( Event *ev ) + { + Entity *other; + + other = ev->GetEntity( 1 ); + + sensoryPerception->Stimuli( STIMULI_SIGHT, other ); + + if ( other->isSubclassOf( Player) ) + { + AddStateFlag( STATE_FLAG_TOUCHED_BY_PLAYER ); + } + + } + +int Actor::ActorFlag_string_to_int( const str &actorflagstr ) const + { + str test; + + for (int i = 0; i < ACTOR_FLAG_MAX; i++) + { + test = actor_flag_strings[i]; + if ( !actorflagstr.icmp( actor_flag_strings[ i ] ) ) + return i; + } + + gi.WDPrintf( "Unknown actor flag - %s\n", actorflagstr.c_str() ); + return -1; + } + + +int Actor::NotifyFlag_string_to_int( const str ¬ifyflagstr ) + { + str test; + + for (int i = 0; i < NOTIFY_FLAG_MAX; i++) + { + test = actor_notify_strings[i]; + if ( !notifyflagstr.icmp( actor_notify_strings[ i ] ) ) + return i; + } + + gi.WDPrintf( "Unknown Notify Flag - %s\n", notifyflagstr.c_str() ); + return -1; + } + +void Actor::ArmorDamage( Event *ev ) + { + + AddStateFlag( STATE_FLAG_ATTACKED ); + + //if (!TakeDamage()) + // return; + + Entity *enemy; + enemy = ev->GetEntity( 3 ); + + if ( enemy->isSubclassOf( Player) ) + { + //Teammates don't count MOD explosion, because it might + //be splash damage + if ( actortype == IS_TEAMMATE ) + { + int MOD = ev->GetInteger(9); + if ( MOD != MOD_EXPLOSION ) + AddStateFlag( STATE_FLAG_ATTACKED_BY_PLAYER ); + } + else + { + AddStateFlag( STATE_FLAG_ATTACKED_BY_PLAYER ); + } + } + + if ( !enemy ) + return; + + ::Damage damage(ev); + + // Only react to an attack if we respond to pain + if ( sensoryPerception && sensoryPerception->ShouldRespondToStimuli( STIMULI_PAIN ) ) + { + enemyManager->TryToAddToHateList( enemy ); + } + + enemyManager->AdjustDamageCaused( enemy , damage.damage ); + strategos->NotifyDamageChanged( enemy , damage.damage ); + + if( GetActorFlag( ACTOR_FLAG_RESPONDING_TO_HITSCAN ) ) + return; + + //Okay, if we're about take damage from a hitscan weapon, we need to give the actor a chance to + //respond to it. If we get a 'true' back, then that means the function handled it and we can bail, + //if not, then we need to deal with the damage + if ( GetActorFlag(ACTOR_FLAG_INCOMING_HITSCAN) ) + { + if ( RespondToHitscan() ) + return; + } + + //Here for Legacy + if ( ondamage_thread.length() ) + { + RunDamageThread(); + } + + //New Way + RunCustomThread( "damaged" ); + + //Handle the GameSpecificStuff + if ( gameComponent ) + gameComponent->HandleArmorDamage( ev ); // What's this? + + if ( GetNotifyFlag(NOTIFY_FLAG_DAMAGED) ) + _notifyGroupOfDamage(); + + //Now Check if the damage is coming from another Actor, if so, we may need to modifiy it. + //This is because the some enemies are so haus, they could knock the living crap out of each other + //if we don't modifiy the damage + /*Entity* ent = ev->GetEntity(3); + + if (ent->isSubclassOf(Actor) ) + { + float modDamage = (ev->GetFloat(1)) * actor_to_actor_damage_modifier; + + Event *event = 0; //Shut up compiler... sheesh... + event = new Event( EV_Damage ); + event->AddFloat(modDamage); + event->AddEntity(ev->GetEntity(2)); + event->AddEntity(ev->GetEntity(3)); + event->AddVector(ev->GetVector(4)); + event->AddVector(ev->GetVector(5)); + event->AddVector(ev->GetVector(6)); + event->AddInteger(ev->GetInteger(7)); + event->AddInteger(ev->GetInteger(8)); + event->AddInteger(ev->GetInteger(9)); + + Sentient::ArmorDamage(event); + delete event; + return; + }*/ + + + Sentient::ArmorDamage(damage); + sensoryPerception->Stimuli( STIMULI_PAIN , enemy ); + } + + +Actor *GetActor ( const str &actor_name ) + { + Actor* testActor; + int i; + + for ( i = 1; i <= SleepList.NumObjects(); i++ ) + { + testActor = (Actor*)SleepList.ObjectAt( i ); + if (testActor->targetname == actor_name) + return testActor; + + } + + for ( i = 1; i <= ActiveList.NumObjects(); i++ ) + { + testActor = (Actor*)ActiveList.ObjectAt( i ); + if (testActor->targetname == actor_name) + return testActor; + + } + + return NULL; + } + +void Actor::SetFlagOnEnemy( Event *ev ) + { + str flag_name; + int flag; + qboolean flag_bool; + Actor* act = 0; + + flag_name = ev->GetString(1); + flag_bool = ev->GetBoolean(2); + + flag = ActorFlag_string_to_int( flag_name ); + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + if ( !currentEnemy->isSubclassOf( Actor ) ) + return; + + act = (Actor*)(Entity*)currentEnemy; + + if ( act ) + act->SetActorFlag( flag, flag_bool ); + } + +void Actor::TurnOnEnemyAI( Event *ev ) + { + Actor* act = 0; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + if ( !currentEnemy->isSubclassOf( Actor ) ) + return; + + act = (Actor*)(Entity*)currentEnemy; + + if ( act ) + act->TurnAIOn(); + } + +void Actor::TurnOffEnemyAI( Event *ev ) + { + Actor* act = 0; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + if ( !currentEnemy->isSubclassOf( Actor ) ) + return; + + act = (Actor*)(Entity*)currentEnemy; + + if ( act ) + act->TurnAIOff(); + } + +void Actor::PickupThrowObject( Event *ev ) + { + + Entity *ent; + str bone; + + ent = (Entity*)enemyManager->GetAlternateTarget(); + if (!ent->isSubclassOf(ThrowObject) ) + return; + + ThrowObject* tobj = 0; + tobj = (ThrowObject*)ent; + + if (!tobj) return; + + bone = ev->GetString( 1 ); + tobj->Pickup(this , bone ); + haveThrowObject = true; + + } + +void Actor::TossThrowObject( Event *ev ) + { + // Due to the changes with enemy management, throw object stuff no longer works!!! + ThrowObject* tobj = 0; + float speed = 0; + float gravity = 1; + float damage = 25; + + Entity* currentEnt; + currentEnt = enemyManager->GetCurrentEnemy(); + if ( !currentEnt ) + return; + + tobj = (ThrowObject*)(Entity*)enemyManager->GetAlternateTarget(); + if(!tobj || !currentEnt->isSubclassOf(Sentient)) return; + + speed = ev->GetFloat( 1 ); + gravity = ev->GetFloat( 2 ); + if (ev->NumArgs() > 2 ) + damage = ev->GetFloat( 3 ); + + //Throw requires a Sentient Pointer, so we need to cast. However, we should + //probably look into changing Throw to take an entity instead + tobj->Throw(this, speed , (Sentient*)currentEnt , gravity, damage ); + + } + +void Actor::SetTurretMode( Event *ev ) + { + qboolean tmode; + + if (ev->NumArgs() > 0 ) + tmode = ev->GetBoolean( 1 ); + else + tmode = true; + + SetActorFlag( ACTOR_FLAG_TURRET_MODE , tmode ); + } + +void Actor::SetOnDamageThread( Event *ev ) + { + ondamage_thread = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + ondamage_threshold = ev->GetInteger( 2 ); + } + +void Actor::SetTimeBetweenSleepChecks( Event *ev ) +{ + timeBetweenSleepChecks = ev->GetFloat( 1 ); +} + + + +void Actor::AttachCurrentEnemy( Event *ev ) + { + Actor *act = 0; + Vector offset; + str bone; + int tagnum; + + bone = ev->GetString( 1 ); + offset = ev->GetVector( 2 ); + tagnum = gi.Tag_NumForName( this->edict->s.modelindex, bone ); + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + if ( !currentEnemy->isSubclassOf( Actor ) ) + return; + + act = (Actor*)(Entity*)currentEnemy; + + if ( act ) + { + act->attach(this->entnum , tagnum , false , offset ); + haveAttached = true; + } + } + +void Actor::AttachActor( Event *ev ) + { + Actor* new_actor = 0; + new_actor = new Actor; + + if ( !new_actor ) + return; + + str modelName = ev->GetString( 1 ); + str targetName = ev->GetString( 2 ); + str bone = ev->GetString( 3 ); + Vector offset; + + new_actor->setModel( modelName ); + new_actor->SetTargetName( targetName.c_str() ); + + if ( ev->NumArgs() > 3 ) + offset = ev->GetVector( 4 ); + + int tagnum = gi.Tag_NumForName( this->edict->s.modelindex, bone ); + + new_actor->attach(this->entnum , tagnum , false , offset ); + + } + +void Actor::SetEnemyAttached( Event *ev ) + { + haveAttached = ev->GetBoolean( 1 ); + } + +void Actor::GiveActorWeapon( Event *ev ) + { + const char *type; + float amount; + float skillLevel; + + amount = 1.0; + skillLevel = 1.0f; + type = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + skillLevel = ev->GetFloat( 2 ); + + giveItem( type, (int)amount, false, skillLevel ); + } + +void Actor::RemoveActorWeapon( Event *ev ) + { + takeItem( ev->GetString( 1 ) ); + } + +void Actor::PutawayWeapon( Event *ev ) +{ + weaponhand_t hand = WEAPON_RIGHT; + if ( ev->NumArgs() > 0 ) + hand = WeaponHandNameToNum(ev->GetString( 1 )); + + Sentient::DeactivateWeapon(hand); +} + +void Actor::UseActorWeapon( Event *ev ) + { + str weaponName; + str handToUse; + weaponhand_t hand; + + weaponName = ev->GetString( 1 ); + hand = WEAPON_DUAL; + + if ( ev->NumArgs() > 1 ) + { + handToUse = ev->GetString( 2 ); + + if ( !stricmp( handToUse.c_str() , "right" ) ) + hand = WEAPON_RIGHT; + + if ( !stricmp( handToUse.c_str() , "left" ) ) + hand = WEAPON_LEFT; + + if ( !stricmp( handToUse.c_str() , "dual" ) ) + hand = WEAPON_DUAL; + } + + combatSubsystem->UseActorWeapon( weaponName , hand ); + } + +void Actor::AttachModelToTag( const str &modelName , const str &tagName ) + { + Event *attach_event; + attach_event = new Event( EV_AttachModel ); + + attach_event->AddString( modelName ); + attach_event->AddString( tagName ); + PostEvent( attach_event, 0.0f ); + } + +void Actor::DetachModelFromTag( const str &tagName ) + { + Event *detach_event; + detach_event = new Event( EV_RemoveAttachedModel ); + + detach_event->AddString( tagName ); + PostEvent ( detach_event, 0.0f ); + } + +//============================================== +// Sensory Perception Initilization +//============================================== + + +void Actor::SetFOV( Event *ev ) + { + sensoryPerception->SetFOV( ev->GetFloat( 1 ) ); + } + +void Actor::SetVisionDistance( Event *ev ) + { + sensoryPerception->SetVisionDistance( ev->GetFloat( 1 ) ); + } + +void Actor::ClearCurrentEnemy( Event *ev ) + { + enemyManager->ClearCurrentEnemy(); + } + + +// +// Name: SetAbsoluteMax() +// Parameters: Event *ev +// Description: Sets an absoluteMax variable that can be used to "leash" +// an actor to an entity +// +void Actor::SetAbsoluteMax( Event *ev ) + { + absoluteMax = ev->GetFloat( 1 ); + } + + +// +// Name: SetAbsoluteMin() +// Parameters: Event *ev +// Description: Sets an absoluteMin variable that can be used to "leash" +// an actor to an entity +// +void Actor::SetAbsoluteMin( Event *ev ) + { + absoluteMin = ev->GetFloat( 1 ); + } + + +// +// Name: SetPreferredMax() +// Parameters: Event *ev +// Description: Sets a preferredMax variable that can be used to "leash" +// an actor to an entity +// +void Actor::SetPreferredMax( Event *ev ) + { + preferredMax = ev->GetFloat( 1 ); + } + + +// +// Name: SetPreferredMin() +// Parameters: Event *ev +// Description: Sets a preferredMin variable that can be used to "leash" +// an actor to an entity +// +void Actor::SetPreferredMin( Event *ev ) + { + preferredMin = ev->GetFloat( 1 ); + } + + +void Actor::SetDisabled( Event *ev ) + { + qboolean disabled = ev->GetBoolean( 1 ); + + SetActorFlag(ACTOR_FLAG_DISABLED , disabled ); + } + +void Actor::SetCrippled( Event *ev ) + { + qboolean crippled = ev->GetBoolean( 1 ); + + SetActorFlag(ACTOR_FLAG_CRIPPLED , crippled ); + } + +void Actor::SetInAlcove( Event *ev ) + { + qboolean inalcove = ev->GetBoolean( 1 ); + + SetActorFlag( ACTOR_FLAG_IN_ALCOVE , inalcove ); + } + +void Actor::SetAimLeadFactors( Event *ev ) + { + minLeadFactor = ev->GetFloat( 1 ); + maxLeadFactor = ev->GetFloat( 2 ); + } + +void Actor::SetActorType( Event *ev ) + { + str aType; + aType = ev->GetString( 1 ); + + if ( !Q_stricmp( aType.c_str() , "inanimate" ) ) + { + // Inanimates are special... be sure to clear the + // monster flag, don't let them move, bleed, or + // gib. + actortype = IS_INANIMATE; + edict->svflags &= ~SVF_MONSTER; + setMoveType( MOVETYPE_STATIONARY ); + flags &= ~FL_BLOOD; + flags &= ~FL_DIE_GIBS; + return; + } + else if ( !Q_stricmp( aType.c_str() , "monster" ) ) + { + actortype = IS_MONSTER; + edict->s.eFlags |= EF_ENEMY; + + level.enemySpawned( this ); + } + else if ( !Q_stricmp( aType.c_str() , "enemy" ) ) + { + actortype = IS_ENEMY; + edict->s.eFlags |= EF_ENEMY; + + level.enemySpawned( this ); + } + else if ( !Q_stricmp( aType.c_str() , "civilian" ) ) + { + actortype = IS_CIVILIAN; + edict->s.eFlags |= EF_FRIEND; + } + else if ( !Q_stricmp( aType.c_str() , "friend" ) ) + { + actortype = IS_FRIEND; + edict->s.eFlags |= EF_FRIEND; + } + else if ( !Q_stricmp( aType.c_str() , "animal" ) ) + { + actortype = IS_ANIMAL; + edict->s.eFlags |= EF_FRIEND; + } + else if ( !Q_stricmp( aType.c_str() , "teammate" ) ) + { + TeamMateList.AddUniqueObject(this); + actortype = IS_TEAMMATE; + + // Teammates Never Go To Sleep + max_inactive_time = -1.0f; + + edict->s.eFlags |= EF_FRIEND; + edict->clipmask = MASK_MONSTERSOLID | MASK_PLAYERSOLID; + } + + } + +void Actor::DebugStates( Event *ev ) + { + int state = ev->GetInteger( 1 ); + + if ( state >= MAX_DEBUG_TYPES ) + return; + + showStates = ( stateDebugType_t )ev->GetInteger( 1 ); + } + + +//*********************************************************************************************** +// +// Combat functions +// +//*********************************************************************************************** + +void Actor::IncomingProjectile( Event *ev ) + { + incoming_proj = ev->GetEntity( 1 ); + incoming_time = level.time + .1f; //+ G_Random( .1 ); + } + +void Actor::FireProjectile( Event *ev ) + { + Vector orig; + Vector dir; + str tag_name; + str projectile_name; + int number_of_tags = 1; + qboolean arc = false; + float speed = 0.0f; + float offset = 0.0f; + bool leadTarget = false; + float spread = 0.0f; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + enemyManager->FindHighestHateEnemy(); + + if ( !currentEnemy ) + return; + + + tag_name = ev->GetString( 1 ); + projectile_name = ev->GetString( 2 ); + + if ( ev->NumArgs() > 2 ) + number_of_tags = ev->GetInteger( 3 ); + + if ( ev->NumArgs() > 3 ) + arc = ev->GetBoolean( 4 ); + + if ( ev->NumArgs() > 4 ) + speed = ev->GetFloat( 5 ); + + if ( ev->NumArgs() > 5 ) + offset = ev->GetFloat( 6 ); + + if ( ev->NumArgs() > 6 ) + leadTarget = ev->GetBoolean( 7 ); + + if ( ev->NumArgs() > 7 ) + spread = ev->GetFloat( 8 ); + + + // Find the closest tag + + if ( !GetClosestTag( tag_name, number_of_tags, currentEnemy->centroid, &orig ) ) + { + // Could not find the tag so just use the centroid of the actor + orig[0] = edict->centroid[0]; + orig[1] = edict->centroid[1]; + orig[2] = edict->centroid[2]; + } + + + // Add projectile to world + Vector targetVelocity = currentEnemy->velocity; + Vector targetPos = currentEnemy->centroid; + Vector newTargetPos = targetPos; + + if ( leadTarget ) + { + float projSpeed = speed; + if ( projSpeed <= 0 ) + projSpeed = 1; + + newTargetPos = combatSubsystem->GetLeadingTargetPos( projSpeed , currentEnemy->centroid , currentEnemy ); + } + + + //Apply Spread + if ( spread > 0 ) + { + newTargetPos.x = newTargetPos.x + ( G_CRandom(spread) ); + newTargetPos.y = newTargetPos.y + ( G_CRandom(spread) ); + newTargetPos.z = newTargetPos.z + ( G_CRandom(spread) ); + } + + dir = newTargetPos - orig; + + if ( arc ) + { + Vector xydir; + float traveltime; + float vertical_speed; + Vector proj_velocity; + + xydir = dir; + xydir.z = 0.0f; + + if ( speed == 0.0f ) + speed = 500.0f; + + traveltime = xydir.length() / speed; + + vertical_speed = ( dir.z / traveltime ) + ( 0.5f * gravity * sv_currentGravity->value * traveltime ); + + xydir.normalize(); + + proj_velocity = speed * xydir; + proj_velocity.z = vertical_speed; + + speed = proj_velocity.length(); + proj_velocity.normalize(); + dir = proj_velocity; + } + + if ( offset ) + { + Vector offset_angle = dir.toAngles(); + offset_angle[YAW] += offset; + offset_angle.AngleVectors( &dir ); + } + + + + dir.normalize(); + + ProjectileAttack( orig, dir, this, projectile_name.c_str(), 1.0f, speed ); + + SaveAttack( orig, dir ); + } + +void Actor::FireRadiusAttack ( Event *ev ) + { + str tagName = ev->GetString( 1 ); + str meansOfDeath = ev->GetString( 2 ); + float damage = ev->GetFloat( 3 ); + float radius = ev->GetFloat( 4 ); + float knockback = ev->GetFloat( 5 ); + qboolean constant = ev->GetBoolean( 6 ); + + int MOD = MOD_NameToNum( meansOfDeath ); + RadiusDamage( this , this, damage, this, MOD, radius, knockback, constant ); + + } + +void Actor::FireBullet( Event *ev ) + { + str tag_name; + qboolean use_current_pitch; + float range = 1000; + float damage; + float knockback; + str means_of_death_string; + int attack_means_of_death; + Vector spread; + Vector pos; + Vector forward; + Vector left; + Vector right; + Vector up; + Vector attack_angles; + Vector dir; + Vector enemy_angles; + + tag_name = ev->GetString( 1 ); + use_current_pitch = ev->GetBoolean( 2 ); + damage = ev->GetFloat( 3 ); + knockback = ev->GetFloat( 4 ); + means_of_death_string = ev->GetString( 5 ); + spread = ev->GetVector( 6 ); + + if ( ev->NumArgs() > 6 ) + range = ev->GetFloat( 7 ); + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + + // Get the position where the bullet starts + + GetTag( tag_name, &pos, &forward, &left, &up ); + + right = left * -1.0f; + + // Get the real pitch of the bullet attack + + if ( !use_current_pitch && currentEnemy ) + { + attack_angles = forward.toAngles(); + + dir = currentEnemy->centroid - pos; + // Temporary Change ( 10/3/01 -- SK ) + dir.z += 10; + + enemy_angles = dir.toAngles(); + + attack_angles[PITCH] = enemy_angles[PITCH]; + + // Temporary Change (10/3/01 -- SK ) + //attack_angles.AngleVectors( &forward, &left, &up ); + enemy_angles.AngleVectors( &forward, &left, &up ); + + right = left * -1.0f; + } + + //Little Sanity Check + if ( shotsFired > 1000 ) + shotsFired = 1000; + + attack_means_of_death = MOD_NameToNum( means_of_death_string ); + + BulletAttack( pos, forward, right, up, range, damage, knockback, 0, attack_means_of_death, spread, 1, this ); + + SaveAttack( pos, forward ); + } + +void Actor::SaveAttack( const Vector &orig, const Vector &dir ) + { + Vector attack_mins; + Vector attack_maxs; + Vector end; + trace_t trace; + qboolean hit; + Entity *ent; + + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + SetActorFlag( ACTOR_FLAG_LAST_ATTACK_HIT, true ); + return; + } + + // Do trace + + attack_mins = Vector( -1.0f, -1.0f, -1.0f ); + attack_maxs = Vector( 1.0f ,1.0f ,1.0f ); + + end = orig + ( dir * 8192.0f ); + + trace = G_Trace( orig, attack_mins, attack_maxs, end, this, MASK_SHOT, false, "Actor::SaveAttack" ); + + // See what we hit + + last_attack_entity_hit = NULL; + + if ( trace.ent ) + { + ent = trace.ent->entity; + + if ( ent == currentEnemy ) + { + hit = true; + } + else if ( ent->isSubclassOf( Entity ) && ( ent != world ) ) + { + last_attack_entity_hit = ent; + last_attack_entity_hit_pos = ent->origin; + hit = false; + } + else + { + hit = false; + } + } + else + { + hit = false; + } + + // Do an extra check because of NOCLIP + + if ( currentEnemy->movetype == MOVETYPE_NOCLIP ) + hit = true; + + // Save last attack info + + last_attack_pos = origin; + + last_attack_enemy_pos = currentEnemy->origin; + + SetActorFlag( ACTOR_FLAG_LAST_ATTACK_HIT, hit ); + } + +qboolean Actor::TestAttack( const str &tag_name ) + { + qboolean hit; + trace_t trace; + Vector attack_mins; + Vector attack_maxs; + Vector start; + + + // Make sure we still have an enemy and he is hitable + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + if ( currentEnemy->movetype == MOVETYPE_NOCLIP ) + return false; + + // Make sure we won't hit any friends + + attack_mins = Vector( -1.0f, -1.0f, -1.0f ); + attack_maxs = Vector( 1.0f, 1.0f, 1.0f ); + + if ( tag_name.length() ) + { + GetTag( tag_name.c_str(), &start ); + } + else + { + start = centroid; + } + + trace = G_Trace( start, attack_mins, attack_maxs, currentEnemy->centroid, this, MASK_SHOT, false, "Actor::TestAttack" ); + + if ( trace.ent && ( trace.ent->entity != currentEnemy ) && trace.ent->entity->isSubclassOf( Sentient ) && !enemyManager->IsValidEnemy( trace.ent->entity ) ) + return false; + + // See if we hit last time + + hit = GetActorFlag( ACTOR_FLAG_LAST_ATTACK_HIT ); + + if ( hit ) + return true; + + // Didn't hit last time so see if anything has changed + + // See if actor has moved + + if ( last_attack_pos != origin ) + return true; + + // See if enemy has moved + + if ( last_attack_enemy_pos != currentEnemy->origin ) + return true; + + // See if entity in the way has moved + + if ( last_attack_entity_hit && ( last_attack_entity_hit_pos != last_attack_entity_hit->origin ) ) + return true; + + // See if entity in the way was a door and has opened + + if ( last_attack_entity_hit && last_attack_entity_hit->isSubclassOf( Door ) ) + { + Door *door; + + door = (Door *)(Entity *)last_attack_entity_hit; + + if ( door->isOpen() ) + return true; + } + + // See if entity in the way has become non-solid + + if ( last_attack_entity_hit && ( last_attack_entity_hit->edict->solid == SOLID_NOT ) ) + return true; + + // Nothing has changed so this attack should fail too + + return false; + } + +void Actor::MeleeEvent( Event *ev ) + { + Vector pos; + Vector end; + Vector dir; + float damage = 20; + qboolean success; + str tag_name; + Vector attack_vector; + float attack_width = 0; + float attack_max_height = 0; + float attack_min_height = 0; + float attack_length = 100; + str means_of_death_string; + meansOfDeath_t attack_means_of_death; + float knockback; + qboolean use_pitch_to_enemy = false; + float attack_final_height; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + + // See if we should really attack + + if ( GetActorFlag( ACTOR_FLAG_DAMAGE_ONCE_ON ) && GetActorFlag( ACTOR_FLAG_DAMAGE_ONCE_DAMAGED ) ) + return; + + if ( !GetActorFlag( ACTOR_FLAG_DAMAGE_ALLOWED ) ) + return; + + // Get all of the parameters + + if ( ev->NumArgs() > 0 ) + damage = ev->GetFloat( 1 ); + + if ( _useWeaponDamage != WEAPON_ERROR ) + { + Weapon *weap = GetActiveWeapon(_useWeaponDamage); + if ( weap ) + damage = weap->GetBulletDamage(); + } + + if ( ev->NumArgs() > 1 ) + tag_name = ev->GetString( 2 ); + + if ( ev->NumArgs() > 2 ) + means_of_death_string = ev->GetString( 3 ); + + if ( ev->NumArgs() > 3 ) + { + attack_vector = ev->GetVector( 4 ); + + attack_width = attack_vector[0]; + attack_length = attack_vector[1]; + attack_max_height = attack_vector[2]; + } + + if ( ev->NumArgs() > 4 ) + { + knockback = ev->GetFloat( 5 ); + } + else + { + knockback = damage * 8.0f; + } + + if ( ev->NumArgs() > 5 ) + { + use_pitch_to_enemy = ev->GetInteger( 6 ); + } + + if ( ev->NumArgs() > 6 ) + attack_min_height = ev->GetFloat( 7 ); + else + attack_min_height = -attack_max_height; + + if ( ev->NumArgs() > 7 ) + attack_final_height = ev->GetFloat( 8 ); + else + attack_final_height = 50.0f; + + if ( tag_name.length() && GetTag( tag_name.c_str(), &pos, &dir ) ) + { + end = pos + ( dir * attack_length ); + } + else + { + pos = edict->centroid; + dir = orientation[0]; + dir.normalize(); + end = pos + ( dir * attack_length ); + + if ( attack_length ) + end[2] += attack_final_height; + } + + if ( use_pitch_to_enemy ) + { + if ( currentEnemy ) + { + Vector enemy_dir; + Vector angles; + Vector enemy_angles; + float length; + + dir = end - pos; + length = dir.length(); + angles = dir.toAngles(); + + enemy_dir = currentEnemy->centroid - pos; + enemy_angles = enemy_dir.toAngles(); + + angles[PITCH] = enemy_angles[PITCH]; + angles.AngleVectors( &dir ); + end = pos + ( dir * length ); + } + } + + if ( means_of_death_string.length() > 0 ) + attack_means_of_death = (meansOfDeath_t)MOD_NameToNum( means_of_death_string ); + else + attack_means_of_death = MOD_CRUSH; + + // Do the actual attack + Weapon *weap = 0; + if ( _useWeaponDamage != WEAPON_ERROR ) + weap = GetActiveWeapon(_useWeaponDamage); + + if ( weap ) + success = MeleeAttack( pos, end, damage, this, attack_means_of_death, attack_width, attack_min_height, attack_max_height, knockback, true, NULL, weap ); + else + success = MeleeAttack( pos, end, damage, this, attack_means_of_death, attack_width, attack_min_height, attack_max_height, knockback ); + + if ( success ) + { + AddStateFlag( STATE_FLAG_MELEE_HIT ); + + RunCustomThread( "meleehit" ); + + if ( GetActorFlag( ACTOR_FLAG_DAMAGE_ONCE_ON ) ) + SetActorFlag( ACTOR_FLAG_DAMAGE_ONCE_DAMAGED, true ); + } + } + +void Actor::ChargeWater( Event *ev ) + { + int cont; + float damage; + float radius; + Entity *ent; + int brushnum; + int entity_brushnum; + float real_damage; + Vector dir; + float dist; + + // See if we are standing in water + + cont = gi.pointcontents( origin, 0 ); + + if ( !(cont & MASK_WATER) ) + return; + + // Get parms + + damage = ev->GetFloat( 1 ); + radius = ev->GetFloat( 2 ); + + if ( !damage || !radius ) + return; + + // Determine what brush we are in + + brushnum = gi.pointbrushnum( origin, 0 ); + + // Find everything in radius + + ent = NULL; + + for( ent = findradius( ent, origin, radius ) ; ent ; ent = findradius( ent, origin, radius ) ) + { + if ( ent->takedamage ) + { + entity_brushnum = gi.pointbrushnum( origin, 0 ); + + if ( brushnum == entity_brushnum ) + { + dir = ent->origin - origin; + dist = dir.length(); + + if ( dist < radius ) + { + real_damage = damage - ( damage * ( dist / radius ) ); + ent->Damage( this, this, real_damage, origin, dir, vec_zero, 0, 0, MOD_ELECTRICWATER ); + } + } + } + } + } + +void Actor::DamageOnceStart( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_DAMAGE_ONCE_ON, true ); + SetActorFlag( ACTOR_FLAG_DAMAGE_ONCE_DAMAGED, false ); + } + +void Actor::DamageOnceStop( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_DAMAGE_ONCE_ON, false ); + } + +void Actor::DamageAllowed( Event *ev ) + { + SetActorFlag( ACTOR_FLAG_DAMAGE_ALLOWED, ev->GetBoolean( 1 ) ); + } + +qboolean Actor::CanAttackFrom( const Vector &pos, const Entity *ent, qboolean usecurrentangles ) + { + int mask; + Vector delta; + Vector start; + Vector end; + float len; + trace_t trace; + Entity *t; + Vector ang; + + if ( usecurrentangles ) + { + Vector dir; + + // Fixme ? + //start = pos + GunPosition() - origin; + start = pos + centroid - origin; + end = ent->centroid; + end.z += ( ent->absmax.z - ent->centroid.z ) * 0.75f; + delta = end - start; + ang = delta.toAngles(); + ang.x = ang.x; + ang.y = angles.y; + len = delta.length(); + ang.AngleVectors( &dir, NULL, NULL ); + dir *= len; + end = start + dir; + } + else + { + // Fixme ? + //start = pos + GunPosition() - origin; + start = pos + centroid - origin; + end = ent->centroid; + end.z += ( ent->absmax.z - ent->centroid.z ) * 0.75f; + delta = end - start; + len = delta.length(); + } + + // shoot past the guy we're shooting at + end += delta * 4.0f; + + // Check if he's visible + mask = MASK_SHOT; + trace = G_Trace( start, vec_zero, vec_zero, end, this, mask, false, "Actor::CanShootFrom" ); + if ( trace.startsolid ) + { + return false; + } + + // see if we hit anything at all + if ( !trace.ent ) + { + return false; + } + + // If we hit the guy we wanted, then shoot + if ( trace.ent == ent->edict ) + { + return true; + } + + // If we hit someone else we don't like, then shoot + t = trace.ent->entity; + if ( enemyManager->IsValidEnemy( t ) ) + { + return true; + } + + // if we hit something breakable, check if shooting it will + // let us shoot someone. + if ( t->isSubclassOf( Object ) || + t->isSubclassOf( ScriptModel ) ) + { + trace = G_Trace( Vector( trace.endpos ), vec_zero, vec_zero, end, t, mask, false, "Actor::CanShootFrom 2" ); + if ( trace.startsolid ) + { + return false; + } + // see if we hit anything at all + if ( !trace.ent ) + { + return false; + } + + // If we hit the guy we wanted, then shoot + if ( trace.ent == ent->edict ) + { + return true; + } + + // If we hit someone else we don't like, then shoot + if ( enemyManager->IsValidEnemy( trace.ent->entity ) ) + { + return true; + } + + // Forget it then + return false; + } + + return false; + } + +qboolean Actor::CanAttack( Entity *ent, qboolean usecurrentangles ) + { + return CanAttackFrom( origin, ent, usecurrentangles ); + } + +qboolean Actor::EntityHasFireType( Entity *ent, firetype_t fire_type ) + { + Player *player; + Weapon *weapon; + firetype_t weapon_fire_type; + + + if ( !ent ) + return false; + + if ( !ent->isSubclassOf( Player ) ) + return true; + + player = (Player *)(Entity *)ent; + + // Try left hand + + weapon = player->GetActiveWeapon( WEAPON_LEFT ); + + if ( weapon ) + { + weapon_fire_type = weapon->GetFireType( FIRE_MODE1 ); + + if ( weapon_fire_type == fire_type ) + return true; + } + + // Try right hand + + weapon = player->GetActiveWeapon( WEAPON_RIGHT ); + + if ( weapon ) + { + weapon_fire_type = weapon->GetFireType( FIRE_MODE1 ); + + if ( weapon_fire_type == fire_type ) + return true; + } + + // Try dual weapons + + weapon = player->GetActiveWeapon( WEAPON_DUAL ); + + if ( weapon ) + { + weapon_fire_type = weapon->GetFireType( FIRE_MODE1 ); + + if ( weapon_fire_type == fire_type ) + return true; + } + + return false; + } + +void Actor::DamageEnemy( Event *ev ) + { + // Get our current enemy + Entity *currentEnemy; + Vector dir; + + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + dir = currentEnemy->origin - origin; + dir.normalize(); + + float damage = 0.0f; + float knockback = 0.0f; + + if ( ev->NumArgs() > 0 ) + damage = ev->GetFloat( 1 ); + + if ( ev->NumArgs() > 1 ) + { + str modelName = ev->GetString( 2 ); + + Event *attachEvent = new Event(EV_SpawnEffect); + if ( !attachEvent ) + return; + + attachEvent->AddString( modelName ); + attachEvent->AddString( "Bip01" ); + attachEvent->AddFloat( 2.5 ); + + currentEnemy->ProcessEvent( attachEvent ); + } + + + if ( ev->NumArgs() > 2 ) + knockback = ev->GetFloat( 3 ); + + + if ( damage > 0.0f ) + currentEnemy->Damage( this, this, damage, vec_zero, dir, vec_zero, (int)knockback, 0, MOD_CRUSH ); + + } + +void Actor::DamageSelf( Event *ev ) + { + float damage = 0; + str MOD_String = "crush"; + int attack_means_of_death; + Event* event; + + if (ev->NumArgs() > 0 ) + damage = ev->GetFloat( 1 ); + + if (ev->NumArgs() > 1 ) + MOD_String = ev->GetString( 2 ); + + attack_means_of_death = MOD_NameToNum( MOD_String ); + + event = new Event( EV_Damage ); + event->AddFloat(damage); + event->AddEntity(this); + event->AddEntity(this); + event->AddVector(vec_zero); + event->AddVector(vec_zero); + event->AddVector(vec_zero); + event->AddInteger( 0 ); + event->AddInteger( 0 ); + event->AddInteger( attack_means_of_death ); + ProcessEvent( event ); + } + +qboolean Actor::IsImmortal ( void ) + { + return GetActorFlag( ACTOR_FLAG_IMMORTAL ); + } + + +qboolean Actor::TakeDamage ( void ) + { + return GetActorFlag( ACTOR_FLAG_TAKE_DAMAGE ); + } + + +void Actor::FireWeapon( Event *ev ) + { + combatSubsystem->FireWeapon(); + } + +void Actor::StopFireWeapon( Event *ev ) + { + combatSubsystem->StopFireWeapon(); + } + +//=================================================================================== +// Init Functions For Helper Classes +//=================================================================================== + +// +// Name: InitGameComponent() +// Parameters: None +// Description: Initalizes the actor's GameComponent +// +void Actor::InitGameComponent() + { + //Here is where the subclass of actorgamecomponents gets set. If you change the + //game (i.e. a mod or new game ) Change this here too + + gameComponent = NULL; + gameComponent = new EFGameComponent( this ); + + if ( !gameComponent ) + gi.Error( ERR_FATAL, "Actor Could not create gameComponent" ); + + } + + +// +// Name: InitSensoryPerception() +// Parameters: None +// Description: Initalizes the actor's SensoryPerception +// +void Actor::InitSensoryPerception() + { + sensoryPerception = NULL; + sensoryPerception = new SensoryPerception( this ); + + if ( !sensoryPerception ) + gi.Error( ERR_FATAL, "Actor Could not create sensoryPerception" ); + + } + + +// +// Name: InitThinkStrategy() +// Parameters: None +// Description: Initalizes the actor's ThinkStrategy +// +void Actor::InitThinkStrategy() + { + thinkStrategy = NULL; + thinkStrategy = new DefaultThink(); + + if ( !thinkStrategy ) + gi.Error( ERR_FATAL, "Actor Could not create thinkStrategy" ); + + } + + +// +// Name: InitStrategos() +// Parameters: None +// Description: Initalizes the actor's Strategos +// +void Actor::InitStrategos() + { + strategos = NULL; + strategos = new DefaultStrategos ( this ); + + if ( !strategos ) + gi.Error( ERR_FATAL, "Actor Could not create strategos" ); + } + + +// +// Name: InitEnemyManager() +// Parameters: None +// Description: Initalizes the actor's EnemyManager +// +void Actor::InitEnemyManager() + { + enemyManager = NULL; + enemyManager = new EnemyManager( this ); + + if ( !enemyManager ) + gi.Error( ERR_FATAL, "Actor Could not create enemyManager" ); + } + + +// +// Name: InitPackageManager() +// Parameters: None +// Description: Initalizes the actor's PackageManager +// +void Actor::InitPackageManager() + { + packageManager = NULL; + packageManager = new PackageManager( this ); + + if ( !packageManager ) + gi.Error( ERR_FATAL, "Actor Could not create packageManager" ); + } + + +// +// Name: InitMovementSubsystem() +// Parameters: None +// Description: Initalizes the actor's MovementSubsystem +// +void Actor::InitMovementSubsystem() + { + movementSubsystem = NULL; + movementSubsystem = new MovementSubsystem( this ); + + if ( !movementSubsystem ) + gi.Error( ERR_FATAL, "Actor Could not create movementSubsystem" ); + + } + + +// +// Name: InitPersonality() +// Parameters: None +// Description: Initialize the actor's Personality +// +void Actor::InitPersonality() + { + personality = NULL; + personality = new Personality( this ); + + if ( !personality ) + gi.Error( ERR_FATAL, "Actor Could not create personality" ); + } + + +// +// Name: InitCombatSubsystem() +// Parameters: None +// Description: Initialize the actor's CombatSubsystem +// +void Actor::InitCombatSubsystem() + { + combatSubsystem = NULL; + combatSubsystem = new CombatSubsystem( this ); + + if ( !combatSubsystem ) + gi.Error( ERR_FATAL, "Actor Could not create personality" ); + + } + +void Actor::InitHeadWatcher() + { + headWatcher = NULL; + headWatcher = new HeadWatcher( this ); + + if ( !headWatcher ) + gi.Error( ERR_FATAL, "Actor Could not create personality" ); + + } + +void Actor::InitPostureController() +{ + postureController = NULL; + postureController = new PostureController( this ); + + if ( !postureController ) + gi.Error( ERR_FATAL, "Actor Could not create postureController" ); +} + +//=================================================================================== +// Private Functions +//=================================================================================== + + +// +// Name: _dropActorToGround() +// Parameters: None +// Description: Tries to drop the actor to the ground +// +void Actor::_dropActorToGround() + { + trace_t trace; + Vector end; + Vector start; + qboolean stuck; + str actor_name; + + stuck = false; + + start = origin + Vector( "0 0 1" ); + end = origin; + end[ 2 ] -= 16; + + + //trace = G_Trace( start, mins, maxs, end, this, MASK_SOLID, false, "Actor::start" ); + trace = G_Trace( start, mins, maxs, end, this, edict->clipmask, false, "Actor::start" ); + + if ( trace.startsolid || trace.allsolid ) + stuck = true; + else if ( !( flags & FL_FLY ) ) + { + setOrigin( trace.endpos ); + groundentity = trace.ent; + } + + if ( name.length() ) + actor_name = name; + else + actor_name = getClassID(); + + if ( trace.fraction == 1 && movetype == MOVETYPE_STATIONARY && !GetActorFlag( ACTOR_FLAG_IGNORE_OFF_GROUND_WARNING ) ) + gi.WDPrintf( "%s (%d) off of ground at '%5.1f %5.1f %5.1f'\n", actor_name.c_str(), entnum, origin.x, origin.y, origin.z ); + + if ( stuck && !GetActorFlag( ACTOR_FLAG_IGNORE_STUCK_WARNING ) ) + { + groundentity = world->edict; + gi.WDPrintf( "%s (%d) stuck in world at '%5.1f %5.1f %5.1f'\n", actor_name.c_str(), entnum, origin.x, origin.y, origin.z ); + } + + SetActorFlag( ACTOR_FLAG_HAVE_MOVED, false ); + + last_origin = origin; + last_ground_z = origin.z; + } + + +//-------------------------------------------------------------- +// Name: turnTowardsEntity() +// Class: Actor +// +// Description: Sets our Angles, AnimDir and MoveDir so that +// we are facing the specified entity +// +// Parameters: Entity *ent +// float extraYaw +// +// Returns: None +//-------------------------------------------------------------- +void Actor::turnTowardsEntity( Entity *ent , float extraYaw ) +{ + + Vector dir; + Vector new_angles; + + //First, get the vector from myself to the entity. + //Then convert it to angles, and add any additional + //requested YAW + dir = ent->centroid - origin; + new_angles = dir.toAngles(); + new_angles[YAW] += extraYaw; + + //Now, Set my angles to these newly calculated Angles + angles[YAW] = new_angles[YAW]; + angles[ROLL] = 0; + angles[PITCH] = 0; + + //Now, before we can set my Anim and Movement Dir Vectors, we + //must convert my current angles back into a direction vector + //this is very very important. If we don't do this, then + //then my angles and my movement dir get out of sync resulting + //in some pretty bad wonk-i-ness. + angles.AngleVectors( &dir ); + + //Okay, now let's actually SET everything + setAngles( angles ); + movementSubsystem->setAnimDir( dir ); + movementSubsystem->setMoveDir( dir ); + +} + +void Actor::_printDebugInfo(const str &laststate , const str ¤tState , const str &legAnim , const str &torsoAnim ) + { + // Print Debug Stuff + gi.Printf( "\n"); + gi.Printf( "Name : %s\n", name.c_str() ); + gi.Printf( "TargetName : %s\n", targetname.c_str() ); + gi.Printf( "EntNum : %d\n", entnum ); + gi.Printf( "\n" ); + + switch( showStates ) + { + case DEBUG_STATES_ONLY: + gi.Printf( "StateMap or Package: %s\n", statemap_name.c_str() ); + gi.Printf( "LastState : %s\n", laststate.c_str() ); + gi.Printf( "CurrentState : %s\n", currentState.c_str() ); + break; + + case DEBUG_STATES_BEHAVIORS: + gi.Printf( "StateMap or Package: %s\n", statemap_name.c_str() ); + gi.Printf( "LastState : %s\n", laststate.c_str() ); + gi.Printf( "CurrentState : %s\n", currentState.c_str() ); + gi.Printf( "\n" ); + if ( behavior ) + { + gi.Printf( "Behavior : %s\n", behavior->getClassname() ); + if ( behavior->GetInternalStateName().length() ) + gi.Printf( "Internal Behavior State: %s\n" , behavior->GetInternalStateName().c_str() ); + else + gi.Printf( "Internal Behavior State: Unspecified\n" ); + } + + if ( headBehavior ) + gi.Printf( "HeadBehavior : %s\n", headBehavior->getClassname() ); + + if ( eyeBehavior ) + gi.Printf( "EyeBehavior : %s\n", eyeBehavior->getClassname() ); + + if ( torsoBehavior ) + gi.Printf( "TorsoBehavior : %s\n", torsoBehavior->getClassname() ); + break; + + case DEBUG_ALL: + gi.Printf( "StateMap or Package: %s\n", statemap_name.c_str() ); + gi.Printf( "LastState : %s\n", laststate.c_str() ); + gi.Printf( "CurrentState : %s\n", currentState.c_str() ); + gi.Printf( "\n" ); + if ( behavior ) + { + gi.Printf( "Behavior : %s\n", behavior->getClassname() ); + if ( behavior->GetInternalStateName().length() ) + gi.Printf( "Internal Behavior State: %s\n" , behavior->GetInternalStateName().c_str() ); + else + gi.Printf( "Internal Behavior State: Unspecified\n" ); + } + + if ( headBehavior ) + gi.Printf( "HeadBehavior : %s\n", headBehavior->getClassname() ); + + if ( eyeBehavior ) + gi.Printf( "EyeBehavior : %s\n", eyeBehavior->getClassname() ); + + if ( torsoBehavior ) + gi.Printf( "TorsoBehavior : %s\n", torsoBehavior->getClassname() ); + gi.Printf( "\n" ); + gi.Printf( "LegAnim : %s\n", legAnim.c_str() ); + gi.Printf( "TorsoAnim : %s\n", torsoAnim.c_str() ); + + break; + + default: + return; + } + } +//=================================================================================== +// Archive Functions +//=================================================================================== + +// +// Name: Archive() +// Parameters: Archiver &arc +// Description: Archives the Actor Data +// +inline void Actor::Archive( Archiver &arc ) +{ + str temp_state_name; + str temp_global_state_name; + str temp_last_state_name; + str temp_master_state_name; + str temp_last_master_state_name; + qboolean behavior_bool; + qboolean hBehavior_bool; + qboolean eBehavior_bool; + qboolean tBehavior_bool; + int num; + StateVar *stateVar; + threadlist_t* threadListEntry; + int i; + + + Sentient::Archive( arc ); + + arc.ArchiveSafePointer( &forcedEnemy ); + arc.ArchiveString( &newanim ); + arc.ArchiveInteger( &newanimnum ); + arc.ArchiveInteger( &animnum ); + arc.ArchiveString( &animname ); + arc.ArchiveEventPointer( &newanimevent ); + arc.ArchiveString( &last_anim_event_name ); + + arc.ArchiveString( &newTorsoAnim ); + arc.ArchiveInteger( &newTorsoAnimNum ); + arc.ArchiveString( &TorsoAnimName ); + arc.ArchiveEventPointer( &newTorsoAnimEvent ); + arc.ArchiveString( &last_torso_anim_event_name ); + + arc.ArchiveFloat( &absoluteMin ); + arc.ArchiveFloat( &absoluteMax ); + arc.ArchiveFloat( &preferredMin ); + arc.ArchiveFloat( &preferredMax ); + arc.ArchiveFloat( &activationDelay ); + arc.ArchiveFloat( &activationStart ); + + ArchiveEnum( actortype, actortype_t ); + ArchiveEnum( targetType, targetType_t ); + arc.ArchiveBoolean( &validTarget ); + arc.ArchiveBool( &_checkedChance ); + arc.ArchiveBool( &_levelAIOff ); + arc.ArchiveFloat( &bounce_off_velocity ); + + + if ( arc.Saving() ) + { + if ( behavior ) + { + behavior_bool = true; + arc.ArchiveBoolean( &behavior_bool ); + arc.ArchiveObject( behavior ); + } + else + { + behavior_bool = false; + arc.ArchiveBoolean( &behavior_bool ); + } + + if ( headBehavior ) + { + hBehavior_bool = true; + arc.ArchiveBoolean( &hBehavior_bool ); + arc.ArchiveObject( headBehavior ); + } + else + { + hBehavior_bool = false; + arc.ArchiveBoolean( &hBehavior_bool ); + } + + if ( eyeBehavior ) + { + eBehavior_bool = true; + arc.ArchiveBoolean( &eBehavior_bool ); + arc.ArchiveObject( eyeBehavior ); + } + else + { + eBehavior_bool = false; + arc.ArchiveBoolean( &eBehavior_bool ); + } + + if ( torsoBehavior ) + { + tBehavior_bool = true; + arc.ArchiveBoolean( &tBehavior_bool ); + arc.ArchiveObject( torsoBehavior ); + } + else + { + tBehavior_bool = false; + arc.ArchiveBoolean( &tBehavior_bool ); + } + } + else + { + arc.ArchiveBoolean( &behavior_bool ); + + if ( behavior_bool ) + { + behavior = ( Behavior * )arc.ReadObject(); + currentBehavior = behavior->getClassname(); + behaviorFailureReason = behavior->GetFailureReason(); + } + else + { + behavior = NULL; + currentBehavior = ""; + behaviorFailureReason = ""; + } + + arc.ArchiveBoolean( &hBehavior_bool ); + + if ( hBehavior_bool ) + { + headBehavior = ( Behavior * )arc.ReadObject(); + currentHeadBehavior = headBehavior->getClassname(); + } + else + { + headBehavior = NULL; + currentHeadBehavior = ""; + } + + arc.ArchiveBoolean( &eBehavior_bool ); + + if ( eBehavior_bool ) + { + eyeBehavior = ( Behavior * )arc.ReadObject(); + currentEyeBehavior = eyeBehavior->getClassname(); + } + else + { + eyeBehavior = NULL; + currentEyeBehavior = ""; + } + + arc.ArchiveBoolean( &tBehavior_bool ); + + if ( tBehavior_bool ) + { + torsoBehavior = ( Behavior * )arc.ReadObject(); + currentTorsoBehavior = torsoBehavior->getClassname(); + } + else + { + torsoBehavior = NULL; + currentTorsoBehavior = ""; + } + } + + ArchiveEnum( behaviorCode, BehaviorReturnCode_t ); + ArchiveEnum( headBehaviorCode, BehaviorReturnCode_t ); + ArchiveEnum( eyeBehaviorCode, BehaviorReturnCode_t ); + ArchiveEnum( torsoBehaviorCode, BehaviorReturnCode_t ); + + arc.ArchiveBoolean( &haveThrowObject ); + + arc.ArchiveString( &animset ); + + arc.ArchiveUnsigned( &actor_flags1 ); + arc.ArchiveUnsigned( &actor_flags2 ); + arc.ArchiveUnsigned( &actor_flags3 ); + arc.ArchiveUnsigned( &actor_flags4 ); + arc.ArchiveUnsigned( ¬ify_flags1 ); + arc.ArchiveUnsigned( &state_flags ); + + arc.ArchiveFloat ( &chattime ); + arc.ArchiveFloat ( &nextsoundtime ); + arc.ArchiveFloat ( &_nextCheckForEnemyPath ); + arc.ArchiveBool ( &_havePathToEnemy ); + arc.ArchiveFloat ( &_nextPathDistanceToFollowTargetCheck ); + + arc.ArchiveFloat( &_nextPlayPainSoundTime ); + arc.ArchiveFloat( &_playPainSoundInterval ); + + // Save dialog stuff + + ArchiveEnum( DialogMode, DialogMode_t ); + arc.ArchiveFloat ( &radiusDialogRange ); + + if ( arc.Saving() ) + { + DialogNode_t *dialog_node; + byte more; + str alias_name; + str parm; + int current_parm; + + dialog_node = dialog_list; + + while( dialog_node ) + { + more = true; + arc.ArchiveByte( &more ); + + alias_name = dialog_node->alias_name; + + arc.ArchiveString( &alias_name ); + arc.ArchiveInteger( &dialog_node->random_flag ); + arc.ArchiveInteger( &dialog_node->number_of_parms ); + arc.ArchiveFloat( &dialog_node->random_percent ); + ArchiveEnum( dialog_node->dType , DialogType_t ); + + for( current_parm = 0 ; current_parm < dialog_node->number_of_parms ; current_parm++ ) + { + arc.ArchiveByte( &dialog_node->parms[ current_parm ].type ); + + parm = dialog_node->parms[ current_parm ].parm; + + arc.ArchiveString( &parm ); + + parm = dialog_node->parms[ current_parm ].parm2; + + arc.ArchiveString( &parm ); + } + + dialog_node = dialog_node->next; + } + + more = false; + arc.ArchiveByte( &more ); + } + else + { + byte more; + DialogNode_t *new_dialog_node; + DialogNode_t *current_dialog_node = NULL; + str alias_name; + str parm; + int current_parm; + + arc.ArchiveByte( &more ); + + while( more ) + { + new_dialog_node = NewDialogNode(); + + if ( current_dialog_node ) + current_dialog_node->next = new_dialog_node; + else + dialog_list = new_dialog_node; + + current_dialog_node = new_dialog_node; + new_dialog_node->next = NULL; + + arc.ArchiveString( &alias_name ); + strcpy( new_dialog_node->alias_name, alias_name.c_str() ); + + arc.ArchiveInteger( &new_dialog_node->random_flag ); + arc.ArchiveInteger( &new_dialog_node->number_of_parms ); + arc.ArchiveFloat( &new_dialog_node->random_percent ); + ArchiveEnum( new_dialog_node->dType , DialogType_t ); + + for( current_parm = 0 ; current_parm < new_dialog_node->number_of_parms ; current_parm++ ) + { + arc.ArchiveByte( &new_dialog_node->parms[ current_parm ].type ); + + arc.ArchiveString( &parm ); + strcpy( new_dialog_node->parms[ current_parm ].parm, parm.c_str() ); + + arc.ArchiveString( &parm ); + strcpy( new_dialog_node->parms[ current_parm ].parm2, parm.c_str() ); + } + + arc.ArchiveByte( &more ); + } + } + + + arc.ArchiveString( &_branchDialogName ); + arc.ArchiveFloat( &dialog_done_time ); + arc.ArchiveString( &dialog_state_name ); + arc.ArchiveString( &dialog_old_state_name ); + arc.ArchiveBool( &_ignoreNextContext ); + arc.ArchiveString( &_nextContextToIgnore ); + arc.ArchiveFloat( &_nextContextTime ); + arc.ArchiveFloat(&_contextInterval); + + arc.ArchiveSafePointer( &scriptthread ); + + arc.ArchiveString( &kill_thread ); + arc.ArchiveString( &escape_thread ); + arc.ArchiveString( &captured_thread ); + arc.ArchiveString( &activate_thread ); + arc.ArchiveString( &onuse_thread_name ); + arc.ArchiveString( &ondamage_thread ); + arc.ArchiveString( &alert_thread ); + arc.ArchiveString( &idle_thread ); + + arc.ArchiveFloat( &pain_threshold ); + arc.ArchiveFloat( &next_drown_time ); + arc.ArchiveFloat( &air_finished ); + arc.ArchiveInteger( &pain_type ); + arc.ArchiveVector( &pain_angles ); + arc.ArchiveInteger( &bullet_hits ); + arc.ArchiveFloat( &next_pain_time ); + arc.ArchiveFloat( &min_pain_time ); + arc.ArchiveFloat( &next_forced_pain_time ); + arc.ArchiveFloat( &max_pain_time ); + arc.ArchiveString( &_deathEffect ); + + if ( arc.Saving() ) + { + arc.ArchiveString( &statemap_name ); + arc.ArchiveString( &masterstatemap_name ); + + if ( currentState ) + temp_state_name = currentState->getName(); + + if ( globalState ) + temp_global_state_name = globalState->getName(); + + if ( lastState ) + temp_last_state_name = lastState->getName(); + + if ( currentMasterState ) + temp_master_state_name = currentMasterState->getName(); + + if ( lastMasterState ) + temp_last_master_state_name = lastMasterState->getName(); + + arc.ArchiveString( &temp_state_name ); + arc.ArchiveString( &temp_global_state_name ); + arc.ArchiveString( &temp_last_state_name ); + arc.ArchiveString( &temp_master_state_name ); + arc.ArchiveString( &temp_last_master_state_name ); + } + else + { + arc.ArchiveString( &statemap_name ); + arc.ArchiveString( &masterstatemap_name ); + + if ( statemap_name.length() ) + { + Event *event; + + event = new Event( EV_Actor_Statemap ); + event->AddString( statemap_name.c_str() ); + event->AddString( "" ); + event->AddInteger( 1 ); + ProcessEvent( event ); + } + + arc.ArchiveString( &temp_state_name ); + arc.ArchiveString( &temp_global_state_name ); + arc.ArchiveString( &temp_last_state_name ); + + if ( statemap ) + { + currentState = statemap->FindState( temp_state_name.c_str() ); + globalState = statemap->FindGlobalState( temp_global_state_name.c_str() ); + lastState = statemap->FindState( temp_last_state_name.c_str() ); + } + + + if ( masterstatemap_name.length() ) + { + Event *event; + + event = new Event( EV_Actor_MasterStateMap ); + event->AddString( masterstatemap_name.c_str() ); + event->AddString( "" ); + event->AddInteger( 1 ); + ProcessEvent ( event ); + } + + arc.ArchiveString( &temp_master_state_name ); + arc.ArchiveString( &temp_last_master_state_name ); + + if ( masterstatemap ) + { + currentMasterState = masterstatemap->FindState( temp_master_state_name.c_str() ); + lastMasterState = masterstatemap->FindState( temp_last_master_state_name.c_str() ); + } + } + + arc.ArchiveFloat( &state_time ); + arc.ArchiveFloat( &masterstate_time ); + arc.ArchiveInteger( ×_done ); + arc.ArchiveInteger( &masterstate_times_done ); + arc.ArchiveFloat( &state_done_time ); + arc.ArchiveFloat( &masterstate_done_time ); + + arc.ArchiveFloat( &last_time_active ); + ArchiveEnum( showStates, stateDebugType_t ); + ArchiveEnum( talkMode, talkModeStates_t ); + arc.ArchiveBool( &useConvAnims ); + + // Don't save these + //static Condition Conditions[]; + //Container conditionals; + //Container master_conditionals; + + arc.ArchiveString( &fuzzyengine_name ); + arc.ArchiveBoolean( &fuzzyEngine_active ); + + if ( arc.Loading() ) + { + if ( fuzzyengine_name.length() ) + { + Event *event; + + event = new Event ( EV_Actor_FuzzyEngine ); + event->AddString( fuzzyengine_name.c_str() ); + ProcessEvent ( event ); + } + } + + // Don't save + //Container fuzzy_conditionals; + + arc.ArchiveFloat( &maxEyeYawAngle ); + arc.ArchiveFloat( &minEyeYawAngle ); + arc.ArchiveFloat( &maxEyePitchAngle ); + arc.ArchiveFloat( &minEyePitchAngle ); + + arc.ArchiveInteger( &saved_mode ); + + if ( arc.Saving() ) + { + if ( saved_behavior ) + { + behavior_bool = true; + arc.ArchiveBoolean( &behavior_bool ); + arc.ArchiveObject( saved_behavior ); + } + else + { + behavior_bool = false; + arc.ArchiveBoolean( &behavior_bool ); + } + + if ( saved_headBehavior ) + { + hBehavior_bool = true; + arc.ArchiveBoolean( &hBehavior_bool ); + arc.ArchiveObject( saved_headBehavior ); + } + else + { + hBehavior_bool = false; + arc.ArchiveBoolean( &hBehavior_bool ); + } + + if ( saved_eyeBehavior ) + { + eBehavior_bool = true; + arc.ArchiveBoolean( &eBehavior_bool ); + arc.ArchiveObject( saved_eyeBehavior ); + } + else + { + eBehavior_bool = false; + arc.ArchiveBoolean( &eBehavior_bool ); + } + + if ( saved_torsoBehavior ) + { + tBehavior_bool = true; + arc.ArchiveBoolean( &tBehavior_bool ); + arc.ArchiveObject( saved_torsoBehavior ); + } + else + { + tBehavior_bool = false; + arc.ArchiveBoolean( &tBehavior_bool ); + } + } + else + { + arc.ArchiveBoolean( &behavior_bool ); + + if ( behavior_bool ) + saved_behavior = ( Behavior * )arc.ReadObject(); + else + saved_behavior = NULL; + + arc.ArchiveBoolean( &hBehavior_bool ); + + if ( hBehavior_bool ) + saved_headBehavior = ( Behavior * )arc.ReadObject(); + else + saved_headBehavior = NULL; + + arc.ArchiveBoolean( &eBehavior_bool ); + + if ( eBehavior_bool ) + saved_eyeBehavior = ( Behavior * )arc.ReadObject(); + else + saved_eyeBehavior = NULL; + + arc.ArchiveBoolean( &tBehavior_bool ); + + if ( tBehavior_bool ) + saved_torsoBehavior = ( Behavior * )arc.ReadObject(); + else + saved_torsoBehavior = NULL; + } + + arc.ArchiveSafePointer( &saved_scriptthread ); + arc.ArchiveSafePointer( &saved_actorthread ); + arc.ArchiveString( &saved_anim_name ); + arc.ArchiveString( &saved_state_name ); + arc.ArchiveString( &saved_anim_event_name ); + + arc.ArchiveString( &part_name ); + + { + part_t part; + int current_part; + int number_of_parts; + part_t *part_ptr; + + if ( arc.Saving() ) + { + number_of_parts = parts.NumObjects(); + } + else + { + parts.ClearObjectList(); + } + + arc.ArchiveInteger( &number_of_parts ); + + if ( arc.Loading() ) + parts.Resize( number_of_parts ); + + for( current_part = 1; current_part <= number_of_parts ; current_part++ ) + { + if ( arc.Saving() ) + { + part = parts.ObjectAt( current_part ); + part_ptr = ∂ + } + else + { + parts.AddObject( part ); + part_ptr = parts.AddressOfObjectAt( current_part ); + } + + arc.ArchiveSafePointer( &part_ptr->ent ); + arc.ArchiveUnsigned( &part_ptr->state_flags ); + } + } + + arc.ArchiveSafePointer( &incoming_proj ); + arc.ArchiveFloat( &incoming_time ); + arc.ArchiveBoolean( &incoming_bullet ); + + arc.ArchiveString( &name ); + arc.ArchiveFloat( &max_inactive_time ); + + arc.ArchiveVector( &eyeoffset ); + arc.ArchiveFloat( &last_jump_time ); + + arc.ArchiveString( &enemytype ); + + arc.ArchiveFloat( &actorrange_time ); + arc.ArchiveFloat( &last_height ); + arc.ArchiveSafePointer( &last_ent ); + + arc.ArchiveFloat( &canseeenemy_time ); + arc.ArchiveFloat( &canseeplayer_time ); + arc.ArchiveInteger( &stage ); + arc.ArchiveInteger( &num_of_spawns ); + arc.ArchiveSafePointer( &spawnparent ); + arc.ArchiveVector( &last_attack_pos ); + arc.ArchiveVector( &last_attack_enemy_pos ); + arc.ArchiveSafePointer( &last_attack_entity_hit ); + arc.ArchiveVector( &last_attack_entity_hit_pos ); + arc.ArchiveInteger( &mode ); + arc.ArchiveVector( &last_known_enemy_pos ); + arc.ArchiveVector( &last_known_player_pos ); + + arc.ArchiveFloat( &feet_width ); + arc.ArchiveVector( &last_origin ); + + arc.ArchiveFloat( &next_find_enemy_time ); + arc.ArchiveFloat( &minimum_melee_height ); + arc.ArchiveFloat( &damage_angles ); + + arc.ArchiveFloat( &real_head_pitch ); + arc.ArchiveFloat( &next_pain_sound_time ); + arc.ArchiveFloat( &last_ground_z ); + + arc.ArchiveString( &emotion ); + arc.ArchiveFloat( &next_blink_time ); + + arc.ArchiveFloat( &actor_to_actor_damage_modifier ); + + arc.ArchiveFloat( &last_used_time ); + + arc.ArchiveFloat( &hitscan_response_chance ); + arc.ArchiveInteger( &shotsFired ); + arc.ArchiveInteger( &ondamage_threshold ); + arc.ArchiveFloat( &timeBetweenSleepChecks ); + + arc.ArchiveInteger ( &saved_bone_hit ); + + arc.ArchiveSafePointer( &_controller ); + ArchiveEnum( _controlType, Actor::ActorControlType ); + + // Save out currentHelperNode + + arc.ArchiveSafePointer( ¤tHelperNode.node ); + arc.ArchiveInteger( ¤tHelperNode.mask ); + arc.ArchiveInteger( ¤tHelperNode.nodeID ); + + arc.ArchiveSafePointer( &ignoreHelperNode.node ); + arc.ArchiveInteger( &ignoreHelperNode.mask ); + arc.ArchiveInteger( &ignoreHelperNode.nodeID ); + + // Save out followTarget + + arc.ArchiveSafePointer( &followTarget.currentFollowTarget ); + arc.ArchiveSafePointer( &followTarget.specifiedFollowTarget ); + arc.ArchiveFloat( &followTarget.maxRangeIdle ); + arc.ArchiveFloat( &followTarget.minRangeIdle ); + arc.ArchiveFloat( &followTarget.maxRangeCombat ); + arc.ArchiveFloat( &followTarget.minRangeCombat ); + + arc.ArchiveInteger( &_steeringDirectionPreference ); + + if ( arc.Saving() ) + { + num = stateVarList.NumObjects(); + + arc.ArchiveInteger( &num ); + + for( i = 1 ; i <= num ; i++ ) + { + stateVar = stateVarList.ObjectAt( i ); + + arc.ArchiveString( &stateVar->varName ); + arc.ArchiveString( &stateVar->varValue ); + arc.ArchiveFloat( &stateVar->varTime ); + } + } + else + { + arc.ArchiveInteger( &num ); + + for( i = 1 ; i <= num ; i++ ) + { + stateVar = new StateVar; + stateVarList.AddObject( stateVar ); + + arc.ArchiveString( &stateVar->varName ); + arc.ArchiveString( &stateVar->varValue ); + arc.ArchiveFloat( &stateVar->varTime ); + } + } + + + if ( arc.Saving() ) + { + num = threadList.NumObjects(); + + arc.ArchiveInteger( &num ); + + for( i = 1 ; i <= num ; i++ ) + { + threadListEntry = threadList.ObjectAt( i ); + + arc.ArchiveString( &threadListEntry->threadType ); + arc.ArchiveString( &threadListEntry->threadName ); + } + } + else + { + arc.ArchiveInteger( &num ); + + for ( i = 1; i <= num; i++ ) + { + threadListEntry = new threadlist_t; + threadList.AddObject( threadListEntry ); + + arc.ArchiveString( &threadListEntry->threadType ); + arc.ArchiveString( &threadListEntry->threadName ); + } + } + + arc.ArchiveSafePointer( &trigger ); + + arc.ArchiveString( &command ); + + arc.ArchiveString( &idle_state_name ); + arc.ArchiveString( &master_idle_state_name ); + arc.ArchiveString( &global_state_name ); + + arc.ArchiveFloat( &next_player_near ); + + arc.ArchiveSafePointer( &pickup_ent ); + + arc.ArchiveFloat( &stunned_end_time ); + + spawn_items.Archive( arc ); + arc.ArchiveFloat( &spawn_chance ); + + arc.ArchiveString( &bounce_off_effect ); + + can_be_finsihed_by_mods.Archive( arc ); + + arc.ArchiveFloat( &max_boss_health ); + + arc.ArchiveBoolean( &haveAttached ); + + arc.ArchiveFloat( ¤tSplineTime ); + arc.ArchiveFloat( &_dialogMorphMult ); + ArchiveEnum( _useWeaponDamage, weaponhand_t ); + arc.ArchiveFloat( &_nextCheckForWorkNodeTime ); + arc.ArchiveFloat( &_nextCheckForHibernateNodeTime ); + + arc.ArchiveFloat( &minLeadFactor ); + arc.ArchiveFloat( &maxLeadFactor ); + + //arc.ArchiveInteger( &groupnumber ); + + // Handle the Archiving of our helper classes + + // Archiving thinkStrategy is a little more complex than normal because thinkStrategy can point to multiple types + // of child classes and when we read it in we need to make sure to have the correct one + + if ( arc.Saving() ) + { + bool simplifiedThink; + + if ( thinkStrategy->isSimple() ) + simplifiedThink = true; + else + simplifiedThink = false; + + arc.ArchiveBool( &simplifiedThink ); + + thinkStrategy->DoArchive ( arc ); + } + else + { + bool simplifiedThink; + + arc.ArchiveBool( &simplifiedThink ); + + if ( simplifiedThink ) + { + delete thinkStrategy; + thinkStrategy = new SimplifiedThink( (Actor *)this ); + } + + thinkStrategy->DoArchive ( arc ); + } + + gameComponent->DoArchive ( arc , this ); + sensoryPerception->DoArchive ( arc , this ); + strategos->DoArchive ( arc , this ); + enemyManager->DoArchive ( arc , this ); + packageManager->DoArchive ( arc , this ); + movementSubsystem->DoArchive ( arc , this ); + personality->DoArchive ( arc , this ); + combatSubsystem->DoArchive ( arc , this ); + headWatcher->DoArchive ( arc , this ); + postureController->DoArchive ( arc , this ); + + arc.ArchiveFloat( &lastPathCheck_Work ); + arc.ArchiveFloat( &lastPathCheck_Flee ); + arc.ArchiveFloat( &lastPathCheck_Patrol ); + arc.ArchiveBoolean( &testing ); + + if ( isThinkOn() ) + Wakeup(); + else + Sleep(); +} + + + + +void Actor::SetAggressiveness( Event *ev ) + { + personality->SetAggressiveness( ev->GetFloat( 1 ) ); + } + + + +qboolean Actor::checkWantsToExecutePackage( Conditional &condition ) + { + float interval; + + if ( condition.numParms() > 0 ) + interval = atof( condition.getParm( 1 ) ); + else + interval = 0.0f; + + return personality->WantsToExecuteCurrentPackage( interval ); + } + +qboolean Actor::checkExecutedPackageInLastTimeFrame( Conditional &condition ) + { + float interval; + + if ( condition.numParms() > 0 ) + interval = atof( condition.getParm( 1 ) ); + else + interval = 0.0f; + + return personality->ExecutedPackageInLastTimeFrame( interval ); + } + + +qboolean Actor::checkIsAggressive( Conditional &condition ) + { + float baseLine; + + baseLine = atof(condition.getParm( 1 )); + + return ( baseLine <= personality->GetAggressiveness() ); + + } + +qboolean Actor::checkInConeOfFire( Conditional &condition ) + { + return GetActorFlag( ACTOR_FLAG_IN_CONE_OF_FIRE ); + } + +qboolean Actor::checkInPlayerConeOfFire( Conditional &condition ) +{ + return GetActorFlag( ACTOR_FLAG_IN_PLAYER_CONE_OF_FIRE ); +} + +void Actor::SetGroupNumber( Event *ev ) + { + //This is here for legacy. In the future we need to + //move all group set up to the group coordinator alone + //however, currently, we have a large number of scripts + //that are assigning actors groups in this manner + AddToGroup( ev->GetInteger( 1 ) ); + } + +void Actor::_notifyGroupOfDamage() + { + _notifyGroupOfEnemy(); + } + +void Actor::_notifyGroupOfKilled() + { + _notifyGroupOfEnemy(); + } + +void Actor::_notifyGroupSpottedEnemy() + { + _notifyGroupOfEnemy(); + } + +void Actor::_notifyGroupOfEnemy() +{ + Actor *act; + Entity *currentEnemy; + int i; + + enemyManager->FindHighestHateEnemy(); + currentEnemy = enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + return; + + for( i = 1; i <= ActiveList.NumObjects(); i++ ) + { + act = ActiveList.ObjectAt( i ); + + if ( act->GetGroupID() == GetGroupID() ) + { + act->sensoryPerception->Stimuli(STIMULI_ALL); + act->enemyManager->TryToAddToHateList( currentEnemy ); + act->enemyManager->SetCurrentEnemy( currentEnemy ); + act->personality->SetAggressiveness( 1.0f); + } + } + + for ( i = 1; i <= SleepList.NumObjects(); i++ ) + { + act = SleepList.ObjectAt( i ); + + if ( act->GetGroupID() == GetGroupID() ) + { + act->sensoryPerception->Stimuli(STIMULI_ALL); + act->enemyManager->TryToAddToHateList( currentEnemy ); + act->enemyManager->SetCurrentEnemy( currentEnemy ); + act->personality->SetAggressiveness( 1.0f); + } + } +} + +qboolean Actor::checkPatrolWaypointNodeInDistance( Conditional &condition ) + { + float distance = atof(condition.getParm( 1 ) ); + + Entity* ent_in_range; + Vector NodeToSelf; + gentity_t *ed; + + Vector pos; + Vector nodeOrigin; + + /* + int wtf; + wtf = lastPathCheck_Flee + HACK_PATH_CHECK; + + if ( wtf >= level.time ) + return false; + + lastPathCheck_Patrol = level.time + HACK_PATH_CHECK + G_Random(); + */ + + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + ent_in_range = g_entities[i].entity; + + pos = origin; + pos.z += 80; + + nodeOrigin = ent_in_range->origin; + + /* + if (!sensoryPerception->CanSeePosition( pos , nodeOrigin , true , true ) ) + continue; + */ + + if( ent_in_range->isSubclassOf( PatrolWayPointNode ) ) + { + NodeToSelf = ent_in_range->origin - origin; + if ( NodeToSelf.length() <= distance ) + return true; + } + } + + return false; + } + +qboolean Actor::checkPathNodeTypeInDistance( Conditional &condition ) + { + str nodeType = condition.getParm( 1 ); + float distance = atof(condition.getParm( 2 )); + + if ( !Q_stricmp( "work" , nodeType.c_str() ) ) + return _WorkNodeInDistance( distance ); + + if ( !Q_stricmp( "flee" , nodeType.c_str() ) ) + return _FleeNodeInDistance( distance ); + + return false; + } + +void Actor::SetHeadWatchTarget( Event *ev ) + { + str watchTarget; + float speed; + + watchTarget = ev->GetString( 1 ); + if ( ev->NumArgs() > 1 ) + { + speed = ev->GetFloat( 2 ); + headWatcher->SetWatchSpeed( speed ); + } + + + if ( !Q_stricmp( "enemy" , watchTarget.c_str() ) ) + { + headWatcher->SetWatchTarget( enemyManager->GetCurrentEnemy() ); + return; + } + + if ( !Q_stricmp( "none" , watchTarget.c_str() ) ) + { + headWatcher->SetWatchTarget( NULL ); + return; + } + + if ( !Q_stricmp( "player" , watchTarget.c_str() ) ) + { + Player *player = GetPlayer(0); + headWatcher->SetWatchTarget( player ); + } + + if ( !Q_stricmp( "teammate" , watchTarget.c_str() ) ) + { + float bestDist = 99999; + Vector selfToTeammate; + Sentient *teammate = GetPlayer(0); + Sentient *closestTeammate = NULL; + + for ( int i = 1 ; i <= TeamMateList.NumObjects() ; i++ ) + { + teammate = TeamMateList.ObjectAt( i ); + if ( teammate->entnum == entnum) + continue; + + selfToTeammate = teammate->origin - origin; + if ( selfToTeammate.length() <= bestDist ) + { + closestTeammate = teammate; + bestDist = selfToTeammate.length(); + } + + } + + if ( !closestTeammate ) + closestTeammate = teammate; + + headWatcher->SetWatchTarget( closestTeammate ); + return; + } + + + headWatcher->SetWatchTarget( watchTarget ); + + } + +void Actor::SetHeadWatchTarget( Entity *ent ) + { + if ( !ent ) + ent = NULL; + + headWatcher->SetWatchTarget( ent ); + } + +void Actor::SetHeadWatchSpeed( Event *ev ) + { + float speed = ev->GetFloat( 1 ); + headWatcher->SetWatchSpeed( speed ); + } + +void Actor::SetHeadWatchSpeed( float speed ) + { + headWatcher->SetWatchSpeed( speed ); + } + +void Actor::setHeadTwitch( Event *ev ) +{ + headWatcher->setHeadTwitch( ev->GetBoolean( 1 ) ); +} + +void Actor::SetFuzzyEngineActive( Event *ev ) + { + qboolean active = ev->GetBoolean( 1 ); + fuzzyEngine_active = active; + } + +qboolean Actor::_isWorkNodeValid( PathNode *node ) + { + WorkTrigger *target = NULL; + + if ( node->targetEntity ) + { + Entity *entity = node->targetEntity; + + if( entity->isSubclassOf( WorkTrigger ) ) + { + target = (WorkTrigger*)entity; + + //If it's not reserved, but still marked as occupied -- Don't go + if ( node->occupiedTime > level.time && !target->isReserved() ) + return false; + + if ( target->isAllowedToWork( targetname , entnum ) ) + return true; + } + } + + /* if ( node->target ) + { + str targetName; + targetName = node->target; + + if ( targetName.length() > 0 ) + { + Entity* ent_in_range; + gentity_t *ed; + + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + + ent_in_range = g_entities[i].entity; + + if( ent_in_range->isSubclassOf( WorkTrigger ) ) + { + if (!Q_stricmp(ent_in_range->targetname.c_str() , targetName.c_str() )) + { + target = (WorkTrigger*)ent_in_range; + + //If it's not reserved, but still marked as occupied -- Don't go + if ( node->occupiedTime > level.time && !target->isReserved() ) + return false; + + if ( target->isAllowedToWork( targetname , entnum ) ) + return true; + + } + } + } + + } + } */ + + if ( !target ) + return true; + + + return false; + } + +qboolean Actor::_FleeNodeInDistance( float dist ) + { + int wtf; + wtf = (int)(lastPathCheck_Flee + HACK_PATH_CHECK); + + if ( wtf >= level.time ) + return false; + + lastPathCheck_Flee = level.time + HACK_PATH_CHECK + G_Random(); + + + Vector delta; + Vector pos; + Vector nodeOrigin; + + for ( int i = 1 ; i <= thePathManager.NumberOfSpecialNodes(); i++ ) + { + PathNode *node = thePathManager.GetSpecialNode( i ); + + pos = origin; + pos.z += 80; + + nodeOrigin = node->origin; + + if (!sensoryPerception->CanSeePosition( pos , nodeOrigin , true , true ) ) + continue; + + if ( node && ( node->nodeflags & ( AI_FLEE ) ) && + ( ( node->occupiedTime <= level.time ) ) && ( node->entnum == 0 || node->entnum == entnum ) ) + { + delta = node->origin - origin; + + if ( delta.length() <= dist ) + return true; + + } + + } + + return false; + } + +qboolean Actor::_WorkNodeInDistance( float dist ) + { + int wtf; + wtf = (int)(lastPathCheck_Work + HACK_PATH_CHECK); + + if ( wtf >= level.time ) + return false; + + lastPathCheck_Work = level.time + G_Random(); + + Vector delta; + Vector pos; + Vector nodeOrigin; + + for ( int i = 1 ; i <= thePathManager.NumberOfSpecialNodes(); i++ ) + { + PathNode *node = thePathManager.GetSpecialNode( i ); + + pos = origin; + pos.z += 80; + + nodeOrigin = node->origin; + + if (!sensoryPerception->CanSeePosition( pos , nodeOrigin , true , true ) ) + continue; + + if ( node && ( node->nodeflags & ( AI_WORK ) ) && + ( ( node->occupiedTime <= level.time ) ) && ( node->entnum == 0 || node->entnum == entnum ) ) + { + if ( !_isWorkNodeValid( node ) ) + continue; + + delta = node->origin - origin; + + if ( delta.length() <= dist ) + return true; + + } + + + } + + return false; + } + +void Actor::ClearArmorAdaptions( Event *ev ) + { + AdaptiveArmor::ClearAdaptionList(); + } + +void Actor::SetMovementMode( Event *ev ) + { + str modeType; + modeType = ev->GetString( 1 ); + + if ( !Q_stricmp( "normal" , modeType.c_str() ) ) + movementSubsystem->setMovementType( MOVEMENT_TYPE_NORMAL ); + else if ( !Q_stricmp( "anim" , modeType.c_str() ) ) + movementSubsystem->setMovementType( MOVEMENT_TYPE_ANIM ); + } + + + +void Actor::SetCinematicAnim( const str &animName ) + { + Entity::SetCinematicAnim( animName); // Ensure entity level cinematic stuff is enabled + movementSubsystem->setMovementType( MOVEMENT_TYPE_ANIM ); + } + + +void Actor::CinematicAnimDone( void ) + { + Entity::CinematicAnimDone(); // Ensures entity level cinematic stuff is disabled + movementSubsystem->setMovementType( MOVEMENT_TYPE_NORMAL ); + } + + + +qboolean Actor::checkForwardDirectionClear( Conditional &condition ) + { + return checkForwardDirectionClear(atof(condition.getParm( 1 ) )); + } + +qboolean Actor::checkForwardDirectionClear(float dist) + { + trace_t trace; + + Vector endPos; + Vector startPos; + Vector forward; + Vector angles; + + startPos = origin; + startPos.z += 32; + + angles = movementSubsystem->getAnimDir(); + angles = angles.toAngles(); + angles.AngleVectors( &forward ); + + endPos = ( forward * dist) + startPos; + + trace = G_Trace(startPos, mins, maxs, endPos, NULL, edict->clipmask, false, "checkForwardDirectionClear" ); + + if (trace.fraction == 1.0 ) + { + return ( movementSubsystem->CanWalkTo( trace.endpos, 0.0f, entnum ) ); + } + + return false; + } + +qboolean Actor::checkRearDirectionClear( Conditional &condition ) + { + return checkRearDirectionClear(atof(condition.getParm( 1 ) )); + } + +qboolean Actor::checkRearDirectionClear(float dist) + { + trace_t trace; + + Vector endPos; + Vector startPos; + Vector forward; + Vector angles; + + startPos = origin; + startPos.z += 32; + + angles = movementSubsystem->getAnimDir(); + angles = angles.toAngles(); + angles[YAW] = AngleNormalize180(angles[YAW] + 180); + angles.AngleVectors( &forward ); + + endPos = ( forward * dist) + startPos; + + trace = G_Trace(startPos, mins, maxs, endPos, NULL, edict->clipmask, false, "checkForwardDirectionClear" ); + + if (trace.fraction == 1.0 ) + { + return ( movementSubsystem->CanWalkTo( trace.endpos, 0.0f, entnum ) ); + } + + return false; + + } + +qboolean Actor::checkLeftDirectionClear( Conditional &condition ) + { + return checkLeftDirectionClear(atof(condition.getParm( 1 ) )); + } + +qboolean Actor::checkLeftDirectionClear(float dist) + { + trace_t trace; + + Vector endPos; + Vector startPos; + Vector left; + Vector angles; + + startPos = origin; + //startPos.z += 32; + + angles = movementSubsystem->getAnimDir(); + angles = angles.toAngles(); + angles.AngleVectors( NULL, &left, NULL ); + + endPos = ( left * dist) + startPos; + + //trace = G_Trace(startPos, mins, maxs, endPos, NULL, edict->clipmask, false, "checkForwardDirectionClear" ); + trace = Trace( endPos , "CheckMyLeft" ); + //G_DebugLine( startPos, endPos, 1.0f, 1.0f, 1.0f, 1.0f ); + + if (trace.fraction == 1.0 ) + { + return ( movementSubsystem->CanWalkTo( trace.endpos, 0.0f, entnum ) ); + } + + + return false; + + } + +qboolean Actor::checkRightDirectionClear( Conditional &condition ) + { + return checkRightDirectionClear(atof(condition.getParm( 1 ) )); + } + +qboolean Actor::checkRightDirectionClear(float dist) + { + trace_t trace; + + Vector endPos; + Vector startPos; + Vector left; + Vector angles; + + startPos = origin; + //startPos.z += 16; + + angles = movementSubsystem->getAnimDir(); + angles = angles.toAngles(); + + angles[YAW] = AngleNormalize180(angles[YAW] + 180); + angles.AngleVectors( NULL, &left, NULL ); + + endPos = ( left * dist) + startPos; + + //trace = G_Trace(startPos, mins, maxs, endPos, NULL, edict->clipmask, false, "checkForwardDirectionClear" ); + trace = Trace( endPos , "CheckMyRight" ); + //G_DebugLine( startPos, endPos, 1.0f, 1.0f, 1.0f, 1.0f ); + + if (trace.fraction == 1.0 ) + { + return ( movementSubsystem->CanWalkTo( trace.endpos, 0.0f, entnum ) ); + } + + return false; + + } + +qboolean Actor::checkbehaviorsuccess( Conditional &condition ) + { + if ( behaviorCode == BEHAVIOR_SUCCESS ) + return true; + + return false; + } + +qboolean Actor::checkbehaviorfailed( Conditional &condition ) + { + if ( behaviorCode == BEHAVIOR_FAILED || + behaviorCode == BEHAVIOR_FAILED_STEERING_BLOCKED_BY_ENEMY || + behaviorCode == BEHAVIOR_FAILED_STEERING_BLOCKED_BY_CIVILIAN || + behaviorCode == BEHAVIOR_FAILED_STEERING_BLOCKED_BY_FRIEND || + behaviorCode == BEHAVIOR_FAILED_STEERING_BLOCKED_BY_TEAMMATE || + behaviorCode == BEHAVIOR_FAILED_STEERING_BLOCKED_BY_WORLD || + behaviorCode == BEHAVIOR_FAILED_STEERING_BLOCKED_BY_DOOR || + behaviorCode == BEHAVIOR_FAILED_STEERING_CANNOT_GET_TO_PATH || + behaviorCode == BEHAVIOR_FAILED_STEERING_NO_PATH + + ) + return true; + + return false; + } + +void Actor::SetNodeID( Event *ev ) + { + currentHelperNode.nodeID = ev->GetInteger( 1 ); + } + +void Actor::SetFollowTarget( Event *ev ) + { + Entity *ent; + ent = NULL; + + ent = ev->GetEntity( 1 ); + + if ( ent ) + followTarget.specifiedFollowTarget = ent; + } + +void Actor::SetSteeringDirectionPreference( Event *ev ) + { + str preference = ev->GetString( 1 ); + if ( preference == "steer_left_always" ) + { + _steeringDirectionPreference = STEER_LEFT_ALWAYS; + } + else if ( preference == "steer_randomly" ) + { + _steeringDirectionPreference = STEER_RANDOMLY; + } + else if ( preference == "steer_best" ) + { + _steeringDirectionPreference = STEER_RANDOMLY; + } + else + { + _steeringDirectionPreference = STEER_RIGHT_ALWAYS; + } + } + +void Actor::SetFollowRange( Event *ev ) + { + followTarget.maxRangeIdle = ev->GetFloat( 1 ); + } + +void Actor::SetFollowRangeMin( Event *ev ) + { + followTarget.minRangeIdle = ev->GetFloat( 1 ); + } + +void Actor::SetFollowCombatRange( Event *ev ) +{ + followTarget.maxRangeCombat = ev->GetFloat( 1 ); +} + +void Actor::SetFollowCombatRangeMin( Event *ev ) +{ + followTarget.minRangeCombat = ev->GetFloat( 1 ); +} + + +qboolean Actor::checkLastState( Conditional &condition ) + { + str lastStateName; + lastStateName = condition.getParm( 1 ); + + if ( !Q_stricmp( lastStateName.c_str() , lastState->getName() ) ) + return true; + else + return false; + } + +void Actor::SetTalkiness( Event *ev ) + { + personality->SetTalkiness( ev->GetFloat( 1 ) ); + } + +void Actor::SetTendency( Event *ev ) + { + personality->SetTendency( ev->GetString( 1 ) , ev->GetFloat( 2 ) ); + } + + + + + + + + + +void AI_DisplayInfo( void ) + { + if ( ai_numactive->integer ) + { + gi.Printf( "Active actors - %d\n", ActiveList.NumObjects() ); + } + } + + + + +// +//================================================================================================= +// Context Dialog Functionality +//================================================================================================= +// + + + +// +// Name: InContext() +// Class: Actor +// +// Description: Generates the proper event based on the context provided in the event +// +// Parameters: Event *ev -- Event containing the context +// +// Returns: None +// +void Actor::InContext( Event *ev ) +{ + str theContext = ev->GetString( 1 ); + bool useDefaultMinDist = false; + + if ( ev->NumArgs() > 1 ) + useDefaultMinDist = ev->GetBoolean( 2 ); + + InContext( theContext , useDefaultMinDist ); +} + +void Actor::InContext( const str &theContext , bool useDefaultMinDist ) +{ + Event *ignoreEvent; + Event *broadcastEvent; + str realDialog; + float dialogLength; + + if ( !WantsToTalk() ) return; + + if ( GetActorFlag( ACTOR_FLAG_DIALOG_PLAYING ) ) return; + + if ( _nextContextTime > level.time ) return; + + if ( _ignoreNextContext ) + { + if ( !stricmp( theContext.c_str() , _nextContextToIgnore.c_str() ) ) + { + _ignoreNextContext = false; + _nextContextToIgnore = ""; + return; + } + } + + realDialog = FindDialog( this, DIALOG_TYPE_CONTEXT_INITIATOR, theContext ); + + if ( !realDialog.length() ) + return; + + ignoreEvent = new Event ( EV_ContextDialog_IgnoreNextContext ); + ignoreEvent->AddInteger( 1 ); + ignoreEvent->AddString( theContext ); + groupcoordinator->SendEventToGroup( ignoreEvent , GetGroupID() ); + + char localizedDialogName[MAX_QPATH]; + gi.LocalizeFilePath( realDialog, localizedDialogName ); + + _nextContextTime = level.time + G_Random() + _contextInterval; + + dialogLength = gi.SoundLength( localizedDialogName ); + broadcastEvent = new Event ( EV_Actor_BroadcastDialog ); + broadcastEvent->AddString( theContext ); + PostEvent( broadcastEvent, dialogLength ); + + + if ( useDefaultMinDist ) + PlayDialog( this, DEFAULT_VOL, DEFAULT_MIN_DIST, realDialog.c_str() , NULL ); + else + PlayDialog( this, DEFAULT_VOL, CONTEXT_WIDE_MIN_DIST, realDialog.c_str() , NULL ); + +} + +// +// Name: BroadcastDialog() +// Class: Actor +// +// Description: Broadcasts the dialog to nearby actors so that they can "hear" it +// +// Parameters: dialogContexts_t context -- The context +// ContextDialogType_t contextType -- the context type +// +// Returns: None +// +void Actor::BroadcastDialog(Event *ev) + { + Entity *ent; + Actor *act; + Actor *bestAct = NULL; + Vector delta; + float dist; + float bestDist; + str context = ev->GetString( 1 ); + str responseDialog; + str bestResponseDialog; + + bestDist = SOUND_RADIUS; + for( int i = 1; i <= SentientList.NumObjects(); i++ ) + { + ent = SentientList.ObjectAt( i ); + if ( ( ent == this ) || ent->deadflag ) + { + continue; + } + + if ( ent->isSubclassOf( Actor ) ) + { + act = (Actor*)ent; + delta = origin - act->centroid; + dist = delta.length(); + + if ( dist <= SOUND_RADIUS && dist < bestDist ) + { + if ( edict->areanum == ent->edict->areanum || gi.AreasConnected( edict->areanum, ent->edict->areanum ) ) + { + responseDialog = act->FindDialog( act, DIALOG_TYPE_CONTEXT_RESPONSE, context ); + if ( responseDialog.length() ) + { + bestAct = act; + bestDist = dist; + bestResponseDialog = responseDialog; + } + } + } + + } + } + + if ( bestAct ) + { + // + // Play the Response + // + bestAct->PlayDialog( bestAct, DEFAULT_VOL, -1.0f, bestResponseDialog.c_str() , NULL ); + } + + } + + + +// +// Name: WantsToTalk() +// Class: Actor +// +// Description: Checks if the actor "wants" to talk -- based on his personality +// +// Parameters: None +// +// Returns: true or false +// +qboolean Actor::WantsToTalk() + { + return ( G_Random() <= personality->GetTalkiness() ); + } + + +qboolean Actor::checkGroupMememberRange( Conditional &condition ) + { + Actor *act; + float dist; + float reqDist; + Vector actToSelf; + + + reqDist = atof(condition.getParm( 1 )); + + for( int i = 1; i <= ActiveList.NumObjects(); i++ ) + { + act = ActiveList.ObjectAt( i ); + if ( act && act != this && act->GetGroupID() == GetGroupID() ) + { + actToSelf = origin - act->origin; + dist = actToSelf.length(); + + if ( dist <= reqDist ) + { + return true; + } + } + + } + + return false; + } + +qboolean Actor::checkActorType( Conditional &condition ) +{ + str aType = condition.getParm( 1 ); + + if ( !stricmp( aType.c_str() , "inanimate" ) ) + { + if ( actortype == IS_INANIMATE ) + return true; + else + return false; + } + else if ( !Q_stricmp( aType.c_str() , "monster" ) ) + { + if ( actortype == IS_MONSTER ) + return true; + else + return false; + } + else if ( !Q_stricmp( aType.c_str() , "enemy" ) ) + { + if ( actortype == IS_ENEMY ) + return true; + else + return false; + } + else if ( !Q_stricmp( aType.c_str() , "civilian" ) ) + { + if ( actortype == IS_CIVILIAN ) + return true; + else + return false; + } + else if ( !Q_stricmp( aType.c_str() , "friend" ) ) + { + if ( actortype == IS_FRIEND ) + return true; + else + return false; + } + else if ( !Q_stricmp( aType.c_str() , "animal" ) ) + { + if ( actortype == IS_ANIMAL ) + return true; + else + return false; + } + else if ( !Q_stricmp( aType.c_str() , "teammate" ) ) + { + if ( actortype == IS_TEAMMATE ) + return true; + else + return false; + } + + return false; +} + +qboolean Actor::checkIsTeammate( Conditional &condition ) +{ + if ( actortype == IS_TEAMMATE ) + return true; + else + return false; +} + +qboolean Actor::checkHaveActiveWeapon( Conditional &condition ) +{ + return combatSubsystem->HaveWeapon(); +} + +qboolean Actor::checkWeaponIsMelee( Conditional &condition ) +{ + return combatSubsystem->WeaponIsFireType( FT_MELEE ); +} + +qboolean Actor::checkWeaponChanged( Conditional &condition ) +{ + return ( state_flags & STATE_FLAG_CHANGED_WEAPON ); +} + +//---------------------------------------------------------------- +// Name: FindActorByName +// Class: Actor +// +// Description: Goes through the ActiveList and finds an actor with +// the matching name. +// +// Parameters: const str &name -- The name +// +// Returns: Actor pointer, or NULL if it's not found +//---------------------------------------------------------------- +Actor* Actor::FindActorByName(const str &charName) +{ + int i; + Actor *act; + for ( i=1; i<=ActiveList.NumObjects(); i++ ) + { + act = ActiveList.ObjectAt(i); + if ( !act ) + continue; + + if ( act->targetname == charName ) + return act; + } + + return NULL; +} + +//---------------------------------------------------------------- +// Name: setDialogMorphMult +// Class: Actor +// +// Description: Sets the multiplier for all dialog morphs for this actor +// +// Parameters: Event *ev - contains, float dialogMorphMultiplier +// +// Returns: none +//---------------------------------------------------------------- +void Actor::setDialogMorphMult( Event *ev ) +{ + _dialogMorphMult = ev->GetFloat( 1 ); +} + +//-------------------------------------------------------------- +// Name: canBeDamageBy() +// Class: Actor +// +// Description: Checks if we can be damaged by the specified +// MeansOfDeath +// +// Parameters: meansOfDeath_t MeansOfDeath +// +// Returns: +//-------------------------------------------------------------- +bool Actor::canBeDamagedBy(meansOfDeath_t MeansOfDeath) +{ + float resistance; + resistance = GetResistanceModifier( MeansOfDeath ); + + if ( resistance >= 100.0 ) + return false; + + if ( currentBaseArmor ) + { + return currentBaseArmor->CanBeDamagedBy( MeansOfDeath ); + } + + return true; +} + +//-------------------------------------------------------------- +// Name: ForceSetClip +// Class: Actor +// +// Description: Forces the actor's mask and contents to be +// "Set" type. This will need to change to be +// a group type mask when more group stuff is +// implemented +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::ForceSetClip( Event *ev ) +{ + edict->contents = CONTENTS_SETCLIP ; + edict->clipmask = MASK_SETCLIP ; +} + + +//-------------------------------------------------------------- +// Name: checkCountOfIdenticalNamesInGroup() +// Class: Actor +// +// Description: Converts the checkvalue imbeded in the conditional +// into an integer then passes it on to another +// checkCountOfIdenticalNamesInGroup() +// +// Parameters: Conditional &condition +// +// Returns: true or false; +//-------------------------------------------------------------- +qboolean Actor::checkCountOfIdenticalNamesInGroup( Conditional &condition ) +{ + int checkValue; + str checkName; + + checkName = condition.getParm( 1 ); + checkValue = atoi(condition.getParm( 2 )); + + return checkCountOfIdenticalNamesInGroup( checkName , checkValue ); +} + + +//-------------------------------------------------------------- +// Name: checkCountOfIdentcialNamesInGroup() +// Class: Actor +// +// Description: Checks if the number of group members with the same +// name as this actor is LESS than the number +// passed into the conditional +// +// Parameters: Conditional &condition +// +// Returns: true or false; +//-------------------------------------------------------------- +qboolean Actor::checkCountOfIdenticalNamesInGroup( const str &checkName , int checkValue ) +{ + ActorGroup* group; + int count; + + group = (ActorGroup*)groupcoordinator->GetGroup( GetGroupID() ); + + if ( !group ) + return true; + + count = group->CountMembersWithThisName( checkName ); + + if ( count < checkValue ) + return true; + + return false; +} + +qboolean Actor::checkCanAttackEnemy(Conditional &condition) +{ + return checkCanAttackEnemy(); +} + +qboolean Actor::checkCanAttackEnemy() +{ + // Get our current enemy + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + return combatSubsystem->CanAttackTarget( currentEnemy ); +} + + +//-------------------------------------------------------------- +// Name: checkGroupAttackerCount +// Class: Actor +// +// Description: Returns true if the number attacking is less than the number +// passed in. False otherwise. +// +// Parameters: Conditional &condition +// +// Returns: true or false; +//-------------------------------------------------------------- +qboolean Actor::checkGroupAttackerCount( Conditional &condition ) +{ + return checkGroupAttackerCountForEntity( condition, NULL ); +} + + +//-------------------------------------------------------------- +// Name: SetBehaviorPackage() +// Class: Actor +// +// Description: Calls SetBehaviorPackage() +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::SetBehaviorPackage( Event *ev ) +{ + SetBehaviorPackage(ev->GetString( 1 ) ); +} + +//-------------------------------------------------------------- +// Name: SetBehaviorPackage() +// Class: Actor +// +// Description: Attempts to set the requested behavior package +// +// Parameters: const str &packageName +// +// Returns: None +//-------------------------------------------------------------- +void Actor::SetBehaviorPackage( const str &packageName ) +{ + if ( !masterstatemap ) + { + assert ( masterstatemap ); + gi.WDPrintf( "You cannot set a behavior package on actor %s because it does not have a masterstatemap, please report this to the AI Programmer\n" , targetname.c_str() ); + return; + } + + if ( !stricmp( packageName.c_str() , "auto" ) ) + { + SetMasterState( "START" ); + } + else + { + SetMasterState( "SCRIPTED" ); + strategos->SetBehaviorPackage( packageName ); + } + +} + +//-------------------------------------------------------------- +// Name: UseBehaviorPackage() +// Class: Actor +// +// Description: Calls UseBehaviorPackage() +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::UseBehaviorPackage( Event *ev ) +{ + UseBehaviorPackage(ev->GetString( 1 ) ); +} + +//-------------------------------------------------------------- +// Name: UseBehaviorPackage() +// Class: Actor +// +// Description: Tells the Strategos to set the requested +// behavior packaged +// +// Parameters: const str &packageName +// +// Returns: None +//-------------------------------------------------------------- +void Actor::UseBehaviorPackage( const str &packageName ) +{ + strategos->SetBehaviorPackage( packageName ); +} + +//-------------------------------------------------------------- +// Name: ChildUseBehaviorPackage() +// Class: Actor +// +// Description: Calls UseBehaviorPackage() +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::ChildUseBehaviorPackage( Event *ev ) +{ + ChildUseBehaviorPackage(ev->GetString( 1 ) , ev->GetString( 2 ) ); +} + + +//-------------------------------------------------------------- +// +// Name: GetAttachedChildActor +// Class: Actor +// +// Description: Gets an attached child actor by name +// +// Parameters: const str& childName -- Child actor to find +// +// Returns: Actor* -- The child or NULL if not found. +// +//-------------------------------------------------------------- +Actor* Actor::GetAttachedChildActor( const str& childName ) +{ + int num; + Entity *child = 0; + Actor *childActor = 0; + + if ( !bind_info ) + return NULL; + + num = bind_info->numchildren; + child = NULL; + childActor = NULL; + + for ( int i=0; i < MAX_MODEL_CHILDREN; i++ ) + { + if ( bind_info->children[i] == ENTITYNUM_NONE ) + continue; + + child = ( Entity * )G_GetEntity( bind_info->children[i] ); + + if ( !stricmp(child->TargetName() , childName.c_str() ) ) + { + if ( child->isSubclassOf(Actor) ) + childActor = (Actor*)child; + + if ( childActor ) + return childActor; + } + } + + return NULL; +} + +//-------------------------------------------------------------- +// Name: ChildUseBehaviorPackage() +// Class: Actor +// +// Description: Tells the Strategos to set the requested +// behavior packaged +// +// Parameters: const str &packageName +// +// Returns: None +//-------------------------------------------------------------- +void Actor::ChildUseBehaviorPackage( const str &childName , const str &packageName ) +{ + Actor *childActor = 0; + + childActor = GetAttachedChildActor(childName); + if ( childActor ) + childActor->SetBehaviorPackage( packageName ); +} + + +//-------------------------------------------------------------- +// Name: ChildSetAnim() +// Class: Actor +// +// Description: Calls ChildSetAnim() +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::ChildSetAnim( Event *ev ) +{ + ChildSetAnim(ev->GetString( 1 ) , ev->GetString( 2 ) ); +} + + +//-------------------------------------------------------------- +// Name: ChildSuicide() +// Class: Actor +// +// Description: Calls ChildSetAnim() +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::ChildSuicide( Event *ev ) +{ + Actor *childActor = 0; + str childName = ev->GetString( 1 ); + + childActor = GetAttachedChildActor(childName); + if ( childActor ) + childActor->ProcessEvent(EV_Actor_Suicide); + +} + +//-------------------------------------------------------------- +// Name: ChildSetAnim() +// Class: Actor +// +// Description: Tells the Strategos to set the requested +// anim +// +// Parameters: const str &childName -- Child to find +// const str &animName -- Name of anim to set +// +// Returns: None +//-------------------------------------------------------------- +void Actor::ChildSetAnim( const str &childName , const str &animName ) +{ + Actor *childActor = 0; + + childActor = GetAttachedChildActor(childName); + if ( childActor ) + childActor->SetAnim(animName); +} + + +//-------------------------------------------------------------- +// Name: WhatsWrong() +// Class: Actor +// +// Description: Reports the failure condition for the current behavior +// to the console +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::WhatsWrong( Event *ev ) +{ + gi.Printf( "\n------------------------------------------" ); + + + if ( !behaviorFailureReason.length() ) + gi.Printf( "\nNo Failure Reason given for behavior %s\n", currentBehavior.c_str() ); + else + gi.Printf( "\nReason: %s" , behaviorFailureReason.c_str() ); + + + + gi.Printf( "\n------------------------------------------" ); + +} + +//-------------------------------------------------------------- +// Name: WhatAreYouDoing() +// Class: Actor +// +// Description: Debug Function that can be called from the console +// to give us state information on this actor +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::WhatAreYouDoing( Event *ev ) +{ + str tName; + tName = targetname; + if ( !tName.length() ) + tName = "None"; + + gi.Printf( "\n-----------------------------------------------" ); + gi.Printf( "\nTargetName : %s\n" , tName.c_str() ); + gi.Printf( "\n-----------------------------------------------" ); + + if ( masterstatemap ) + { + PrintMasterStateInfo(); + PrintBehaviorPackageInfo(); + } + else + { + gi.Printf( "\nNo Master State" ); + gi.Printf( "\n" ); + + if ( statemap ) + PrintStateMapInfo(); + else + gi.Printf( "\nNo State Map\n" ); + } + + gi.Printf( "\n-----------------------------------------------" ); +} + +//-------------------------------------------------------------- +// Name: PrintMasterStateInfo() +// Class: Actor +// +// Description: Prints information about the MasterState in the console +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Actor::PrintMasterStateInfo() +{ + gi.Printf( "\nMasterState Information:" ); + gi.Printf( "\nMasterState File: %s" , masterstatemap->Filename() ); + gi.Printf( "\nMasterState Current State: %s" , currentMasterState->getName() ); + gi.Printf( "\nMasterState Last State: %s" , lastMasterState->getName() ); + gi.Printf( " \n" ); + gi.Printf( " \n" ); + +} + +//-------------------------------------------------------------- +// Name: PrintBehaviorPackageInfo() +// Class: Actor +// +// Description: Prints information about the BehaviorPackage in the console +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Actor::PrintBehaviorPackageInfo() +{ + gi.Printf( "\nBehaviorPackage Information:" ); + gi.Printf( "\nCurrent Behavior Package Name: %s" , packageManager->GetCurrentPackageName().c_str() ); + + if ( currentState ) + gi.Printf( "\nCurrent Behavior Package State: %s" , currentState->getName() ); + else + gi.Printf( "\nCurrent Behavior Package State: !!!!!NULL!!!!!"); + + gi.Printf( "\nLast Behavior Package State: %s" , lastState->getName() ); + gi.Printf( " \n" ); + gi.Printf( " \n" ); +} + +//-------------------------------------------------------------- +// Name: PrintStateMapInfo() +// Class: Actor +// +// Description: Prints information about the state map in the console +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Actor::PrintStateMapInfo() +{ + gi.Printf( "\nState Map Information: " ); + gi.Printf( "\nState Map File: %s" , statemap->Filename() ); + gi.Printf( "\nCurrent State: %s" , currentState->getName() ); + gi.Printf( "\nLast State: %s" , lastState->getName() ); + gi.Printf( " \n" ); + gi.Printf( " \n" ); +} + +//-------------------------------------------------------------- +// Name: SetCombatTraceInterval() +// Class: Actor +// +// Description: Sets how often the actor will re-trace when doing +// a can_attack type of check +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::SetCombatTraceInterval( Event *ev ) +{ + combatSubsystem->SetTraceInterval( ev->GetFloat( 1 ) ); +} + +//-------------------------------------------------------------- +// Name: ActorTypeStringToInt() +// Class: Actor +// +// Description: Returns an Actor Type ID based on the string passed in +// +// Parameters: const str &type +// +// Returns: unsigned int +//-------------------------------------------------------------- +unsigned int Actor::ActorTypeStringToInt( const str &type ) +{ + unsigned int retValue = 99999; + + if ( type == "inanimate" ) + retValue = IS_INANIMATE; + else if ( type == "monster" ) + retValue = IS_MONSTER; + else if ( type == "enemy" ) + retValue = IS_ENEMY; + else if ( type == "civilian" ) + retValue = IS_CIVILIAN; + else if ( type == "friend" ) + retValue = IS_FRIEND; + else if ( type == "animal" ) + retValue = IS_ANIMAL; + else if ( type == "teammate" ) + retValue = IS_TEAMMATE; + + + assert ( retValue != 99999 ); + return retValue; +} + +//-------------------------------------------------------------- +// Name: GroupMemberInjured() +// Class: Actor +// +// Description: Appropriately sets the ACTOR_FLAG_GROUPMEMBER_INJURED flag +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::GroupMemberInjured( Event *ev ) +{ + bool injured = ev->GetBoolean( 1 ); + + if ( injured ) + SetActorFlag( ACTOR_FLAG_GROUPMEMBER_INJURED , true ); + else + SetActorFlag( ACTOR_FLAG_GROUPMEMBER_INJURED , false ); +} + + + +//-------------------------------------------------------------- +// Name: StrictlyFollowPath() +// Class: Actor +// +// Description: Appropriately sets the ACTOR_FLAG_STRICTLY_FOLLOW_PATHS flag +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::StrictlyFollowPath( Event *ev ) +{ + SetActorFlag( ACTOR_FLAG_STRICTLY_FOLLOW_PATHS , ev->GetBoolean( 1 ) ); +} + + + +//-------------------------------------------------------------- +// +// Name: IsFinishable +// Class: Actor +// +// Description: Checks to see if this is a 'finishable' actor +// +// Parameters: None +// +// Returns: True if so, false otherwise. +// +//-------------------------------------------------------------- +bool Actor::IsFinishable() +{ + // More conditions? Non-hardcoded min health? + if ( health > 0 && health < 30 ) + return true; + return false; +} + +//-------------------------------------------------------------- +// +// Name: UseWeaponDamage +// Class: Actor +// +// Description: Causes the MeleeEvent to use the damage from the +// weapon in the specified hand +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Actor::UseWeaponDamage( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + _useWeaponDamage = WeaponHandNameToNum(ev->GetString( 1 )); + else + _useWeaponDamage = WEAPON_RIGHT; + + if ( ev->NumArgs() > 1 ) + { + if ( !ev->GetBoolean( 2 ) ) + _useWeaponDamage = WEAPON_ERROR; + } +} + +//-------------------------------------------------------------- +// Name: HelperNodeCommand() +// Class: Actor +// +// Description: Takes the event from the helper node +// and passes it on to the behavior for +// it to deal with +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::HelperNodeCommand( Event *ev ) +{ +} + +//-------------------------------------------------------------- +// Name: SetIgnoreNextContext() +// Class: Actor +// +// Description: Sets the _ignoreNextContext flag +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::SetIgnoreNextContext( Event *ev ) +{ + _ignoreNextContext = ev->GetBoolean( 1 ); + _nextContextToIgnore = ev->GetString( 2 ); +} + +void Actor::EvaluateEnemies( Event *ev ) +{ + if ( !enemyManager->IsLockedOnCurrentEnemy() ) + enemyManager->FindHighestHateEnemy(); +} + +void Actor::ForgetEnemies( Event* ev ) +{ + if( enemyManager ) + { + enemyManager->ClearCurrentEnemy(); + enemyManager->ClearHateList(); + } +} + + +//============================================================== +// Controller functions +//============================================================== + + +//=============================================================== +// Name: RequestControl +// Class: Actor +// +// Description: Requests control of the actor from a listener. If +// the actor is not under control, the request is granted. +// If the actor is under control, but the previous +// request was not exclusive, then the previous controller +// loses control, is notified of this, and the new +// controller takes over. +// +// If the previous controller did take exclusive control, +// this request is denied. +// +// I considered adding ACTOR_CONTROL_SHARED, but decided +// I'll wait on that until its needed. +// +// Parameters: Listener* -- the controller +// ActorControlType -- one of: +// Actor::ACTOR_CONTROL_AUTO_RELEASE +// Actor::ACTOR_CONTROL_LOCKED +// +// Returns: bool -- true if the request was granted. +// +//=============================================================== +bool Actor::RequestControl +( + Listener *controller, + ActorControlType controlType +) +{ + if ( !_controller ) + { + _controller = controller ; + _controlType = controlType ; + return true ; + } + + if ( _controlType != ACTOR_CONTROL_LOCKED ) + { + Event *ev = new Event( EV_Actor_ControlLost ); + _controller->ProcessEvent( ev ); + _controller = controller ; + return true ; + } + + return false ; +} + + +//=============================================================== +// Name: ReleaseControl +// Class: Actor +// +// Description: Releases control of an actor by a controller. This +// request is only denied if the requester is not the +// controller. +// +// Parameters: Listener* -- the controller releasing control. +// +// Returns: bool -- true unless requester is not controller, +// or actor isn't under control. +// +//=============================================================== +bool Actor::ReleaseControl +( + Listener *controller +) +{ + if ( !_controller ) return false ; + if ( _controller != controller ) return false ; + + _controller = 0 ; + _controlType = ACTOR_CONTROL_NONE ; + return true ; +} + +//-------------------------------------------------------------- +// Name: SetMaxHeadYaw() +// Class: Actor +// +// Description: Sets the max head yaw for the headwatcher class +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::SetMaxHeadYaw( Event *ev ) +{ + headWatcher->SetMaxHeadYaw( ev->GetFloat( 1 ) ); +} + +//-------------------------------------------------------------- +// Name: SetMaxHeadPitch() +// Class: Actor +// +// Description: Sets the max head pitch for the headwatcher class +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::SetMaxHeadPitch( Event *ev ) +{ + headWatcher->SetMaxHeadPitch( ev->GetFloat( 1 ) ); +} + + +//-------------------------------------------------------------- +// Name: LoadPostureStateMachine() +// Class: Actor +// +// Description: Creates a new Posture State Machine +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::LoadPostureStateMachine( Event *ev ) +{ + bool loading; + if ( ev->NumArgs() > 1 ) + loading = ev->GetBoolean( 2 ); + else + loading = false; + + postureController->setPostureStateMap( ev->GetString( 1 ) , loading ); +} + +//-------------------------------------------------------------- +// Name: PostureAnimDone() +// Class: Actor +// +// Description: Event Handler for the completion of a posture animation +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::PostureAnimDone( Event *ev ) +{ + SetActorFlag(ACTOR_FLAG_POSTURE_ANIM_DONE , true ); +} + +//-------------------------------------------------------------- +// Name: checkRequestedPosture() +// Class: Actor +// +// Description: Queries the posture controller to compare the current +// posture state name with the requested posture state +// name +// +// Parameters: Conditional &condition +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkRequestedPosture( Conditional &condition ) +{ + str postureName = condition.getParm( 1 ); + str requestedPostureName = postureController->getRequestedPostureName(); + + if ( postureName == requestedPostureName ) + return true; + + return false; +} + +//-------------------------------------------------------------- +// Name: checkPostureAnimDone() +// Class: Actor +// +// Description: Checks the Posture Anim Done Flag +// +// Parameters: Conditional &condition +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkPostureAnimDone( Conditional &condition ) +{ + return GetActorFlag(ACTOR_FLAG_POSTURE_ANIM_DONE ); +} + +//-------------------------------------------------------------- +// Name: checkDamageThresholdExceeded() +// Class: Actor +// +// Description: Calls checkDamageThresholdExceeded() +// +// Parameters: Conditional &condition +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkDamageThresholdExceeded( Conditional &condition ) +{ + return checkDamageThresholdExceeded(); +} + +//-------------------------------------------------------------- +// Name: checkDamageThresholdExceeded() +// Class: Actor +// +// Description: Checks if the amount of damage taken exceeds +// the amount of damage allowed in a specified +// amount of time +// +// Parameters: None +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkDamageThresholdExceeded() +{ + return state_flags & STATE_FLAG_DAMAGE_THRESHOLD_EXCEEDED; +} + +//-------------------------------------------------------------- +// Name: checkhealthpercent() +// Class: Actor +// +// Description: Checks if the health is at or below the +// given percent ( in whole numbers ) +// example: 50 is 50% +// +// Parameters: Conditional &condition +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkhealthpercent( Conditional &condition ) +{ + float percent = (.01 ) * atof( condition.getParm( 1 ) ); + + return ( health <= ( percent * max_health ) ); +} + +//-------------------------------------------------------------- +// Name: checkHelperNodeWithFlagInRange() +// Class: Actor +// +// Description: Calls checkHelperNodeWithFlagInRange() +// +// Parameters: Conditional &condition +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkHelperNodeWithFlagInRange( Conditional &condition ) +{ + str flagName = condition.getParm( 1 ); + float range = atof(condition.getParm( 2 ) ); + + return checkHelperNodeWithFlagInRange( flagName , range ); +} + +//-------------------------------------------------------------- +// Name: checkHelperNodeWithFlagInRange() +// Class: Actor +// +// Description: Checks if a helper node with a specified flag +// is within a specified range of the actor +// +// Parameters: const str &flag +// float range +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkHelperNodeWithFlagInRange( const str &flag, float range ) +{ + int mask; + mask = HelperNode::GetHelperNodeMask( flag ); + + return HelperNode::isHelperNodeInRange( *this , mask , range ); +} + +//-------------------------------------------------------------- +// Name: SendEventToGroup() +// Class: Actor +// +// Description: Sends an event to the Actor's group +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::SendEventToGroup( Event *ev ) +{ + int parmCount = ev->NumArgs(); + str eventToSend = ev->GetString( 1 ); + + Event *event = new Event( eventToSend.c_str() ); + + for ( int i = 2 ; i <= parmCount ; i++ ) + { + event->AddToken( ev->GetToken( i ) ); + } + + groupcoordinator->SendEventToGroup( event , GetGroupID() ); +} + +//-------------------------------------------------------------- +// Name: checkEnemyWeaponNamed() +// Class: Actor +// +// Description: Calls checkEnemyWeaponNamed +// +// Parameters: Conditional &condition +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkEnemyWeaponNamed( Conditional &condition ) +{ + str weaponName = condition.getParm( 1 ); + return checkEnemyWeaponNamed( weaponName ); + +} + +//-------------------------------------------------------------- +// Name: checkEnemyWeaponNamed() +// Class: Actor +// +// Description: Checks if the Actor's current enemy is using +// a weapon with the specified name +// +// Parameters: const str &anme +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkEnemyWeaponNamed( const str& name ) +{ + Entity *enemy; + enemyManager->FindHighestHateEnemy(); + enemy = enemyManager->GetCurrentEnemy(); + + if ( enemy ) + { + if ( enemy->isSubclassOf( Actor ) ) + { + Actor *act; + act = (Actor*)enemy; + return act->combatSubsystem->UsingWeaponNamed( name ); + } + + if ( enemy->isSubclassOf( Player ) ) + { + Player *player; + Weapon *pWeapon; + player = (Player*)enemy; + + pWeapon = player->GetActiveWeapon(WEAPON_DUAL); + if ( pWeapon && pWeapon->getName() == name ) + return true; + + pWeapon = player->GetActiveWeapon(WEAPON_LEFT); + if ( pWeapon && pWeapon->getName() == name ) + return true; + + pWeapon = player->GetActiveWeapon(WEAPON_RIGHT); + if ( pWeapon && pWeapon->getName() == name ) + return true; + + } + } + + return false; + +} + + +//-------------------------------------------------------------- +// Name: checkPlayerWeaponNamed() +// Class: Actor +// +// Description: Calls checkEnemyWeaponNamed +// +// Parameters: Conditional &condition +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkPlayerWeaponNamed( Conditional &condition ) +{ + str weaponName = condition.getParm( 1 ); + return checkPlayerWeaponNamed( weaponName ); + +} + +//-------------------------------------------------------------- +// Name: checkPlayerWeaponNamed() +// Class: Actor +// +// Description: Checks if the Actor's current enemy is using +// a weapon with the specified name +// +// Parameters: const str &anme +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkPlayerWeaponNamed( const str& name ) +{ + Player *player; + player = GetPlayer ( 0 ); + + if ( player ) + { + Weapon *pWeapon; + + pWeapon = player->GetActiveWeapon(WEAPON_DUAL); + if ( pWeapon && pWeapon->getName() == name ) + return true; + + pWeapon = player->GetActiveWeapon(WEAPON_LEFT); + if ( pWeapon && pWeapon->getName() == name ) + return true; + + pWeapon = player->GetActiveWeapon(WEAPON_RIGHT); + if ( pWeapon && pWeapon->getName() == name ) + return true; + } + + return false; + +} + +//-------------------------------------------------------------- +// Name: GroupAttack() +// Class: Actor +// +// Description: Sends an attack event to the Actor's group +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::GroupAttack( Event *ev ) +{ + Event *attackEvent; + Entity *enemy; + bool force = false; + + enemy = enemyManager->GetCurrentEnemy(); + if ( !enemy ) + return; + + if ( ev->NumArgs() > 0 ) + force = ev->GetBoolean( 1 ); + + attackEvent = new Event(EV_Actor_Attack); + attackEvent->AddEntity( enemy ); + attackEvent->AddInteger( force ); + + groupcoordinator->SendEventToGroup( attackEvent, GetGroupID() ); + +} + +//-------------------------------------------------------------- +// Name: GroupAttack() +// Class: Actor +// +// Description: Sends an attack event to the Actor's group +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Actor::GroupActorType( Event *ev ) +{ + Event *typeEvent; + str type; + + typeEvent = new Event(EV_Actor_SetActorType); + typeEvent->AddString( ev->GetString( 1 ) ); + + groupcoordinator->SendEventToGroup( typeEvent, GetGroupID() ); + +} + +//-------------------------------------------------------------- +// Name: checkEnemyWithinRange() +// Class: Actor +// +// Description: Calls checkEnemyWithinRange() +// +// Parameters: Conditional &condition +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkEnemyWithinRange( Conditional &condition ) +{ + return checkEnemyWithinRange( atof(condition.getParm( 1 ) ) , atof(condition.getParm(2) ) ); +} + +//-------------------------------------------------------------- +// Name: checkEnemyWithinRange() +// Class: Actor +// +// Description: Checks if the linear distance to the actors enemy is +// greater than or equal to the min AND less than or equal +// to the max +// +// Parameters: float min +// float max +// +// Returns: true or false +//-------------------------------------------------------------- +qboolean Actor::checkEnemyWithinRange( float min , float max ) +{ + float dist = enemyManager->GetDistanceFromEnemy(); + + return ( dist <= max && dist >= min ); +} + +qboolean Actor::checkhealthpercentinrange( Conditional &condition ) +{ + float minpercent = (.01 ) * atof( condition.getParm( 1 ) ); + float maxpercent = (.01 ) * atof( condition.getParm( 2 ) ); + + return ( (health >= ( minpercent * max_health ) && (health <= ( maxpercent * max_health ) ) ) ); +} + +void Actor::PrintDebugMessage( Event *ev ) +{ + str msg = ev->GetString( 1 ); + gi.WDPrintf( "\n--------------------------------------------------------------\n" ); + gi.WDPrintf( msg + "\n" ); + gi.WDPrintf( "--------------------------------------------------------------\n" ); + +} + +qboolean Actor::checkAttacked( Conditional &condition ) +{ + return checkAttacked(); +} + +qboolean Actor::checkAttacked() +{ + return state_flags & STATE_FLAG_ATTACKED; +} + +qboolean Actor::checkAttackedByPlayer( Conditional &condition ) +{ + return checkAttackedByPlayer(); +} + +qboolean Actor::checkAttackedByPlayer() +{ + return state_flags & STATE_FLAG_ATTACKED_BY_PLAYER; +} + +qboolean Actor::checkShowPain( Conditional &condition ) +{ + return checkShowPain(); +} + +qboolean Actor::checkShowPain() +{ + return state_flags & STATE_FLAG_SHOW_PAIN; +} + +qboolean Actor::checkPropEnemyRange( Conditional &condition ) +{ + str propname; + str objname = condition.getParm( 1 ); + if ( condition.numParms() > 1 ) + propname = condition.getParm( 2 ); + + return checkPropEnemyRange( objname , propname ); +} + +qboolean Actor::checkPropEnemyRange( const str& objname , const str& propname ) +{ + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str scopestr = getArchetype() + "." + objname; + if ( !gpm->hasObject(scopestr) ) + return false; + + float range; + if ( propname.length() ) + range = gpm->getFloatValue(scopestr, propname); + else + range = gpm->getFloatValue(scopestr, "value"); + + // Get our current enemy + Entity *currentEnemy; + + currentEnemy = enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + enemyManager->FindHighestHateEnemy(); + + currentEnemy = enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + enemyManager->FindHighestHateEnemy(); + + if ( !currentEnemy ) + return false; + + return EntityInRange( currentEnemy, range, 0, 0 , false ); +} + +void Actor::processGameplayData( Event *ev ) +{ + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(getArchetype()) ) + return; + + str objname = getArchetype(); + + // Grab our FOV From the GPD + if ( gpm->hasProperty(objname, "fov") ) + sensoryPerception->SetFOV( gpm->getFloatValue( objname , "fov" )); + + // Grab our PlayerHateModifier + + // Grab our VisionDistance + if ( gpm->hasProperty(objname, "visiondistance" ) ) + sensoryPerception->SetVisionDistance( gpm->getFloatValue( objname , "visiondistance" ) ); +} + +void Actor::SelectNextEnemy( Event* ev ) +{ + enemyManager->FindNextEnemy(); +} + +void Actor::SelectClosestEnemy( Event *ev ) +{ + enemyManager->FindClosestEnemy(); +} + + +//-------------------------------------------------------------- +// Name: checkCurrentEnemyGroupAttackerCount +// Class: Actor +// +// Description: Returns true if the number of actors in this actor's group that +// are attacking current enemy is less than the number passed in. False otherwise. +// +// Parameters: Conditional &condition +// +// Returns: true or false; +// +//-------------------------------------------------------------- +qboolean Actor::checkCurrentEnemyGroupAttackerCount( Conditional &condition ) +{ + Entity* e = enemyManager->GetCurrentEnemy(); + return checkGroupAttackerCountForEntity( condition, e ); +} + + +//-------------------------------------------------------------- +// Name: checkGroupAttackerCountForEntity +// Class: Actor +// +// Note: This is a helper function for checkEnemyAttackerCount and checkEnemyGroupCount +// +// Description: Returns true if the number of actors in this actor's group that +// are attacking passed enemy is less than the number passed in. False otherwise. +// +// Parameters: Conditional& condition +// Entity* attackTarget - pass NULL to signify any attack target is OK; +// i.e., count how many of my groupmates are attacking at all. +// +// Returns: true or false; +// +//-------------------------------------------------------------- +qboolean Actor::checkGroupAttackerCountForEntity( Conditional& condition, Entity* attackTarget ) +{ + int checkValue = atoi(condition.getParm( 1 )); + + return checkGroupAttackerCountForEntity( checkValue , attackTarget ); +} + +qboolean Actor::checkGroupAttackerCountForEntity( int checkValue, Entity* attackTarget ) +{ + int count; + ActorGroup* group; + + group = (ActorGroup*)groupcoordinator->GetGroup( GetGroupID() ); + + if ( !group ) + return true; + + count = group->CountMembersAttackingEnemy( attackTarget ); + + if ( count < checkValue ) + return true; + + return false; +} + +void Actor::SetGroupDeathThread( Event *ev ) +{ + groupcoordinator->SetGroupDeathThread( ev->GetString( 1 ) , GetGroupID() ); +} + +qboolean Actor::checkHaveBestWeapon( Conditional &condition ) +{ + return checkHaveBestWeapon(); +} + +qboolean Actor::checkHaveBestWeapon() +{ + str bestWeaponName; + float powerRating; + str currentPostureState; + Entity *currentEnemy; + Weapon *bestWeapon; + + currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return true; + + //First Check if our current weapon is useless + powerRating = combatSubsystem->GetActiveWeaponPowerRating( currentEnemy ); + if ( powerRating < .05 ) + { + //Put WeaponUseless Context Here + } + + bestWeapon = combatSubsystem->GetBestAvailableWeapon( currentEnemy ); + if( !bestWeapon ) + return false; + + if ( combatSubsystem->GetActiveWeaponName() == bestWeapon->getName() ) + return true; + + return false; + +} + +qboolean Actor::checkPosture( Conditional &condition ) +{ + return checkPosture( condition.getParm( 1 ) ); +} + +qboolean Actor::checkPosture( const str &postureName ) +{ + if ( postureController->getCurrentPostureName() == postureName ) + return true; + + return false; +} + +qboolean Actor::checkAnyEnemyInRange( Conditional &condition ) +{ + float range = atof( condition.getParm( 1 ) ); + return checkAnyEnemyInRange( range ); +} + +qboolean Actor::checkAnyEnemyInRange( float range ) +{ + return enemyManager->IsAnyEnemyInRange( range ); +} + +void Actor::SetAnimSet( Event *ev ) +{ + SetAnimSet(ev->GetString( 1 ) ); +} + +void Actor::SetAnimSet( const str& animSet ) +{ + animset = animSet; +} + +void Actor::SetSelfDetonateModel ( Event * ev ) +{ + explosionModel = ev->GetString ( 1 ); +} + + +const str& Actor::GetAnimSet() +{ + return animset; +} + +const str Actor::GetStateVar( const str& varName ) +{ + StateVar *checkVar = 0; + + for (int i = 1; i <= stateVarList.NumObjects() ; i++ ) + { + checkVar = stateVarList.ObjectAt( i ); + if( checkVar->varName == varName ) + { + return checkVar->varValue; + } + } + + return ""; +} + +void Actor::ClearTorsoAnim( Event *ev ) +{ + ClearTorsoAnim(); +} + +void Actor::ClearTorsoAnim() +{ + newTorsoAnimNum = -1; + newTorsoAnim = ""; + newTorsoAnimEvent = NULL; + TorsoAnimName = ""; + animate->ClearTorsoAnim(); +} + +void Actor::ClearLegAnim() +{ + + newanimnum = -1; + newanim = ""; + newanimevent = NULL; + animate->ClearLegsAnim(); +} + +qboolean Actor::checkValidCoverNodeInRange( Conditional &condition ) +{ + float minDistanceFromPlayer = 96.0f; + + float maxDistanceFromSelf = atof(condition.getParm( 1 )); + + if ( GetActorFlag(ACTOR_FLAG_USE_FOLLOWRANGE_FOR_NODES) ) + { + if ( enemyManager->HasEnemy() ) + { + maxDistanceFromSelf = followTarget.maxRangeCombat; + } + else + maxDistanceFromSelf = followTarget.minRangeCombat; + + //Fudge the value a little bit to allow for some "spring" + maxDistanceFromSelf = maxDistanceFromSelf * .85; + } + + + float minDistanceFromCurrentEnemy = atof(condition.getParm( 2 )); + + if ( condition.numParms() > 2 ) + minDistanceFromPlayer = atof(condition.getParm(3) ); + + return checkValidCoverNodeInRange( maxDistanceFromSelf , minDistanceFromCurrentEnemy, minDistanceFromPlayer ); +} + +qboolean Actor::checkValidCoverNodeInRange( float maxDistanceFromSelf, float minDistanceFromCurrentEnemy, float minDistanceFromPlayer ) +{ + HelperNode* coverNode = NULL; + Entity* currentEnemy = enemyManager->GetCurrentEnemy(); + + //If we don't have a current enemy, give it one more evaluation chance + if ( !currentEnemy ) + { + enemyManager->FindHighestHateEnemy(); + currentEnemy = enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + return false; + } + + // + //See if we have a cover node that meets or requirements. + //If we do, the FindClosestHelperNodeThatCannotSeeEntity will set our currentHelperNode information for us + // + coverNode = HelperNode::FindClosestHelperNodeThatCannotSeeEntity(*this , NODETYPE_COVER , edict->clipmask, maxDistanceFromSelf, minDistanceFromCurrentEnemy, currentEnemy , minDistanceFromPlayer ); + + if ( coverNode ) + return true; + + return false; +} + + +qboolean Actor::checkValidCombatNodeInRange( Conditional &condition ) +{ + float minDistanceFromPlayer = 96.0f; + float maxDistanceFromSelf = atof(condition.getParm( 1 )); + + if ( GetActorFlag(ACTOR_FLAG_USE_FOLLOWRANGE_FOR_NODES) ) + { + if ( enemyManager->HasEnemy() ) + { + maxDistanceFromSelf = followTarget.maxRangeCombat; + } + else + maxDistanceFromSelf = followTarget.minRangeCombat; + } + + int unreserveCurrentNode = true; // this is int to avoid compiler warning C4800 :( + if ( condition.numParms() > 1 ) + minDistanceFromPlayer = atof(condition.getParm( 2 ) ); + if( condition.numParms() > 2 ) + unreserveCurrentNode = atoi( condition.getParm( 3 ) ); + + return checkValidCombatNodeInRange( maxDistanceFromSelf , minDistanceFromPlayer, (unreserveCurrentNode != 0) ); +} + +qboolean Actor::checkValidCombatNodeInRange( float maxDistanceFromSelf , float minDistanceFromPlayer, bool unreserveCurrentNode ) +{ + HelperNode* coverNode = NULL; + + // + //First, see if the level designer gave us a specific node + // + if ( currentHelperNode.node && (currentHelperNode.node->target.length())) + { + coverNode = currentHelperNode.node->GetTargetedHelperNode(currentHelperNode.node->target); + if ( coverNode ) + { + if ( coverNode->isOfType( NODETYPE_COMBAT ) ) + { + currentHelperNode.node->UnreserveNode(); + currentHelperNode.node = coverNode; + currentHelperNode.mask = NODETYPE_COMBAT; + currentHelperNode.node->ReserveNode(); + return true; + } + } + } + + // + //See if we have a cover node that meets our requirements. + // + coverNode = HelperNode::FindClosestHelperNode(*this , NODETYPE_COMBAT , maxDistanceFromSelf, minDistanceFromPlayer, unreserveCurrentNode); + //if ( actortype == IS_ENEMY ) + // coverNode = HelperNode::FindClosestHelperNode(*this , NODETYPE_COMBAT , maxDistanceFromSelf); + //else + // coverNode = HelperNode::FindHelperNodeClosestTo(*this, GetPlayer(0) , NODETYPE_COMBAT , maxDistanceFromSelf ); + + if ( coverNode ) + return true; + + return false; +} + +qboolean Actor::checkEnemyCanSeeCurrentNode( Conditional &condition ) +{ + return checkEnemyCanSeeCurrentNode(); +} + +qboolean Actor::checkEnemyCanSeeCurrentNode() +{ + trace_t trace; + Entity *currentEnemy; + currentEnemy = enemyManager->GetCurrentEnemy(); + + if ( !currentHelperNode.node ) + return false; + + if ( !currentEnemy ) + { + enemyManager->FindClosestEnemy(); + currentEnemy = enemyManager->GetCurrentEnemy(); + } + + if ( !currentEnemy ) + return false; + + if ( currentEnemy->isSubclassOf(Sentient) ) + { + Sentient* theEnemy; + theEnemy = (Sentient*)currentEnemy; + trace = G_Trace( currentHelperNode.node->origin , vec_zero , vec_zero , theEnemy->EyePosition() , NULL , MASK_OPAQUE, false, "CoverCombatWithRangedWeapon::think"); + //G_DebugLine( currentHelperNode.node->origin, theEnemy->EyePosition(), 1.0f, 0.0f, 0.0f, 1.0f ); + } + else + { + trace = G_Trace( currentHelperNode.node->origin , vec_zero , vec_zero , currentEnemy->centroid , NULL , MASK_OPAQUE, false, "CoverCombatWithRangedWeapon::think"); + //G_DebugLine( currentHelperNode.node->origin, currentEnemy->centroid, 1.0f, 0.0f, 0.0f, 1.0f ); + } + + + if ( trace.fraction >= .95 ) + return true; + + return false; +} + +qboolean Actor::checkShouldDoAction( Conditional &condition ) +{ + str tendencyName = condition.getParm( 1 ); + return checkShouldDoAction( tendencyName ); +} + +qboolean Actor::checkShouldDoAction( const str &tendencyName ) +{ + float tendency = personality->GetTendency( tendencyName ); + + return ( G_Random() < tendency ); +} + +qboolean Actor::checkValidWorkNodeInRange( Conditional &condition ) +{ + int unreserveCurrentNode = true; // this is int to avoid compiler warning C4800 :( + float maxDistanceFromSelf = atof(condition.getParm( 1 )); + if( condition.numParms() > 1 ) + unreserveCurrentNode = atoi( condition.getParm( 2 ) ); + return checkValidWorkNodeInRange( maxDistanceFromSelf, (unreserveCurrentNode != 0) ); +} + +qboolean Actor::checkValidWorkNodeInRange( float maxDistanceFromSelf, bool unreserveCurrentNode ) +{ + HelperNode* coverNode = NULL; + + if ( level.time < _nextCheckForWorkNodeTime ) + return false; + + // + //See if we have a work node that meets our requirements. + // + coverNode = HelperNode::FindClosestHelperNode(*this , NODETYPE_WORK , maxDistanceFromSelf , 0.0f, unreserveCurrentNode ); + _nextCheckForWorkNodeTime = ( G_Random() + level.time + 0.50f ); + + if ( coverNode ) + return true; + + return false; +} + +qboolean Actor::checkValidHibernateNodeInRange( Conditional &condition ) +{ + float maxDistanceFromSelf = atof( condition.getParm( 1 ) ); + return checkValidHibernateNodeInRange( maxDistanceFromSelf ); +} + +qboolean Actor::checkValidHibernateNodeInRange( float maxDistanceFromSelf ) +{ + HelperNode* hibernateNode = NULL; + + if ( level.time < _nextCheckForHibernateNodeTime ) + return false; + + // + //See if we have a work node that meets our requirements. + // + hibernateNode = HelperNode::FindClosestHelperNode(*this , "hibernate" , maxDistanceFromSelf ); + _nextCheckForHibernateNodeTime = ( G_Random() + level.time + 0.50f ); + + if ( hibernateNode ) + return true; + + return false; +} + +qboolean Actor::checkValidPatrolNodeInRange( Conditional &condition ) +{ + float maxDistanceFromSelf = atof(condition.getParm( 1 )); + return checkValidPatrolNodeInRange( maxDistanceFromSelf ); +} + +qboolean Actor::checkValidPatrolNodeInRange( float maxDistanceFromSelf ) +{ + HelperNode* patrolNode = NULL; + + // + //See if we have a cover node that meets or requirements. + // + patrolNode = HelperNode::FindClosestHelperNode(*this , NODETYPE_PATROL , maxDistanceFromSelf, 0); + + if ( patrolNode ) + return true; + + return false; +} + +qboolean Actor::checkValidSniperNodeInRange( Conditional &condition ) +{ + float maxDistanceFromSelf = atof(condition.getParm( 1 )); + return checkValidSniperNodeInRange( maxDistanceFromSelf ); +} + +qboolean Actor::checkValidSniperNodeInRange( float maxDistanceFromSelf ) +{ + HelperNode* sniperNode = NULL; + + // + //See if we have a cover node that meets or requirements. + // + sniperNode = HelperNode::FindClosestHelperNode(*this , NODETYPE_SNIPER , maxDistanceFromSelf); + + if ( sniperNode ) + return true; + + return false; +} + +qboolean Actor::checkValidCustomNodeInRange( Conditional &condition ) +{ + str customType = condition.getParm( 1 ); + float maxDistanceFromSelf = atof( condition.getParm( 2 ) ); + + return checkValidCustomNodeInRange( customType , maxDistanceFromSelf ); +} + +qboolean Actor::checkValidCustomNodeInRange( const str &customType , float maxDistanceFromSelf ) +{ + HelperNode* customNode = NULL; + + // + //See if we have a cover node that meets or requirements. + // + customNode = HelperNode::FindClosestHelperNode(*this , customType , maxDistanceFromSelf); + + if ( customNode ) + return true; + + return false; +} + +qboolean Actor::checkSpecifiedFollowTargetOutOfRange( Conditional &condition ) +{ + return checkSpecifiedFollowTargetOutOfRange(); +} + +qboolean Actor::checkSpecifiedFollowTargetOutOfRange() +{ + if ( !followTarget.specifiedFollowTarget ) + followTarget.specifiedFollowTarget = GetPlayer( 0 ); + + if ( followTarget.specifiedFollowTarget ) + { + float range = followTarget.maxRangeIdle; + Entity *enemy; + + enemy = enemyManager->GetCurrentEnemy(); + if ( !enemy ) + { + enemyManager->FindHighestHateEnemy(); + enemy = enemyManager->GetCurrentEnemy(); + } + + if ( enemy ) + range = followTarget.maxRangeCombat; + + if ( EntityInRange( followTarget.specifiedFollowTarget, range, 0, 0 , false ) ) + return false; + else + return true; + } + + return false; +} + +void Actor::SetPostureState( Event *ev ) +{ + postureController->setPostureState( ev->GetString( 1 ) , ev->GetString( 2 ) ); +} + + + + +//-------------------------------------------------------------- +// Name: FindDialog +// Class: Actor +// +// Description: Finds an appropriate dialog alias based on the dialogType +// +// Parameters: DialogType_t -- The type of dialog to find +// +// Returns: const str +//-------------------------------------------------------------- +const str Actor::FindDialog( Sentient *user, DialogType_t dialogType , const str& context ) +{ + DialogNode_t *dialog_node; + int good_dialog; + ScriptVariable *script_var; + const char* the_dialog; + str dialog_name; + bool usingContext = false; + + dialog_node = dialog_list; + + while(dialog_node != NULL) + { + // See if we should play the current dialog + good_dialog = true; + + if ( dialogType == DIALOG_TYPE_NORMAL ) + { + //If we're looking for normal dialog, + //We don't play radius dialog. + if ( dialog_node->dType != DIALOG_TYPE_NORMAL ) + { + dialog_node = dialog_node->next; + continue; + } + } + + if ( dialogType == DIALOG_TYPE_RADIUS ) + { + //If we're looking for radius dialog + //It's got to be radius dialog + if ( dialog_node->dType != DIALOG_TYPE_RADIUS ) + { + dialog_node = dialog_node->next; + continue; + } + } + + if ( dialogType == DIALOG_TYPE_GREETING ) + { + //If we're looking for greeting dialog + //It's got to be greeting dialog + if ( dialog_node->dType != DIALOG_TYPE_GREETING ) + { + dialog_node = dialog_node->next; + continue; + } + } + + if ( dialogType == DIALOG_TYPE_COMBAT ) + { + //If we're looking for combat dialog + //It's got to be combat dialog + if ( dialog_node->dType != DIALOG_TYPE_COMBAT ) + { + dialog_node = dialog_node->next; + continue; + } + } + + if ( dialogType == DIALOG_TYPE_CONTEXT_INITIATOR || dialogType == DIALOG_TYPE_CONTEXT_RESPONSE ) + { + // + // If we're context dialog we HAVE to have at least 1 parameter + // + if ( dialog_node->number_of_parms < 1 ) + { + dialog_node = dialog_node->next; + continue; + } + } + + for(int i = 0 ; i < dialog_node->number_of_parms ; i++) + { + // Test to see if this parm passes + switch(dialog_node->parms[ i ].type) + { + case DIALOG_PARM_TYPE_PLAYERHAS : + if (!user || !user->HasItem(dialog_node->parms[ i ].parm)) + good_dialog = false; + break; + case DIALOG_PARM_TYPE_PLAYERHASNOT : + if (!user || user->HasItem(dialog_node->parms[ i ].parm)) + good_dialog = false; + break; + case DIALOG_PARM_TYPE_HAS : + if (!HasItem(dialog_node->parms[ i ].parm)) + good_dialog = false; + break; + case DIALOG_PARM_TYPE_HASNOT : + if (HasItem(dialog_node->parms[ i ].parm)) + good_dialog = false; + break; + case DIALOG_PARM_TYPE_DEPENDS : + script_var = NULL; + + if ( strnicmp( dialog_node->parms[ i ].parm, "game.", 5 ) == 0 ) + script_var = gameVars.GetVariable( dialog_node->parms[ i ].parm + 5 ); + else if ( strnicmp( dialog_node->parms[ i ].parm, "level.", 6 ) == 0 ) + script_var = levelVars.GetVariable( dialog_node->parms[ i ].parm + 6 ); + + if ( !script_var || !script_var->intValue() ) + good_dialog = false; + + break; + case DIALOG_PARM_TYPE_DEPENDSNOT : + script_var = NULL; + + if ( strnicmp( dialog_node->parms[ i ].parm, "game.", 5 ) == 0 ) + script_var = gameVars.GetVariable( dialog_node->parms[ i ].parm + 5 ); + else if ( strnicmp( dialog_node->parms[ i ].parm, "level.", 6 ) == 0 ) + script_var = levelVars.GetVariable( dialog_node->parms[ i ].parm + 6 ); + + if ( script_var && script_var->intValue() ) + good_dialog = false; + + break; + case DIALOG_PARM_TYPE_DEPENDSINT : + script_var = NULL; + + if ( strnicmp( dialog_node->parms[ i ].parm, "game.", 5 ) == 0 ) + script_var = gameVars.GetVariable( dialog_node->parms[ i ].parm + 5 ); + else if ( strnicmp( dialog_node->parms[ i ].parm, "level.", 6 ) == 0 ) + script_var = levelVars.GetVariable( dialog_node->parms[ i ].parm + 6 ); + + if ( !script_var || ( script_var->intValue() != atoi( dialog_node->parms[ i ].parm2 ) ) ) + good_dialog = false; + + break; + + case DIALOG_PARM_TYPE_CONTEXT_INITIATOR: + if ( dialogType != DIALOG_TYPE_CONTEXT_INITIATOR ) + good_dialog = false; + + if ( stricmp( dialog_node->parms[i].parm , context.c_str() ) ) + good_dialog = false; + + usingContext = true; + break; + + case DIALOG_PARM_TYPE_CONTEXT_RESPONSE: + if ( dialogType != DIALOG_TYPE_CONTEXT_RESPONSE ) + good_dialog = false; + + if ( stricmp( dialog_node->parms[i].parm , context.c_str() ) ) + good_dialog = false; + + usingContext = true; + break; + } + + + // If dialog is already not good go to next dialog + if (!good_dialog) + break; + } + + + if ( ( dialog_node->random_percent < 1.0f ) && ( G_Random() > dialog_node->random_percent ) ) + good_dialog = false; + + if ( dialogType == DIALOG_TYPE_CONTEXT_INITIATOR || dialogType == DIALOG_TYPE_CONTEXT_RESPONSE ) + { + if ( !usingContext ) + good_dialog = false; + } + + if (good_dialog) + { + // Found a good dialog now get the real sound name from the alias + the_dialog = NULL; + the_dialog = gi.Alias_FindDialog( edict->s.modelindex, dialog_node->alias_name, dialog_node->random_flag, entnum); + + if ( the_dialog ) + { + dialog_name = the_dialog; + break; + } + } + + // Try the next dialog in the list + dialog_node = dialog_node->next; + } + + return dialog_name; +} + + +qboolean Actor::checkHaveArmor( Conditional &condition ) +{ + return checkHaveArmor(); +} + +qboolean Actor::checkHaveArmor() +{ + if ( !currentBaseArmor ) + return false; + + if ( currentBaseArmor->getAmount() <= 0 ) + return false; + + return true; +} + +qboolean Actor::checkWithinFollowRangeMin( Conditional &condition ) +{ + return checkWithinFollowRangeMin(); +} + +qboolean Actor::checkWithinFollowRangeMin() +{ + if ( !followTarget.specifiedFollowTarget ) + followTarget.specifiedFollowTarget = GetPlayer( 0 ); + + if ( followTarget.specifiedFollowTarget ) + { + float range = followTarget.minRangeIdle; + Entity *enemy; + + enemy = enemyManager->GetCurrentEnemy(); + if ( !enemy ) + { + enemyManager->FindHighestHateEnemy(); + enemy = enemyManager->GetCurrentEnemy(); + } + + if ( enemy ) + range = followTarget.minRangeCombat; + + if ( EntityInRange( followTarget.specifiedFollowTarget, range, 0, 0 , false ) ) + { + if ( level.time > _nextPathDistanceToFollowTargetCheck ) + { + FindMovementPath find; + Path *path; + float pathLen; + + _nextPathDistanceToFollowTargetCheck = G_Random(.33 ) + DEFAULT_PATH_TO_ENEMY_INTERVAL + level.time; + + // Set up our pathing heuristics + find.heuristic.self = this; + find.heuristic.setSize( size ); + find.heuristic.entnum = entnum; + + path = find.FindPath( origin, followTarget.specifiedFollowTarget->origin ); + + if ( !path ) + { + return true; + } + + pathLen = path->Length(); + + delete path; + path = NULL; + + + if ( pathLen > range ) + return false; + else + return true; + + } + } + else + return false; + } + + return false; +} + +void Actor::SetTalkWatchMode( Event *ev ) +{ + str mode = ev->GetString( 1 ); + + if ( mode == "ignore" ) + talkMode = TALK_IGNORE; + + if ( mode == "headwatchonly" ) + talkMode = TALK_HEADWATCH; + + if ( mode == "turnto" ) + talkMode = TALK_TURNTO; + + if ( ev->NumArgs() > 1 ) + useConvAnims = ev->GetBoolean(2); +} + +qboolean Actor::checkAllowedToMeleeEnemy( Conditional &condition ) +{ + return checkAllowedToMeleeEnemy(); +} + +qboolean Actor::checkAllowedToMeleeEnemy() +{ + Entity *enemy = enemyManager->GetCurrentEnemy(); + + if ( !enemy ) return false; + + if ( enemy->isSubclassOf(Player) ) return true; + + if ( !enemy->isSubclassOf(Actor) ) return false; + + + Actor *actor = (Actor*)enemy; + if ( actor->GetActorFlag(ACTOR_FLAG_MELEE_ALLOWED) ) + return true; + + return false; + +} + +qboolean Actor::checkCurrentNodeHasThisCoverType( Conditional &condition ) +{ + str coverType = condition.getParm( 1 ); + return checkCurrentNodeHasThisCoverType( coverType ); +} + +qboolean Actor::checkCurrentNodeHasThisCoverType( const str &coverType ) +{ + if ( !currentHelperNode.node ) + return false; + + if ( coverType == "none" ) + { + if ( currentHelperNode.node->GetCoverType() == COVER_TYPE_NONE ) + return true; + } + + if ( coverType == "crate" ) + { + if ( currentHelperNode.node->GetCoverType() == COVER_TYPE_CRATE ) + return true; + } + + if ( coverType == "wall" ) + { + if ( currentHelperNode.node->GetCoverType() == COVER_TYPE_WALL ) + return true; + } + + return false; +} + +void Actor::PrepareToFailMission( Event *ev ) +{ + float time; + str reason; + + time = ev->GetFloat( 1 ); + reason = "DefaultFailure"; + if ( ev->NumArgs() > 1 ) + reason = ev->GetString( 2 ); + + Event *failureEvent; + failureEvent = new Event(EV_Actor_FailMission); + failureEvent->AddString( reason ); + + PostEvent( failureEvent, time ); +} + +void Actor::FailMission( Event *ev ) +{ + str reason = "DefaultFailure"; + if ( ev->NumArgs() > 0 ) + reason = ev->GetString( 1 ); + + G_MissionFailed(reason); +} + +void Actor::DebugEvent( Event *ev ) +{ + //Here to catch when a state hits a debug event + int x; + x = 0; +} + +qboolean Actor::checkSteeringFailed( Conditional &condition ) +{ + return ( state_flags & STATE_FLAG_STEERING_FAILED ); +} + +qboolean Actor::checkHavePathToEnemy( Conditional &condition ) +{ + return checkHavePathToEnemy(); +} + +qboolean Actor::checkHavePathToEnemy() +{ + if ( !enemyManager->HasEnemy() ) + { + _nextCheckForEnemyPath = 0; + return false; + } + + if ( level.time >= _nextCheckForEnemyPath ) + { + Entity *currentEnemy = enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) return false; + + FindMovementPath find; + Path *path; + + _nextCheckForEnemyPath = G_Random() + DEFAULT_PATH_TO_ENEMY_INTERVAL; + // Set up our pathing heuristics + find.heuristic.self = this; + find.heuristic.setSize( size ); + find.heuristic.entnum = entnum; + + path = find.FindPath( origin, currentEnemy->origin ); + + if ( !path ) + { + _havePathToEnemy = false; + return false; + } + + delete path; + path = NULL; + + + _havePathToEnemy = true; + } + + return _havePathToEnemy; +} + +void Actor::UnreserveCurrentHelperNode( Event *ev ) +{ + UnreserveCurrentHelperNode(); +} + +void Actor::UnreserveCurrentHelperNode() +{ + if ( currentHelperNode.node ) + currentHelperNode.node->UnreserveNode(); +} + +qboolean Actor::checkBlockedByEnemy( Conditional &condition ) +{ + if ( !(state_flags & STATE_FLAG_BLOCKED_BY_ENTITY) ) + return false; + + Entity *blockingEntity = movementSubsystem->getBlockingEntity(); + + if ( !blockingEntity ) + return false; + + sensoryPerception->Stimuli( STIMULI_SIGHT, blockingEntity ); + + + if ( enemyManager->Hates( blockingEntity ) ) + return true; + + return false; + +} + +void Actor::ProjectileClose( Event *ev ) +{ + Entity *owner; + owner = ev->GetEntity( 1 ); + if ( !owner ) return; + + if ( enemyManager->Hates( owner ) ) + { + AddStateFlag(STATE_FLAG_ENEMY_PROJECTILE_CLOSE); + } +} + +qboolean Actor::checkEnemyProjectileClose( Conditional &condition ) +{ + return checkEnemyProjectileClose(); +} + +qboolean Actor::checkEnemyProjectileClose() +{ + return ( state_flags & STATE_FLAG_ENEMY_PROJECTILE_CLOSE ); +} + +void Actor::SaveOffLastHitBone( Event *ev ) +{ + saved_bone_hit = last_bone_hit; +} + +void Actor::SetPlayPainSoundInterval( Event *ev ) +{ + _playPainSoundInterval = ev->GetFloat( 1 ); +} + +void Actor::SetContextInterval( Event *ev ) +{ + SetContextInterval(ev->GetFloat(1)); +} + +void Actor::SetContextInterval( float interval ) +{ + _contextInterval = interval; +} + +void Actor::SetMinPainTime( Event *ev ) +{ + SetMinPainTime( ev->GetFloat( 1 ) ); +} + +void Actor::SetMinPainTime( float time ) +{ + min_pain_time = time; +} + +void Actor::SetEnemyTargeted( Event *ev ) +{ + SetEnemyTargeted(ev->GetBoolean(1) ); +} + +void Actor::SetEnemyTargeted( bool targeted ) +{ + Entity* enemy; + enemy = enemyManager->GetCurrentEnemy(); + + if ( !enemy ) return; + + if ( enemy->isSubclassOf(Player) ) + { + Player* player; + player = (Player*)enemy; + player->setTargeted(targeted); + } +} + +void Actor::SetActivationDelay( Event *ev ) +{ + SetActivationDelay(ev->GetFloat(1) ); +} + +void Actor::SetActivationDelay( float delay ) +{ + activationDelay = delay; +} + +void Actor::SetActivationStart( Event *ev ) +{ + SetActivationStart(); +} + +void Actor::SetActivationStart() +{ + activationStart = level.time; +} + +qboolean Actor::checkActivationDelayTime( Conditional &condition ) +{ + return checkActivationDelayTime(); +} + +qboolean Actor::checkActivationDelayTime() +{ + //Yes, I could do this all 1337-like in one line, but debugging that sucks. + if ( level.time >= activationStart + activationDelay ) + return true; + + return false; +} + +void Actor::SetCheckConeOfFireDistance( Event* ev ) +{ + SetCheckConeOfFireDistance( ev->GetFloat( 1 ) ); +} + +void Actor::SetCheckConeOfFireDistance( float distance ) +{ + assert( strategos ); + assert( distance > 0 ); + this->strategos->SetCheckInConeDistMax( distance ); +} + + +//----------------------------------------------------------------- +// Note: This Needs to be its own class +//----------------------------------------------------------------- +void Actor::AddCustomThread( Event *ev ) +{ + AddCustomThread( ev->GetString( 1 ) , ev->GetString( 2 ) ); +} + +void Actor::AddCustomThread( const str& threadType , const str& threadName ) +{ + threadlist_t* threadListEntry; + + //First Search the container to see if we already have a custom thread with + //the specified type. If we don't great, we'll just add this thing. If we do + //then we are going to replace the threadName with the one passed in + if ( threadList.NumObjects() ) + { + for ( int i = 1; i <= threadList.NumObjects() ; i++ ) + { + threadListEntry = threadList.ObjectAt( i ); + if ( !stricmp(threadListEntry->threadType.c_str() , threadType.c_str() ) ) + { + threadListEntry->threadName = threadName; + return; + } + } + } + + //Since we didn't find a matching threadType, we'll just add what we need. + threadListEntry = new threadlist_t; + + threadListEntry->threadType = threadType; + threadListEntry->threadName = threadName; + + threadList.AddObject( threadListEntry ); + +} + +bool Actor::HaveCustomThread( const str& threadType ) +{ + threadlist_t* threadListEntry; + + // Search the container to see if we have a threadType entry matching the + // threadType we're looking for. + if ( threadList.NumObjects() ) + { + for ( int i = 1; i <= threadList.NumObjects() ; i++ ) + { + threadListEntry = threadList.ObjectAt( i ); + if ( !stricmp(threadListEntry->threadType.c_str() , threadType.c_str() ) ) + { + return true; + } + } + } + + return false; +} + +void Actor::RunCustomThread( const str& threadType ) +{ + + threadlist_t* threadListEntry; + str threadName; + + // Search the container to see if we have a threadType entry matching the + // threadType we're looking for. + if ( threadList.NumObjects() ) + { + for ( int i = 1; i <= threadList.NumObjects() ; i++ ) + { + threadListEntry = threadList.ObjectAt( i ); + if ( !stricmp(threadListEntry->threadType.c_str() , threadType.c_str() ) ) + { + threadName = threadListEntry->threadName; + if ( threadName.length() ) + { + ExecuteThread(threadName,true,this); + } + } + } + } +} + +const str Actor::GetCustomThread( const str& threadType ) +{ + threadlist_t* threadListEntry; + str threadName; + + // Search the container to see if we have a threadType entry matching the + // threadType we're looking for. + if ( threadList.NumObjects() ) + { + for ( int i = 1; i <= threadList.NumObjects() ; i++ ) + { + threadListEntry = threadList.ObjectAt( i ); + if ( !stricmp(threadListEntry->threadType.c_str() , threadType.c_str() ) ) + { + return threadListEntry->threadName; + } + } + } + + return threadName; +} + +void Actor::LevelAIOff() +{ + //Okay, since we don't want to turn on every single AI in the game + //when we get a level.ai_on event -- Only the AI that were on before + //we need to only turn off the AI that is on + if ( GetActorFlag( ACTOR_FLAG_AI_ON ) ) + { + _levelAIOff = true; + TurnAIOff(); + } +} + +void Actor::LevelAIOn() +{ + if ( GetActorFlag( ACTOR_FLAG_AI_ON ) ) return; + + if ( _levelAIOff ) + { + _levelAIOff = false; + TurnAIOn(); + } +} + +void Actor::SetHeadWatchMaxDistance( Event *ev ) +{ + headWatcher->SetMaxDistance( ev->GetFloat( 1 ) ); +} + +void Actor::SetBounceOffVelocity( Event *ev ) +{ + bounce_off_velocity = ev->GetFloat( 1 ); +} + +qboolean Actor::checkTalking( Conditional &condition ) +{ + return checkTalking(); +} + +qboolean Actor::checkTalking() +{ + if ( mode == ACTOR_MODE_TALK ) + return true; + + return false; +} + +qboolean Actor::checkEnemiesNearby(Conditional &condition ) +{ + float dist = atof(condition.getParm(1) ); + return checkEnemiesNearby(dist); +} + +qboolean Actor::checkEnemiesNearby(float distance) +{ + Entity *ent; + for ( int i = 1 ; i <= ActiveList.NumObjects() ; i++ ) + { + ent = ActiveList.ObjectAt( i ); + if ( (enemyManager->Hates( ent )) && WithinDistance(ent, distance ) ) + return true; + } + + return false; +} + +void Actor::SetDeathKnockbackValues(Event *ev) +{ + deathKnockbackVerticalValue = ev->GetFloat( 1 ); + deathKnockbackHorizontalValue = ev->GetFloat( 2 ); +} + +void Actor::SetIgnoreWatchTarget( bool ignore ) +{ + headWatcher->SetIgnoreWatchTarget( ignore ); +} diff --git a/dlls/game/actor.h b/dlls/game/actor.h new file mode 100644 index 0000000..0870a7a --- /dev/null +++ b/dlls/game/actor.h @@ -0,0 +1,1531 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actor.h $ +// $Revision:: 280 $ +// $Author:: Sketcher $ +// $Date:: 5/04/03 5:49p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class for character AI. +// + +//============================== +// Forward Declarations +//============================== +class Actor; +class FindCoverMovement; +class FindMovement; +class FindFleeMovement; +class FindEnemyMovement; + +#ifndef __ACTOR_H__ +#define __ACTOR_H__ + +#include "g_local.h" +#include "weapon.h" +#include "sentient.h" +#include "container.h" +#include "stack.h" +#include "navigate.h" +#include "behavior.h" +#include "behaviors_general.h" +#include "behaviors_specific.h" +#include "scriptmaster.h" +#include "characterstate.h" +#include "actorstrategies.h" +#include "actorgamecomponents.h" +#include "helper_node.h" +#include "actorutil.h" +#include "actor_sensoryperception.h" +#include "actor_enemymanager.h" +#include "actor_locomotion.h" +#include "actor_combatsubsystem.h" +#include "actor_headwatcher.h" +#include "actor_posturecontroller.hpp" +#include "actorincludes.h" +#include "RageAI.h" +#include "FollowPath.h" + +//------------------------------------------- +// Global Functions +//------------------------------------------- +Actor *GetActor( const str &actor_name ); +Player *GetPlayer(int index); +void AI_SenseEnemies( void ); +void AI_DisplayInfo( void ); + + +//------------------------------------------- +// Safe Pointer +//------------------------------------------- +typedef SafePtr ActorPtr; + + +//------------------------------------------- +// External Data +//------------------------------------------- +extern Event EV_Torso_Anim_Done; +extern Event EV_EntityFlags; +extern Event EV_Sentient_GroupMemberInjured; +extern Event EV_HelperNodeCommand; +extern Event EV_Actor_SetAnim ; +extern Event EV_Actor_Blink; + + +enum SteeringDirectionPreference +{ + STEER_RIGHT_ALWAYS, + STEER_LEFT_ALWAYS, + STEER_RANDOMLY, + STEER_BEST, +}; + + +//------------------------- CLASS ------------------------------ +// +// Name: Actor +// Base Class: Sentient +// +// Description: Defines an Actor +// +// Method of Use: Spawned into the world +//-------------------------------------------------------------- +class Actor : public Sentient +{ + public: + + // E3 2002 Hack Stuff + EntityPtr forcedEnemy; + + typedef enum { + ACTOR_CONTROL_NONE, + ACTOR_CONTROL_AUTO_RELEASE, + ACTOR_CONTROL_SHARED, + ACTOR_CONTROL_LOCKED, + } ActorControlType ; + + //----------------------------------------------------- + // Animation Data: + // The first chunk is for "leg" animation, the second + // chunck is for "torso" animations. Currently, I know + // of no way to "blend" animations, and you need to be + // careful of which animations you play together. + // Also, I believe there are still some issues with playing + // simultaneous animations + // + // Note: To play 2 animations simultaneously, the actors + // tiki file _MUST_ have leg bones marked as LEGBONE instead + // of just BONE. + //----------------------------------------------------- + str newanim; + int newanimnum; + int animnum; + str animname; + Event *newanimevent; + str last_anim_event_name; + + str newTorsoAnim; + int newTorsoAnimNum; + str TorsoAnimName; + Event *newTorsoAnimEvent; + str last_torso_anim_event_name; + + //----------------------------------------------------- + // Range Data + // This was original placed here so that level designers + // could have a way to control the "leash" so to speak + // on the distance actors followed other actors. It turned + // out this didn't work as well as I had wanted. I'm leaving + // it here for right now as I don't want to break any type + // of legacy. However, we should keep an eye on this to + // see if can be removed. + //----------------------------------------------------- + float absoluteMin; + float absoluteMax; + float preferredMin; + float preferredMax; + + //----------------------------------------------------- + // Actor and Target Types + // the actortype is the primary determinant of who the + // actor will "like" and "hate". I think this system + // is kind of restricting and should be replaced with a + // "real" faction system in the future. + // + // targetType determines what the actor will attempt to + // attack, i.e. other actors only, players only, or + // "interaction" objects only. I think this needs to + // be revamped as well. + // + // I'm not sure what validTarget is for. It is likely + // that this can be removed, but I'm not going to do it + // right now for fear of breaking stuff + //----------------------------------------------------- + actortype_t actortype; + targetType_t targetType; + qboolean validTarget; + + //----------------------------------------------------- + // Behavior Data + // We have the ability to play multiple behaviors simultaneously + // however, I don't recommend it. In all honestly headBehavior and + // eyeBehavior really need to be removed. I guess I could see + // keeping a torsoBehavior as we do have leg and torso animation + // sets. However, I'm not convinced as of yet, that all the bugs + // are worked out of using multiple behaviors ( especially when + // relying on Anim Done or other such conditionals + //-------------------------------------------------------- + BehaviorPtr behavior; + str currentBehavior; + str behaviorFailureReason; + BehaviorReturnCode_t behaviorCode; + + BehaviorPtr headBehavior; + str currentHeadBehavior; + BehaviorReturnCode_t headBehaviorCode; + + BehaviorPtr eyeBehavior; + str currentEyeBehavior; + BehaviorReturnCode_t eyeBehaviorCode; + + BehaviorPtr torsoBehavior; + str currentTorsoBehavior; + BehaviorReturnCode_t torsoBehaviorCode; + + //--------------------------------------------------------- + // Lead Factors + // These define a range - [minLeadFactor, maxLeadFactor] - + // which is used to determine how much - if any - the + // actor will lead its target when using a projectile + // weapon. A factor of 0 indicates the position where + // the target is presently; a factor of 1 indicates the + // position where the target is predicted to be at + // impact, taking into account the target's current + // motion. Each time the actor fires a projectile, + // a random number in this range is chosen and applied + // as the lead factor for that shot. Thus, [0,0] means + // the actor never leads his target; [1,1] means he + // always leads it perfectly; [0,2] means he sometimes + // over-leads and sometimes under-leads it. + //--------------------------------------------------------- + float minLeadFactor; + float maxLeadFactor; + + //--------------------------------------------------------- + // ThrowObject Flag + // This is here in case the actor has an object deemed as + // "throwable". This is so so weak. I can say that, because + // I'm the one who put it in here. The whole "throw" object + // stuff needs to be jetisoned and placed in a behavior + // where it belongs. At a minimum, if we need to maintain + // that we "have" a throw object, it should be made an bit + // in the Actor Flag stuff not its own bool + //---------------------------------------------------------- + qboolean haveThrowObject; + + //----------------------------------------------------------- + // AnimSet -- Animation Sets can be setup in the GPD. What + // animset the actor is currently using is set up in script + //----------------------------------------------------------- + str animset; + + //---------------------------------------------------------- + // Flags + // Stores various bits of information about the actor. + // We do have a bit vector class now, and sometime someone + // should experiment with converting these flags into a + // bit vector + //--------------------------------------------------------- + unsigned int actor_flags1; + unsigned int actor_flags2; + unsigned int actor_flags3; + unsigned int actor_flags4; + unsigned int notify_flags1; + unsigned int state_flags; + + + //-------------------------------------------------------- + // Some Dialog and Babble Stuff + // Okay, now that we have context dialog stuff in addition + // to this, the dialog stuff is SCREAMING for it's own class + // even if we do it as a helper class, it needs something + // it really needs to be moved + //--------------------------------------------------------- + float chattime; + float nextsoundtime; + DialogMode_t DialogMode; + float radiusDialogRange; + DialogNode_t *dialog_list; + float dialog_done_time; + str dialog_state_name; + str dialog_old_state_name; + bool _ignoreNextContext; + str _nextContextToIgnore; + float _nextContextTime; + float _contextInterval; + + //--------------------------------------------------------- + // Thread Management + // Here we need to take a long look at what we're doing with + // these. To me it seems ridiculous that we should maintain + // all these str when they are seldom used. What I think + // we need is a type of thread controller that keeps a container + // of strings for threads we're using and an enum for type + // then we'd only add a string to the container if it's used + // it would also allow us to have many many more types of these + // threads since they would only be stored "as required" + //---------------------------------------------------------- + ThreadPtr scriptthread; + str kill_thread; + str escape_thread; + str captured_thread; + str activate_thread; + str onuse_thread_name; + str ondamage_thread; + str alert_thread; + str idle_thread; + Container threadList; + + //---------------------------------------------------------- + // Pain and Miscellaneous Damage + // I honestly don't know what some of these variables + // are for. We need to look at how we are handling "pain" + // for actors and see if there's a better way to do it, + // and verify that we need all of this stuff + //---------------------------------------------------------- + float pain_threshold; + float next_drown_time; + float air_finished; + int pain_type; + Vector pain_angles; + int bullet_hits; + float next_pain_time; + float min_pain_time; + float next_forced_pain_time; + float max_pain_time; + str _deathEffect; + + //---------------------------------------------------------- + // State map and "Thinking" stuff + // Here's where we store all the stuff for the Actors state + // map. One thing to note is stateDebugType_t. This is a + // very very primitive debugging tool that will print + // various information about state transitions to the console + // for a specified actor. + // We really really need better debuging tools for actors + //---------------------------------------------------------- + + str masterstatemap_name; + StateMap *masterstatemap; + State *currentMasterState; + State *lastMasterState; + + str statemap_name; + StateMap *statemap; + State *currentState; + State *lastState; + + State *globalState; + + float state_time; + float masterstate_time; + int times_done; + int masterstate_times_done; + float state_done_time; + float masterstate_done_time; + float last_time_active; + stateDebugType_t showStates; + talkModeStates_t talkMode; + bool useConvAnims; + + + static Condition Conditions[]; + Container conditionals; + Container master_conditionals; + + + //----------------------------------------------------------- + // Fuzzy Engine Stuff + // Here's where we store our fuzzy engine stuff. The fuzzy + // engine is sort of a state machine for state machines. However + // instead of direct transitions, each conditional in the + // fuzzy engine is assigned a point value. All the fuzzyvars + // are evaluated and the one with the highest cumulative score + // executes + //------------------------------------------------------------ + str fuzzyengine_name; + FuzzyEngine *fuzzyEngine; + qboolean fuzzyEngine_active; + Container fuzzy_conditionals; + + //------------------------------------------------------------ + // Eye Angle stuff + // This is in here for when we were actually moving the eyes + // with bone controllers. I don't think its being used any more + // nor do I think it should ever be used again. I believe that + // this "eye watch" thing is cool in theory, but would hardly ever + // be seen by the player. It also introduces a whole lot of problems + // "chaining" watches together properly. Basically, for this level + // of detail to really be effective. During a watch, the eyes would + // have to move, then the head, then the torso, then the legs. + // Currently we don't have a way to accomplish that chain easily + // However, something to handle that might be good to look into + // for the future + //-------------------------------------------------------------- + float maxEyeYawAngle; + float minEyeYawAngle; + float maxEyePitchAngle; + float minEyePitchAngle; + + //-------------------------------------------------------------- + // Saved Items (from switching to talk mode and stuff) + // This saved stuff presents some interesting possiblities for + // the future, however, for right now, it is used when the + // actor steps out of his normal "behavior" into a dialog mode + // to talk, when the dialog is completed, they step back into + // the behaivors they saved off here + //--------------------------------------------------------------- + int saved_mode; + BehaviorPtr saved_behavior; + BehaviorPtr saved_headBehavior; + BehaviorPtr saved_eyeBehavior; + BehaviorPtr saved_torsoBehavior; + ThreadPtr saved_scriptthread; + ThreadPtr saved_actorthread; + str saved_anim_name; + str saved_state_name; + str saved_anim_event_name; + + + //--------------------------------------------------------------- + // Part Stuff + // I'm not entirely sure what this is, or how to use it. I + // remember being told that we could seperate actors into multiple + // "parts" each executing its own state machines, but I'm not + // sure how to go about setting such a thing up + //---------------------------------------------------------------- + str part_name; + Container parts; + + + //---------------------------------------------------------------- + // Incoming Attack Stuff + // Here to let us handle incoming attacks -- Could probably + // be improved + //---------------------------------------------------------------- + EntityPtr incoming_proj; + float incoming_time; + qboolean incoming_bullet; + + //----------------------------------------------------------------- + // Basic Data about the Actor + // All this should probably be made private and have accessors + // We also need to take some time and verify we're using all of + // this stuff + //----------------------------------------------------------------- + str name; + float max_inactive_time; + Vector eyeoffset; + float last_jump_time; + str enemytype; + float actorrange_time; + float last_height; + EntityPtr last_ent; + float canseeenemy_time; + float canseeplayer_time; + int stage; + int num_of_spawns; + ActorPtr spawnparent; + Vector last_attack_pos; + Vector last_attack_enemy_pos; + EntityPtr last_attack_entity_hit; + Vector last_attack_entity_hit_pos; + int mode; + Vector last_known_enemy_pos; + Vector last_known_player_pos; + float feet_width; + Vector last_origin; + float next_find_enemy_time; + float minimum_melee_height; + float damage_angles; + float real_head_pitch; + float next_pain_sound_time; + float last_ground_z; + str emotion; + float next_blink_time; + float actor_to_actor_damage_modifier; + float last_used_time; + float hitscan_response_chance; + int shotsFired; + int ondamage_threshold; + float timeBetweenSleepChecks; + int saved_bone_hit; + float activationDelay; + float activationStart; + float deathKnockbackVerticalValue; + float deathKnockbackHorizontalValue; + + //----------------------------------------------------- + // Pain Sound Stuff + //----------------------------------------------------- + float _nextPlayPainSoundTime; + float _playPainSoundInterval; + + + //----------------------------------------------------- + // Controller stuff + // If set, this is the listener controlling the actor. + // This controller is notifyied whenever the actor ends + // a behavior. + //----------------------------------------------------- + ListenerPtr _controller ; + ActorControlType _controlType ; + + + //----------------------------------------------------- + // Group Number + // Right now, groups are assigned in script or tiki + // and are not used very much yet. I will be implementing + // a group data controller very soon which should handle + // all the book keeping for group stuff, and allow + // actors to ask questions about their group + //------------------------------------------------------ + //int groupnumber; + + + //------------------------------------------------------ + // Current Helper Node + // Data structure that holds information about the Actor's + // "current" helper node. + //------------------------------------------------------- + CurrentHelperNodeData_t currentHelperNode; + IgnoreHelperNodeData_t ignoreHelperNode; + + //-------------------------------------------------------- + // Follow Target + // This data structure maintains who the actor would + // currently follow if it decided to execute a follow behavior + // it also maintains who it is currently following if it + // is following someone else who is currently following the same + // target. + //-------------------------------------------------------- + FollowTargetData_t followTarget; + int _steeringDirectionPreference; + + + + //----------------------------------------------------- + // General Stuff (stuff I can't think of a group for) + // Like other stuff, we need to take a long look + // at these things + //----------------------------------------------------- + Container stateVarList; + EntityPtr trigger; + str command; + str idle_state_name; + str master_idle_state_name; + str global_state_name; + float next_player_near; + EntityPtr pickup_ent; + float stunned_end_time; + Container spawn_items; + float spawn_chance; + str bounce_off_effect; + float bounce_off_velocity; + Container can_be_finsihed_by_mods; + float max_boss_health; + qboolean haveAttached; + float currentSplineTime; + float _dialogMorphMult; + weaponhand_t _useWeaponDamage; + float _nextCheckForWorkNodeTime; + float _nextCheckForHibernateNodeTime; + float _nextCheckForEnemyPath; + bool _havePathToEnemy; + float _nextPathDistanceToFollowTargetCheck; + bool _checkedChance; + bool _levelAIOff; + + //------------------------------------------------------ + // Helper Classes + // These are classes that help the actor function + // We need a couple more of these, specifically one + // for dialog, and another for "thread" management + //------------------------------------------------------ + ActorThink *thinkStrategy; + ActorGameComponent *gameComponent; + SensoryPerception *sensoryPerception; + Strategos *strategos; + EnemyManager *enemyManager; + PackageManager *packageManager; + MovementSubsystem *movementSubsystem; + Personality *personality; + CombatSubsystem *combatSubsystem; + HeadWatcher *headWatcher; + PostureController *postureController; + + //------------------------------------------------------ + // 1st Playable Hack + // Obviously, this needs to be removed as soon as possible + //------------------------------------------------------- + float lastPathCheck_Work; + float lastPathCheck_Flee; + float lastPathCheck_Patrol; + qboolean testing; + + str _branchDialogName; + + //------------------------------------------------------ + // Class Prototype + //------------------------------------------------------ + CLASS_PROTOTYPE( Actor ); + + + //------------------------------------------------------- + // Initialization functions + //------------------------------------------------------- + Actor(); + ~Actor(); + void Start( Event *ev ); + void Sleep( void ); + void Sleep( Event *ev ); + void Wakeup( void ); + void Wakeup( Event *ev ); + void InitGameComponent( void ); + void InitThinkStrategy( void ); + void InitSensoryPerception( void ); + void InitStrategos( void ); + void InitEnemyManager( void ); + void InitPackageManager( void ); + void InitMovementSubsystem( void ); + void InitPersonality( void ); + void InitCombatSubsystem( void ); + void InitHeadWatcher( void ); + void InitPostureController( void ); + + //--------------------------------------------------------- + // Event Interface for Accessor Functions + //--------------------------------------------------------- + void SetTargetType ( Event *ev ); + void SetDamageAngles( Event *ev ); + void SetImmortal( Event *ev ); + void SetTakeDamage( Event *ev ); + void SetEnemyAttached ( Event *ev ); + void SetEnemyType( Event *ev ); + void SetOnUseThread( Event *ev ); + void SetRadiusDialogRange( Event *ev ); + void SetDialogMode( Event *ev ); + void SetActivateThread( Event *ev ); + void SetAlertThread( Event *ev ); + void SetValidTarget( Event *ev ); + void SetTurretMode( Event *ev ); + void SetOnDamageThread( Event *ev ); + void SetTimeBetweenSleepChecks( Event *ev ); + void SetDieCompletely( Event *ev ); + void SetBleedAfterDeath( Event *ev ); + void SetPainThresholdEvent( Event *ev ); + void SetKillThreadEvent( Event *ev ); + void SetDeathSize( Event *ev ); + void SetCanWalkOnOthers( Event *ev ); + void SetFeetWidth( Event *ev ); + void SetCanBeFinishedBy( Event *ev ); + void SetIdleThread( Event *ev ); + void SetMaxBossHealth( Event *ev ); + void SetTargetable( Event *ev ); + void SetSpawnChance( Event *ev ); + void SetNotAllowedToKill( Event *ev ); + void SetUseGravity( Event *ev ); + void SetAllowFall( Event *ev ); + void SetUseMovement( Event *ev ); + void SetHaveThing( Event *ev ); + void SetHaveThing( int thing_number,qboolean thing_bool ); + void SetBounceOff( Event *ev ); + void SetBounceOffEffect( Event *ev ); + void SetWatchOffset( Event *ev ); + void SetMaxInactiveTime( Event *ev ); + void SetTurnSpeed( Event *ev ); + void SetHealth( Event *ev ); + void SetMaxHealth( Event *ev ); + void SetVar( Event *ev ); + void SetVarTime( Event *ev ); + void SetMask( Event *ev ); + void SetWeaponReady( Event *ev ); + void SetSimplifiedThink( Event *ev ); + void SetActorToActorDamageModifier( Event *ev ); + void SetEmotion( Event *ev ); + void SetEyeAngles( Event *ev ); + void SetHitscanResponse( Event *ev ); + void SetFOV( Event *ev ); + void SetVisionDistance( Event *ev ); + void SetAimLeadFactors( Event *ev ); + void SetActorType( Event *ev ); + void SetAbsoluteMax( Event *ev ); + void SetAbsoluteMin( Event *ev ); + void SetPreferredMax( Event *ev ); + void SetPreferredMin( Event *ev ); + void SetDisabled( Event *ev ); + void SetCrippled( Event *ev ); + void SetInAlcove( Event *ev ); + void SetGroupNumber( Event *ev ); + void SetMovementMode( Event *ev ); + void SetHeadWatchTarget( Event *ev ); + void SetHeadWatchMaxDistance( Event *ev ); + void SetHeadWatchSpeed( Event *ev ); + void setHeadTwitch( Event *ev ); + void SetFuzzyEngineActive( Event *ev ); + void SetNodeID( Event *ev ); + void SetFollowTarget( Event *ev ); + void SetFollowRange( Event *ev ); + void SetFollowRangeMin( Event *ev ); + void SetFollowCombatRange( Event *ev ); + void SetFollowCombatRangeMin( Event *ev ); + void SetSteeringDirectionPreference( Event *ev ); + void SetIgnoreNextContext( Event *ev ); + void SetGroupDeathThread( Event *ev ); + void SaveOffLastHitBone( Event *ev ); + void SetMinPainTime( Event *ev ); + void SetMinPainTime( float time ); + void SetBounceOffVelocity( Event *ev ); + + void SetSelfDetonateModel ( Event *ev ); + + void SetCombatTraceInterval( Event *ev ); + + void SetHeadWatchTarget( Entity *ent ); + void SetHeadWatchSpeed( float speed ); + void ResetTorso( Event *ev ); + void EyeOffset( Event *ev ); + void ForceSetClip( Event *ev ); + void GroupMemberInjured( Event *ev ); + void StrictlyFollowPath( Event *ev ); + void SetIgnoreWatchTarget( bool ignore ); + + void HelperNodeCommand( Event *ev ); + + void SetPlayPainSoundInterval( Event *ev ); + + void SetBehaviorPackage( Event *ev ); + void SetBehaviorPackage( const str &packageName ); + + void SetMaxHeadYaw( Event *ev ); + void SetMaxHeadPitch( Event *ev ); + + void SetPlayerHateModifier( Event *ev ); + + void UseBehaviorPackage( Event *ev ); + void UseBehaviorPackage( const str &packageName ); + + void ChildUseBehaviorPackage( Event *ev ); + void ChildUseBehaviorPackage( const str &childName , const str &packageName ); + + void ChildSetAnim( Event *ev ); + void ChildSetAnim( const str &childName , const str &animName ); + + void ChildSuicide( Event *ev ); + + void GroupAttack( Event *ev ); + void GroupActorType( Event *ev ); + + void SetMasterState( Event *ev ); + + void PrintDebugMessage( Event *ev ); + + void SelectNextEnemy( Event* ev ); + void SelectClosestEnemy ( Event *ev ); + void SetAnimSet( Event *ev ); + void SetAnimSet( const str &animSet ); + const str& GetAnimSet(); + + void SetTalkWatchMode( Event *ev ); + void SetPostureState( Event *ev ); + /*virtual*/void processGameplayData( Event *ev ); + + void UnreserveCurrentHelperNode( Event *ev ); + void UnreserveCurrentHelperNode(); + + //----------------------------------------------------------------- + // Note: This Needs to be its own class + //----------------------------------------------------------------- + void AddCustomThread( Event *ev ); + void AddCustomThread( const str& threadType , const str& threadName ); + bool HaveCustomThread( const str& threadType ); + void RunCustomThread( const str& threadType ); + const str GetCustomThread( const str& threadType ); + + //---------------------------------------------------------- + // Personality Tendencies + //---------------------------------------------------------- + void SetAggressiveness( Event *ev ); + void SetTalkiness( Event *ev ); + void SetTendency( Event *ev ); + + void RegisterBehaviorPackage( Event *ev ); + void UnRegisterBehaviorPackage( Event *ev ); + void SetPackageTendency( Event *ev ); + + //----------------------------------------------------------- + // Clear Anim Functions + //----------------------------------------------------------- + void ClearTorsoAnim(); + void ClearTorsoAnim( Event *ev ); + + void ClearLegAnim(); + + //----------------------------------------------------------- + // Debug Functions + //----------------------------------------------------------- + void DebugStates( Event *ev ); + void ShowInfo( void ); + void WhatAreYouDoing( Event *ev ); + void WhatsWrong( Event *ev ); + void PrintMasterStateInfo(); + void PrintBehaviorPackageInfo(); + void PrintStateMapInfo(); + + + //----------------------------------------------------------- + // Set Stimuli Response + //----------------------------------------------------------- + void RespondTo( Event *ev ); + void PermanentlyRespondTo( Event *ev ); + + //----------------------------------------------------------- + // Combat functions + //----------------------------------------------------------- + void MeleeEvent( Event *ev ); + void ChargeWater( Event *ev ); + void DamageOnceStart( Event *ev ); + void DamageOnceStop( Event *ev ); + void DamageAllowed( Event *ev ); + virtual qboolean CanAttackFrom( const Vector &pos, const Entity *ent, qboolean usecurrentangles ); + virtual qboolean CanAttack( Entity *ent, qboolean usecurrentangles ); + void FireProjectile( Event *ev ); + void FireBullet( Event *ev ); + void FireRadiusAttack( Event *ev ); + void SaveAttack( const Vector &orig, const Vector &dir ); + qboolean TestAttack( const str &tag_name ); + void IncomingProjectile( Event *ev ); + void ProjectileClose( Event *ev ); + qboolean EntityHasFireType( Entity *ent, firetype_t fire_type ); + void DamageEnemy( Event *ev ); + void DamageSelf( Event *ev ); + void TurnTowardsEnemy( Event *ev ); + void TurnTowardsPlayer( Event *ev ); + void TurnTowardsEntity( Event *ev ); + void SetMinimumMeleeHeight( Event *ev ); + + qboolean IsImmortal( void ); + qboolean TakeDamage( void ); + + void FireWeapon( Event *ev ); + void StopFireWeapon( Event *ev ); + void ClearArmorAdaptions( Event *ev ); + + //----------------------------------------------------------------- + // Enemy management + //----------------------------------------------------------------- + void ClearCurrentEnemy( Event *ev ); + + //---------------------------------------------------------------- + // Targeting functions + //---------------------------------------------------------------- + qboolean CloseToEnemy( const Vector &pos, float howclose ); + qboolean EntityInRange( Entity *ent, float range, float min_height, float max_height , bool XYOnly = false ); + + + + void ClearOnUseThread( Event *ev ); + void AttachCurrentEnemy( Event *ev ); + void AttachActor( Event *ev ); + void PickupThrowObject( Event *ev ); + void TossThrowObject( Event *ev ); + + //--------------------------------------------------------------- + // State machine functions + //-------------------------------------------------------------- + void SetIdleStateName( Event *ev ); + void SetState( const char *state_name ); + void resetStateMachine( void ); + void SetMasterState( const str &state_name ); + void SetGlobalState( const char *state_name ); + void InitState( void ); + void InitMasterState( void ); + void LoadStateMap( Event *ev ); + void LoadMasterStateMap( Event *ev ); + void ProcessActorStateMachine( void ); + void ProcessMasterStateMachine( void ); + void LoadFuzzyEngine( Event *ev ); + + void LoadPostureStateMachine( Event *ev ); + + //-------------------------------------------------------------- + // Thread management + //-------------------------------------------------------------- + void RunThread( Event *ev ); + void RunThread( const str &thread_name ); + void SetThread( const str &filename, const str &label ); + void RunDamageThread( void ); + void RunAlertThread( Event *ev ); + + //-------------------------------------------------------------- + // Behavior management + //-------------------------------------------------------------- + void SendMoveDone( CThread *script_thread ); + void EndBehavior( void ); + void EndHeadBehavior( void ); + void EndEyeBehavior( void ); + void EndTorsoBehavior( void ); + void SetBehavior( Behavior *newbehavior, Event *argevent = NULL, CThread *thread = NULL ); + void SetHeadBehavior( Behavior *newbehavior, Event *argevent = NULL, CThread *thread = NULL ); + void SetEyeBehavior( Behavior *newbehavior, Event *argevent = NULL, CThread *thread = NULL ); + void SetTorsoBehavior( Behavior *newbehavior, Event *argevent = NULL, CThread *thread = NULL ); + void EndBehaviorEvent( Event *ev ); + void EndHeadBehaviorEvent( Event *ev ); + void EndEyeBehaviorEvent( Event *ev ); + void EndTorsoBehaviorEvent( Event *ev ); + void NotifyBehavior( Event *ev ); + void NotifyHeadBehavior( Event *ev ); + void NotifyEyeBehavior( Event *ev ); + void NotifyTorsoBehavior( Event *ev ); + void EndAllBehaviors( void ); + + + //----------------------------------------------------------------- + // Path and node management + //----------------------------------------------------------------- + PathNode *NearestNodeInPVS( const Vector &pos ); + void SetPath( Path *newpath ); + void ReserveNodeEvent( Event *ev ); + void ReleaseNodeEvent( Event *ev ); + trace_t Trace(const Vector &end, const char *reason) const; + trace_t Trace(const float distance, const char *reason) const; + trace_t Trace(const float angle, const float distance, const char *reason) const; + trace_t Trace(const Vector &begin, const Vector &end, const char *reason) const; + trace_t Trace(const Vector &begin, const Vector &end, const int contentMask, const char *reason ) const; + + + //----------------------------------------------------------------- + // Animation control functions + //----------------------------------------------------------------- + void RemoveAnimDoneEvent( void ); + void ChangeAnim( void ); + qboolean SetAnim( const str &anim, Event *ev = NULL, bodypart_t part = legs, const float animationRate = 1.0f ); + qboolean SetAnim( const str &anim, Event &ev, bodypart_t part = legs, const float animationRate = 1.0f ); + void SetAnim( Event *ev ); + void AnimDone( Event *ev ); + void TorsoAnimDone( Event *ev ); + void SetCinematicAnim( const str &anim ); + void CinematicAnimDone( ); + void PostureAnimDone( Event *ev ); + + //----------------------------------------------------------------- + // Script commands + //----------------------------------------------------------------- + void GoIdle( Event *ev ); + void LookAt( Event *ev ); + void TurnToEvent( Event *ev ); + void HeadAndEyeWatchEvent ( Event *ev ); + void HeadWatchEvent( Event *ev ); + void ResetHeadEvent( Event *ev ); + void EyeWatchEvent( Event *ev ); + void ResetEyeEvent( Event *ev ); + void ResetTorsoEvent( Event *ev ); + void FallToDeathEvent( Event *ev ); + void WalkTo( Event *ev ); + void BlindlyFollowPath ( Event *ev ); + void WalkWatch( Event *ev ); + void WarpTo( Event *ev ); + void PickupEnt( Event *ev ); + void ThrowEnt( Event *ev ); + void AttackEntity( Event *ev ); + void AttackPlayer( Event *ev ); + void JumpToEvent( Event *ev ); + void Anim( Event *ev ); + void RepostEvent( Event *ev, const Event &event_type ); + void FollowWayPoints( Event *ev ); + + + //----------------------------------------------------------------- + // Weapon Stuff + //----------------------------------------------------------------- + void GiveActorWeapon( Event *ev ); + void RemoveActorWeapon( Event *ev ); + void UseActorWeapon( Event *ev ); + void PutawayWeapon( Event *ev ); + void UseWeaponDamage( Event *ev ); + + void AttachModelToTag( const str &modelName , const str &tagName ); + void DetachModelFromTag( const str &tagName ); + + + //----------------------------------------------------------------- + // Script conditionals + //----------------------------------------------------------------- + void IfEnemyVisibleEvent( Event *ev ); + void IfNearEvent( Event *ev ); + void IfCanHideAtEvent( Event *ev ); + void IfEnemyWithinEvent( Event *ev ); + + + //----------------------------------------------------------------- + // Sound reaction functions + //----------------------------------------------------------------- + void NoPainSounds( Event *ev ); + void HeardSound( Event *ev ); + void BroadcastAlert( float pos = SOUND_RADIUS ); + void BroadcastAlert( Event *ev ); + void BroadcastAlert( float pos, int soundtype ); + + + //----------------------------------------------------------------- + // Pain and death related functions + //----------------------------------------------------------------- + void Pain( Event *ev ); + void StunEvent( Event *ev ); + void CheckStun( void ); + void Dead( Event *ev ); + void Killed( Event *ev ); + void KilledEffects( Entity *attacker ); + void RemoveUselessBody( Event *ev ); + void DeathFadeEvent( Event *ev ); + void setDeathEffect( Event *ev ); + void DeathShrinkEvent( Event *ev ); + void DeathSinkEvent( Event *ev ); + void StaySolidEvent( Event *ev ); + void SpawnGib( Event *ev ); + void SpawnGibAtTag( Event *ev ); + void RealSpawnGib( qboolean use_tag, Event *ev ); + void SpawnNamedGib( Event *ev ); + float SpawnGetTime( float vel, const Vector &orig, const Vector &gib_mins, const Vector &gib_maxs ); + void SpawnBlood( Event *ev ); + void Suicide( Event *ev ); + void FadeEvent( Event *ev ); + qboolean RespondToHitscan( void ); + bool canBeDamagedBy(meansOfDeath_t MeansOfDeath ); + + //----------------------------------------------------------------- + // Movement functions + //----------------------------------------------------------------- + void SimplePathfinding( Event *ev ); + void ForwardSpeedEvent( Event *ev ); + void SwimEvent( Event *ev ); + void FlyEvent( Event *ev ); + void NotLandEvent( Event *ev ); + void Push( Event *ev ); + void Push( const Vector &dir ); + void Pushable( Event *ev ); + + //----------------------------------------------------------------- + // AI Functions + //----------------------------------------------------------------- + void ActivateAI( void ); + void TurnAIOn( Event *ev ); + void TurnAIOn( void ); + void TurnAIOff( Event *ev ); + void TurnAIOff( void ); + void LevelAIOff(); + void LevelAIOn(); + + //----------------------------------------------------------------- + // Parts functions + //----------------------------------------------------------------- + void RegisterParts( Event *ev ); + void RegisterSelf( Event *ev ); + void PartName( Event *ev ); + Actor *FindPartActor( const char *name ); + void SendCommand( Event *ev ); + + + + //----------------------------------------------------------------- + // Dialog Functionality + //----------------------------------------------------------------- + DialogNode_t *NewDialogNode(void); + void FreeDialogList( void ); + void AddDialogParms( DialogNode_t *dialog_node, Event *ev ); + void AddDialog ( Event *ev ); + void DialogDone( Event *ev ); + void DialogAnimDone( Event *ev ); + void setDialogMorphMult( Event *ev ); + void PlayDialog( Event *ev ); + void StopDialog( Event *ev ); + void PlayRadiusDialog( Sentient *user ); + void StopDialog( void ); + void PlayDialog( Sentient *user, float volume = -1.0f, float min_dist = -1.0f, + const char *dialog_name = NULL, const char *state_name = NULL, qboolean headDisplay = false , bool useTalk = false , bool important = false ); + qboolean DialogExists( const str &aliasName ); + float GetDialogRemainingTime( void ); + const str FindDialog( Sentient *user, DialogType_t dialogType, const str& context = "" ); + + + void BranchDialog(Event* ev); + void BranchOptionSelected(Event* ev); + + //----------------------------------------------------------------- + // Branch Dialog Functionality + //---------------------------------------------------------------- + void clearBranchDialog( void ); + void setBranchDialog( void ); + + + //----------------------------------------------------------------- + // Context Dialog Functionality + //----------------------------------------------------------------- + void InContext ( Event *ev ); + void InContext ( const str& theContext , bool useDefaultMinDist ); + + + //----------------------------------------------------------------- + // Context Dialog Functions -- Resolve the alias and actually play the sound, and broadcast the soundtype to + // nearby actors + //----------------------------------------------------------------- + void BroadcastDialog ( Event *ev ); + qboolean WantsToTalk(); + + + //------------------------------------------------------------------- + // Twitch Functions + //------------------------------------------------------------------- + void SetMouthAngle( Event *ev ); + + + //----------------------------------------------------------------- + // Mode functions + //----------------------------------------------------------------- + qboolean ModeAllowed( int new_mode ); + void StartMode( int new_mode ); + void EndMode( void ); + void SaveMode( void ); + void RestoreMode( void ); + + //----------------------------------------------------------------- + // Finishing functions + //----------------------------------------------------------------- + qboolean CanBeFinished( void ); + qboolean CanBeFinishedBy( int meansofdeath ); + + void Finish( int meansofdeath ); + void StartLimbo( void ); + qboolean InLimbo( void ); + + + //------------------------------------------------------------------- + // Controller functions + //------------------------------------------------------------------- + bool RequestControl( Listener *controller, ActorControlType controlType=ACTOR_CONTROL_AUTO_RELEASE ); + bool ReleaseControl( Listener *controller ); + + + const str GetStateVar ( const str& varName ); + + //----------------------------------------------------------------- + // General functions + //----------------------------------------------------------------- + static Actor* FindActorByName(const str &name); + static unsigned int ActorTypeStringToInt( const str &type ); + void IgnorePainFromActors( Event *ev ); + void UpdateBossHealth( Event *ev ); + void TouchTriggers( Event *ev ); + void IgnoreWater( Event *ev ); + void IgnorePlacementWarning( Event *ev ); + qboolean CanTarget( void ) const; + void AddSpawnItem( Event *ev ); + void ClearSpawnItems( Event *ev ); + void SpawnItems( void ); + void SpawnItem( const str &spawn_item_name ); + qboolean CanJump( void ); + void SetActorFlag( const str &flag_name, qboolean flag_value ); + void SetStickToGround( const bool stick ); + void SetStickToGround( Event *ev ); + const bool GetStickToGround( void ) const; + void SetActorFlag( int flag, qboolean flag_value ); + void SetActorFlag( Event *ev ); + qboolean GetActorFlag( const int flag ) const; + qboolean GetActorFlag( const str &flag_name ) const; + void SetNotifyFlag( const str &flag_name, qboolean flag_value ); + void SetNotifyFlag( int flag, qboolean flag_value ); + void SetNotifyFlag( Event *ev ); + qboolean GetNotifyFlag( int flag ); + void BounceOffEvent( Event *ev ); + void NotifyOthersAtDeath( Event *ev ); + void NotifyOthersOfDeath( void ); + void GotoNextStage( Event *ev ); + void GotoPrevStage( Event *ev ); + void GotoStage( Event *ev ); + void GetStage( Event *ev ); + void Pickup( Event *ev ); + void Throw( Event *ev ); + virtual void setSize( Vector min, Vector max ); + void NoChatterEvent( Event *ev ); + virtual void Chatter( const char *sound, float chance = 10, float volume = 1.0f, int channel = CHAN_VOICE ); + void ActivateEvent( Event *ev ); + void UseEvent( Event *ev ); + void Think( void ); + void Active( Event *ev ); + bool IsEntityAlive( const Entity *ent ); + void Name( Event *ev ); + void SetupTriggerField( Event *ev ); + void TriggerTouched( Event *ev ); + qboolean GetClosestTag( const str &tag_name, int number_of_tags, const Vector &target, Vector *orig ); + void AddStateFlag( unsigned int flag ); + void ClearStateFlags( void ); + void SpawnActorAtTag( Event *ev ); + void SpawnActorAtLocation( Event *ev ); + void SpawnActorAboveEnemy( Event *ev ); + void SpawnActor( const str &model_name, const Vector &orig, const Vector &ang, int how_many, qboolean attack, float width, float height, qboolean force = false ); + void TryTalkToPlayer( void ); + void AllowTalk( Event *ev ); + void AllowHangBack( Event *ev ); + void SolidMask( Event *ev ); + void IgnoreMonsterClip( Event *ev ); + void NotSolidMask( Event *ev ); + void NoMask( Event *ev ); + void ResetMoveDir( Event *ev ); + int ActorFlag_string_to_int( const str &actorflagstr ) const; + int NotifyFlag_string_to_int( const str ¬ifyflagstr ); + void ArmorDamage( Event *ev ); + qboolean CheckBottom( void ); + void ChangeType( Event *ev ); + void GetStateAnims( Container *c ); + void Touched( Event *ev ); + void TryBlink( void ); + void SetBlink( Event *ev ); + void ReturnProjectile ( Event *ev ); + void checkActorDead( Event *ev ); + qboolean checkActorDead( void ); + qboolean checkplayerrange ( float range , float height = 0 ); + void SetFlagOnEnemy( Event *ev ); + void TurnOnEnemyAI( Event *ev ); + void TurnOffEnemyAI( Event *ev ); + void HandleGameSpecificEvent( Event *ev ); + void EvaluateEnemies( Event *ev ); + void ForgetEnemies( Event* ev ); + bool IsFinishable(); + const str getName() const { return name; } + void SendEventToGroup( Event *ev ); + Actor* GetAttachedChildActor( const str& childName ); + void turnTowardsEntity(Entity *ent , float extraYaw ); + + void PrepareToFailMission( Event *ev ); + void FailMission( Event *ev ); + + void DebugEvent( Event *ev ); + void StartTalkBehavior(Sentient *user); + + void SetContextInterval( Event *ev ); + void SetContextInterval( float interval ); + + void SetEnemyTargeted( Event *ev ); + void SetEnemyTargeted( bool targeted ); + + void SetActivationDelay( Event *ev ); + void SetActivationDelay( float delay ); + + void SetActivationStart( Event *ev ); + void SetActivationStart(); + + void SetCheckConeOfFireDistance( Event* ev ); + void SetCheckConeOfFireDistance( float distance ); + + void AnimateOnce( Event *ev ); + + void SetDeathKnockbackValues( Event *ev ); + + + //----------------------------------------------------------------- + // State machine conditions + //----------------------------------------------------------------- + qboolean returntrue( Conditional &condition ); + qboolean checkanimname( Conditional &condition ); + qboolean checkinactive( Conditional &condition ); + qboolean checkanimdone( Conditional &condition ); + qboolean checktorsoanimdone( Conditional &condition ); + qboolean checkdead( Conditional &condition ); + qboolean checkhaveenemy( Conditional &condition ); + qboolean checkenemydead( Conditional &condition ); + qboolean checkenemydead( void ); + qboolean checkenemynoclip( Conditional &condition ); + qboolean checkcanseeenemy( Conditional &condition ); + qboolean checkcanseeplayer( Conditional &condition ); + qboolean checkcanshootenemy( Conditional &condition ); + qboolean checkenemyinfov( Conditional &condition ); + qboolean checkenemyonground( Conditional &condition ); + qboolean checkenemyrelativeyaw( Conditional &condition ); + qboolean checkenemyyawrange( Conditional &condition ); + qboolean checkenemyrange( Conditional &condition ); + qboolean checkcanjumptoenemy( Conditional &condition ); + qboolean checkcanflytoenemy( Conditional &condition ); + qboolean checkinpain( Conditional &condition ); + qboolean checksmallpain( Conditional &condition ); + qboolean checkpainyaw( Conditional &condition ); + qboolean checkpainpitch( Conditional &condition ); + qboolean checkstunned( Conditional &condition ); + qboolean checkfinished( Conditional &condition ); + qboolean checkmeleehit( Conditional &condition ); + qboolean checkblockedhit( Conditional &condition ); + qboolean checkblocked( Conditional &condition ); + qboolean checkBlockedByEnemy( Conditional &condition ); + qboolean checkonfire( Conditional &condition ); + qboolean checkotherdied( Conditional &condition ); + qboolean checkstuck( Conditional &condition ); + qboolean checknopath( Conditional &condition ); + qboolean checkSteeringFailed( Conditional &condition ); + qboolean checkbehaviordone( Conditional &condition ); + qboolean checkheadbehaviordone( Conditional &condition ); + qboolean checkeyebehaviordone( Conditional &condtion ); + qboolean checktorsobehaviordone( Conditional &condition ); + qboolean checktorsobehaviorfailed( Conditional &condition ); + qboolean checktorsobehaviorsuccess( Conditional &condition ); + qboolean checkbehaviorsuccess( Conditional &condition ); + qboolean checkbehaviorfailed( Conditional &condition ); + qboolean checktimedone( Conditional &condition ); + qboolean checkdone( Conditional &condition ); + qboolean checkplayerrange( Conditional &condition ); + qboolean checkparentrange ( Conditional &condition ); + qboolean checkmovingactorrange( Conditional &condition ); + qboolean checkchance( Conditional &condition ); + qboolean checkstatetime( Conditional &condition ); + qboolean checktimesdone( Conditional &condition ); + qboolean checkmeansofdeath( Conditional &condition ); + qboolean checknoiseheard( Conditional &condition ); + qboolean checkpartstate( Conditional &condition ); + qboolean checkpartflag( Conditional &condition ); + qboolean checkpartdead( Conditional &condition ); + qboolean checknumspawns( Conditional &condition ); + qboolean checkcommand( Conditional &condition ); + qboolean checktouched( Conditional &condition ); + qboolean checktouchedbyplayer( Conditional &condition ); + qboolean checktouchedbyplayer(); + qboolean checkactivated( Conditional &condition ); + qboolean checkused( Conditional &condition ); + qboolean checktwitch( Conditional &condition ); + qboolean checkhealth( Conditional &condition ); + qboolean checkhealthpercent( Conditional &condition ); + qboolean checkhealthpercentinrange( Conditional &condition ); + qboolean checkonground( Conditional &condition ); + qboolean checkinwater( Conditional &condition ); + qboolean checkincomingmeleeattack( Conditional &condition ); + qboolean checkincomingmeleeattack(); + qboolean checkincomingrangedattack( Conditional &condition ); + qboolean checkincomingprojectile( Conditional &condition ); + qboolean checkenemystunned( Conditional &condition ); + qboolean checkenemyinpath( Conditional &condition ); + qboolean checkstage( Conditional &condition ); + qboolean checkheld( Conditional &condition ); + qboolean checkenemymelee( Conditional &condition ); + qboolean checkenemyranged( Conditional &condition ); + qboolean checkhasthing( Conditional &condition ); + qboolean checkatcovernode( Conditional &condition ); + qboolean checkallowhangback( Conditional &condition ); + qboolean checkname( Conditional &condition ); + qboolean checkVar( Conditional &condtion ); + qboolean checkNodeExists( Conditional &condition ); + qboolean checkCoverNodes( Conditional &condition ); + qboolean checkSurfaceDamaged( Conditional &condition ); + qboolean checkBoneDamaged( Conditional &condition ); + qboolean checkRegionDamaged( Conditional &condition ); + qboolean checkCaptured( Conditional &condition ); + qboolean checkEnemyAttached( Conditional &condition ); + qboolean checkCanWalkForward( Conditional &condition ); + qboolean checkHasThrowObject( Conditional &condition ); + qboolean checkEnemyIsThrowObject( Conditional &condition ); + qboolean checkTurretMode( Conditional &condition ); + qboolean checkMeleeHitWorld( Conditional &condition ); + qboolean checkGameSpecific( Conditional &condition ); + qboolean checkWeaponReady( Conditional &condition ); + qboolean checkPlayerValid( Conditional &condition ); + qboolean checkInPreferredRange( Conditional &condtion ); + qboolean checkInAbsoluteRange( Conditional &condition ); + qboolean checkDisabled( Conditional &condition ); + qboolean checkCrippled( Conditional &condition ); + qboolean checkInAlcove( Conditional &condition ); + qboolean checkInConeOfFire( Conditional &condition ); + qboolean checkInPlayerConeOfFire( Conditional &condition ); + qboolean checkPatrolWaypointNodeInDistance( Conditional &condition ); + qboolean checkPathNodeTypeInDistance( Conditional &condition ); + qboolean checkPlayerInCallVolume( Conditional &condition ); + qboolean checkInCallVolume( Conditional &condition ); + qboolean checkUsingWeaponNamed( Conditional &condition ); + qboolean checkUsingWeaponNamed ( const str &name ); + qboolean checkHaveActiveWeapon( Conditional &condition ); + qboolean checkOutOfTorsoRange(Conditional &condition ); + qboolean checkCanAttackAnyEnemy(Conditional &condition ); + qboolean checkActorFlag( Conditional &condition ); + qboolean checkplayerranged( Conditional &condition ); + qboolean checkplayerranged(); + qboolean checkForwardDirectionClear( Conditional &condition ); + qboolean checkRearDirectionClear( Conditional &condition ); + qboolean checkLeftDirectionClear( Conditional &condition ); + qboolean checkRightDirectionClear( Conditional &condition ); + qboolean checkForwardDirectionClear(float dist); + qboolean checkRearDirectionClear(float dist); + qboolean checkLeftDirectionClear(float dist); + qboolean checkRightDirectionClear(float dist); + qboolean checkLastState( Conditional &condition ); + qboolean checkGroupMememberRange ( Conditional &condition ); + qboolean checkActorType( Conditional &condition ); + qboolean checkIsTeammate( Conditional &condition ); + qboolean checkWeaponIsMelee( Conditional &condition ); + qboolean checkWeaponChanged( Conditional &condition ); + qboolean checkPersonality( Conditional &condition ); + qboolean checkVarTimeDifference( Conditional &condition ); + qboolean checkRequestedPosture( Conditional &condition ); + qboolean checkPostureAnimDone( Conditional &condition ); + + qboolean checkCurrentEnemyLastInList( Conditional &condition ); + qboolean checkGroupAttackerCount( Conditional &condition ); + qboolean checkCurrentEnemyGroupAttackerCount( Conditional &condition ); + qboolean checkGroupAttackerCountForEntity( Conditional& condition, Entity* attackTarget ); + qboolean checkGroupAttackerCountForEntity( int checkValue, Entity* attackTarget ); + + + qboolean checkCountOfIdenticalNamesInGroup( Conditional &condition ); + qboolean checkCountOfIdenticalNamesInGroup( const str &checkName , int checkValue ); + + qboolean checkCanAttackEnemy( Conditional &condition ); + qboolean checkCanAttackEnemy(); + + qboolean checkDamageThresholdExceeded( Conditional &condition ); + qboolean checkDamageThresholdExceeded(); + + qboolean checkAttacked( Conditional &condition ); + qboolean checkAttacked(); + + qboolean checkAttackedByPlayer( Conditional &condition ); + qboolean checkAttackedByPlayer(); + + qboolean checkHelperNodeWithFlagInRange( Conditional &condition ); + qboolean checkHelperNodeWithFlagInRange( const str& flag, float range ); + + qboolean checkEnemyWeaponNamed( Conditional &condition ); + qboolean checkEnemyWeaponNamed( const str &name ); + + qboolean checkPlayerWeaponNamed( Conditional &condition ); + qboolean checkPlayerWeaponNamed( const str &name ); + + qboolean checkEnemyWithinRange( Conditional &condition ); + qboolean checkEnemyWithinRange( float min , float max ); + + qboolean checkPropChance( Conditional &condition ); + qboolean checkPropExists( Conditional &condition ); + + qboolean checkShowPain( Conditional &condition ); + qboolean checkShowPain(); + + qboolean checkPropEnemyRange( Conditional &condition ); + qboolean checkPropEnemyRange( const str& objname , const str& propname ); + + qboolean checkHaveBestWeapon( Conditional &condition ); + qboolean checkHaveBestWeapon(); + + qboolean checkPosture( Conditional &condition ); + qboolean checkPosture( const str& postureName ); + + qboolean checkAnyEnemyInRange( Conditional &condition ); + qboolean checkAnyEnemyInRange( float range ); + + qboolean checkValidCoverNodeInRange( Conditional &condition ); + qboolean checkValidCoverNodeInRange( float maxDistanceFromSelf, float minDistanceFromCurrentEnemy, float minDistanceFromPlayer ); + + qboolean checkValidCombatNodeInRange( Conditional &condition ); + qboolean checkValidCombatNodeInRange( float maxDistanceFromSelf, float minDistanceFromPlayer, bool unreserveCurrentNode = true ); + + qboolean checkValidWorkNodeInRange( Conditional &condition ); + qboolean checkValidWorkNodeInRange( float maxDistanceFromSelf, bool unreserveCurrentNode = true); + + qboolean checkValidHibernateNodeInRange( Conditional &condition ); + qboolean checkValidHibernateNodeInRange( float maxDistanceFromSelf ); + + qboolean checkValidPatrolNodeInRange( Conditional &condition ); + qboolean checkValidPatrolNodeInRange( float maxDistanceFromSelf ); + + qboolean checkValidSniperNodeInRange( Conditional &condition ); + qboolean checkValidSniperNodeInRange( float maxDistanceFromSelf ); + + qboolean checkValidCustomNodeInRange( Conditional &condition ); + qboolean checkValidCustomNodeInRange( const str &customType , float maxDistanceFromSelf ); + + qboolean checkEnemyCanSeeCurrentNode( Conditional &condition ); + qboolean checkEnemyCanSeeCurrentNode(); + + qboolean checkSpecifiedFollowTargetOutOfRange( Conditional &condition ); + qboolean checkSpecifiedFollowTargetOutOfRange(); + + qboolean checkCurrentNodeHasThisCoverType( Conditional &condition ); + qboolean checkCurrentNodeHasThisCoverType( const str &coverType ); + + qboolean checkShouldDoAction( Conditional &condition ); + qboolean checkShouldDoAction( const str &tendencyName ); + + qboolean checkInTheWay( Conditional &condition ); + qboolean checkInTheWay(); + + qboolean checkHaveArmor( Conditional &condition ); + qboolean checkHaveArmor(); + + qboolean checkWithinFollowRangeMin( Conditional &condition ); + qboolean checkWithinFollowRangeMin(); + + qboolean checkAllowedToMeleeEnemy( Conditional &condition ); + qboolean checkAllowedToMeleeEnemy(); + + qboolean checkHavePathToEnemy( Conditional &condition ); + qboolean checkHavePathToEnemy(); + + qboolean checkEnemyProjectileClose( Conditional &condition ); + qboolean checkEnemyProjectileClose(); + + qboolean checkActivationDelayTime( Conditional &condition ); + qboolean checkActivationDelayTime(); + + qboolean checkTalking( Conditional &condition ); + qboolean checkTalking(); + + qboolean checkEnemiesNearby( Conditional &condition ); + qboolean checkEnemiesNearby( float distance ); + + //----------------------------------------------------------------- + // Tendency Checks + //----------------------------------------------------------------- + qboolean checkIsAggressive ( Conditional &condition ); + qboolean checkWantsToExecutePackage( Conditional &condition ); + qboolean checkExecutedPackageInLastTimeFrame( Conditional &condition ); + + + //----------------------------------------------------------------- + // Temporary + //----------------------------------------------------------------- + qboolean checkInAIMode( Conditional &condition ); + + + virtual void Archive( Archiver &arc ); + + + protected: + void _dropActorToGround(); + void _printDebugInfo(const str &laststate , const str ¤tState , const str &legAnim , const str &torsoAnim); + void _notifyGroupOfDamage(); + void _notifyGroupSpottedEnemy(); + void _notifyGroupOfKilled(); + void _notifyGroupOfEnemy(); + qboolean _isWorkNodeValid(PathNode* node); + + qboolean _WorkNodeInDistance( float dist ); + qboolean _FleeNodeInDistance( float dist ); +}; + +typedef PathFinder FindMovementPath; +typedef PathFinder FindCoverPath; +typedef PathFinder FindFleePath; +typedef PathFinder FindEnemyPath; + + + +#endif /* actor.h */ diff --git a/dlls/game/actor_combatsubsystem.cpp b/dlls/game/actor_combatsubsystem.cpp new file mode 100644 index 0000000..7b0d77d --- /dev/null +++ b/dlls/game/actor_combatsubsystem.cpp @@ -0,0 +1,1002 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/actor_combatsubsystem.cpp $ +// $Revision:: 65 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:35p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// + +#include "_pch_cpp.h" +#include "actor_combatsubsystem.h" +#include "player.h" +#include "object.h" +#include + + +//====================================== +// CombatSubsystem Implementation +//====================================== + +// +// Name: CombatSubsystem() +// Parameters: None +// Description: Constructor() +// +CombatSubsystem::CombatSubsystem() +{ + // Should always use other constructor + gi.Error( ERR_FATAL, "CombatSubsystem::CombatSubsystem -- Default Constructor Called" ); +} + + +// +// Name: CombatSubsystem() +// Parameters: Actor *actor +// Description: Constructor +// +CombatSubsystem::CombatSubsystem( Actor *actor ) +{ + //Initialize our Actor + if ( actor ) + act = actor; + else + gi.Error( ERR_DROP, "MovementSubsystem::MovementSubsystem -- actor is NULL" ); + + _init(); +} + + +// +// Name: ~CombatSubystem() +// Parameters: None +// Description: Destructor +// +CombatSubsystem::~CombatSubsystem() +{ + +} + + +// +// Name: UseActorWeapon() +// Parameters: const str &weaponName +// Description: Sets the weapon to be active, and attaches it +// +void CombatSubsystem::UseActorWeapon(const str &weaponName , weaponhand_t hand ) +{ + Weapon *weapon; + + //First see if we just want to put our current item away + if ( !stricmp( weaponName.c_str() , "none" ) ) + { + act->DeactivateWeapon(WEAPON_LEFT); + act->DeactivateWeapon(WEAPON_RIGHT); + act->DeactivateWeapon(WEAPON_DUAL); + act->AddStateFlag( STATE_FLAG_CHANGED_WEAPON ); + act->ClearTorsoAnim(); + _activeWeapon.weapon = NULL; + + return; + } + + weapon = ( Weapon * )act->FindItem( weaponName.c_str() ); + + // Check to see if player has the weapon + if ( !weapon ) + { + act->warning( "CombatSubsystem::UseActorWeapon", "Actor does not have weapon %s", weaponName.c_str() ); + return; + } + + // If we don't already have an active weapon, just go ahead and make this one active + if ( !_activeWeapon.weapon ) + { + _activeWeapon.weapon = weapon; + _activeWeapon.hand = hand; + _activeWeapon.weapon->SetOwner( act ); + act->ActivateWeapon(weapon, hand); + act->AddStateFlag( STATE_FLAG_CHANGED_WEAPON ); + return; + } + + // See if we are already using this weapon + if ( weapon == _activeWeapon.weapon && hand == _activeWeapon.hand ) + return; + + // Well, we have a weapon in the same hand, put away the weapon thats there. + Weapon *oldweapon = act->GetActiveWeapon(hand); + if ( oldweapon ) + act->DeactivateWeapon(hand); + + // Activate this weapon + _activeWeapon.weapon = weapon; + _activeWeapon.hand = hand; + _activeWeapon.weapon->SetOwner( act ); + act->ActivateWeapon(weapon, hand); + act->AddStateFlag( STATE_FLAG_CHANGED_WEAPON ); +} + +// +// Name: UsingWeaponNamed() +// Parameters: None +// Description: Checks if our _activeWeapon has the same name +// +bool CombatSubsystem::UsingWeaponNamed( const str &weaponName ) +{ + if ( !_activeWeapon.weapon ) + return false; + + + if ( !Q_stricmp(_activeWeapon.weapon->getName().c_str() , weaponName.c_str() ) ) + return true; + + return false; +} + +// +// Name: HaveWeapon() +// Parameters: None +// Description: Returns True or False depending on _activeWeapon +// +bool CombatSubsystem::HaveWeapon() +{ + if ( _activeWeapon.weapon ) + return true; + + return false; +} + +bool CombatSubsystem::CanAttackEnemy() +{ + Entity *currentEnemy = NULL; + + currentEnemy = act->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + //Did this so I could check the f'ng return value + bool attack = CanAttackTarget( currentEnemy ); + return attack; + + +} + +bool CombatSubsystem::WeaponIsFireType( firetype_t fire_type ) +{ + firetype_t weapon_fire_type; + + if ( !_activeWeapon.weapon ) + return false; + + weapon_fire_type = _activeWeapon.weapon->GetFireType( FIRE_MODE1 ); + + if ( weapon_fire_type == fire_type ) + return true; + + + return false; +} + +// +// Name: CanShootTarget() +// Parameters: Entity *target +// Description: Checks if the Actor can shoot the target from its currentPosition +// +bool CombatSubsystem::CanAttackTarget( Entity *target ) +{ + Vector startPos; + + if ( !target ) + return false; + + if ( act->CurrentAnim( legs ) < 0 ) + return false; + + if ( _activeWeapon.weapon && ( FT_MELEE != _activeWeapon.weapon->GetFireType( FIRE_MODE1 ) )) + GetGunPositionData(&startPos); + else + startPos = act->centroid; + + bool attack = CanAttackTargetFrom( target , startPos ); + return attack; + +} + + +// +// Name: CanShootTargetFrom() +// Parameters: Entity *target +// Vector &startPos +// Description: Checks if the Actor can shoot the target from startPos +// +bool CombatSubsystem::CanAttackTargetFrom( Entity *target , const Vector &startPos ) +{ + if ( (_activeWeapon.weapon && !IsTargetInWeaponRange(target)) || !target ) + return false; + + //Make sure target isn't too far "behind" us. + Vector startToTarget = target->centroid - startPos; + startToTarget.normalize(); + + if ( _activeWeapon.weapon ) + { + float dot; + dot = DotProduct(startToTarget , act->movementSubsystem->getAnimDir() ); + + if ( dot <= -.10 ) + return false; + } + + + //We don't want to check constantly + if ( level.time < _nextTimeTracedToTarget ) + return _canShootTarget; + + _canShootTarget = _traceHitTarget( target , startPos ); + _nextTimeTracedToTarget = level.time + _traceInterval + G_Random(); + //_nextTimeTracedToTarget = 0.0f; + + return _canShootTarget; +} + +// +// Name: IsTargetInWeaponRange() +// Parameters: Entity *target +// Description: Checks if the target is within the range of _activeWeapon +// +bool CombatSubsystem::IsTargetInWeaponRange( Entity *target ) +{ + if ( !_activeWeapon.weapon ) + return false; + + Vector distance; + distance = target->origin - act->origin; + + if ( distance.length() <= _activeWeapon.weapon->GetMaxRange() ) + return true; + + return false; +} + + +// +// Name: SetTraceInterval() +// Parameters: float interval +// Description: Sets _traceInterval +// +void CombatSubsystem::SetTraceInterval( const float interval ) +{ + _traceInterval = interval; +} + + +// +// Name: DoArchive() +// Parameters: Archiver &arc +// Actor *actor +// Description: Sets the Actor Pointer and Calls Archive() +// +void CombatSubsystem::DoArchive( Archiver &arc , Actor *actor ) +{ + Archive( arc ); + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "MovementSubsystem::DoArchive -- actor is NULL" ); + +} + +void CombatSubsystem::FireWeapon() +{ + firemode_t mode=FIRE_MODE1; + mode = WeaponModeNameToNum("primary"); + + if ( _activeWeapon.weapon ) + _activeWeapon.weapon->Fire( mode ); + +} + +void CombatSubsystem::StopFireWeapon() +{ + + if ( _activeWeapon.weapon ) + { + if ( _activeWeapon.weapon->animate->HasAnim("endfire") ) + _activeWeapon.weapon->SetAnim( "endfire" ); + else + _activeWeapon.weapon->ForceIdle(); + } +} + +void CombatSubsystem::AimWeaponTag(const Vector &targetPos ) +{ + Vector controllerAngles; + Vector aimAngles; + Vector barrelToEnemy; + + Vector gunPos, gunForward, gunRight, gunUp; + + //currentEnemy = act->enemyManager->GetCurrentEnemy(); + + if ( !_activeWeapon.weapon ) + return; + + _activeWeapon.weapon->setOrigin(); + _activeWeapon.weapon->setAngles(); + + _activeWeapon.weapon->SetControllerAngles( WEAPONBONE_BARREL_TAG, vec_zero ); + + GetGunPositionData( &gunPos, &gunForward, &gunRight, &gunUp ); + + + + //G_DebugLine( gunPos, targetPos, 1.0f, 0.0f, 1.0f, 1.0f ); + //G_DebugLine( gunPos, newTargetPos, 1.0f, 1.0f, 1.0f, 1.0f ); + + + Vector mypos, myforward, myleft, myup; + _activeWeapon.weapon->GetTag( WEAPONBONE_BARREL_TAG, &mypos, &myforward, &myleft, &myup ); + + Vector tagforwardPos = gunPos + (100.0f * myforward); + Vector tagleftPos = gunPos + (100.0f * myleft); + Vector tagupPos = gunPos + (100.0f * myup); + //G_DebugLine( gunPos, tagforwardPos, 0.9f, 0.0f, 0.0f, 1.0f ); + //G_DebugLine( gunPos, tagleftPos, 0.0f, 0.9f, 0.0f, 1.0f ); + //G_DebugLine( gunPos, tagupPos, 0.0f, 0.0f, 0.9f, 1.0f ); + + float gfToWorld[ 3 ][ 3 ]; + float worldToGf[ 3 ][ 3 ]; + gunForward.copyTo( gfToWorld[ 0 ] ); + gunRight.copyTo( gfToWorld[ 1 ] ); + gunUp.copyTo( gfToWorld[ 2 ] ); + + TransposeMatrix( gfToWorld, worldToGf ); + + Vector barrelToEnemyVector = targetPos - gunPos; + vec3_t barrelToEnemyVec3_t; + barrelToEnemyVector.copyTo( barrelToEnemyVec3_t ); + + vec3_t barrelToEnemyTransformedVec3_t; + MatrixTransformVector( barrelToEnemyVec3_t, worldToGf, barrelToEnemyTransformedVec3_t ); + Vector barrelToEnemyTransformed( barrelToEnemyTransformedVec3_t ); + barrelToEnemyTransformed.normalize(); + barrelToEnemyTransformed = barrelToEnemyTransformed.toAngles(); + barrelToEnemyTransformed.EulerNormalize(); + aimAngles = -barrelToEnemyTransformed; + + Vector aimUp, aimLeft, aimForward; + float spreadX, spreadY; + aimAngles.AngleVectors( &aimForward, &aimLeft, &aimUp ); + spreadX = GetDataForMyWeapon( "spreadx" ); + spreadY = GetDataForMyWeapon( "spready" ); + + // figure the new projected impact point based upon computed spread + if ( _activeWeapon.weapon->GetFireType(FIRE_MODE1) == FT_PROJECTILE ) + { + // Apply Spread + aimForward = ( aimForward * _activeWeapon.weapon->GetRange(FIRE_MODE1) ) + + ( aimLeft * G_CRandom(spreadX) ) + + ( aimUp * G_CRandom(spreadY) ); + } + + // after figuring spread location, re-normalize vectors + aimForward.normalize(); + //aimLeft = Vector::Cross(&aimForward,&aimUp); + //aimUp = Vector::Cross(*aimLeft,*aimUp); + + aimAngles = aimForward.toAngles(); + + + + // aimAngles = vec_zero; + + /* + barrelToEnemy = targetPos - gunPos; + barrelToEnemy.normalize(); + barrelToEnemy = barrelToEnemy.toAngles(); + barrelToEnemy.EulerNormalize(); + gunForward = gunForward.toAngles(); + gunForward.EulerNormalize(); + + aimAngles = barrelToEnemy - gunForward; + //aimAngles = gunForward - barrelToEnemy; + + aimAngles.EulerNormalize(); + aimAngles = vec_zero; + Vector oldAngles = _activeWeapon.weapon->GetControllerAngles( WEAPONBONE_BARREL_TAG ); + */ + _activeWeapon.weapon->SetControllerTag( WEAPONBONE_BARREL_TAG, gi.Tag_NumForName( _activeWeapon.weapon->edict->s.modelindex, "tag_barrel" ) ); + _activeWeapon.weapon->SetControllerAngles( WEAPONBONE_BARREL_TAG, aimAngles ); + + //GetGunPositionData( &gunPos, &gunForward, &gunRight, &gunUp ); + + //Draw Trace + if ( g_showbullettrace->integer ) + { + Vector test; + GetGunPositionData( &gunPos , &gunForward ); + test = gunForward * 1000 + gunPos; + + G_DebugLine( gunPos, test, 1.0f, 0.0f, 0.0f, 1.0f ); + + Vector barrelForward; + Vector barrelStart; + aimAngles.AngleVectors ( &barrelForward ); + barrelStart = barrelForward * 1000 + gunPos; + //G_DebugLine( gunPos, barrelStart, 1.0f, 0.0f, 0.0f, 1.0f ); + + //G_DebugLine( gunPos, targetPos, 1.0f, 1.0f, 0.0f, 1.0f ); + } +} + +void CombatSubsystem::AimWeaponTag(Entity *target) +{ + str targetBone = target->getTargetPos(); + Vector targetPos = target->centroid; + + if ( targetBone.length() ) + { + if ( gi.Tag_NumForName( target->edict->s.modelindex , targetBone ) > 0 ) + { + target->GetTag( targetBone.c_str() , &targetPos , NULL , NULL , NULL ); + } + } + + Vector newTargetPos = targetPos; + + if ( !_activeWeapon.weapon ) return; + + if ( _activeWeapon.weapon->GetFireType(FIRE_MODE1) == FT_PROJECTILE ) + newTargetPos = GetLeadingTargetPos(_activeWeapon.weapon->GetProjectileSpeed() , targetPos , target ); + + AimWeaponTag(newTargetPos); +} + +void CombatSubsystem::ClearAim() +{ + _activeWeapon.weapon->SetControllerTag( WEAPONBONE_BARREL_TAG, gi.Tag_NumForName( _activeWeapon.weapon->edict->s.modelindex, "tag_barrel" ) ); + _activeWeapon.weapon->SetControllerAngles( WEAPONBONE_BARREL_TAG, vec_zero ); +} + +// +// Name: Archive() +// Parameters: Archiver &arc +// Description: Archives Class Data +// +void CombatSubsystem::Archive(Archiver &arc ) +{ + _activeWeapon.Archive( arc ); + arc.ArchiveFloat ( &_nextTimeTracedToTarget ); + arc.ArchiveFloat ( &_traceInterval ); + arc.ArchiveBool ( &_canShootTarget ); + + arc.ArchiveFloat( &_yawDiff ); +} + + +// +// Name: GetGunPosition() +// Parameters: None +// Description: Gets the GunBarrel Position +// +void CombatSubsystem::GetGunPositionData(Vector *pos , Vector *forward , Vector *right , Vector *up) +{ + int tag_num; + + if ( !_activeWeapon.weapon ) + gi.Error( ERR_FATAL, "Actor has no activeweapon" ); + + tag_num = gi.Tag_NumForName( _activeWeapon.weapon->edict->s.modelindex, "tag_barrel" ); + if ( tag_num < 0 ) + gi.Error( ERR_FATAL, "Weapon has no tag_barrel" ); + + _activeWeapon.weapon->GetActorMuzzlePosition( pos, forward, right, up ); + +} + +float CombatSubsystem::GetAimGunYaw( const Vector &target ) +{ + Vector GunPos, GunForward, GunRight, GunUp; + Vector GunPosToOrigin , OriginToGunPos, OriginToTarget; + Vector TagForwardAngles, GunToOriginAngles, OriginToGunAngles, OriginToTargetAngles; + + float AngleDiff; + float yaw; + + // Vars for calculating the yaw... + float a , r , d; + + + // Get our Gun Data + GetGunPositionData( &GunPos, &GunForward, &GunRight, &GunUp ); + + + // Get Vectors to Gun and To Target; + GunPosToOrigin = act->origin - GunPos; + OriginToGunPos = GunPos - act->origin; + OriginToTarget = target - act->origin; + + // Zero out the Z + GunPosToOrigin.z = 0.0f; + OriginToTarget.z = 0.0f; + OriginToGunPos.z = 0.0f; + + // Get Lengths + a = GunPosToOrigin.length(); + d = OriginToTarget.length(); + + // Get Angle Difference + TagForwardAngles = GunForward.toAngles(); + GunToOriginAngles = GunPosToOrigin.toAngles(); + OriginToGunAngles = OriginToGunPos.toAngles(); + OriginToTargetAngles = OriginToTarget.toAngles(); + + AngleDiff = AngleNormalize180( GunToOriginAngles[YAW] - TagForwardAngles[YAW] ); + AngleDiff = 180.0f - AngleDiff; + r = a * sin( DEG2RAD(AngleDiff) ); + + float angleA; + angleA = RAD2DEG(acos ( r / d )); + + float angleB; + angleB = RAD2DEG(acos ( r / a )); + + float angleC; + angleC = AngleNormalize180( OriginToTargetAngles[YAW] - OriginToGunAngles[YAW] ); + + float totalAngle; + totalAngle = angleB + angleC; + + float finalAngle; + finalAngle = totalAngle - angleA; + + yaw = AngleNormalize180(finalAngle); + + return yaw; + +} + + +float CombatSubsystem::GetAimGunPitch( const Vector &target ) +{ + Vector GunPos, GunForward, GunRight, GunUp , TagToTarget; + Vector TagForwardAngles, TagToTargetAngles; + + // Get our Gun Data + GetGunPositionData( &GunPos, &GunForward, &GunRight, &GunUp ); + TagToTarget = target - GunPos; + + + // Get Angle Difference + TagForwardAngles = GunForward.toAngles(); + TagToTargetAngles = TagToTarget.toAngles(); + + return AngleNormalize180(TagToTargetAngles[PITCH] - TagForwardAngles[PITCH]); + +} + +// +// Name: _traceHitTarget() +// Parameters: Entity *target +// const Vector &startPos +// Description: Checks of the a trace hits the target +// +bool CombatSubsystem::_traceHitTarget(Entity *target , const Vector &startPos ) +{ + Entity *t; + int mask; + trace_t trace; + Vector targetPos; + str targetBone; + + mask = MASK_SHOT; + + targetBone = target->getTargetPos(); + + targetPos = target->centroid; + + if ( targetBone.length() ) + { + if ( gi.Tag_NumForName( target->edict->s.modelindex , targetBone ) > 0 ) + { + target->GetTag( targetBone.c_str() , &targetPos , NULL , NULL , NULL ); + } + } + + Vector attackDir = targetPos - startPos; + float attackDirLength = attackDir.length(); + attackDir.normalize(); + attackDir *= attackDirLength + ( 32.0f ); + attackDir += startPos; + targetPos = attackDir; + + trace = G_FullTrace( startPos, vec_zero, vec_zero, targetPos, act, mask, false, "CombatSubsystem::_traceHitTarget" ); + //G_DebugLine( startPos, targetPos, .5f, 1.0f, .5f, 1.0f ); + + if ( trace.startsolid ) + { + if ( trace.ent == target->edict ) + return true; + + return false; + } + + + /*if ( trace.startsolid ) + { + // If we hit the guy we wanted, then shoot + if ( trace.ent == target->edict ) + return true; + + trace = G_FullTrace( act->centroid, vec_zero, vec_zero, targetPos, act, mask, false, "CombatSubsystem::_traceHitTarget" ); + + if ( trace.startsolid ) + return false; + }*/ + + + // see if we hit anything at all + if ( !trace.ent ) + return false; + + + // If we hit the guy we wanted, then shoot + if ( trace.ent == target->edict ) + return true; + + + // If we hit someone else we don't like, then shoot + t = trace.ent->entity; + if ( act->enemyManager->IsValidEnemy( t ) ) + { + act->enemyManager->SetCurrentEnemy( trace.ent->entity ); + // We want to return false though, so we don't add + // the attempted enemy to the hatelist + return false; + } + + + + // if we hit something breakable, check if shooting it will + // let us shoot someone. + if ( t->isSubclassOf( Object ) || t->isSubclassOf( ScriptModel ) ) + { + trace = G_Trace( Vector( trace.endpos ), vec_zero, vec_zero, target->centroid, t, mask, false, "CombatSubsystem::_traceHitTarget 2" ); + if ( trace.startsolid ) + return false; + + // see if we hit anything at all + if ( !trace.ent ) + return false; + + // If we hit the guy we wanted, then shoot + if ( trace.ent == target->edict ) + return true; + + // If we hit someone else we don't like, then shoot + if ( act->enemyManager->IsValidEnemy( trace.ent->entity ) ) + return true; + + // Forget it then + return false; + } + + return false; + +} + +// +// Name: _init() +// Parameters: None +// Description: Initializes Class Data +// +void CombatSubsystem::_init() +{ + _activeWeapon.weapon = NULL; + _nextTimeTracedToTarget = 0; + _traceInterval = DEFAULT_TRACE_INTERVAL; + _canShootTarget = false; + _yawDiff = 0.0f; +} + +//-------------------------------------------------------------- +// Name: GetBestAvailableWeapon() +// Class: CombatSubsystem +// +// Description: Iterates through the actor's inventory list and +// checks the power rating ( modified by range and armor ) +// of each weapon, returning the best one. +// +// Parameters: Entity *target -- The target we are trying to shoot +// +// Returns: None +//-------------------------------------------------------------- +WeaponPtr CombatSubsystem::GetBestAvailableWeapon( Entity *target ) +{ + Item *item; + Weapon *weapon; + Weapon *bestWeapon; + float bestPowerRating; + float powerRating; + + item = NULL; + weapon = NULL; + bestWeapon = NULL; + bestPowerRating = 0.0f; + + // Try and grab the first item. If you pass a null into the + // NextItem() it will attempt to grab the first item in the + // inventory list + item = act->NextItem(item); + + // Loop as long as we have inventory items to check + while ( item ) + { + if ( item->isSubclassOf(Weapon) ) + { + weapon = (Weapon*)(Item*)item; + if ( weapon ) + { + powerRating = getModifiedPowerRating( target, weapon ); + if ( powerRating > bestPowerRating ) + { + bestPowerRating = powerRating; + bestWeapon = weapon; + } + } + } + + item = act->NextItem(item); + } + + return bestWeapon; +} + + +//-------------------------------------------------------------- +// Name: getModifiedPowerRating() +// Class: CombatSubsystem +// +// Description: Returns the powerRating of the weapon modified by +// such conditions as Armor, Range, Spread, and speed +// of the projectile +// +// Parameters: Entity *target -- Target we're trying to shoot +// Weapon *weapon -- Weapon we're trying to shoot with +// +// Returns: weaponPowerRating +//-------------------------------------------------------------- +float CombatSubsystem::getModifiedPowerRating( Entity *target, Weapon *weapon ) +{ + float rangeToTarget; + float weaponPowerRating; + float weaponRange; + float weaponSpread; + float hitPercentage; + float skillLevel; + Actor *actTarget; + Vector selfToTarget; + + // If we don't have a target we can't shoot it + if ( !target ) + { + //Assert to let us know this occurred + assert ( target != NULL ); + return 0.0f; + } + + // Set our default values + weaponPowerRating = weapon->GetPowerRating(); + weaponRange = weapon->GetRange( FIRE_MODE1 ); + + // Calculate our distance to the target + selfToTarget = target->origin - act->origin; + rangeToTarget = selfToTarget.length(); + + + // If we are out of range, we can't shoot target + if ( rangeToTarget > weaponRange ) + return 0.0f; + + // Check if we can even hurt the target with this weapon + if ( target->isSubclassOf(Actor) ) + { + actTarget = (Actor*)target; + if ( !actTarget->canBeDamagedBy( weapon->GetMeansOfDeath(FIRE_MODE1) ) ) + return 0.0f; + } + + //Calculate our hit % + hitPercentage = 0.0; + if ( (weapon->flags |= FT_BULLET) || (weapon->flags |= FT_MELEE) ) + { + weaponSpread = weapon->GetBulletSpreadX(FIRE_MODE1); + if ( weaponSpread < 1.0 ) + weaponSpread = 1.0f; + + hitPercentage = rangeToTarget/weaponRange * weaponSpread; + } + else + { + hitPercentage = weapon->GetProjectileSpeed() / rangeToTarget; + } + + // Get our skill level with this weapon -- Should be a number between 0.0 and 1.0 + // all skill levels default to 1.0 so it won't affect anything directly + skillLevel = weapon->GetSkillLevel(); + + return hitPercentage * weaponPowerRating * skillLevel; +} + +//-------------------------------------------------------------- +// Name: GetActiveWeaponPowerRating() +// Class: CombatSubsystem +// +// Description: Returns the power rating of the ActiveWeapon +// +// Parameters: Entity *target +// +// Returns: float +//-------------------------------------------------------------- +float CombatSubsystem::GetActiveWeaponPowerRating( Entity *target ) +{ + if ( _activeWeapon.weapon ) + return getModifiedPowerRating( target, _activeWeapon.weapon ); + + return 0.0f; +} + +str CombatSubsystem::GetActiveWeaponName() +{ + str name; + + if ( _activeWeapon.weapon ) + name = _activeWeapon.weapon->getName(); + + return name; +} + +str CombatSubsystem::GetActiveWeaponArchetype() +{ + str atype; + + if ( _activeWeapon.weapon ) + atype = _activeWeapon.weapon->getArchetype(); + + return atype; +} + +bool CombatSubsystem::GetProjectileLaunchAngles( Vector &launchAngles, const Vector &launchPoint, const float initialSpeed, const float gravity , const bool useHighTrajectory ) const +{ + Entity const *target = act->enemyManager->GetCurrentEnemy(); + + if ( target ) + { + const Vector targetPoint( target->centroid ); + + Trajectory projectileTrajectory( launchPoint, targetPoint, initialSpeed, gravity * -sv_currentGravity->value , useHighTrajectory ); + if ( projectileTrajectory.GetTravelTime() > 0.0f ) + { + launchAngles.setPitch( projectileTrajectory.GetLaunchAngle() ); + + Vector direction( targetPoint - launchPoint ); + direction.z = 0.0f; + direction.normalize(); + + launchAngles.setYaw( direction.toYaw() ); + launchAngles.setRoll( 0.0f ); + return true; + } + + } + return false; +} + +bool CombatSubsystem::shouldArcProjectile() +{ + if ( _activeWeapon.weapon ) + return _activeWeapon.weapon->shouldArcProjectile(); + + return false; +} + +float CombatSubsystem::GetLowArcRange() +{ + if ( _activeWeapon.weapon ) + return _activeWeapon.weapon->GetLowArcRange(); + + return 0.0; +} + +const str CombatSubsystem::GetAnimForMyWeapon(const str &property ) +{ + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(act->getArchetype()) ) + return ""; + + str objname = act->combatSubsystem->GetActiveWeaponArchetype(); + objname = "Hold" + objname; + + if ( gpm->hasProperty(objname, property ) ) + return gpm->getStringValue( objname , property ); + + return ""; +} + +float CombatSubsystem::GetDataForMyWeapon( const str &property ) +{ + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(act->getArchetype()) ) + return 0.0; + + str objname = act->combatSubsystem->GetActiveWeaponArchetype(); + objname = "Hold" + objname; + + if ( gpm->hasProperty(objname, property ) ) + return gpm->getFloatValue( objname , property ); + + return 0.0; +} + +void CombatSubsystem::OverrideSpread( float spreadX , float spreadY ) +{ + if ( !_activeWeapon.weapon ) return; + + _activeWeapon.weapon->SetBulletSpread( spreadX , spreadY ); +} + +Vector CombatSubsystem::GetLeadingTargetPos( float projSpeed , Vector originalTargetPos , Entity *target ) +{ + Vector newTargetPos = originalTargetPos; + + if ( !target ) + return newTargetPos; + + if ( target->groundentity ) + { + + // Here we're going to try and lead the target; + Vector targetVelocity = target->velocity; + if ( projSpeed <= 0 ) + projSpeed = 1; + + Vector selfToTarget = originalTargetPos - act->centroid; + float distToTarget = selfToTarget.length(); + float timeToTarget = distToTarget/projSpeed; + Vector enemyMoveDir = targetVelocity; + float targetSpeed = targetVelocity.length(); + + enemyMoveDir.normalize(); + + // Parameterize aim leading over the interval [minLeadFactor,maxLeadFactor] + // such that 0 is targetPos and 1 is newTargetPos. + // + // Select a random parameter value in that range and interpolate + // between the two positions. + // Essentially, the shot will aim at some random point between + // where the target is now and where it (presumably) will be + // given the lead distance. + // + // When is 0, shots aim at the current position + // of the target. When is 1, shots aim at + // the best-guess lead-position of the target. When + // is greater than 1, shots will over-lead the player. Note + // that in the latter case shots may actually hit the player + // if he is circle-strafing or moving closer/farther in addition + // to his lateral movement. + // + float leadFraction = act->minLeadFactor + G_Random( act->maxLeadFactor - act->minLeadFactor ); + Vector leadVector = enemyMoveDir * targetSpeed * timeToTarget; + newTargetPos = originalTargetPos + (leadFraction * leadVector); + } + + + return newTargetPos; +} diff --git a/dlls/game/actor_combatsubsystem.h b/dlls/game/actor_combatsubsystem.h new file mode 100644 index 0000000..6d39bc0 --- /dev/null +++ b/dlls/game/actor_combatsubsystem.h @@ -0,0 +1,97 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actor_combatsubsystem.h $ +// $Revision:: 21 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Combat Related Classes +// + +class CombatSubsystem; + +#ifndef __ACTOR_COMBAT_SUBSYSTEM_H__ +#define __ACTOR_COMBAT_SUBSYSTEM_H__ + +#include "actor.h" +#include "actorincludes.h" +#include "weapon.h" + +//============================ +// Class CombatSubsystem +//============================ +// +// Encapsulates combat related data and functionality for the actor +// +class CombatSubsystem + { + public: + CombatSubsystem(); + CombatSubsystem( Actor *actor ); + ~CombatSubsystem(); + + bool CanAttackTarget ( Entity *target ); + bool CanAttackTargetFrom ( Entity *target , const Vector &startPos ); + bool IsTargetInWeaponRange ( Entity *target ); + bool UsingWeaponNamed ( const str &weaponName ); + bool WeaponIsFireType ( firetype_t fire_type ); + bool HaveWeapon (); + bool CanAttackEnemy (); + + void UseActorWeapon (const str &weaponName , weaponhand_t hand ); + void SetTraceInterval ( float interval ); + void FireWeapon (); + void StopFireWeapon (); + void AimWeaponTag (Entity *target); + void AimWeaponTag (const Vector &targetPos); + void ClearAim (); + + void GetGunPositionData ( Vector *pos , Vector *forward = NULL, Vector *right = NULL, Vector *up = NULL ); + float GetAimGunYaw ( const Vector &target ); + float GetAimGunPitch ( const Vector &target ); + WeaponPtr GetBestAvailableWeapon ( Entity *target ); + float GetActiveWeaponPowerRating ( Entity *target ); + str GetActiveWeaponName (); + str GetActiveWeaponArchetype(); + + bool GetProjectileLaunchAngles( Vector &launchAngles, const Vector &launchPoint, const float initialSpeed, const float gravity , const bool useHighTrajectory = false ) const; + bool shouldArcProjectile(); + float GetLowArcRange(); + + void OverrideSpread ( float spreadX , float spreadY ); + + const str GetAnimForMyWeapon( const str& property ); + float GetDataForMyWeapon( const str& property ); + + Vector GetLeadingTargetPos( float projSpeed , Vector originalTargetPos , Entity *target ); + + // Archiving + void DoArchive ( Archiver &arc , Actor *actor ); + virtual void Archive ( Archiver &arc ); + + protected: + void _init(); + bool _traceHitTarget ( Entity *target , const Vector &startPos ); + float getModifiedPowerRating ( Entity *target , Weapon *weapon ); + + + private: + ActiveWeapon _activeWeapon; + float _nextTimeTracedToTarget; + float _traceInterval; + bool _canShootTarget; + float _yawDiff; + + Actor *act; + }; + + +#endif /* __ACTOR_COMBAT_SUBSYSTEM_H__ */ diff --git a/dlls/game/actor_enemymanager.cpp b/dlls/game/actor_enemymanager.cpp new file mode 100644 index 0000000..4e1de8b --- /dev/null +++ b/dlls/game/actor_enemymanager.cpp @@ -0,0 +1,1074 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/actor_enemymanager.cpp $ +// $Revision:: 41 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:35p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// + +#include "_pch_cpp.h" +#include "actor_enemymanager.h" +#include "player.h" +#include "object.h" + +//====================================== +// EnemyManager Implementation +//===================================== + +// +// Name: EnemyManager() +// Parameters: None +// Description: Constructor +// +EnemyManager::EnemyManager() +{ + // Should always use other constructor + gi.Error( ERR_FATAL, "EnemyManager::EnemyManager -- Default Constructor Called" ); +} + +// +// Name: EnemyManager() +// Parameters: Actor *actor +// Description: Constructor +// +EnemyManager::EnemyManager( Actor *actor ) +{ + //Initialize our Actor + if ( actor ) + act = actor; + else + gi.Error( ERR_DROP, "EnemyManager::EnemyManager -- actor is NULL" ); + + _lockedOnCurrentEnemy = false; + +} + +// +// Name: ~EnemyManager() +// Parameters: None +// Description: Destructor +// +EnemyManager::~EnemyManager() +{ +} + + +// +// Name: GetCurrentEnemy() +// Parameters: None +// Description: Returns the _currentEnemy +// +EntityPtr EnemyManager::GetCurrentEnemy() +{ + if ( act->forcedEnemy ) + { + if ( act->forcedEnemy->health <= 0 ) + { + act->forcedEnemy = NULL; + _lockedOnCurrentEnemy = false; + return _currentEnemy; + } + else + return act->forcedEnemy; + } + + if ( !_currentEnemy ) + FindHighestHateEnemy(); + + return _currentEnemy; +} + +// +// Name: SetCurrentEnemy() +// Parameters: Entity *enemy +// Description: sets _currentEnemy +// +void EnemyManager::SetCurrentEnemy( Entity *enemy ) +{ + if ( enemy ) + _currentEnemy = enemy; + else + { + _lockedOnCurrentEnemy = false; + } +} + + +EntityPtr EnemyManager::GetAlternateTarget() +{ + return _alternateTarget; +} + +void EnemyManager::SetAlternateTarget( Entity *target ) +{ + _alternateTarget = target; +} +// +// Name: FindHightestHateEnemy() +// Parameters: None +// Description: Sets the _currentEnemy to the highest enemy on the hate list +// +void EnemyManager::FindHighestHateEnemy() +{ + float hateValue = 0; + HateListEntry_t listIndex; + float hateFactor; + + + + if ( IsLockedOnCurrentEnemy() ) + return; + + + for ( int i = _hateList.NumObjects() ; i > 0 ; i-- ) + { + hateFactor = 100; + + listIndex = _hateList.ObjectAt( i ); + listIndex.hate = listIndex.hate * ( 1 / listIndex.lastDistance ); + + if ( !listIndex.enemy ) + { + _hateList.RemoveObjectAt( i ); + continue; + } + + //If the enemy is a player, make sure to modify our hate + if ( listIndex.enemy->isSubclassOf( Sentient ) && !act->GetActorFlag(ACTOR_FLAG_UPDATE_HATE_WITH_ATTACKERS) ) + { + Sentient *theEnemy; + theEnemy = (Sentient*)(Entity*)listIndex.enemy; + hateFactor *= theEnemy->GetHateModifier(); + } + + + listIndex.hate *= hateFactor; + + if ( listIndex.hate >= hateValue && listIndex.hate > 0.0 ) + { + _currentEnemy = listIndex.enemy; + hateValue = listIndex.hate; + } + + } +} + +// +// Name: FindClosestEnemy() +// Parameters: None +// Description: Sets the _currentEnemy to the enemy closest to the actor +// +void EnemyManager::FindClosestEnemy() +{ + float distance = 99999999.9; + + for ( int i = 1 ; i <= _hateList.NumObjects() ; i++ ) + { + EntityPtr enemy; + HateListEntry_t listIndex; + + listIndex = _hateList.ObjectAt( i ); + + if ( !act->combatSubsystem->CanAttackTarget( listIndex.enemy ) ) + continue; + + if ( listIndex.lastDistance < distance ) + { + _currentEnemy = listIndex.enemy; + distance = listIndex.lastDistance; + } + } +} + +void EnemyManager::FindNextEnemy() +{ + HateListEntry_t listIndex; + float hateValue; + + hateValue = 0; + for ( int i = 1 ; i <= _hateList.NumObjects() ; i++ ) + { + listIndex = _hateList.ObjectAt( i ); + if ( listIndex.hate <= _currentEnemyHate && listIndex.hate > hateValue ) + { + hateValue = listIndex.hate; + _lastEnemy = _currentEnemy; + _currentEnemy = listIndex.enemy; + _currentEnemyHate = hateValue; + } + } +} + +// +// Name: ClearCurrentEnemy() +// Parameters: None +// Description: sets the _currentEnemy to NULL +// +void EnemyManager::ClearCurrentEnemy() +{ + _lockedOnCurrentEnemy = false; + _currentEnemy = NULL; +} + +// +// Name: ClearHateList() +// Parameters: None +// Description: Wipes out the hate list +// +void EnemyManager::ClearHateList() +{ + for ( int i = _hateList.NumObjects() ; i > 0 ; i-- ) + { + _hateList.RemoveObjectAt( i ); + } + + _hateList.FreeObjectList(); +} + +// +// Name: Likes() +// Parameters: Entity *ent +// Description: Checks if Actor likes the entity +// +qboolean EnemyManager::Likes( Entity *ent ) +{ + Actor *actor; + + if ( !ent ) + return false; + + if ( ent->isClient() ) + { + return ( act->actortype == IS_FRIEND ); + } + + if ( act->actortype == IS_MONSTER ) + { + // Monsters don't like anyone + return false; + } + + if ( ent->isSubclassOf( Actor ) ) + { + actor = ( Actor * )ent; + + if ( actor->enemytype == act->enemytype ) + return true; + + if ( actor->actortype != act->actortype ) + return false; + else if ( act->actortype == IS_FRIEND ) + return true; + } + + return false; +} + +// +// Name: Hates() +// Parameters: Entity *ent +// Description: Checks if Actor hates the entity +// +qboolean EnemyManager::Hates( Entity *ent ) +{ + Actor *actor; + + if ( !ent ) + return false; + + if ( ent->isClient() ) + { + return ( act->actortype != IS_CIVILIAN ) && (( act->actortype != IS_FRIEND ) && ( act->actortype != IS_TEAMMATE)); + } + else if ( ent->isSubclassOf( Actor ) && ( act->actortype != IS_INANIMATE ) ) + { + actor = ( Actor * )ent; + if ( ( actor->actortype <= IS_ENEMY ) && ( act->actortype <= IS_ENEMY ) ) + { + return false; + } + if ( ( actor->actortype == IS_FRIEND ) && ( act->actortype <= IS_ENEMY ) ) + { + return true; + } + if ( ( actor->actortype == IS_TEAMMATE ) && ( act->actortype <= IS_ENEMY ) ) + { + return true; + } + if ( ( actor->actortype <= IS_ENEMY ) && ( act->actortype == IS_FRIEND ) ) + { + return true; + } + if ( ( actor->actortype <= IS_ENEMY ) && ( act->actortype == IS_TEAMMATE ) ) + { + return true; + } + if ( ( actor->actortype == IS_TEAMMATE ) && ( act->actortype == IS_TEAMMATE ) ) + { + return false; + } + + } + + return false; +} + +// +// Name: IsValidEnemy +// Parameters: Entity *enemy +// Description: Checks if the passed in entity is a valid target or not +// +qboolean EnemyManager::IsValidEnemy( Entity *enemy ) +{ + + if ( !enemy || ( enemy == world ) || ( enemy == act ) || ( enemy->flags & FL_NOTARGET ) || ( enemy->takedamage == DAMAGE_NO ) || enemy->deadflag ) + return false; + + if ( !Hates( enemy ) ) + return false; + + if ( !CanAttack( enemy ) ) + return false; + + if ((enemy->edict->s.renderfx & RF_DONTDRAW) ) + return false; + + return true; +} + +// +// Name: CanAttack +// Parameters: Entity *ent +// Description: Checks if the actor is allowed to attack the passed in entity +// +qboolean EnemyManager::CanAttack( Entity *ent ) +{ + + if ( !ent ) + return false; + + if ( act->targetType == ATTACK_ANY ) + return true; + + + if ( ent->isSubclassOf( Player ) && ( act->targetType == ATTACK_PLAYER_ONLY ) ) + return true; + + if ( ent->isSubclassOf( Actor ) && ( act->targetType == ATTACK_ACTORS_ONLY ) ) + return true; + + if ( ent->isSubclassOf ( LevelInteractionTrigger ) && ( act->targetType == ATTACK_LEVEL_INTERACTION ) ) + return true; + + + return false; + +} + +// +// Name: CanAttackAnyEnemy +// Parameters: None +// Description: Checks if we can shoot any of our enemies in our list +qboolean EnemyManager::CanAttackAnyEnemy() +{ + HateListEntry_t *listIndex; + Entity *target; + + for ( int i = 1 ; i <= _hateList.NumObjects() ; i++ ) + { + listIndex = &_hateList.ObjectAt( i ); + + target = listIndex->enemy; + if ( target ) + { + if ( act->combatSubsystem->CanAttackTarget( target ) ) + return true; + } + + } + + return false; +} + +// +// Name: AdjustHate() +// Parameters: Entity *enemy -- The entity to adjust +// float adjustment -- how much to adjust +// Description: Adjusts the hate value of the enemy +// +void EnemyManager::AdjustHate( Entity *enemy , float adjustment ) +{ + HateListEntry_t *listIndex; + + if ( !enemy ) + return; + + + for ( int i = 1 ; i <= _hateList.NumObjects() ; i++ ) + { + listIndex = &_hateList.ObjectAt( i ); + + if ( listIndex->enemy == enemy ) + { + listIndex->hate += adjustment; + if ( listIndex->hate < 0.0f ) + listIndex->hate = 0.0f; + + return; + } + + } + + +} + +// +// Name: AdjustDamageCaused() +// Parameters: Entity *enemy -- The entity to adjust +// float adjustment -- how much to adjust +// Description: Adjusts the damage caused +// +void EnemyManager::AdjustDamageCaused( Entity *enemy , float adjustment ) +{ + HateListEntry_t listIndex; + + if ( !enemy ) + return; + + for ( int i = 1 ; i <= _hateList.NumObjects() ; i++ ) + { + listIndex = _hateList.ObjectAt( i ); + + if ( listIndex.enemy == enemy ) + { + listIndex.damageCaused += adjustment; + return; + } + + } +} + +// +// Name: TryToAddToHateList +// Parameters: Entity *enemy +// Description: Checks if the passed in entity should be added to the hate list +// and calls _AddToHateList if it does +// +void EnemyManager::TryToAddToHateList( Entity *enemy ) +{ + if ( !enemy ) + return; + + if ( IsValidEnemy( enemy ) && !IsInHateList( enemy ) ) + { + if ( !act->combatSubsystem->CanAttackTarget( enemy ) && !CanGetToEntity( enemy ) ) + { + return; + } + + _AddToHateList( enemy ); + } + + +} + +// +// Name: _AddToHateList() +// Parameters: Entity *enemy -- The enemy to add +// Description: Adds the enemy to the hate list +// +void EnemyManager::_AddToHateList( Entity *enemy ) +{ + HateListEntry_t listIndex; + + if ( !enemy ) + return; + + listIndex.enemy = enemy; + listIndex.damageCaused = 0; + listIndex.hate = DEFAULT_INITIAL_HATE; + listIndex.lastSightTime = level.time ; + listIndex.canSee = false; + //listIndex.lastDistance = -1; + listIndex.nextSightTime = level.time + MIN_SIGHT_DELAY; + UpdateDistance( &listIndex ); + _hateList.AddObject(listIndex); + +} + +// +// Name: IsInHateList() +// Parameters: Entity *enemy +// Description: Checks if enemy is in the hate list +// +qboolean EnemyManager::IsInHateList( Entity *enemy ) +{ + int index = _findEntityInHateList( enemy ); + return index > 0; +} + +//---------------------------------------------------------------- +// Name: IsLastInHateList +// Class: EnemyManager +// +// Description: Determine if the Entity passed is in the hate list, +// and if it is at the end of the list. +// +// Parameters: Entity* enemy - the entity to look for +// +// Returns: qboolean +//---------------------------------------------------------------- +qboolean EnemyManager::IsLastInHateList( Entity* enemy ) +{ + int index = _findEntityInHateList( enemy ); + return index == _hateList.NumObjects(); +} + + +//---------------------------------------------------------------- +// Name: _findEntityInHateList +// Class: EnemyManager +// +// Description: Search the hate list for the passed entity. +// +// Parameters: Entity* searchEnt - the entity to look for +// +// Returns: The index of the entity in the list +// 0 means it isn't in the list. +//---------------------------------------------------------------- +int EnemyManager::_findEntityInHateList( Entity *searchEnt ) +{ + if( !searchEnt ) + { + return false; + } + + // manually iterate through the hatelist + HateListEntry_t listIndex; + for( int i = 1; i <= _hateList.NumObjects(); i++ ) + { + listIndex = _hateList.ObjectAt( i ); + + if ( listIndex.enemy == searchEnt ) + { + return i; + } + } + + // was not found, return 0 + return 0; +} + + +//---------------------------------------------------------------- +// Name: TrivialUpdate +// Class: EnemyManager +// +// Description: Minimal Hatelist update required for Actors +// +// Parameters: +// None +// +// Returns: None +//---------------------------------------------------------------- +void EnemyManager::TrivialUpdate() +{ + for ( int i = _hateList.NumObjects() ; i > 0 ; i-- ) + { + _hateList.ObjectAt( i ).lastDistance = 0.0f; + } +} + +// +// Name: Update() +// Parameters: None +// Description: Updates HateListEntry_ts +// +void EnemyManager::Update() +{ + HateListEntry_t *listIndex; + + for ( int i = _hateList.NumObjects() ; i > 0 ; i-- ) + { + listIndex = &_hateList.ObjectAt( i ); + + //Check if enemy is alive + if ( !act->IsEntityAlive( listIndex->enemy ) ) + { + if ( listIndex->enemy == _currentEnemy ) + ClearCurrentEnemy(); + + _hateList.RemoveObjectAt(i); + continue; + } + + UpdateDistance( listIndex ); + UpdateCanSee( listIndex ); + + // Bump the player's action level, TODO: this needs to change to a general you have been targeted call sometime + + if ( listIndex->enemy && listIndex->enemy->isSubclassOf( Player ) ) + { + if ( act->GetActorFlag(ACTOR_FLAG_UPDATE_ACTION_LEVEL) ) + { + Player *player = (Player *)(Entity *)listIndex->enemy; + player->IncreaseActionLevel( 100 ); + } + } + + if ( act->GetActorFlag(ACTOR_FLAG_UPDATE_HATE_WITH_ATTACKERS) ) + UpdateAttackers( listIndex ); + + /*if ( !act->combatSubsystem->CanAttackTarget( listIndex->enemy ) ) + { + listIndex->hate = 0.0f; + if ( listIndex->enemy == _currentEnemy ) + ClearCurrentEnemy(); + + _hateList.RemoveObjectAt(i); + } + */ + + + //If we haven't seen + if ( + ( act->timeBetweenSleepChecks > 0.0f ) && + ( listIndex->lastSightTime + act->timeBetweenSleepChecks <= level.time && !gi.inPVS( listIndex->enemy->centroid, act->centroid ) ) + ) + { + if ( listIndex->enemy == _currentEnemy && !_lockedOnCurrentEnemy ) + ClearCurrentEnemy(); + _hateList.RemoveObjectAt(i); + } + + } + // Try and go back to sleep + TrySleep(); + + + //If we don't have an enemy, tell our senses to keep looking + act->sensoryPerception->SenseEnemies(); + + + if ( GetCurrentEnemy() || act->GetActorFlag( ACTOR_FLAG_INVESTIGATING ) || ( act->mode == ACTOR_MODE_SCRIPT ) || ( act->mode == ACTOR_MODE_TALK ) ) + act->last_time_active = level.time; + + // Take care of enemies + act->strategos->Evaluate(); + +} + +// +// Name: UpdateDistance +// Parameters: HateListEntry_t -- The List Entry to update +// Description: Updates the distance of the enemy +// +void EnemyManager::UpdateDistance( HateListEntry_t *listIndex ) +{ + Vector distance; + + //float distanceLength; + + if ( !listIndex || !listIndex->enemy ) + return; + + Vector enemyDistance; + enemyDistance = listIndex->enemy->origin; + + distance = enemyDistance - act->origin; + //distanceLength = distance.length(); + + listIndex->lastDistance = distance.length(); + +} + +void EnemyManager::UpdateAttackers( HateListEntry_t *listIndex ) +{ + if ( _currentEnemy == listIndex->enemy ) + return; + + int numberOfAttackers; + ActorGroup* group; + + group = (ActorGroup*)groupcoordinator->GetGroup( act->GetGroupID() ); + + if ( !group ) + return; + + numberOfAttackers = group->CountMembersAttackingEnemy( listIndex->enemy ); + + listIndex->hate = listIndex->hate / (numberOfAttackers + 1); + +} + +// +// Name: UpdateCanSee +// Parameters: HateListEntry_t -- The List Entry to update +// Description: Updates the cansee of the enemy +// +void EnemyManager::UpdateCanSee( HateListEntry_t *listIndex ) +{ + if ( !listIndex || ( listIndex->nextSightTime > level.time ) ) + return; + + qboolean canSee; + canSee = act->sensoryPerception->CanSeeEntity( act , listIndex->enemy , true , true ); + + // Notify Strategos of status change + if ( canSee != listIndex->canSee ) + act->strategos->NotifySightStatusChanged( listIndex->enemy , canSee ); + + // Update the SightCheckTimes + listIndex->canSee = canSee; + + // We use a faster delay for our current enemy than everyone else + float next_sight_delay; + if ( listIndex->enemy == _currentEnemy ) + next_sight_delay = act->max_inactive_time / 5.0f + G_CRandom( 0.5f ); + else + next_sight_delay = MIN_SIGHT_DELAY + ( act->max_inactive_time / 2.0f + G_CRandom( 0.5f ) ); + + if ( next_sight_delay < 1.0f ) + next_sight_delay = 1.0f; + + listIndex->nextSightTime = level.time + next_sight_delay; + + if ( canSee ) + listIndex->lastSightTime = level.time; + + if ( listIndex->lastSightTime + act->max_inactive_time < level.time ) + ClearCurrentEnemy(); +} + + +// +// Name: LockOnCurrentEnemy +// Parameters: qboolean lock +// Description: Sets the _lockedOnCurrentEnemy flag +// +void EnemyManager::LockOnCurrentEnemy( qboolean lock ) +{ + _lockedOnCurrentEnemy = lock; +} + +// +// Name: IsLockedOnCurrentEnemy +// Parameters: None +// Description: Returns the _lockedOnCurrentEnemy flag +// +qboolean EnemyManager::IsLockedOnCurrentEnemy() +{ + return _lockedOnCurrentEnemy; +} + + +Vector EnemyManager::GetAwayFromEnemies() +{ + EntityPtr enemy; + HateListEntry_t listIndex; + + Vector enemyToSelf; + Vector temp; + float mag; + + Vector bestAwayVector; + + for ( int i = 1 ; i <= _hateList.NumObjects() ; i++ ) + { + listIndex = _hateList.ObjectAt( i ); + enemy = listIndex.enemy; + + enemyToSelf = act->origin - enemy->origin; + mag = enemyToSelf.lengthSquared(); + + temp = enemyToSelf * (1 / mag); + + bestAwayVector = bestAwayVector + temp; + } + + bestAwayVector.normalize(); + + return bestAwayVector; +} + +qboolean EnemyManager::InEnemyLineOfFire() +{ + Actor *enemyActor = NULL; + Player *enemyPlayer = NULL; + + Vector enemyToSelf; + + float angleDiff = 0.0f; + + + if ( !_currentEnemy ) + return false; + + enemyToSelf = act->origin - _currentEnemy->origin; + enemyToSelf = enemyToSelf.toAngles(); + + if ( _currentEnemy->isSubclassOf(Actor) ) + { + enemyActor = (Actor*)(Entity*)_currentEnemy; + + if ( !enemyActor ) + return false; + + angleDiff = abs( (int) AngleNormalize180(enemyActor->angles[YAW] - enemyToSelf[YAW]) ); + } + + if ( _currentEnemy->isSubclassOf(Player) ) + { + enemyPlayer = (Player*)(Entity*)_currentEnemy; + + if ( !enemyPlayer ) + return false; + + Vector pAngles = enemyPlayer->GetVAngles(); + + angleDiff = abs( (int) AngleNormalize180(pAngles[YAW] - enemyToSelf[YAW]) ); + + } + + if ( angleDiff < 10.0f ) + return true; + + + return false; + +} + +float EnemyManager::GetDistanceFromEnemy() +{ + Vector selfToEnemy; + + if ( !_currentEnemy ) + return -1; + + selfToEnemy = _currentEnemy->origin - act->origin; + + return selfToEnemy.length(); + +} + +// +// Name: DoArchive +// Parameters: Archiver &arc -- The archiver object +// Actor *actor -- The actor +// Description: Sets the act pointer and calls Archive +// +void EnemyManager::DoArchive( Archiver &arc , Actor *actor ) +{ + Archive( arc ); + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "EnemyManager::DoArchive -- actor is NULL" ); +} + +// +// Name: Archive() +// Parameters: Archiver &arc -- The archiver object +// Description: Archives the class +// +void EnemyManager::Archive ( Archiver &arc ) +{ + HateListEntry_t hateEntry; + HateListEntry_t *realHateEntry; + int numEntries; + + + if ( arc.Saving() ) + { + numEntries = _hateList.NumObjects(); + arc.ArchiveInteger( &numEntries ); + + for ( int i = 1 ; i <= numEntries ; i++ ) + { + hateEntry = _hateList.ObjectAt( i ); + + arc.ArchiveSafePointer( &hateEntry.enemy ); + arc.ArchiveFloat( &hateEntry.lastSightTime ); + arc.ArchiveFloat( &hateEntry.nextSightTime ); + arc.ArchiveBoolean( &hateEntry.canSee ); + arc.ArchiveFloat( &hateEntry.damageCaused ); + arc.ArchiveFloat( &hateEntry.hate ); + arc.ArchiveFloat( &hateEntry.lastDistance ); + } + } + else + { + arc.ArchiveInteger( &numEntries ); + + _hateList.Resize( numEntries ); + + for ( int i = 1 ; i <= numEntries ; i++ ) + { + _hateList.AddObject( hateEntry ); + + realHateEntry = &_hateList.ObjectAt( i ); + + arc.ArchiveSafePointer( &realHateEntry->enemy ); + arc.ArchiveFloat( &realHateEntry->lastSightTime ); + arc.ArchiveFloat( &realHateEntry->nextSightTime ); + arc.ArchiveBoolean( &realHateEntry->canSee ); + arc.ArchiveFloat( &realHateEntry->damageCaused ); + arc.ArchiveFloat( &realHateEntry->hate ); + arc.ArchiveFloat( &realHateEntry->lastDistance ); + } + } + + arc.ArchiveSafePointer( &_currentEnemy ); + arc.ArchiveSafePointer( &_lastEnemy ); + + arc.ArchiveSafePointer( &_alternateTarget ); + + arc.ArchiveBoolean( &_lockedOnCurrentEnemy ); + arc.ArchiveFloat( &_currentEnemyHate ); +} + +//---------------------------------------------------------------- +// Name: TrySleep +// Class: EnemyManager +// +// Description: Puts the actor to sleep if it can +// +// Parameters: +// None +// +// Returns: None +//---------------------------------------------------------------- +void EnemyManager::TrySleep( void ) +{ + const Entity *currentEnemy = GetCurrentEnemy(); + // This is here because, if the ai gets turned off in the level, we don't want to go to sleep for if/when + // the ai gets turned back on in the level + if ( !level.ai_on ) + act->last_time_active = level.time; + + // See if we should go back to sleep -- If max_inactive_time < 1 , then the actor will never go to sleep + // If we DO have a max_inactive_time > 0 AND our last_time_active == 0 ( meaning it's our first time in here ) we + // should go ahead and try to fall asleep even though we have hit our max_inactive_time + if ( !currentEnemy && ( act->max_inactive_time > 0.0f ) && ( (act->last_time_active + act->max_inactive_time < level.time))) + { + //Well, we haven't had an enemy for a while, so let's see if the player is nearby + Player *player = 0; + for(int i = 0; i < game.maxclients; i++) + { + player = GetPlayer(i); + + if ( player ) + { + //We should NOT fall asleep if: + // our mode is ACTOR_MODE_IDLE _AND_ player flags are not equal to FL_NOTARGET + // OR + // Player is within our vision distance + // OR + // Player is within the PVS + + // + // 7/15/02 -- SK + // Added via && the "gi.inPVS( player->centroid, act->centroid )" to the ACTOR_MODE_IDLE + // checks because, without it, all AI_OFF'd actors would still be considered "active" + // and this was ruining the framerate... I do not believe this is going to have any + // detrimental side effects. + // + if ( ( act->mode == ACTOR_MODE_IDLE ) && !(player->flags & FL_NOTARGET) && gi.inPVS( player->centroid, act->centroid ) || act->sensoryPerception->WithinVisionDistance(player) || gi.inPVS( player->centroid, act->centroid ) ) + { + act->last_time_active = level.time; + } + + else + { + if ( act->mode == ACTOR_MODE_AI ) + { + act->EndMode(); + + act->Sleep(); + + if ( act->idle_thread.length() > 1 ) + { + ExecuteThread( act->idle_thread, false ); + } + } + else + { + act->Sleep(); + } + } + } + } + } +} + +bool EnemyManager::IsAnyEnemyInRange( float range ) +{ + HateListEntry_t *listIndex; + + for ( int i = _hateList.NumObjects() ; i > 0 ; i-- ) + { + listIndex = &_hateList.ObjectAt( i ); + + //Check if enemy is alive + if ( !act->IsEntityAlive( listIndex->enemy ) ) + { + if ( listIndex->enemy == _currentEnemy ) + ClearCurrentEnemy(); + + _hateList.RemoveObjectAt(i); + continue; + } + + if ( act->WithinDistance( listIndex->enemy , range ) ) + return true; + + } + + return false; + +} + +float EnemyManager::getEnemyCount() +{ + return _hateList.NumObjects(); +} + +bool EnemyManager::CanGetToEntity(Entity *ent) +{ + FindMovementPath find; + Path *path; + bool success; + + success = false; + // Set up our pathing heuristics + find.heuristic.self = act; + find.heuristic.setSize( act->size ); + find.heuristic.entnum = act->entnum; + + path = find.FindPath( act->origin, ent->origin ); + + if ( path ) + { + success = true; + } + + delete path; + path = NULL; + + return success; +} + +bool EnemyManager::HasEnemy() +{ + if ( _currentEnemy ) + return true; + + return false; +} diff --git a/dlls/game/actor_enemymanager.h b/dlls/game/actor_enemymanager.h new file mode 100644 index 0000000..2ed154e --- /dev/null +++ b/dlls/game/actor_enemymanager.h @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actor_enemymanager.h $ +// $Revision:: 16 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// EnemyManager class for Actors -- Handles all the hatelist management +// + +class EnemyManager; + +#ifndef __ACTOR_ENEMYMANAGER_H__ +#define __ACTOR_ENEMYMANAGER_H__ + +#include "actor.h" +#include "actorincludes.h" +#include "weapon.h" + +//============================ +// Class EnemyManager +//============================ +// +// Class used to handle all enemy management by actors. +// +// Notes: +// _currentEnemy and _alternateTarget -- _currentEnemy should always be used to +// house the Sentient ( i.e. Actor or Player ) at the top of the hatelist. +// _alternateTarget should be used for "non-living" things ( Like ThrowObjects or +// ExplodingObjects... things like that. +// +// As behaviors get used, and proves necessary, we can add a flag that will allow the +// state machine to set and use the _alternateTarget +class EnemyManager + { + public: + EnemyManager(); + EnemyManager( Actor *actor ); + ~EnemyManager(); + + void FindHighestHateEnemy(); + void FindNextEnemy(); + void FindClosestEnemy(); + void ClearCurrentEnemy(); + void ClearHateList(); + qboolean Hates( Entity *ent ); + qboolean Likes( Entity *ent ); + qboolean CanAttack( Entity *ent ); + qboolean CanAttackAnyEnemy(); + + EntityPtr GetCurrentEnemy(); + void SetCurrentEnemy( Entity *enemy ); + + bool CanGetToEntity(Entity *enemy); + + EntityPtr GetAlternateTarget(); + void SetAlternateTarget( Entity *target ); + + void TryToAddToHateList( Entity *enemy ); + qboolean IsInHateList( Entity *enemy ); + qboolean IsLastInHateList( Entity* enemy ); + + void AdjustHate( Entity *enemy , float adjustment ); + void AdjustDamageCaused( Entity *enemy, float adjustment ); + + void TrivialUpdate(); + void Update(); + void UpdateDistance( HateListEntry_t *listIndex ); + void UpdateCanSee( HateListEntry_t *listIndex ); + void UpdateAttackers( HateListEntry_t *listIndex ); + + void LockOnCurrentEnemy( qboolean lock ); + qboolean IsLockedOnCurrentEnemy(); + + Vector GetAwayFromEnemies(); + qboolean InEnemyLineOfFire(); + float GetDistanceFromEnemy(); + void TrySleep( void ); + + bool HasEnemy(); + + // Utility Functions + qboolean IsValidEnemy( Entity *enemy ); + bool IsAnyEnemyInRange( float range ); + float getEnemyCount(); + + + // Archiving + virtual void Archive( Archiver &arc ); + void DoArchive ( Archiver &arc , Actor *actor ); + + + protected: //Member Functions + void _AddToHateList( Entity *enemy ); + int _findEntityInHateList( Entity *searchEnt ); + + + private: //Member Variables + Container _hateList; + EntityPtr _currentEnemy; + EntityPtr _lastEnemy; + EntityPtr _alternateTarget; + qboolean _lockedOnCurrentEnemy; + float _currentEnemyHate; + + + Actor *act; + }; + + + +#endif /* __ACTOR_ENEMYMANAGER_H__ */ diff --git a/dlls/game/actor_headwatcher.cpp b/dlls/game/actor_headwatcher.cpp new file mode 100644 index 0000000..8f3d0f9 --- /dev/null +++ b/dlls/game/actor_headwatcher.cpp @@ -0,0 +1,365 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actor_headwatcher.cpp $ +// $Revision:: 21 $ +// $Author:: Sketcher $ +// $Date:: 5/04/03 5:49p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// + +#include "_pch_cpp.h" +#include "actor_enemymanager.h" +#include "player.h" +#include "object.h" + +HeadWatcher::HeadWatcher() +{ + // Should always use other constructor + gi.Error( ERR_FATAL, "HeadWatcher::HeadWatcher -- Default Constructor Called" ); + + +} + +HeadWatcher::HeadWatcher( Actor *actor ) +{ + //Initialize our Actor + if ( actor ) + act = actor; + else + gi.Error( ERR_DROP, "HeadWatcher::HeadWatcher -- actor is NULL" ); + + _init(); + +} + +HeadWatcher::~HeadWatcher() +{ + +} + +void HeadWatcher::_init() +{ + act->SetControllerTag( ACTOR_HEAD_TAG, gi.Tag_NumForName( act->edict->s.modelindex, "Bip01 Head" ) ); + + _currentHeadAngles = act->GetControllerAngles( ACTOR_HEAD_TAG ); + _watchTarget = NULL; + + // Numbers here for testing, should come from events so they can be set via tiki or script + _maxHeadTurnSpeed = 30.0f; + _turnThreshold = 0.0f; + _maxHeadYaw = 60.0f; + _maxHeadPitch = 45.0f; + + _twitchHead = false; + _nextTwitchHeadTime = 0.0f; + _maxDistance = -1.0; + + _explicitSet = false; + _ignoreWatchTarget = false; +} + +void HeadWatcher::SetWatchTarget( Entity *ent ) +{ + _watchTarget = ent; + _explicitSet = true; +} + +void HeadWatcher::SetWatchTarget( const str &targetName ) +{ + TargetList *tlist; + int numObjects; + + + tlist = world->GetTargetList( targetName ); + numObjects = tlist->list.NumObjects(); + + if ( numObjects == 0 ) + { + //Hey, No targets with that name + gi.WDPrintf( "No target with target name %s specified\n", targetName.c_str() ); + return; + } + else if ( numObjects > 1 ) + { + //Uh Oh... We have more than one target... Let's throw up an error + gi.WDPrintf( "More than one target with target name %s specified, grabbing first one\n", targetName.c_str() ); + } + + _watchTarget = tlist->list.ObjectAt( 1 ); + _explicitSet = true; +} + +void HeadWatcher::SetWatchSpeed( float speed ) +{ + _maxHeadTurnSpeed = speed; +} + +void HeadWatcher::ClearWatchTarget() +{ + _watchTarget = NULL; + _explicitSet = false; +} + +Entity *HeadWatcher::GetWatchTarget() +{ + return _watchTarget; +} + +void HeadWatcher::HeadWatchTarget() +{ + int tagNum; + Vector tagPos; + Vector watchPosition; + Actor *actTarget; + actTarget = NULL; + + + tagNum = gi.Tag_NumForName( act->edict->s.modelindex, "Bip01 Head" ); + + if ( tagNum < 0 ) + return; + + //Check if we even have an animation set yet + if ( act->animate->CurrentAnim(legs) < 0 ) + return; + + act->GetTag( "Bip01 Head", &tagPos ); + + if ( !_watchTarget || _ignoreWatchTarget ) + { + if ( _twitchHead ) + { + twitchHead(); + return; + } + else + { + LerpHeadBySpeed( vec_zero , false ); + return; + } + } + + if (act->GetActorFlag( ACTOR_FLAG_DIALOG_PLAYING ) && act->GetActorFlag(ACTOR_FLAG_USING_HUD) ) + { + LerpHeadBySpeed( vec_zero , false ); + return; + } + + // Check if our _watchTarget is within distance ) + if ( _maxDistance > 0 ) + { + Vector selfToTarget = _watchTarget->origin - act->origin; + float dist = selfToTarget.length(); + + if ( dist > _maxDistance ) + { + LerpHeadBySpeed( vec_zero , false ); + return; + } + } + + if ( _watchTarget->isSubclassOf( Actor ) ) + { + actTarget = (Actor *)(Entity *)_watchTarget; + + // Don't watch if the target is dead. + if ( !actTarget->isThinkOn() ) + { + _watchTarget = NULL; + actTarget = NULL; + return; + } + } + + if ( actTarget && ( actTarget->watch_offset != vec_zero ) ) + { + MatrixTransformVector( actTarget->watch_offset, _watchTarget->orientation, watchPosition ); + watchPosition += _watchTarget->origin; + } + else + { + tagNum = gi.Tag_NumForName( _watchTarget->edict->s.modelindex, "Bip01 Head" ); + + if ( tagNum < 0 ) + watchPosition = _watchTarget->centroid; + else + { + _watchTarget->GetTag( "Bip01 Head", &watchPosition ); + } + } + + + AdjustHeadAngles( tagPos , watchPosition ); + +} + +void HeadWatcher::AdjustHeadAngles( const Vector &tagPos , const Vector &watchPosition ) +{ + Vector dir; + Vector angles; + Vector anglesDiff; + float yawChange; + float pitchChange; + + + dir = watchPosition - tagPos; + angles = dir.toAngles(); + + anglesDiff = angles - act->angles; + + + anglesDiff[YAW] = AngleNormalize180( anglesDiff[YAW] ); + anglesDiff[PITCH] = AngleNormalize180( anglesDiff[PITCH] ); + + yawChange = anglesDiff[YAW]; + pitchChange = anglesDiff[PITCH]; + + if ( _turnThreshold && ( yawChange < _turnThreshold ) && ( yawChange > -_turnThreshold ) && ( pitchChange < _turnThreshold ) && ( pitchChange > -_turnThreshold ) ) + { + return; + } + + + // Make sure we don't turn neck too far + if ( anglesDiff[YAW] < -_maxHeadYaw ) + anglesDiff[YAW] = -_maxHeadYaw; + else if ( anglesDiff[YAW] > _maxHeadYaw ) + anglesDiff[YAW] = _maxHeadYaw; + + if ( anglesDiff[PITCH] < -_maxHeadPitch ) + anglesDiff[PITCH] = -_maxHeadPitch; + else if ( anglesDiff[PITCH] > _maxHeadPitch ) + anglesDiff[PITCH] = _maxHeadPitch; + + anglesDiff[ROLL] = 0.0f; + + + LerpHeadBySpeed( anglesDiff ); + +} + +void HeadWatcher::LerpHeadBySpeed( const Vector &angleDelta , bool useTorsoAngles ) +{ + Vector anglesDiff; + Vector change; + Vector finalAngles; + Vector currentTorsoAngles; + + anglesDiff = angleDelta; + + // Get our Torso Angles + act->SetControllerTag( ACTOR_TORSO_TAG , gi.Tag_NumForName( act->edict->s.modelindex, "Bip01 Spine1" ) ); + currentTorsoAngles = act->GetControllerAngles( ACTOR_TORSO_TAG ); + + //Reset our Controller Tag + act->SetControllerTag( ACTOR_HEAD_TAG, gi.Tag_NumForName( act->edict->s.modelindex, "Bip01 Head" ) ); + + + // Make sure we don't change our head angles too much at once + change = anglesDiff - _currentHeadAngles; + + if ( change[YAW] > _maxHeadTurnSpeed ) + anglesDiff[YAW] = _currentHeadAngles[YAW] + _maxHeadTurnSpeed; + else if ( change[YAW] < -_maxHeadTurnSpeed ) + anglesDiff[YAW] = _currentHeadAngles[YAW] - _maxHeadTurnSpeed; + + if ( change[PITCH] > _maxHeadTurnSpeed ) + anglesDiff[PITCH] = _currentHeadAngles[PITCH] + _maxHeadTurnSpeed; + else if ( change[PITCH] < -_maxHeadTurnSpeed ) + anglesDiff[PITCH] = _currentHeadAngles[PITCH] - _maxHeadTurnSpeed; + + if ( change[ROLL] > _maxHeadTurnSpeed ) + anglesDiff[ROLL] = _currentHeadAngles[ROLL] + _maxHeadTurnSpeed; + else if ( change[ROLL] < -_maxHeadTurnSpeed ) + anglesDiff[ROLL] = _currentHeadAngles[ROLL] - _maxHeadTurnSpeed; + + + finalAngles = anglesDiff; + + if ( useTorsoAngles ) + finalAngles[YAW] = anglesDiff[YAW] - currentTorsoAngles[YAW]; + else + finalAngles[YAW] = anglesDiff[YAW]; + + act->SetControllerAngles( ACTOR_HEAD_TAG, finalAngles ); + act->real_head_pitch = anglesDiff[PITCH]; + + _currentHeadAngles = anglesDiff; + +} + +void HeadWatcher::setHeadTwitch( bool twitchHead ) +{ + _twitchHead = twitchHead; + + if ( _twitchHead ) + { + _nextTwitchHeadTime = 0.0f; + } +} + +void HeadWatcher::twitchHead( void ) +{ + Vector headAngles; + float oldMaxHeadTurnSpeed; + + + if ( level.time > _nextTwitchHeadTime ) + { + _headTwitchAngles = Vector( G_CRandom( 4.0f ), G_CRandom( 4.0f ), G_CRandom( 4.0f ) ); + + _nextTwitchHeadTime = level.time + 1.0f + G_CRandom( 0.5f ); + } + + oldMaxHeadTurnSpeed = _maxHeadTurnSpeed; + + _maxHeadTurnSpeed = 0.25f; + + LerpHeadBySpeed( _headTwitchAngles, false ); + + _maxHeadTurnSpeed = oldMaxHeadTurnSpeed; +} + + +// +// Name: DoArchive() +// Parameters: Archiver &arc +// Actor *actor +// Description: Sets the Actor Pointer and Calls Archive() +// +void HeadWatcher::DoArchive( Archiver &arc , Actor *actor ) +{ + Archive( arc ); + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "HeadWatcher::DoArchive -- actor is NULL" ); + +} + +// +// Name: Archive() +// Parameters: Archiver &arc +// Description: Archives Class Data +// +void HeadWatcher::Archive( Archiver &arc ) +{ + arc.ArchiveSafePointer( &_watchTarget ); + arc.ArchiveVector( &_currentHeadAngles ); + arc.ArchiveFloat( &_maxHeadTurnSpeed ); + arc.ArchiveFloat( &_turnThreshold ); + arc.ArchiveBoolean( &_explicitSet ); + arc.ArchiveFloat( &_maxHeadYaw ); + arc.ArchiveFloat( &_maxHeadPitch ); + arc.ArchiveBool( &_twitchHead ); + arc.ArchiveFloat( &_nextTwitchHeadTime ); + arc.ArchiveVector( &_headTwitchAngles ); + arc.ArchiveFloat( &_maxDistance ); + arc.ArchiveBool( &_ignoreWatchTarget ); +} diff --git a/dlls/game/actor_headwatcher.h b/dlls/game/actor_headwatcher.h new file mode 100644 index 0000000..2a223db --- /dev/null +++ b/dlls/game/actor_headwatcher.h @@ -0,0 +1,101 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actor_headwatcher.h $ +// $Revision:: 10 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Head Watcher Class +// + + +#ifndef __ACTOR_HEADWATCHER_H__ +#define __ACTOR_HEADWATCHER_H__ + +#include "actor.h" +#include "actorincludes.h" +#include "weapon.h" + +//============================ +// Class HeadWatcher +//============================ +class HeadWatcher + { + public: + HeadWatcher(); + HeadWatcher( Actor *actor ); + ~HeadWatcher(); + + void SetWatchTarget( Entity *ent ); + void SetWatchTarget( const str &targetName ); + void SetWatchSpeed ( float speed ); + void SetMaxHeadYaw ( float max ); + void SetMaxHeadPitch ( float max ); + void SetMaxDistance( float distance ); + void SetIgnoreWatchTarget( bool ignore ); + + Entity* GetWatchTarget(); + + void ClearWatchTarget(); + void HeadWatchTarget(); + void AdjustHeadAngles( const Vector &tagPos , const Vector &watchPosition ); + void LerpHeadBySpeed( const Vector &angleDelta , bool useTorsoAngles = true ); + + void setHeadTwitch( bool twitchHead ); + void twitchHead( void ); + + // Archiving + virtual void Archive( Archiver &arc ); + void DoArchive( Archiver &arc , Actor *actor ); + + protected: + void _init(); + + private: + EntityPtr _watchTarget; + Vector _currentHeadAngles; + float _maxHeadTurnSpeed; + float _turnThreshold; + qboolean _explicitSet; + float _maxHeadYaw; + float _maxHeadPitch; + + bool _twitchHead; + float _nextTwitchHeadTime; + Vector _headTwitchAngles; + float _maxDistance; + bool _ignoreWatchTarget; + + Actor *act; + + }; + +inline void HeadWatcher::SetMaxHeadPitch( float max ) +{ + _maxHeadPitch = max; +} + +inline void HeadWatcher::SetMaxHeadYaw( float max ) +{ + _maxHeadYaw = max; +} + +inline void HeadWatcher::SetMaxDistance( float distance ) +{ + _maxDistance = distance; +} + +inline void HeadWatcher::SetIgnoreWatchTarget( bool ignore ) +{ + _ignoreWatchTarget = ignore; +} + +#endif /* __ACTOR_HEADWATCHER_H__ */ diff --git a/dlls/game/actor_locomotion.cpp b/dlls/game/actor_locomotion.cpp new file mode 100644 index 0000000..cc0ef85 --- /dev/null +++ b/dlls/game/actor_locomotion.cpp @@ -0,0 +1,2178 @@ +// +// $Logfile:: /Code/DLLs/game/actor_locomotion.cpp $ +// $Revision:: 47 $ +// $Author:: Sketcher $ +// $Date:: 4/26/03 4:24p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// + +#include "_pch_cpp.h" +#include "actor_locomotion.h" +#include "player.h" +#include "object.h" + +//====================================== +// LocomotionController Implementation +//===================================== + + +// +// Name: LocomotionController() +// Class: LocomotionController +// +// Description: Default Constructor +// +// Parameters: None +// +// Returns: None +// +LocomotionController::LocomotionController() +{ + // Should always use other constructor + gi.Error( ERR_FATAL, "LocomotionController::LocomotionController -- Default Constructor Called" ); +} + + +// +// Name: LocomotionController() +// Class: LocomotionController +// +// Description: Default Constructor +// +// Parameters: Actor *actor +// +// Returns: None +// +LocomotionController::LocomotionController( Actor *actor ) +{ + //Initialize our Actor + if ( actor ) + act = actor; + else + gi.Error( ERR_DROP, "LocomotionController::LocomotionController -- actor is NULL" ); + + _init(); +} + + +// +// Name: ~LocomotionController() +// Class: LocomotionController() +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +LocomotionController::~LocomotionController() +{ + +} + + +// +// Name: Begin() +// Class: LocomotionController +// +// Description: Begins a session of locomotion +// +// Parameters: None +// +// Returns: None +// +void LocomotionController::Begin() +{ + _chase.Begin( *act ); +} + + +// +// Name: Evaluate() +// Class: LocomotionController +// +// Description: Evaluation Iteration for the locomotion session +// +// Parameters: None +// +// Returns: None +// +void LocomotionController::Evaluate() +{ + _chase.Evaluate( *act ); +} + + +// +// Name: End() +// Class: LocomotionController +// +// Description: Ends a locomotion session +// +// Parameters: None +// +// Returns: None +// +void LocomotionController::End() +{ + _chase.End( *act ); +} + + +// +// Name: SetMovementStyle() +// Class: LocomotionController +// +// Description: Sets the _movementStyle +// +// Parameters: MovementStyle Style -- The style to set +// +// Returns: None +// +void LocomotionController::SetMovementStyle( MovementStyle style ) +{ + _movementStyle = style; +} + + +// +// Name: GetMovementStyle() +// Class: LocomotionController +// +// Description: Gets the _movementStyle +// +// Parameters: None +// +// Returns: _movementStyle +// +MovementStyle LocomotionController::GetMovementStyle() +{ + return _movementStyle; +} + + +// +// Name: DoArchive() +// Class: LocomotionController +// +// Description: Sets the Actor pointer and calls Archive() +// +// Parameters: Archiver &arc -- The archiver object +// Actor *actor -- The actor +// +// Returns: None +// +void LocomotionController::DoArchive( Archiver &arc , Actor *actor ) +{ + Archive( arc ); + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "LocomotionController::DoArchive -- actor is NULL" ); +} + +// +// Name: Archive() +// Class: LocomotionController +// +// Description: Archives the class +// +// Parameters: Archiver &arc -- The archiver object +// +// Returns: None +// +void LocomotionController::Archive( Archiver &arc ) +{ + ArchiveEnum( _movementStyle, MovementStyle ); + arc.ArchiveObject( &_chase ); +} + + +// +// Name: _init() +// Class: LocomotionController +// +// Description: Initializes the class +// +// Parameters: None +// +// Returns: None +// +void LocomotionController::_init() +{ + _movementStyle = MOVEMENT_STYLE_NONE; +} + + + + + +//====================================== +// MovementSubsystem Implementation +//===================================== + +// Init Static Vars +Vector MovementSubsystem::_step = Vector( 0.0f, 0.0f, STEPSIZE ); + + +// +// Name: MovementSubsystem() +// Class: MovementSubsystem +// +// Description: Default Constructor +// +// Parameters: None +// +// Returns: None +// +MovementSubsystem::MovementSubsystem() +{ + // Should always use other constructor + gi.Error( ERR_FATAL, "MovementSubsystem::MovementSubsystem -- Default Constructor Called" ); +} + + +// +// Name: MovementSubsystem() +// Class: MovementSubsystem +// +// Description: Constructor +// +// Parameters: Actor *actor +// +// Returns: None +// +MovementSubsystem::MovementSubsystem( Actor *actor ) +{ + //Initialize our Actor + if ( actor ) + act = actor; + else + gi.Error( ERR_DROP, "MovementSubsystem::MovementSubsystem -- actor is NULL" ); + + _init(); +} + + +// +// Name: ~MovementSubsystem() +// Class: MovementSubsystem +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +MovementSubsystem::~MovementSubsystem() +{ + if ( _path ) + { + delete _path; + _path = NULL; + } +} + + +// +// Name: CanMoveTo() +// Class: MovementSubsystem +// +// Description: Pass through to _canMoveSimplePath() +// +// Parameters: Vector &pos -- The location to check +// +// Returns: True or False +// +qboolean MovementSubsystem::CanMoveTo ( const Vector &pos ) +{ + return _canMoveSimplePath( act->mins, act->maxs , pos ); +} + + +// +// Name: CanWalkTowardsPoint() +// Class: MovementSubsystem +// +// Description: Tests to see if Actor can move in the desired direction a small amount +// +// Parameters: Vector direction -- Desired direction of movement +// +// Returns: whether the direction is clear and has a floor +// + +bool MovementSubsystem::CanWalkTowardsPoint( const Vector &goalPoint, const int mask ) +{ + Vector step( goalPoint - act->origin ); + const float distance = Vector::Distance( goalPoint, act->origin ); + step.normalize(); + step *= min( distance, max ( 64.0f, Vector::Distance( act->origin, act->last_origin ) ) ); + return CanWalkTo( act->origin + step, 0, ENTITYNUM_NONE, mask ) == 1; +} + + +// +// Name: CanWalkTo() +// Class: MovementSubsystem +// +// Description: Pass through to CanWalkToFrom() +// +// Parameters: Vector &pos -- The location to check +// float bounding_box_extra -- Additional volume to the bounding box +// int entnum -- entity number +// +// Returns: +// +qboolean MovementSubsystem::CanWalkTo( const Vector &pos, float bounding_box_extra, int entnum, const int mask ) +{ + return CanWalkToFrom( act->origin, pos, bounding_box_extra, entnum, mask ); +} + + +// +// Name: CanWalkToFrom() +// Class: MovementSubsystem +// +// Description: Checks if a location is reachable +// +// Parameters: const Vector &origin -- Starting position +// const Vector &pos -- Ending position +// float bounding_box_extra -- Extra volume added to the bounding box +// int entnum -- Entity number +// +// Returns: True or False +// +qboolean MovementSubsystem::CanWalkToFrom ( const Vector &origin, const Vector &pos, float bounding_box_extra, int entnum, const int mask ) +{ + Vector real_pos; + Vector test_mins; + Vector test_maxs; + + + // Setup bounding box + test_mins = act->mins; + test_maxs = act->maxs; + + test_mins.x -= bounding_box_extra; + test_mins.y -= bounding_box_extra; + + test_maxs.x += bounding_box_extra; + test_maxs.y += bounding_box_extra; + + + // Calculate the real position we have to get to + if ( entnum != ENTITYNUM_NONE ) + real_pos = _getRealDestinationPosition( pos ); + else + real_pos = pos; + + Vector startPos; + startPos = act->origin; + startPos.z += 15; + + // Do simple CanWalkTo if specified + + if ( act->GetActorFlag( ACTOR_FLAG_SIMPLE_PATHFINDING ) ) + return _canMoveSimplePath( test_mins, test_maxs, real_pos ); + + // Check to make sure the ground is good each step of the move + return _checkHaveGroundEachStep( origin, real_pos, test_mins, test_maxs, mask ); + + +} + + +// +// Name: Acclerate() +// Class: MovementSubsystem +// +// Description: Applies steering force to the actor's movement +// +// Parameters: const Vector &original_steering -- steering force from the steering classes +// +// Returns: None +// +void MovementSubsystem::Accelerate( const Vector &original_steering ) +{ + Vector steering = original_steering; + Vector newDir = _movedir.toAngles(); + + + // If we didn't move last frame ( stopped ) then we can just set our moveDir to where we + // want to go, however, if we are moving, we want to adjust our turning based on our turnspeed + // so that we can't just turn 90 degrees at a snap + + if ( !act->GetActorFlag(ACTOR_FLAG_HAVE_MOVED) ) + { + /* if ( steering.y > _turnspeed ) + steering.y = _turnspeed; + else if ( steering.y < -_turnspeed ) + steering.y = -_turnspeed; + */ + } + + if( steering.x ) + steering.x = steering.x; + + if( steering.y ) + newDir.y += steering.y; + + newDir.EulerNormalize(); + + + if ( act->animate->frame_delta.x > 4.0f ) + { + // make him lean into the turn a bit + newDir.z = _movespeed * ( 0.4f / 320.0f ) * steering.y; + + if ( ( act->flags & FL_FLY ) || ( ( act->flags & FL_SWIM ) && act->waterlevel > 0 ) ) + newDir.z = bound( act->angles.z, -2.0f, 2.0f ); + else + newDir.z = bound( act->angles.z, -5.0f, 5.0f ); + } + else + newDir.z = 0.0f; + + /* + if ( _movingBackwards ) + newDir[YAW] = AngleNormalize180( newDir[YAW] - 180.0f ); + */ + + newDir.AngleVectors( &_movedir ); + + //Set my turn angles; + Vector newAng = _animdir.toAngles(); + + float newDirYaw; + if ( act->bind_info && act->bind_info->bindmaster ) + { + float orientation[3][3]; + float parentOrientation[3][3]; + float mat[3][3]; + AnglesToAxis( newDir, mat ); + Vector parentAngles; + + MatrixToEulerAngles( act->bind_info->bindmaster->orientation, parentAngles ); + parentAngles[ YAW ] = AngleNormalize180( -parentAngles[ YAW ] ); + AnglesToAxis( parentAngles, parentOrientation ); + + R_ConcatRotations( mat, parentOrientation, orientation ); + MatrixToEulerAngles( orientation, newDir ); + + + AnglesToAxis( newAng, mat ); + R_ConcatRotations( mat, parentOrientation, orientation ); + MatrixToEulerAngles( orientation, newAng ); + } + + newDirYaw = AngleNormalize180(newDir.yaw() ); + float newAngYaw = AngleNormalize180(newAng.yaw() ); + + + if ( _movingBackwards ) + newDirYaw = AngleNormalize180( newDirYaw - 180.0f ); + + + float AngleDiff = newDirYaw - newAngYaw; + AngleDiff = AngleNormalize180(AngleDiff); + + //First check if we're close enough just to set our angles + if ( ( ( AngleDiff >= 0.0f ) && ( AngleDiff < _turnspeed ) ) || ( ( AngleDiff <= 0.0f ) && ( AngleDiff > -_turnspeed ) ) ) + { + newAng[YAW] = AngleNormalize360(newDirYaw); + + if ( _faceEnemy ) + { + Entity *currentEnemy; + currentEnemy = act->enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + { + act->enemyManager->FindHighestHateEnemy(); + currentEnemy = act->enemyManager->GetCurrentEnemy(); + } + + if ( currentEnemy ) + { + Vector selfToEnemy; + selfToEnemy = currentEnemy->origin - act->origin; + selfToEnemy = selfToEnemy.toAngles(); + selfToEnemy[PITCH] = 0.0f; + + newAng = selfToEnemy; + } + } + + if ( _adjustAnimDir ) + act->setAngles( newAng ); + return; + } + + //Update our angles + if ( AngleDiff > 0.0f ) + newAng[YAW] = AngleNormalize360( newAng[YAW] += _turnspeed ); + else + newAng[YAW] = AngleNormalize360( newAng[YAW] -= _turnspeed ); + + + + /* + if ( _fliplegs ) + newAng[YAW] += 180; + */ + + + + if ( _movingBackwards ) + newAng[YAW] = AngleNormalize180( newDir[YAW] - 180.0f ); + + + if ( _faceEnemy ) + { + Entity *currentEnemy; + currentEnemy = act->enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + { + act->enemyManager->FindHighestHateEnemy(); + currentEnemy = act->enemyManager->GetCurrentEnemy(); + } + + if ( currentEnemy ) + { + Vector selfToEnemy; + selfToEnemy = currentEnemy->origin - act->origin; + selfToEnemy = selfToEnemy.toAngles(); + selfToEnemy[PITCH] = 0.0f; + + newAng = selfToEnemy; + } + } + + if ( _adjustAnimDir ) + act->setAngles( newAng ); + +} + +//-------------------------------------------------------------- +// Name: CalcMove() +// Class: MovementSubsystem +// +// Description: Calculates the _move Vector +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void MovementSubsystem::CalcMove ( void ) +{ + + // Use total_delta from the animation if we can, but + // over-ride it if we set a forward speed ( When the + // new animation stuff is availiable ) + _totallen = 0; + + if ( act->total_delta != vec_zero ) + _totallen = act->total_delta.length(); + + if ( _forwardspeed ) + { + _totallen = _forwardspeed; + } + + if ( _movementType == MOVEMENT_TYPE_ANIM ) + { + MatrixTransformVector( act->total_delta, act->orientation, _move ); + } + else + { + _movedir.normalize(); + _move = _movedir; + _move *= _totallen; + } + + // If we are not allowed to move, make sure we set the length of our + // movement vector to 0.0 + if ( act->movetype == MOVETYPE_NONE ) + _move *= 0.0f; + + act->total_delta = vec_zero; + _animdir = act->orientation[0]; + +} + + +// +// Name: WaterMove() +// Class: MovementSubsystem +// +// Description: Attempts to move based on being in water +// +// Parameters: None +// +// Returns: stepmoveresult_t +// +stepmoveresult_t MovementSubsystem::WaterMove ( void ) +{ + Vector oldorg; + Vector neworg; + trace_t trace; + int oldwater; + + if ( ( _totallen <= 0.01f ) || ( _move == vec_zero ) ) + { + return STEPMOVE_OK; + } + + // try the move + oldorg = act->origin; + neworg = act->origin + _move; + + trace = G_Trace( oldorg, act->mins, act->maxs, neworg, act, act->edict->clipmask, false, "Actor::WaterMove 1" ); + if ( trace.fraction == 0.0f ) + return STEPMOVE_STUCK; + + oldwater = act->waterlevel; + + act->setOrigin( trace.endpos ); + + CheckWater(); + + // swim monsters don't exit water voluntarily + if ( ( oldwater > 1 ) && ( act->waterlevel < 2 ) ) + { + act->waterlevel = oldwater; + act->setOrigin( oldorg ); + return STEPMOVE_STUCK; + } + + return STEPMOVE_OK; +} + + +// +// Name: AirMove() +// Class: MovementSubsystem +// +// Description: Attempts to move based on being in the air +// +// Parameters: None +// +// Returns: stepmoveresult_t +// +stepmoveresult_t MovementSubsystem::AirMove ( void ) +{ + Vector oldorg; + Vector neworg; + trace_t trace; + int oldwater; + + if ( ( _totallen <= 0.01f ) || ( _move == vec_zero ) ) + { + return STEPMOVE_OK; + } + + // try the move + oldorg = act->origin; + neworg = act->origin + _move; + + trace = G_Trace( oldorg, act->mins, act->maxs, neworg, act, act->edict->clipmask, false, "Actor::AirMove 1" ); + if ( trace.fraction < 0.0001f ) + { + return STEPMOVE_BLOCKED_BY_WATER; + } + + oldwater = act->waterlevel; + + act->setOrigin( trace.endpos ); + + if ( !act->GetActorFlag( ACTOR_FLAG_IGNORE_WATER ) ) + { + CheckWater(); + + // fly monsters don't enter water voluntarily + if ( !oldwater && act->waterlevel ) + { + act->waterlevel = oldwater; + act->setOrigin( oldorg ); + return STEPMOVE_STUCK; + } + } + + return STEPMOVE_OK; +} + + +// +// Name: TryMove() +// Class: MovementSubsystem +// +// Description: Tries to move based on being on the ground +// +// Parameters: None +// +// Returns: stepmoveresult_t +// +stepmoveresult_t MovementSubsystem::IsMoveValid( trace_t &horizontalTrace, trace_t &verticalTrace, const Vector &moveBegin, const Vector &moveEnd ) +{ + horizontalTrace = act->Trace( moveBegin, moveEnd, "MovementSubsystem::IsMoveValid" ); + + if ( horizontalTrace.startsolid ) + { + horizontalTrace = G_Trace( moveBegin, act->mins, act->maxs, moveBegin + _move, act, act->edict->clipmask, false, "MovementSubsystem::IsMoveValid2" ); + } + + if ( horizontalTrace.startsolid || ( horizontalTrace.fraction < 0.0001f ) ) + return STEPMOVE_STUCK; + + // See if we are blocked by a door + if ( _isBlockedByDoor(horizontalTrace) ) + return STEPMOVE_BLOCKED_BY_DOOR; + + // Don't step down an extra step if gravity is turned off for this actor right now or the actor is dead + if ( !act->GetActorFlag( ACTOR_FLAG_USE_GRAVITY ) || (act->gravity == 0) || act->deadflag ) + { + return _noGravityTryMove( horizontalTrace.endpos, verticalTrace ); + } + + stepmoveresult_t result = STEPMOVE_OK; + if ( horizontalTrace.fraction < 1.0f ) + { + if ( horizontalTrace.entityNum == ENTITYNUM_WORLD ) + { + result = STEPMOVE_BLOCKED_BY_WORLD; + } + else + { + result = STEPMOVE_BLOCKED_BY_ENTITY; + _blockingEntity = horizontalTrace.ent->entity; + //if ( _blockingEntity->isSubclassOf( Player ) ) + // act->InContext( "blockedbyplayer" ); + + act->AddStateFlag(STATE_FLAG_BLOCKED_BY_ENTITY); + } + } + + // Phase 2: Send a trace down from the end of the first trace to try and found ground -- and thus + // what should be our new origin + Vector traceBegin( horizontalTrace.endpos ); + Vector traceEnd( traceBegin - ( _step * 2.0f ) ); + + verticalTrace = G_Trace( traceBegin, act->mins, act->maxs, traceEnd, act, act->edict->clipmask, false, "MovementSubsystem::IsMoveValid3" ); + // Check if we are blocked by a fall + if ( _isBlockedByFall( verticalTrace ) ) + return STEPMOVE_BLOCKED_BY_FALL; + + + // If the feet width is set make sure the actor's feet are really on the ground + if ( act->feet_width ) + { + verticalTrace = _feetWidthTrace( verticalTrace.endpos , verticalTrace.endpos - ( _step * 3.0f ) , verticalTrace.endpos, act->edict->clipmask ); + + // Check if we are blocked by a fall + if ( _isBlockedByFall( verticalTrace ) ) + return STEPMOVE_BLOCKED_BY_FALL; + + } + + /* Experiment to deal with uneven floor geometry + if ( result == STEPMOVE_OK ) + { + if ( !_checkHaveGroundEachStep ( moveBegin , moveEnd , act->mins , act->maxs )) + return STEPMOVE_BLOCKED_BY_FALL; + } + */ + + return result; +} +// +// Name: TryMove() +// Class: MovementSubsystem +// +// Description: Tries to move based on being on the ground +// +// Parameters: None +// +// Returns: stepmoveresult_t +// +stepmoveresult_t MovementSubsystem::TryMove ( void ) +{ + + // See if we should bother doing any movement + if ( !_shouldTryMove() ) + return STEPMOVE_OK; + + + // Phase 1: Send a trace out from our origin ( plus stepsize ) along our move Vector. + Vector moveBegin = act->origin; + + trace_t horizontalTrace; + trace_t verticalTrace; + verticalTrace.ent = 0 ; + stepmoveresult_t returnValue = IsMoveValid( horizontalTrace, verticalTrace, moveBegin, moveBegin + _move ); + if ( returnValue == STEPMOVE_OK ) + { + // The move is ok + act->setOrigin( verticalTrace.endpos ); + + // Save the ground information now so we don't have to do it later + if ( verticalTrace.fraction < 1.0f ) + _saveGroundInformation( verticalTrace ); + + + act->flags &= ~FL_PARTIALGROUND; // set in ActorThink + CheckWater(); + + } + else if ( returnValue == STEPMOVE_BLOCKED_BY_FALL ) + { + act->AddStateFlag( STATE_FLAG_STUCK ); + } + return returnValue; +} + + +// +// Name: SimpleMove() +// Class: MovementSubsystem +// +// Description: Move with no collision and no ground following +// +// Parameters: None +// +// Returns: stepmoveresult_t +// +stepmoveresult_t MovementSubsystem::SimpleMove( const bool stickToGround ) +{ + Vector newPosition ( act->origin + _move ); + if ( stickToGround ) + { + Vector traceBegin( newPosition + _step ); + Vector traceEnd( traceBegin - ( _step * 2.0f ) ); + + trace_t trace = G_Trace( traceBegin, act->mins, act->maxs, traceEnd, act, MASK_PATHSOLID, false, "MovementSubsystem::SimpleMove" ); + newPosition = trace.endpos; + } + act->setOrigin( newPosition ); + return STEPMOVE_OK; +} + + +// +// Name: Push() +// Class: MovementSubsystem +// +// Description: Moves the actor based on the push direction +// +// Parameters: const Vector &dir -- The direction of the push +// +// Returns: True or False +// +qboolean MovementSubsystem::Push ( const Vector &dir ) +{ + Vector oldorg; + Vector neworg; + trace_t trace; + int i; + + + if ( !act->GetActorFlag( ACTOR_FLAG_PUSHABLE ) ) + return false; + + for( i = 0 ; i < 5 ; i++ ) + { + oldorg = act->origin + _step; + neworg = oldorg + dir; + + trace = G_Trace( oldorg, act->mins, act->maxs, neworg, act, act->edict->clipmask, false, "Actor::Push 1" ); + + if ( trace.startsolid ) + { + oldorg = act->origin; + neworg = oldorg + dir; + + trace = G_Trace( oldorg, act->mins, act->maxs, neworg, act, act->edict->clipmask, false, "Actor::Push 2" ); + + if ( trace.startsolid ) + return false; + } + + if ( trace.ent && trace.ent->entity->isSubclassOf( Actor ) ) + { + Actor *actor = (Actor *) trace.ent->entity; + actor->Push( dir ); + continue; + } + else + break; + } + + if ( trace.endpos == oldorg ) + return false; + + // Step down to a step height below our original height to account for gravity + + oldorg = trace.endpos; + + if ( act->flags & FL_FLY ) + neworg = oldorg - _step; + else + neworg = oldorg - _step * 2.0f; + + trace = G_Trace( oldorg, act->mins, act->maxs, neworg, act, act->edict->clipmask, false, "Actor::Push 3" ); + + act->setOrigin( trace.endpos ); + + return true; +} + + +// +// Name: CheckWater() +// Class: MovementSubsystem +// +// Description: Sets the waterlevel +// Waterlevel 1 means actor's feet are in the water +// Waterlevel 2 means actor's waist is in the water +// Waterlevel 3 means actor's eyes are in the water -- Important +// because at level 3, we start to choke. +// +// Parameters: None +// +// Returns: None +// +void MovementSubsystem::CheckWater( void ) +{ + Vector sample[3]; + int cont; + + // + // get waterlevel and type + // + act->waterlevel = 0; + act->watertype = 0; + + sample[ 0 ] = act->origin; + sample[ 2 ] = act->EyePosition(); + sample[ 1 ] = ( sample[ 0 ] + sample[ 2 ] ) * 0.5f; + + cont = gi.pointcontents( sample[ 0 ], 0 ); + + if ( ( cont != -1 ) && ( cont & MASK_WATER ) ) + { + act->watertype = cont; + act->waterlevel = 1; + cont = gi.pointcontents( sample[ 2 ], 0 ); + if (cont & MASK_WATER) + { + act->waterlevel = 3; + } + else + { + cont = gi.pointcontents( sample[ 1 ], 0 ); + if (cont & MASK_WATER) + { + act->waterlevel = 2; + } + } + } +} + + +// +// Name: JumpTo() +// Class: MovementSubsystem +// +// Description: Jumps the actor to the specified target +// +// Parameters: const Vector &targ -- The target +// float speed -- Jump speed +// float vertical_speed -- Vertical speed +// +// Returns: float traveltime +// +float MovementSubsystem::JumpTo( const Vector &targetPosition, const Angle angle ) +{ + Trajectory trajectory( act->origin, targetPosition, angle, act->gravity * -sv_currentGravity->value ); + act->velocity = trajectory.GetInitialVelocity(); + + Vector directionXY( targetPosition - act->origin ); + directionXY.z = 0.0f; + act->setAngles( directionXY.toAngles() ); + _movedir = directionXY; + + act->groundentity = NULL; + return trajectory.GetTravelTime(); +} + + +// +// Name: JumpTo() +// Class: MovementSubsystem +// +// Description: Wrapper for JumpTo +// +// Parameters: PathNode *goal -- Jump target +// float speed -- Jump speed +// float vertical_speed -- Vertical jump speed +// +// Returns: float JumpTo result +// +float MovementSubsystem::JumpTo ( PathNode *goal, const Angle angle ) +{ + if ( goal ) + return JumpTo( goal->origin, angle ); + else + return 0; +} + + + +// +// Name: JumpTo() +// Class: MovementSubsystem +// +// Description: Wrapper for JumpTo +// +// Parameters: Entity *goal -- Jump target +// float speed -- Jump speed +// float vertical_speed -- Vertical jump speed +// +// Returns: float JumpTo result +// +float MovementSubsystem::JumpTo ( Entity *goal, const Angle angle ) +{ + if ( goal ) + return JumpTo( goal->origin, angle ); + else + return 0; +} + +// +// Name: SteerTowardsPoint() +// Class: MovementSubsystem +// +// Description: Basic Seek/Intercept Steering +// +// Parameters: const Vector &targetPosition - Point to steer towards +// const Vector &targetVelocity - current velocity used to predict future position of target point +// const Vector &moveDirection - the actors current direction of movement +// const float maxSpeed - maximum speed of actor +// +// Returns: Euler angles containing rotation that will point actor in a direction to intercept the target +// +// x - predictedPosition +// A - myPosition( moveDirection is towards the point of the "A") +// + - centerOfTurn +// +// +// _x +// / +// | +// A + +// +// +// +const Vector MovementSubsystem::SteerTowardsPoint( const Vector &targetPosition, const Vector &targetVelocity, const Vector &moveDirection, const float maxSpeed, const bool adjustSpeed) +{ + assert(act != NULL); + Vector myPosition (act->origin); + Vector predictedPosition = targetPosition + targetVelocity * ( Vector::Distance(targetPosition, myPosition) / maxSpeed ); + Vector desiredDirection = predictedPosition - myPosition; + + Vector newSteeringForce = Vector::AnglesBetween(desiredDirection, moveDirection); + newSteeringForce[ROLL]=0.0f; + newSteeringForce.EulerNormalize(); + + if ( adjustSpeed ) + { + const float currentSpeed = _forwardspeed; + const float turnRate = _turnspeed; + const float turnRadius = currentSpeed / turnRate; + + Vector right; + right.CrossProduct( moveDirection, Vector( 0, 0, 1 ) ); + const Vector centerOfTurn( myPosition + ( right * DotProduct( right, desiredDirection ) ) ); + + const float forwardComponent = DotProduct( desiredDirection, moveDirection ); + const float rightComponent = DotProduct( desiredDirection, right ); + + if ( Vector::DistanceXY( centerOfTurn, predictedPosition ) < turnRadius ) + { + const float newTurnRadius = ( ( rightComponent * rightComponent ) + ( forwardComponent * forwardComponent ) ) / ( 2.0f * rightComponent ); + _forwardspeed = 2.0f * turnRate * newTurnRadius; + } + } + return newSteeringForce; +} + +// +// Name: _canMoveSimplePath() +// Class: MovementSubsystem +// +// Description: Checks if an Actor can do a simple move to the specified destination +// +// Parameters: const Vector &mins -- mins of the actor +// const Vector &maxs -- maxs of the actor +// const Vector &pos -- target destination +// +// Returns: True or False +// +qboolean MovementSubsystem::_canMoveSimplePath( const Vector &mins, const Vector &maxs , const Vector &pos ) const +{ + trace_t trace = act->Trace( act->origin + _step, pos + _step, "Actor::_canMoveSimplePath" ); + if ( trace.fraction == 1.0f ) + return true; + + return false; +} + + +// +// Name: _getRealDestinationPosition() +// Class: MovementSubsystem +// +// Description: uses the mins and maxs to calculate the actual final position +// +// Parameters: const Vector &pos +// +// Returns: Vector target destination +// +Vector MovementSubsystem::_getRealDestinationPosition( const Vector &pos ) const +{ + Vector temp_dir = pos - act->origin; + float temp_length = temp_dir.length(); + + temp_length -= sqrt( act->maxs.x * act->maxs.x * 2.0f ) + 5.0f; + + if ( temp_length < 0.0f ) + temp_length = 0.0f; + + temp_dir.normalize(); + temp_dir *= temp_length; + + return act->origin + temp_dir; +} + + +// +// Name: _feetWidthTrace() +// Class: MovementSubsystem +// +// Description: Does a trace based off of the actor's feet -- to help prevent corner +// of the boundingbox Wile E Coyote problems. +// +// Parameters: const Vector ¤tLoc -- Starting position +// const Vector &bottom -- The end point of the trace +// const Vector &endPos -- The end position being copied over +// +// Returns: trace_t trace +// +trace_t MovementSubsystem::_feetWidthTrace( const Vector ¤tLoc, const Vector &bottom, const Vector &endPos, const int mask ) const +{ + trace_t trace; + Vector temp_mins; + Vector temp_maxs; + Vector saved_endpos; + + temp_mins[0] = -act->feet_width; + temp_mins[1] = -act->feet_width; + temp_mins[2] = act->mins[2]; + + temp_maxs[0] = act->feet_width; + temp_maxs[1] = act->feet_width; + temp_maxs[2] = act->maxs[2]; + + trace = G_Trace( currentLoc, temp_mins, temp_maxs, bottom, act, mask, false, "Actor::CanWalkTo2" ); + + saved_endpos = endPos; + saved_endpos.copyTo( trace.endpos ); + + return trace; +} + + +// +// Name: _getTraceStep() +// Class: MovementSubsystem +// +// Description: Calculates the trace step size +// +// Parameters: None +// +// Returns: float trace_step +// +float MovementSubsystem::_getTraceStep() const +{ + // Find the step amount + float trace_step = act->maxs[0] * 2.0f; //was 2.0 + + // Make sure trace_step is divisible by 8 + trace_step = ( (int)(trace_step / 8.0f ) ) * 8.0f; + + if ( trace_step < 8.0f ) + trace_step = 8.0f; + + return trace_step; +} + + +// +// Name: _checkHaveGroundEachStep() +// Class: MovementSubsystem +// +// Description: Checks each step required to traverse the vector, and checks if there is ground to step on +// +// Parameters: const Vector &start -- starting position +// const Vector &end -- target destination +// const Vector &test_mins -- mins used in the check +// const Vector &test_maxs -- maxs used in the check +// +// +// Returns: True or False +// +qboolean MovementSubsystem::_checkHaveGroundEachStep( const Vector &start, const Vector &end, const Vector &test_mins, const Vector &test_maxs, const int mask ) const +{ + int clipMask = act->edict->clipmask; + if ( mask >= 0 ) + { + clipMask = mask; + } + + // Find the vector to walk + Vector dir = end - start; + float length = dir.length(); + dir.normalize(); + + // Get Trace Steps; + float trace_step = _getTraceStep(); + float small_trace_step = 8; + + // Test each step to see if the ground is not too far below + float last_height = end[2]; + + Vector last_loc = Vector::Identity(); + // Vector last_loc = start; + for( float i = 0 ; i < length ; i += trace_step ) + { + Vector current_loc = start + ( dir * i ); + current_loc[2] = last_height + STEPSIZE; + + Vector bottom = current_loc; + + if ( !act->GetActorFlag( ACTOR_FLAG_ALLOW_FALL ) ) + bottom[2] = last_height - STEPSIZE; + else + bottom[2] = last_height - 1000.0f; + + trace_t trace = G_Trace( current_loc, test_mins, test_maxs, bottom, act, clipMask, false, "Actor::CanWalkTo1" ); + + if ( !( ( trace.fraction == 1.0f ) || trace.startsolid || trace.allsolid ) && act->feet_width ) + trace = _feetWidthTrace( current_loc , bottom , trace.endpos, clipMask ); + + if ( ( trace.fraction == 1.0f ) || trace.startsolid || trace.allsolid ) + { + // The wide one failed, do the small traces for this segment + if ( ( i == 0 ) || ( trace_step == small_trace_step ) ) + return false; + + for( float j = small_trace_step ; j <= trace_step ; j += small_trace_step ) + { + current_loc = last_loc + ( dir * j ); + current_loc[2] = last_height + STEPSIZE; + + bottom = current_loc; + bottom[2] = last_height - STEPSIZE; + + trace = G_Trace( current_loc, test_mins, test_maxs, bottom, act, clipMask, false, "Actor::CanWalkTo3" ); + + if ( ( trace.fraction == 1.0f ) || trace.startsolid || trace.allsolid || + ( trace.ent && trace.ent->entity->isSubclassOf( Sentient ) && !act->GetActorFlag( ACTOR_FLAG_CAN_WALK_ON_OTHERS ) ) ) + return false; + + if ( act->feet_width ) + trace = _feetWidthTrace( current_loc , bottom , trace.endpos, clipMask ); + + last_height = trace.endpos[2]; + } + } + + last_height = trace.endpos[2]; + last_loc = current_loc; + } + + if ( ( last_height > ( end.z + ( STEPSIZE * 2.0f ) ) ) || ( last_height < ( end.z - ( STEPSIZE * 2.0f ) ) ) ) + return false; + + return true; +} + + +// +// Name: _isBlockedByDoor() +// Class: MovementSubsystem +// +// Description: Checks if the actor is blocked by a door +// +// Parameters: trace_t &trace -- the trace +// +// Returns: True or False +// +qboolean MovementSubsystem::_isBlockedByDoor( trace_t &trace ) const +{ + Door *door; + + if ( !act->deadflag && trace.ent ) + { + // Check if we hit a door + + if ( trace.ent->entity->isSubclassOf( Door ) ) + { + door = ( Door * )trace.ent->entity; + if ( !door->locked && !door->isOpen() ) + return true; + } + } + + return false; +} + + +// +// Name: _noGravityTryMove() +// Class: MovementSubsystem +// +// Description: Traces to see if actor can be positioned in space, without worrying if +// ground is underneath -- Should be used by flying creatures +// +// Parameters: const Vector &oldorg -- The origin before trying the move +// +// Returns: stepmoveresult_t +// +stepmoveresult_t MovementSubsystem::_noGravityTryMove( const Vector &oldorg, trace_t &verticalTrace ) const +{ + Vector neworg = oldorg - _step; + //trace_t newTrace; + + // try stepping down + verticalTrace = G_Trace( oldorg, act->mins, act->maxs, neworg, act, act->edict->clipmask, false, "Actor::TryMove 2" ); + + if ( verticalTrace.startsolid ) + return STEPMOVE_STUCK; + + + //act->setOrigin( verticalTrace.endpos ); + + return STEPMOVE_OK; +} + + +// +// Name: _isBlockedByFall() +// Class: MovementSubsystem +// +// Description: Checks if the Actor is blocked by fall +// +// Parameters: trace_t &trace +// +// Returns: True or False +// +qboolean MovementSubsystem::_isBlockedByFall( trace_t &trace ) const +{ + // Determine if we should allow the actor to fall + qboolean allow_fall = _allowFall(); + + // We never want to step on a flying creature, even if we are allowed to + // step on sentients in general. This is because, if we were to step on a flying + // creature, and the flying creature moved, then we might fall, where had we not + // stepped on the flying creature we would still be safe and sound + if ( trace.ent && ( trace.ent->entity->flags & FL_FLY ) && !allow_fall ) + return true; + + // Don't voluntarilty step on sentients + if ( trace.ent && trace.ent->entity->isSubclassOf( Sentient ) && + !allow_fall && !act->GetActorFlag( ACTOR_FLAG_CAN_WALK_ON_OTHERS ) ) + return true; + + // Check if the move places us on solid ground + if ( trace.fraction == 1.0f ) + { + if ( allow_fall ) + { + // don't let guys get stuck standing on other guys + // if monster had the ground pulled out, go ahead and fall + act->groundentity = NULL; + return false; + } + else + { + // walked off an edge + return true; + } + } + + // Make sure ground is not too slopped or we will just slide off + + if ( ( trace.plane.normal[ 2 ] <= 0.7f ) && !allow_fall ) + return true; + + return false; +} + + + +// +// Name: _allowFall() +// Class: MovementSubsystem +// +// Description: Checks if the actor is allowed to fall +// +// Parameters: None +// +// Returns: True or False +// +qboolean MovementSubsystem::_allowFall() const +{ + if ( ( act->flags & FL_PARTIALGROUND ) || + ( act->groundentity && act->groundentity->entity && ( act->groundentity->entity->isSubclassOf( Sentient ) ) ) || + ( act->GetActorFlag( ACTOR_FLAG_ALLOW_FALL ) ) ) + return true; + + return false; +} + + +// +// Name: _shouldTryMove() +// Class: MovementSubsystem +// +// Description: Checks if the Actor should even try to move +// +// Parameters: None +// +// Returns: True or False +// +qboolean MovementSubsystem::_shouldTryMove() const +{ + // We have a velocity so movement of the actor is done in physics + if ( ( act->velocity != vec_zero ) && !act->deadflag ) + return false; + + if ( ( _totallen <= 0.01f ) || ( _move == vec_zero ) ) + return false; + + return true; +} + + +// +// Name: _saveGroundInformation() +// Class: MovementSubsystem +// +// Description: Saves trace information on the actor +// +// Parameters: trace_t &trace +// +// Returns: None +// +void MovementSubsystem::_saveGroundInformation(trace_t &trace) +{ + act->groundentity = trace.ent; + act->groundplane = trace.plane; + act->groundcontents = trace.contents; + act->last_origin = act->origin; + act->SetActorFlag( ACTOR_FLAG_HAVE_MOVED, true ); +} + + +// +// Name: _init() +// Class: MovementSubsystem +// +// Description: Initializes the class +// +// Parameters: None +// +// Returns: None +// +void MovementSubsystem::_init() +{ + _lastmove = STEPMOVE_OK; + _path = NULL; + _turnspeed = TURN_SPEED; + _startpos = act->origin; + _forwardspeed = 0; + _divedir = vec_zero; + act->angles.AngleVectors( &_movedir ); + + //Set our internal step var + _step = Vector( 0.0f, 0.0f, STEPSIZE ); + _movespeed = 1.0f; + + _fliplegs = false; + _movingBackwards = false; + _faceEnemy = false; + _adjustAnimDir = true; + _useCodeDrivenSpeed = false; + + _movementType = MOVEMENT_TYPE_NORMAL; + _stickToGround = true; + CheckWater(); + +} + + +// +// Name: setMove() +// Class: MovementSubsystem +// +// Description: Sets the _move +// +// Parameters: const Vector &move -- The move to set +// +// Returns: None +// +void MovementSubsystem::setMove( const Vector &move ) +{ + _move = move; +} + + +// +// Name: getMove() +// Class: MovementSubsystem +// +// Description: Returns _move +// +// Parameters: None +// +// Returns: Vector _move +// +Vector MovementSubsystem::getMove() +{ + return _move; +} + + + +// +// Name: setMoveDir() +// Class: MovementSubsystem +// +// Description: Sets _moveDir +// +// Parameters: const Vector &moveDir -- The move dir to set +// +// Returns: None +// +void MovementSubsystem::setMoveDir( const Vector &moveDir ) +{ + _movedir = moveDir; +} + + +// +// Name: getMoveDir() +// Class: MovementSubsystem +// +// Description: Returns _movedir +// +// Parameters: None +// +// Returns: Vector _movedir +// +Vector MovementSubsystem::getMoveDir() +{ + return _movedir; +} + + + +// +// Name: setMoveVelocity() +// Class: const Vector &moveVelocity +// +// Description: Sets _movevelocity +// +// Parameters: const Vector &moveVelocity -- the move velocity to set +// +// Returns: None +// +void MovementSubsystem::setMoveVelocity( const Vector &moveVelocity ) +{ + _movevelocity = moveVelocity; +} + + +// +// Name: getMoveVelocity() +// Class: MovementSubsystem +// +// Description: Returns _movevelocity +// +// Parameters: None +// +// Returns: None +// +Vector MovementSubsystem::getMoveVelocity() +{ + return _movevelocity; +} + + +// +// Name: setAnimDir() +// Class: MovementSubsystem +// +// Description: Sets _animdir +// +// Parameters: const Vector &animDir +// +// Returns: None +// +void MovementSubsystem::setAnimDir( const Vector &animDir ) +{ + _animdir = animDir; +} + + +// +// Name: getAnimDir() +// Class: MovementSubsystem +// +// Description: Returns _animdir +// +// Parameters: None +// +// Returns: Vector _animdir +// +Vector MovementSubsystem::getAnimDir() +{ + return _animdir; +} + + +// +// Name: setDiveDir() +// Class: const Vector &diveDir +// +// Description: Sets _divedir +// +// Parameters: const Vector &diveDir -- the dive dir to set +// +// Returns: None +// +void MovementSubsystem::setDiveDir( const Vector &diveDir ) +{ + _divedir = diveDir; +} + + +// +// Name: getDiveDir() +// Class: MovementSubsystem +// +// Description: returns _divedir +// +// Parameters: None +// +// Returns: Vector _divedir +// +Vector MovementSubsystem::getDiveDir() +{ + return _divedir; +} + + +// +// Name: setStartPos() +// Class: MovementSubsystem +// +// Description: Sets _startpos +// +// Parameters: const Vector &startPos -- the start pos to set +// +// Returns: None +// +void MovementSubsystem::setStartPos( const Vector &startPos ) +{ + _startpos = startPos; +} + + +// +// Name: getStartPos +// Class: MovementSubsystem +// +// Description: Returns _startpos +// +// Parameters: None +// +// Returns: Vector _startpos +// +Vector MovementSubsystem::getStartPos() +{ + return _startpos; +} + + + +// +// Name: setTotalLen() +// Class: MovementSubsystem +// +// Description: sets _totallen +// +// Parameters: float totallen -- the total len to set +// +// Returns: None +// +void MovementSubsystem::setTotalLen( float totalLen ) +{ + _totallen = totalLen; +} + + +// +// Name: getTotalLen() +// Class: MovementSubsystem +// +// Description: Returns _totallen +// +// Parameters: None +// +// Returns: float _totallen +// +float MovementSubsystem::getTotalLen() +{ + return _totallen; +} + + +// +// Name: setTurnSpeed() +// Class: MovementSubsystem +// +// Description: Sets _turnspeed +// +// Parameters: float turnSpeed -- the turn speed to set +// +// Returns: None +// +void MovementSubsystem::setTurnSpeed( float turnSpeed ) +{ + _turnspeed = turnSpeed; +} + + +// +// Name: getTurnSpeed() +// Class: MovementSubsystem +// +// Description: Returns _turnspeed +// +// Parameters: None +// +// Returns: float _turnspeed +// +float MovementSubsystem::getTurnSpeed() +{ + return _turnspeed; +} + + +// +// Name: setForwardSpeed() +// Class: MovementSubsystem +// +// Description: Sets _forwardspeed +// +// Parameters: float forwardSpeed +// +// Returns: None +// +void MovementSubsystem::setForwardSpeed( float forwardSpeed ) +{ + _forwardspeed = forwardSpeed; +} + + +// +// Name: getForwardSpeed() +// Class: MovementSubsystem +// +// Description: Returns _forwardspeed +// +// Parameters: None +// +// Returns: float _forwardspeed +// +float MovementSubsystem::getForwardSpeed() +{ + return _forwardspeed; +} + + + +// +// Name: setMoveSpeed() +// Class: MovementSubsystem +// +// Description: Sets _movespeed +// +// Parameters: float moveSpeed +// +// Returns: None +// +void MovementSubsystem::setMoveSpeed( float moveSpeed ) +{ + _movespeed = moveSpeed; +} + + +// +// Name: getMoveSpeed() +// Class: MovementSubsystem +// +// Description: Returns _movespeed +// +// Parameters: None +// +// Returns: float _movespeed +// +float MovementSubsystem::getMoveSpeed() +{ + return _movespeed; +} + + +// +// Name: setFlipLegs() +// Class: MovementSubsystem +// +// Description: Sets _fliplegs +// +// Parameters: qboolean flip +// +// Returns: None +// +void MovementSubsystem::setFlipLegs( qboolean flip ) +{ + _fliplegs = flip; +} + + +// +// Name: getFlipLegs() +// Class: MovementSubsystem +// +// Description: Returns _fliplegs +// +// Parameters: None +// +// Returns: qboolean _fliplegs +// +qboolean MovementSubsystem::getFlipLegs() +{ + return _fliplegs; +} + + +// +// Name: flipLegs() +// Class: MovementSubsystem +// +// Description: Turns the actor around, and allows for backward movement +// +// Parameters: None +// +// Returns: None +// +void MovementSubsystem::flipLegs() +{ + Vector Angles; + + _fliplegs = !_fliplegs; + + + if ( _fliplegs ) + _movingBackwards = true; + else + _movingBackwards = false; + + + Angles = _animdir; + Angles = Angles.toAngles(); + + Angles[PITCH] = AngleNormalize180( Angles[PITCH] ); + Angles[ROLL] = AngleNormalize180( Angles[ROLL] ); + + Angles[YAW] = AngleNormalize180( Angles[YAW] + 180.0f ); + + Angles.AngleVectors( &_animdir ); + + //act->setAngles( Angles ); + +} + + +// +// Name: setMovingBackwards() +// Class: MovementSubsystem +// +// Description: Sets _movingBackwards +// +// Parameters: qboolean backwards +// +// Returns: None +// +void MovementSubsystem::setMovingBackwards( qboolean backwards ) +{ + _movingBackwards = backwards; +} + + +void MovementSubsystem::setFaceEnemy( bool faceEnemy ) +{ + _faceEnemy = faceEnemy; +} + +void MovementSubsystem::setAdjustAnimDir( bool adjustAnimDir ) +{ + _adjustAnimDir = adjustAnimDir; +} + +bool MovementSubsystem::getAdjustAnimDir() +{ + return _adjustAnimDir; +} + +// +// Name: getMovingBackwards +// Class: MovementSubsystem +// +// Description: Returns _movingBackwards +// +// Parameters: None +// +// Returns: None +// +qboolean MovementSubsystem::getMovingBackwards() +{ + return _movingBackwards; +} + +bool MovementSubsystem::getFaceEnemy() +{ + return _faceEnemy; +} + +// +// Name: setPath() +// Class: MovementSubsystem +// +// Description: Sets _path +// +// Parameters: Path *path +// +// Returns: None +// +void MovementSubsystem::setPath( Path *path ) +{ + if ( _path && ( _path != path ) ) + delete _path; + + _path = path; +} + + +// +// Name: getPath() +// Class: MovementSubsystem +// +// Description: Returns _path +// +// Parameters: None +// +// Returns: Path *_path +// +Path* MovementSubsystem::getPath() +{ + return _path; +} + +// +// Name: setStep() +// Class: MovementSubsystem +// +// Description: Sets _step +// +// Parameters: const Vector & lastMove +// +// Returns: None +// +void MovementSubsystem::setStep( const Vector &step ) +{ + _step = step; +} + + +// +// Name: getLastMove() +// Class: MovementSubsystem +// +// Description: Returns _step +// +// Parameters: None +// +// Returns: stepmoveresult_t _lastmove +// +const Vector & MovementSubsystem::getStep() const +{ + return _step; +} + +// +// Name: setLastMove() +// Class: MovementSubsystem +// +// Description: Sets _lastmove +// +// Parameters: stepmoveresult_t lastMove +// +// Returns: None +// +void MovementSubsystem::setLastMove( stepmoveresult_t lastMove ) +{ + _lastmove = lastMove; +} + + +// +// Name: getLastMove() +// Class: MovementSubsystem +// +// Description: Returns _lastmove +// +// Parameters: None +// +// Returns: stepmoveresult_t _lastmove +// +stepmoveresult_t MovementSubsystem::getLastMove() +{ + return _lastmove; +} + +// +// Name: setMovementType() +// Class: MovementSubsystem +// +// Description: Sets the _movementType +// +// Parameters: MovementType_t mType -- the type to set +// +// Returns: None +// +void MovementSubsystem::setMovementType( MovementType_t mType ) +{ + _movementType = mType; +} + +// +// Name: getMovementType() +// Class: MovementSubsystem +// +// Description: Returns the _movementType +// +// Parameters: None +// +// Returns: MovementType_t _movementType; +// +MovementType_t MovementSubsystem::getMovementType() +{ + return _movementType; +} + +void MovementSubsystem::SetStickToGround( const bool stick ) +{ + _stickToGround = stick; +} + +const bool MovementSubsystem::GetStickToGround( void ) const +{ + return _stickToGround; +} + +Entity* MovementSubsystem::getBlockingEntity() +{ + return _blockingEntity; +} + +void MovementSubsystem::clearBlockingEntity() +{ + _blockingEntity = NULL; +} + +// +// Name: DoArchive() +// Class: MovementSubsystem +// +// Description: Sets up the class for archiving +// +// Parameters: Archiver &arc -- the archiving object +// Actor *actor -- the class' actor pointer +// +// Returns: None +// +void MovementSubsystem::DoArchive( Archiver &arc , Actor *actor ) +{ + Archive( arc ); + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "MovementSubsystem::DoArchive -- actor is NULL" ); +} + + +// +// Name: Archive() +// Class: MovementSubsystem +// +// Description: Archives the class +// +// Parameters: Archiver &arc -- the archiving object +// +// Returns: None +// +void MovementSubsystem::Archive( Archiver &arc ) +{ + // Don't archive + //static Vector _step; + + ArchiveEnum( _lastmove, stepmoveresult_t ); + arc.ArchiveFloat( &_forwardspeed ); + arc.ArchiveSafePointer( &_path ); + arc.ArchiveVector( &_move ); + arc.ArchiveVector( &_movedir ); + arc.ArchiveFloat( &_movespeed ); + arc.ArchiveVector( &_movevelocity ); + arc.ArchiveFloat( &_totallen ); + arc.ArchiveFloat( &_turnspeed ); + arc.ArchiveVector( &_animdir ); + arc.ArchiveVector( &_divedir ); + arc.ArchiveVector( &_startpos ); + arc.ArchiveBoolean( &_fliplegs ); + arc.ArchiveBoolean( &_movingBackwards ); + arc.ArchiveBool ( &_faceEnemy ); + arc.ArchiveBool ( &_adjustAnimDir ); + ArchiveEnum( _movementType, MovementType_t ); + arc.ArchiveBool( &_stickToGround ); + arc.ArchiveBool( &_useCodeDrivenSpeed ); + arc.ArchiveSafePointer( &_blockingEntity ); +} diff --git a/dlls/game/actor_locomotion.h b/dlls/game/actor_locomotion.h new file mode 100644 index 0000000..c9eac6c --- /dev/null +++ b/dlls/game/actor_locomotion.h @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actor_locomotion.h $ +// $Revision:: 22 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Motion and Movement Related Classes +// + +class MovementSubsystem; +class LocomotionController; + +#ifndef __ACTOR_LOCOMOTION_H__ +#define __ACTOR_LOCOMOTION_H__ + +#include "actor.h" +#include "actorincludes.h" +#include "weapon.h" +#include "path.h" +#include "steering.h" + +class LocomotionController + { + public: + LocomotionController(); + LocomotionController( Actor *actor ); + ~LocomotionController(); + + void Begin(); + void Evaluate(); + void End(); + + // Accessors & Mutators + void SetMovementStyle( MovementStyle style ); + MovementStyle GetMovementStyle(); + + // Archiving + virtual void Archive( Archiver &arc ); + void DoArchive( Archiver &arc , Actor *actor ); + + protected: + void _init(); + + private: + Actor *act; + MovementStyle _movementStyle; + FollowPath _chase; + + }; + +//============================ +// Class MovementSubsystem +//============================ +// +// Encapsulates movement related data and functionality for the actor +// +class MovementSubsystem + { + public: + MovementSubsystem(); + MovementSubsystem( Actor *actor ); + ~MovementSubsystem(); + + qboolean CanMoveTo( const Vector &pos ); + bool CanWalkTowardsPoint( const Vector &goalPoint, const int mask = -1); + qboolean CanWalkTo( const Vector &pos, float bounding_box_extra = 0, int entnum = ENTITYNUM_NONE, const int mask = -1 ); + qboolean CanWalkToFrom( const Vector &origin, const Vector &pos, float bounding_box_extra = 0, int entnum = ENTITYNUM_NONE, const int mask = -1 ); + void Accelerate( const Vector &steering ); + void CalcMove( void ); + stepmoveresult_t WaterMove( void ); + stepmoveresult_t AirMove( void ); + stepmoveresult_t IsMoveValid( trace_t &horizontalTrace, trace_t &verticalTrace, const Vector &moveBegin, const Vector &moveEnd ); + stepmoveresult_t TryMove( void ); + stepmoveresult_t SimpleMove( const bool stickToGround ); + + qboolean Push( const Vector &dir ); + void CheckWater( void ); + float JumpTo( PathNode * goal, const Angle angle = 45.0f ); + float JumpTo( Entity * goal, const Angle angle = 45.0f ); + float JumpTo( const Vector &targ, const Angle angle = 45.0f ); + const Vector SteerTowardsPoint(const Vector &targetPosition, const Vector &targetVelocity, const Vector &moveDirection, const float maxSpeed=1.0f, const bool adjustSpeed = false); + + + // Archiving + virtual void Archive( Archiver &arc ); + void DoArchive( Archiver &arc , Actor *actor ); + + // Accessors and Mutators + void setStep( const Vector &step ); + const Vector & getStep() const; + + void setLastMove( stepmoveresult_t lastMove ); + stepmoveresult_t getLastMove(); + + void setMove( const Vector &move ); + Vector getMove(); + + void setMoveDir( const Vector &moveDir ); + Vector getMoveDir(); + + void setMoveVelocity( const Vector &moveVelocity ); + Vector getMoveVelocity(); + + void setAnimDir( const Vector &animDir ); + Vector getAnimDir(); + + void setDiveDir( const Vector &diveDir ); + Vector getDiveDir(); + + void setStartPos( const Vector &startPos ); + Vector getStartPos(); + + void setTotalLen( float totalLen ); + float getTotalLen(); + + void setTurnSpeed( float turnSpeed ); + float getTurnSpeed(); + + void setForwardSpeed( float forwardSpeed ); + float getForwardSpeed(); + + void setMoveSpeed( float moveSpeed ); + float getMoveSpeed(); + + void setPath( Path* path ); + Path* getPath(); + + void setFlipLegs( qboolean flip ); + qboolean getFlipLegs(); + void flipLegs(); + + void setMovingBackwards( qboolean backwards ); + qboolean getMovingBackwards(); + + void setFaceEnemy ( bool faceEnemy ); + bool getFaceEnemy (); + + void setAdjustAnimDir( bool adjustAnimDir ); + bool getAdjustAnimDir(); + + + void setMovementType( MovementType_t mType ); + MovementType_t getMovementType(); + + void SetStickToGround( const bool stick ); + const bool GetStickToGround( void ) const; + + void setUseCodeDrivenSpeed( bool useCode ); + bool getUseCodeDrivenSpeed(); + + Entity* getBlockingEntity(); + void clearBlockingEntity(); + + + protected: // Functions + + Vector _getRealDestinationPosition( const Vector &pos ) const; + stepmoveresult_t _noGravityTryMove( const Vector &oldorg, trace_t &verticalTrace ) const; + trace_t _feetWidthTrace( const Vector ¤tLoc , const Vector &bottom , const Vector &endPos, const int mask ) const; + float _getTraceStep() const; + void _saveGroundInformation(trace_t &trace); + qboolean _isBlockedByDoor(trace_t &trace ) const; + qboolean _canMoveSimplePath(const Vector &mins, const Vector &maxs, const Vector &pos ) const; + qboolean _isBlockedByFall(trace_t &trace ) const; + qboolean _allowFall() const; + qboolean _shouldTryMove() const; + qboolean _checkHaveGroundEachStep( const Vector &start, const Vector &end, const Vector &test_mins , const Vector &test_maxs, const int mask = -1 ) const; + void _init(); + + private: // Member Variables + static Vector _step; + + stepmoveresult_t _lastmove; + float _forwardspeed; + PathPtr _path; + Vector _move; + Vector _movedir; + float _movespeed; + Vector _movevelocity; + float _totallen; + float _turnspeed; + Vector _animdir; + Vector _divedir; + Vector _startpos; + qboolean _fliplegs; + qboolean _movingBackwards; + bool _faceEnemy; + bool _adjustAnimDir; + MovementType_t _movementType; + bool _stickToGround; + bool _useCodeDrivenSpeed; + + Actor *act; + EntityPtr _blockingEntity; + + }; + +inline void MovementSubsystem::setUseCodeDrivenSpeed( bool useCode ) +{ + _useCodeDrivenSpeed = useCode; +} + +inline bool MovementSubsystem::getUseCodeDrivenSpeed() +{ + return _useCodeDrivenSpeed; +} + +#endif /* __ACTOR_LOCOMOTION_H__ */ diff --git a/dlls/game/actor_posturecontroller.cpp b/dlls/game/actor_posturecontroller.cpp new file mode 100644 index 0000000..724cea9 --- /dev/null +++ b/dlls/game/actor_posturecontroller.cpp @@ -0,0 +1,308 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actor_posturecontroller.cpp $ +// $Revision:: 8 $ +// $Author:: Steven $ +// $Date:: 5/04/03 2:02p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +//----------------------------------------------------------------------------- + +#include "_pch_cpp.h" +#include "actor_posturecontroller.hpp" + +extern Event EV_Actor_SetPostureStateMap; + +//-------------------------------------------------------------- +// Name: PostureController() +// Class: PostureController +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +PostureController::PostureController() +{ + // Should always use other constructor + gi.Error( ERR_FATAL, "PostureController::PostureController -- Default Constructor Called" ); +} + +//-------------------------------------------------------------- +// Name: PostureController() +// Class: PostureController +// +// Description: Constructor +// +// Parameters: Actor *actor +// +// Returns: None +//-------------------------------------------------------------- +PostureController::PostureController( Actor *actor ) +{ + if ( actor ) + act = actor; + else + gi.Error( ERR_DROP, "PostureController::PostureController -- actor is NULL" ); + + init(); +} + + +//-------------------------------------------------------------- +// Name: ~PostureController() +// Class: PostureController +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +PostureController::~PostureController() +{ + act->freeConditionals( _postureConditionals ); +} + + +//-------------------------------------------------------------- +// Name: DoArchive() +// Class: PostureController +// +// Description: Archives our Actor and Calls our Archive Function +// +// Parameters: Archiver &arc -- The Archiver Object +// Actor *actor -- The pointer to our actor +// +// Returns: None +//-------------------------------------------------------------- +void PostureController::DoArchive( Archiver &arc, Actor *actor ) +{ + Archive( arc ); + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "PostureController::DoArchive -- actor is NULL" ); + +} + +//-------------------------------------------------------------- +// Name: Archive() +// Class: PostureController +// +// Description: Archives the class +// +// Parameters: Archiver &arc +// +// Returns: None +//-------------------------------------------------------------- +void PostureController::Archive( Archiver &arc ) +{ + arc.ArchiveString( &_postureStateMap_Name ); + arc.ArchiveString( &_currentPostureState_Name ); + arc.ArchiveString( &_requestedPostureState_Name ); + arc.ArchiveSafePointer ( &_requestor ); + + if ( !arc.Saving() ) + { + if ( _postureStateMap_Name.length() ) + { + Event *event; + + event = new Event( EV_Actor_SetPostureStateMap ); + event->AddString( _postureStateMap_Name ); + event->AddInteger( 1 ); + act->ProcessEvent ( event ); + + _currentPostureState = _postureStateMap->FindState( _currentPostureState_Name.c_str() ); + _requestedPostureState = _postureStateMap->FindState(_requestedPostureState_Name.c_str() ); + } + } +} + +//-------------------------------------------------------------- +// Name: init() +// Class: PostureController +// +// Description: Initializes the class +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void PostureController::init() +{ + _postureStateMap = NULL; + _postureStateMap_Name = ""; + + _currentPostureState = NULL; + _currentPostureState_Name = ""; + + _requestedPostureState = NULL; + _requestedPostureState_Name = ""; + + _requestor = NULL; +} + + +//-------------------------------------------------------------- +// Name: evaluate() +// Class: PostureController +// +// Description: Evaluation Routine, Called Every Frame +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void PostureController::evaluate() +{ + int count; + State *laststate = NULL; + str currentanim; + str stateLegAnim; + + stateLegAnim = act->animname; + count = 0; + + if ( !_postureStateMap ) + return; + + if ( act->deadflag || !_currentPostureState ) + return; + + do + { + // Since we could get into an infinite loop here, do a check to make sure we don't. + + count++; + if ( count > 10 ) + { + gi.WDPrintf( "Possible infinite loop in posture state '%s'\n", _currentPostureState->getName() ); + if ( count > 50 ) + { + gi.Error( ERR_DROP, "Stopping due to possible infinite state loop\n" ); + break; + } + } + + // Determine the next state to go to + + laststate = _currentPostureState; + _currentPostureState = _currentPostureState->Evaluate( *act, &_postureConditionals ); + + if ( !_currentPostureState ) + return; + + _currentPostureState_Name = _currentPostureState->getName(); + + // Change the behavior if the state has changed + if ( laststate != _currentPostureState ) + { + // Initialize some stuff for changing states + act->SetActorFlag( ACTOR_FLAG_POSTURE_ANIM_DONE, false ); + } + + if ( _currentPostureState == _requestedPostureState ) + { + if ( _requestor ) + _requestor->ProcessEvent( EV_PostureChanged_Completed ); + } + + // Change the animation if it has changed + currentanim = _currentPostureState->getLegAnim( *act, &_postureConditionals ); + + if ( currentanim.length() && ( stricmp( stateLegAnim , currentanim.c_str() ) != 0 ) ) + { + act->SetAnim( currentanim, EV_Posture_Anim_Done, legs ); + stateLegAnim = currentanim; + } + } + while( laststate != _currentPostureState ); + + + +} + + +//-------------------------------------------------------------- +// Name: requestPosture() +// Class: PostureController +// +// Description: Request Handler for a specific Posture +// +// Parameters: PostureStates_t postureState +// +// Returns: true -- if the posture is viable +// false -- if the posture in not viable +//-------------------------------------------------------------- +bool PostureController::requestPosture( const str &postureState , Listener *requestor ) +{ + if ( !_postureStateMap ) + return false; + + _requestedPostureState = _postureStateMap->FindState( postureState.c_str() ); + + if ( _requestedPostureState ) + { + _requestor = requestor; + _requestedPostureState_Name = postureState; + return true; + } + + + + return false; +} + +void PostureController::setPostureStateMap( const str &stateMap , bool loading ) +{ + str animName; + + // Load the new state map + _postureStateMap_Name = stateMap; + + //_postureConditionals.FreeObjectList(); + act->freeConditionals( _postureConditionals ); + _postureStateMap = GetStatemap( _postureStateMap_Name, ( Condition * )act->Conditions, &_postureConditionals, false ); + + // Set the first state + _currentPostureState_Name = "START"; + + // Initialize the actors first animation + if ( !loading ) + setPostureState( _currentPostureState_Name.c_str() ); +} + +void PostureController::setPostureState( const str &postureState ) +{ + + if ( !_postureStateMap ) + return; + + if ( act->deadflag ) + return; + + _currentPostureState = _postureStateMap->FindState( postureState.c_str() ); + + +} + +void PostureController::setPostureState( const str &postureState, const str &requestedState ) +{ + if ( !_postureStateMap ) + return; + + if ( act->deadflag ) + return; + + _currentPostureState = _postureStateMap->FindState( postureState.c_str() ); + _requestedPostureState = _postureStateMap->FindState( requestedState.c_str() ); + _requestedPostureState_Name = requestedState.c_str(); +} diff --git a/dlls/game/actor_posturecontroller.hpp b/dlls/game/actor_posturecontroller.hpp new file mode 100644 index 0000000..48d4d11 --- /dev/null +++ b/dlls/game/actor_posturecontroller.hpp @@ -0,0 +1,93 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actor_posturecontroller.h $ +// $Revision:: 15 $ +// $Author:: Sketcher $ +// $Date:: 5/20/02 3:55p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +//----------------------------------------------------------------------------- + +class PostureController; + +#ifndef __ACTOR_POSTURECONTROLLER_HPP__ +#define __ACTOR_POSTURECONTROLLER_HPP__ + +#include "actor.h" +#include "actorincludes.h" + +extern Event EV_Posture_Anim_Done; +extern Event EV_PostureChanged_Completed; + +//------------------------- CLASS ------------------------------ +// +// Name: PostureController +// Base Class: None +// +// Description: This class will control the posture of the Actor. It will maintain +// an enum of the current posture and will have methods for changing +// posture via Animations... Eventually, these will need to be modified +// to call functions on some type of animation manager +// +// Method of Use: Instantiated by the Actor +// +//-------------------------------------------------------------- +class PostureController +{ + public: + PostureController(); + PostureController( Actor *actor ); + ~PostureController(); + + virtual void Archive( Archiver &arc ); + void DoArchive( Archiver &arc , Actor *actor ); + + void evaluate(); + + bool requestPosture( const str &postureState , Listener *requestor ); + + const str& getRequestedPostureName(); + const str& getCurrentPostureName(); + + void setPostureStateMap( const str &stateMap , bool loading ); + void setPostureState( const str &postureState ); + void setPostureState( const str &postureState , const str &requestedState ); + + protected: + void init(); + + private: + StateMap *_postureStateMap; + str _postureStateMap_Name; + + State *_currentPostureState; + str _currentPostureState_Name; + + State *_requestedPostureState; + str _requestedPostureState_Name; + + SafePtr _requestor; + Container _postureConditionals; + + + + Actor *act; +}; + +inline const str& PostureController::getRequestedPostureName() +{ + return _requestedPostureState_Name; +} + +inline const str& PostureController::getCurrentPostureName() +{ + return _currentPostureState_Name; +} + +#endif /* __ACTOR_POSTURECONTROLLER_HPP__ */ + diff --git a/dlls/game/actor_sensoryperception.cpp b/dlls/game/actor_sensoryperception.cpp new file mode 100644 index 0000000..afb1f8a --- /dev/null +++ b/dlls/game/actor_sensoryperception.cpp @@ -0,0 +1,1085 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actor_sensoryperception.cpp $ +// $Revision:: 21 $ +// $Author:: Sketcher $ +// $Date:: 4/09/03 5:04p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// + +#include "_pch_cpp.h" +#include "actor_sensoryperception.h" +#include "player.h" +#include "object.h" + +//====================================== +// SensoryPerception Implementation +//====================================== + +// +// Name: SensoryPerception() +// Parameters: None +// Description: Constructor +// +SensoryPerception::SensoryPerception() +{ + // Should always use other constructor + gi.Error( ERR_FATAL, "SensoryPerception::SensoryPerception -- Default Constructor Called" ); +} + +// +// Name: SensoryPerception() +// Parameters: Actor* actor -- The actor pointer to be assigned +// to the internal actor pointer +// Description: Constructor +// +SensoryPerception::SensoryPerception(Actor *actor ) +{ + //Initialize our Actor + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "SensoryPerception::SensoryPerception -- actor is NULL" ); + + _init(); +} + +// +// Name: ~SensoryPerception() +// Parameters: None +// Description: Destructor +// +SensoryPerception::~SensoryPerception() +{ + +} + +qboolean SensoryPerception::isInLineOfSight( const Vector &position , const int entNum ) +{ + trace_t trace; + Vector startPos; + Vector endPos; + Entity *traceEnt; + + + startPos = act->origin; + startPos.z += 15; + + endPos = position; + endPos.z += 15; + + Vector modMins; + Vector modMaxs; + + modMins = act->mins; + modMins *= 1.5; + + modMaxs = act->maxs; + modMaxs *= 1.5; + + trace = G_Trace( startPos, modMins, modMaxs, endPos, act, act->edict->clipmask, false, "isInLineOfSight" ); + //G_DebugLine( startPos , trace.endpos, 1.0f, 0.0f, 1.0f, 1.0f ); + + _lineOfSight.entNum = entNum; + _lineOfSight.time = level.time; + + if ( trace.entityNum == entNum || entNum == ENTITYNUM_NONE ) + { + _lineOfSight.inLineOfSight = true; + } + else + { + traceEnt = G_GetEntity( trace.entityNum ); + _lineOfSight.inLineOfSight = false; + + if ( traceEnt && traceEnt->isSubclassOf( Actor) ) + { + Actor *traceActor; + traceActor = (Actor*)traceEnt; + + _lineOfSight.inLineOfSight = traceActor->sensoryPerception->checkInLineOfSight( position , entNum ); + } + } + + return _lineOfSight.inLineOfSight; +} + +qboolean SensoryPerception::checkInLineOfSight( const Vector &position , const int entNum ) +{ + float timeDelta; + + timeDelta = level.time - _lineOfSight.time; + + if ( timeDelta > .5 || entNum != _lineOfSight.entNum ) + return isInLineOfSight( position , entNum ); + else + return _lineOfSight.inLineOfSight; + +} + +void SensoryPerception::_init() +{ + + // Set up Stimuli + _stimuli = STIMULI_ALL; + _permanent_stimuli = STIMULI_ALL; + + //Member Vars + _noise_position = vec_zero; + _noise_time = 0; + _fov = DEFAULT_FOV; + _fovdot = (float)cos( (double)_fov * 0.5 * M_PI / 180.0 ); + _vision_distance = DEFAULT_VISION_DISTANCE; + _last_soundType = SOUNDTYPE_NONE; + _nextSenseTime = 0.0f; + + _lineOfSight.entNum = 0; + _lineOfSight.inLineOfSight = false; + _lineOfSight.time = 0.0f; +} + +// +// Name: SenseEnemies() +// Parameters: None +// Description: Checks for players and teammates, to see if the actor +// needs to wake up +// +void SensoryPerception::SenseEnemies() +{ + int i; + + if ( _nextSenseTime > level.time ) + return; + + _nextSenseTime = level.time + 0.5f + G_Random( 0.2f ); + //_nextSenseTime = 0; + + //Well, who your enemies are depends on what side your on + if ( ( act->actortype == IS_ENEMY ) || ( act->actortype == IS_MONSTER ) ) + { + // + //If we an ENEMY or a MONSTER than teammates are our enemies + // + Sentient *teammate; + for ( i = 1 ; i <= TeamMateList.NumObjects() ; i++ ) + { + teammate = TeamMateList.ObjectAt( i ); + + if ( _SenseEntity( teammate ) ) return; + + } + + } + else + { + // + //If we an CIVILIAN, FRIEND, or TEAMMATE our potiential enemies are active monsters + // + Entity *ent; + + for ( i = 1 ; i <= ActiveList.NumObjects() ; i++ ) + { + ent = ActiveList.ObjectAt( i ); + _SenseEntity(ent); + } + + //In case we didn't find an enemy, but if the players nearby, we want to wake up anyway + _SenseEntity( GetPlayer( 0 ) ); + } + +} + +qboolean SensoryPerception::_SenseEntity( Entity *ent ) +{ + // Dont want to target the enemy if he's not a valid target + + if (!ent) + return false; + + if ( !EntityIsValidTarget(ent) ) + return false; + + // Dont wake ourselves up + if ( ent->entnum == act->entnum ) + return false; + + if ( ent->isSubclassOf ( Actor ) ) + { + if ( !ent->isThinkOn() ) + return false; + } + + /* + // Check if we're in the PVS + if ( gi.inPVS( ent->centroid, act->centroid ) ) + { + // Check if we can see the enemy + if ( CanSeeEntity ( act , ent , true , true ) ) + Stimuli( STIMULI_SIGHT , ent ); + else + { + // Lets not idle for teammates, just players + if ( ent->isSubclassOf( Player ) ) + { + // We couldn't see the enemy, but we're in the PVS, so we need to wake up and idle a bit. + if ( ( world->farplane_distance == 0 ) || ( Distance( ent->centroid, act->centroid ) < world->farplane_distance ) ) + { + act->Wakeup(); + //act->ActivateAI(); + } + + + } + else + return false; + } + + return true; + } + */ + if ( gi.inPVS( ent->centroid, act->centroid ) ) + { + Vector enemyToSelf; + enemyToSelf = act->origin - ent->origin; + + float visionDistance = GetVisionDistance(); + if ( enemyToSelf.length() < visionDistance ) + { + if ( CanSeeEntity( act, ent, true, true ) ) + Stimuli( STIMULI_SIGHT , ent ); + } + + } + + + return false; +} + +// +// Name: Stimuli() +// Parameters: int stimuli -- The Stimuli +// Description: Wakes up the actor if it should respond to the stimuli +// +void SensoryPerception::Stimuli( int stimuli ) +{ + if ( ShouldRespondToStimuli( stimuli ) ) + { + act->Wakeup(); + act->ActivateAI(); + } +} + +// +// Name: Stimuli() +// Parameters: int new_stimuli -- The Stimuli +// Entity ent -- The Entity +// qboolean force -- Force the actor to make the entity an enemy +// Description: Wakes up the actor if it should respond to the stimuli +// +void SensoryPerception::Stimuli( int new_stimuli, Entity *ent ) +{ + if ( !ent ) + return; + + if ( ShouldRespondToStimuli( new_stimuli ) && EntityIsValidTarget(ent)) + { + act->enemyManager->TryToAddToHateList( ent ); + act->Wakeup(); + act->ActivateAI(); + } + +} + +// +// Name: Stimuli() +// Parameters: int new_stimuli -- The Stimuli +// const Vector &pos -- The location of the stimuli +// Description: Wakes up the actor if it should respond to the stimuli +// +void SensoryPerception::Stimuli( int new_stimuli, const Vector &pos ) +{ + + if ( ShouldRespondToStimuli( new_stimuli ) ) + { + // Investigate the position + + if ( !act->GetActorFlag( ACTOR_FLAG_NOISE_HEARD ) ) + { + _noise_position = pos; + _noise_time = level.time; + act->SetActorFlag( ACTOR_FLAG_NOISE_HEARD, true ); + } + + if ( act->last_time_active + 10.0f < level.time ) + { + act->Wakeup(); + act->ActivateAI(); + } + } + +} + +// +// Name: Stimuli() +// Parameters: int new_stimuli -- The stimuli +// const Vector &pos -- The location of the stimuli +// int sound_Type -- sound type of the stimuli +// Description: Wakes up the actor if it should respond to the stimuli +// +void SensoryPerception::Stimuli( int new_stimuli, const Vector &pos, int sound_Type ) +{ + + if ( ShouldRespondToStimuli( new_stimuli ) ) + { + + if ( !act->GetActorFlag( ACTOR_FLAG_NOISE_HEARD ) ) + { + _noise_position = pos; + _last_soundType = sound_Type; + _noise_time = level.time; + act->SetActorFlag( ACTOR_FLAG_NOISE_HEARD, true ); + } + + if ( act->last_time_active + 10.0f < level.time ) + { + act->Wakeup(); + act->ActivateAI(); + } + } +} + +// +// Name: RespondTo() +// Parameters: const str &stimuli_name -- string name for the stimuli +// qboolean respond -- Respond or Not to the stimuli +// Description: sets if the actor will respond to a particular stimulus +// +void SensoryPerception::RespondTo(const str &stimuli_name , qboolean respond ) +{ + if ( !Q_stricmp( stimuli_name.c_str() , "sight" ) ) + RespondTo(STIMULI_SIGHT , respond ); + else if ( !Q_stricmp( stimuli_name.c_str() , "sound" ) ) + RespondTo(STIMULI_SOUND , respond ); + else if ( !Q_stricmp( stimuli_name.c_str() , "pain" ) ) + RespondTo(STIMULI_PAIN , respond ); + else if ( !Q_stricmp( stimuli_name.c_str() , "script" ) ) + RespondTo(STIMULI_SCRIPT , respond ); + else if ( !Q_stricmp( stimuli_name.c_str() , "all" ) ) + RespondTo(STIMULI_ALL , respond ); + else if ( !Q_stricmp( stimuli_name.c_str() , "none" ) ) + RespondTo(STIMULI_NONE , respond ); +} + +// +// Name: RespondTo() +// Parameters: int stimuli -- the stimuli +// qboolean respond -- Respond or Not to the stimuli +// Description: sets if the actor will respond to a particular stimulus +// +void SensoryPerception::RespondTo( int stimuli , qboolean respond ) +{ + if ( stimuli == STIMULI_ALL ) + { + if ( respond ) + _stimuli = STIMULI_ALL; + else + _stimuli = STIMULI_NONE; + + return; + } + + if ( stimuli == STIMULI_NONE ) + { + if ( respond ) + _stimuli = STIMULI_NONE; + else + _stimuli = STIMULI_ALL; + + return; + } + + if ( respond ) + _stimuli |= stimuli; + else + _stimuli &= ~stimuli; +} + +// +// Name: PermanentlyRespondTo +// Parameters: const str &stimuli_name -- string name for the stimuli +// qboolean respond -- Respond or Not to the stimuli +// Description: Allows the actor to ALWAYS respond or not to a particular stimulus +// this allows us to make actors that are deaf or blind... etc +// +void SensoryPerception::PermanentlyRespondTo(const str &stimuli_name , qboolean respond ) +{ + if ( !Q_stricmp( stimuli_name.c_str() , "sight" ) ) + { + if ( respond ) + _permanent_stimuli |= STIMULI_SIGHT; + else + _permanent_stimuli &= ~STIMULI_SIGHT; + } + else if ( !Q_stricmp( stimuli_name.c_str() , "sound" ) ) + { + if ( respond ) + _permanent_stimuli |= STIMULI_SOUND; + else + _permanent_stimuli &= ~STIMULI_SOUND; + } + else if ( !Q_stricmp( stimuli_name.c_str() , "pain" ) ) + { + if ( respond ) + _permanent_stimuli |= STIMULI_PAIN; + else + _permanent_stimuli &= ~STIMULI_PAIN; + + } + else if ( !Q_stricmp( stimuli_name.c_str() , "script" ) ) + { + if ( respond ) + _permanent_stimuli |= STIMULI_SCRIPT; + else + _permanent_stimuli &= ~STIMULI_SCRIPT; + + } + else if ( !Q_stricmp( stimuli_name.c_str() , "all" ) ) + { + if ( respond ) + _permanent_stimuli = STIMULI_ALL; + else + _permanent_stimuli = STIMULI_NONE; + + } + else if ( !Q_stricmp( stimuli_name.c_str() , "none" ) ) + { + if ( respond ) + _permanent_stimuli = STIMULI_NONE; + else + _permanent_stimuli = STIMULI_ALL; + } +} + +// +// Name: ShouldRespondToStimuli() +// Parameters: int new_stimuli -- the stimuli to check +// Description: Checks if the actor should respond to the stimuli +// +qboolean SensoryPerception::ShouldRespondToStimuli( int new_stimuli ) +{ + if( ( act->targetType == ATTACK_SCRIPTED_ONLY ) && ( new_stimuli != STIMULI_SCRIPT ) ) return false; + + if ( _stimuli == STIMULI_ALL ) + return true; + + if ( _stimuli == STIMULI_NONE ) + return false; + + return ( (( new_stimuli & _stimuli ) && ( new_stimuli & _permanent_stimuli )) || ( new_stimuli == STIMULI_SCRIPT ) ); +} + + +// +// Name: ShowInfo() +// Parameters: None +// Description: Prints sensoryinformation to the console +// +void SensoryPerception::ShowInfo() +{ + if ( ShouldRespondToStimuli( STIMULI_ALL ) ) + { + gi.Printf( "Actor is Responding To: ALL" ); + return; + } + + if ( ShouldRespondToStimuli( STIMULI_NONE ) ) + { + gi.Printf( "Actor is Responding To: NONE" ); + return; + } + + if ( ShouldRespondToStimuli( STIMULI_SIGHT ) ) + gi.Printf( "Actor is Responding To: SIGHT" ); + + if ( ShouldRespondToStimuli( STIMULI_SOUND ) ) + gi.Printf( "Actor is Responding To: SOUND" ); + + if ( ShouldRespondToStimuli( STIMULI_PAIN ) ) + gi.Printf( "Actor is Responding To: PAIN" ); + + if ( ShouldRespondToStimuli( STIMULI_SCRIPT ) ) + gi.Printf( "Actor is Responding To: SCRIPT" ); + +} + + +// +// Name: WithinVisionDistance() +// Parameters: Entity *ent -- The entity to be checked +// Description: Checks if the passed in entity is +// within the vision_distance of the actor, or the +// farplane_distance of the world ( whichever is smaller ) +// +qboolean SensoryPerception::WithinVisionDistance( const Entity *ent ) +{ + float distance; + + if ( !ent ) + return false; + + if ( !act ) + gi.Error( ERR_DROP, "SensoryPerception::WithinVisionDistance -- actor is NULL" ); + + + // Use whichever is less : the actor's vision distance or the distance of the farplane (fog) + if ( ( world->farplane_distance != 0.0f ) && ( world->farplane_distance < _vision_distance ) ) + distance = world->farplane_distance; + else + distance = _vision_distance; + + return act->WithinDistance( ent, distance ); +} + +qboolean SensoryPerception::WithinVisionDistance( const Vector &pos ) +{ + float distance; + + if ( !act ) + gi.Error( ERR_DROP, "SensoryPerception::WithinVisionDistance -- actor is NULL" ); + + // Use whichever is less : the actor's vision distance or the distance of the farplane (fog) + if ( ( world->farplane_distance != 0.0f ) && ( world->farplane_distance < _vision_distance ) ) + distance = world->farplane_distance; + else + distance = _vision_distance; + + return act->WithinDistance( pos, distance ); +} + +// +// Name: InFOV() +// Parameters: Vector &pos -- The position vector to be checked +// float check_fov -- The fov number to be used +// float check_fovdot -- the dot product +// Description: Checks if the passed in pos to see if it is in the FOV +// +qboolean SensoryPerception::InFOV( const Vector &pos, float check_fov, float check_fovdot ) +{ + Vector delta; + float dot; + Vector temp; + int tagNum; + + + if ( !act ) + gi.Error( ERR_DROP, "SensoryPerception::InFOV -- actor is NULL" ); + + if ( check_fov == 360.0f ) + return true; + temp = act->EyePosition(); + delta = pos - act->EyePosition(); + + if ( !delta.x && !delta.y ) + { + // special case for straight up and down + return true; + } + + // give better vertical vision + delta.z = 0; + + delta.normalize(); + + tagNum = gi.Tag_NumForName( act->edict->s.modelindex, "tag_eyes" ); + + if ( tagNum >= 0 ) + { + Vector tag_pos; + Vector forward; + + act->GetTag( tagNum, &tag_pos, &forward ); + dot = DotProduct( &forward[0] , delta ); + } + else + { + dot = DotProduct( act->orientation[ 0 ], delta ); + } + + return ( dot > check_fovdot ); + +} + + +// +// Name: InFOV() +// Parameters: Vector &pos -- The position to be checked +// Description: Calls another version of InFOV +// +qboolean SensoryPerception::InFOV( const Vector &pos ) +{ + return InFOV( pos, _fov, _fovdot ); +} + + +// +// Name: InFOV() +// Parameters: Entity* ent -- Provides the Position to check +// Description: Calls another version InFOV +// +qboolean SensoryPerception::InFOV( const Entity *ent ) +{ + return InFOV( ent->centroid ); +} + +// +// Name: CanSeeEntity() +// Parameters: Entity *start - The entity trying to see +// Entity *target - The entity that needs to be seen +// qboolean useFOV - take FOV into consideration or not +// qboolean useVisionDistance - take visionDistance into consideration +// Description: Wraps a lot of the different CanSee Functions into one +// +qboolean SensoryPerception::CanSeeEntity( Entity *start, const Entity *target, qboolean useFOV, qboolean useVisionDistance ) +{ + + // Check for NULL + if ( !start || !target ) + return false; + + // Check if This Actor can even see at all + if ( !ShouldRespondToStimuli( STIMULI_SIGHT ) ) + return false; + + // Check for FOV + if ( useFOV ) + { + if ( !InFOV( target ) ) + return false; + } + + // Check for vision distance + if ( useVisionDistance ) + { + if ( !WithinVisionDistance( target ) ) + return false; + } + + // Do Trace + trace_t trace; + Vector p; + Vector eyePos; + + p = target->centroid; + eyePos = vec_zero; + + // If the start entity is an actor, then we want to add in the eyeposition + if ( start->isSubclassOf ( Actor ) ) + { + Actor *a; + a = (Actor*)start; + + if ( !a ) + return false; + + eyePos = a->EyePosition(); + + } + + // Check if he's visible + trace = G_Trace( eyePos, vec_zero, vec_zero, p, target, MASK_OPAQUE, false, "SensoryPerception::CanSeeEntity" ); + + //if ( act->actortype == IS_TEAMMATE ) + // { + // G_DebugLine( eyePos , target->centroid, 0.0f, 0.0f, 1.0f, 1.0f ); + // G_DebugLine( eyePos , trace.endpos , 1.0f, 0.0f, 1.0f, 1.0f ); + // } + + if ( ( trace.fraction == 1.0f ) || ( trace.ent == target->edict ) ) + return true; + + // Check if his head is visible + p.z = target->absmax.z; + trace = G_Trace( eyePos, vec_zero, vec_zero, p, target, MASK_OPAQUE, false, "SensoryPerception::CanSeeEntity" ); + if ( trace.fraction == 1.0f || trace.ent == target->edict ) + return true; + + return false; + +} + + +// +// Name: CanSeeEntity() +// Parameters: Vector &start -- The starting position +// Entity *target - The entity that needs to be seen +// qboolean useFOV - take FOV into consideration or not +// qboolean useVisionDistance - take visionDistance into consideration +// Description: Wraps a lot of the different CanSee Functions into one +// +qboolean SensoryPerception::CanSeeEntity( const Vector &start , const Entity *target, qboolean useFOV , qboolean useVisionDistance ) +{ + Vector realStart; + + // Check for NULL + if ( !target ) + return false; + + // Check if This Actor can even see at all + if ( !ShouldRespondToStimuli( STIMULI_SIGHT ) ) + return false; + + + // Check for FOV + if ( useFOV ) + { + if ( !InFOV( target ) ) + return false; + } + + // Check for vision distance + if ( useVisionDistance ) + { + if ( !WithinVisionDistance( target ) ) + return false; + } + + // Do Trace + trace_t trace; + Vector p; + Vector eyePos; + + realStart = start; + + p = target->centroid; + + // Add in the eye offset + + eyePos = act->EyePosition() - act->origin; + realStart += eyePos; + + // Check if he's visible + trace = G_Trace( realStart, vec_zero, vec_zero, p, act, MASK_OPAQUE, false, "SensoryPerception::CanSeeEntity" ); + if ( trace.fraction == 1.0f || trace.ent == target->edict ) + return true; + + // Check if his head is visible + p.z = target->absmax.z; + trace = G_Trace( realStart, vec_zero, vec_zero, p, act, MASK_OPAQUE, false, "SensoryPerception::CanSeeEntity" ); + if ( trace.fraction == 1.0f || trace.ent == target->edict ) + return true; + + + return false; +} + + +// +// Name: CanSeeEntityComplex +// Parameters: Entity *start - The entity trying to see +// Entity *target - The entity that needs to be seen +// qboolean useFOV - take FOV into consideration or not +// qboolean useVisionDistance - take visionDistance into consideration +// Description: More detailed can see check +// +qboolean SensoryPerception::CanSeeEntityComplex(Entity *start, Entity *target, qboolean useFOV, qboolean useVisionDistance ) +{ + + // Check for NULL + if ( !start || !target ) + return false; + + // Check if This Actor can even see at all + if ( !ShouldRespondToStimuli( STIMULI_SIGHT ) ) + return false; + + if ( !act ) + gi.Error( ERR_DROP, "SensoryPerception::CanSeeEntityComplex -- actor is NULL" ); + + // Check if target is alive + if ( !act->IsEntityAlive( target ) ) + return false; + + return _CanSeeComplex(start->centroid, target, useFOV, useVisionDistance ); + +} + + + +// +// Name: CanSeeEntityComplex +// Parameters: Entity *start - The entity trying to see +// Entity *target - The entity that needs to be seen +// qboolean useFOV - take FOV into consideration or not +// qboolean useVisionDistance - take visionDistance into consideration +// Description: More detailed can see check +// +qboolean SensoryPerception::CanSeeEntityComplex( Vector &start, Entity *target, qboolean useFOV , qboolean useVisionDistance ) +{ + // Check for NULL + if ( !target ) + return false; + + // Check if This Actor can even see at all + if ( !ShouldRespondToStimuli( STIMULI_SIGHT ) ) + return false; + + if ( !act ) + gi.Error( ERR_DROP, "SensoryPerception::CanSeeEntityComplex -- actor is NULL" ); + + + // Check if target is alive + if ( !act->IsEntityAlive( target ) ) + return false; + + return _CanSeeComplex(start, target, useFOV, useVisionDistance ); + +} + +qboolean SensoryPerception::CanSeePosition( const Vector &start, const Vector &position, qboolean useFOV , qboolean useVisionDistance ) +{ + + // Check if This Actor can even see at all + if ( !ShouldRespondToStimuli( STIMULI_SIGHT ) ) + return false; + + // Check for FOV + if ( useFOV ) + { + if ( !InFOV( position ) ) + return false; + } + + // Check for vision distance + if ( useVisionDistance ) + { + if ( !WithinVisionDistance( position ) ) + return false; + } + + // Do Trace + trace_t trace; + Vector eyePos; + + eyePos = vec_zero; + + // Check if he's visible + trace = G_Trace( start, vec_zero, vec_zero, position, act, MASK_OPAQUE, false, "SensoryPerception::CanSeeEntity" ); + if ( trace.fraction == 1.0f ) + return true; + + return false; +} + +// +// Name: _CanSeeComplex +// Parameters: Vector &start - start position +// Vector &end - end position +// qboolean useFOV - take FOV into consideration or not +// qboolean useVisionDistance +// Description: Creates a plane to do a more complex check of vision +// +qboolean SensoryPerception::_CanSeeComplex( Vector &start, Entity *target , qboolean useFOV, qboolean useVisionDistance ) +{ + Vector d; + Vector p1; + Vector p2; + + if ( !target ) + return false; + + d = target->centroid - start; + d.z = 0; + d.normalize(); + + p1.x = -d.y; + p1.y = d.x; + p1 *= max( act->size.x, act->size.y ) * 1.44f * 0.5f; + p2 = p1; + + p1.z = act->mins.z; + p2.z = act->maxs.z; + if ( CanSeeEntity( start + p1, target , useFOV , useVisionDistance ) ) + { + return true; + } + if ( CanSeeEntity( start + p2, target , useFOV , useVisionDistance ) ) + { + return true; + } + + p1.z = -p1.z; + p2.z = -p2.z; + if ( CanSeeEntity( start - p1, target , useFOV , useVisionDistance ) ) + { + return true; + } + if ( CanSeeEntity( start - p2, target , useFOV , useVisionDistance ) ) + { + return true; + } + + return false; +} + +// +// Name: SetNoisePosition() +// Parameters: Vector pos +// Description: Sets the _noise_position vector +// +void SensoryPerception::SetNoisePosition( const Vector &pos ) +{ + _noise_position = pos; +} + +// +// Name: GetNoisePosition() +// Parameters: None +// Description: Returns _noise_position +// +Vector SensoryPerception::GetNoisePosition() +{ + return _noise_position; +} + +// +// Name: SetLastSoundType() +// Parameters: int soundtype +// Description: sets _last_soundType +// +void SensoryPerception::SetLastSoundType( int soundtype ) +{ + _last_soundType = soundtype; +} + +// +// Name: GetLastSoundType() +// Parameters: None +// Description: returns _last_soundType +// +int SensoryPerception::GetLastSoundType() +{ + return _last_soundType; +} + +// +// Name: SetNoiseTime() +// Parameters: float noisetime +// Description: sets _noise_time +// +void SensoryPerception::SetNoiseTime( float noisetime ) +{ + _noise_time = noisetime; +} + +// +// Name: GetNoiseTime() +// Parameters: None +// Description: returns _noise_time +// +float SensoryPerception::GetNoiseTime() +{ + return _noise_time; +} + +// +// Name: SetFOV() +// Parameters: float fov +// Description: sets _fov +// +void SensoryPerception::SetFOV( float fov ) +{ + _fov = fov; +} + +// +// Name: GetFOV() +// Parameters: None +// Description: returns _fov +// +float SensoryPerception::GetFOV() +{ + return _fov; +} + +// +// Name: setFOVdot +// Parameters: float fov_dot +// Description: _fovdot +// +void SensoryPerception::SetFOVdot( float fov_dot ) +{ + _fovdot = fov_dot; +} + +// +// Name: GetFOVdot() +// Parameters: none +// Description: returns _fovdot +// +float SensoryPerception::GetFOVdot() +{ + return _fovdot; +} + +// +// Name: SetVisionDistance() +// Parameters: float vision_distance +// Description: Sets _vision_distance +// +void SensoryPerception::SetVisionDistance( float vision_distance ) +{ + _vision_distance = vision_distance; +} + +// +// Name: GetVisionDistance() +// Parameters: None +// Description: returns _vision_distance +// +float SensoryPerception::GetVisionDistance() +{ + return _vision_distance; +} + +// +// Name: DoArchive +// Parameters: Archiver &arc -- The archiver object +// Actor *actor -- The actor +// Description: Sets the act pointer and calls Archive +// +void SensoryPerception::DoArchive( Archiver &arc , Actor *actor ) +{ + Archive( arc ); + if ( actor ) + act = actor; + else + gi.Error( ERR_FATAL, "SensoryPerception::DoArchive -- actor is NULL" ); +} + +// +// Name: Archive() +// Parameters: Archiver &arc -- The archiver object +// Description: Archives the class +// +void SensoryPerception::Archive ( Archiver &arc ) +{ + arc.ArchiveInteger ( &_stimuli ); + arc.ArchiveInteger ( &_permanent_stimuli ); + arc.ArchiveVector ( &_noise_position ); + arc.ArchiveInteger ( &_last_soundType ); + arc.ArchiveFloat ( &_noise_time ); + arc.ArchiveFloat ( &_fov ); + arc.ArchiveFloat ( &_fovdot ); + arc.ArchiveFloat ( &_vision_distance ); + arc.ArchiveFloat ( &_nextSenseTime ); + + arc.ArchiveInteger( &_lineOfSight.entNum ); + arc.ArchiveFloat( &_lineOfSight.time ); + arc.ArchiveBoolean( &_lineOfSight.inLineOfSight ); +} diff --git a/dlls/game/actor_sensoryperception.h b/dlls/game/actor_sensoryperception.h new file mode 100644 index 0000000..7818aca --- /dev/null +++ b/dlls/game/actor_sensoryperception.h @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actor_sensoryperception.h $ +// $Revision:: 7 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// SensoryPerception class for Actors -- Handles all the sensory related funtionality +// + +class SensoryPerception; + +#ifndef __ACTOR_SENSORYPERCEPTION_H__ +#define __ACTOR_SENSORYPERCEPTION_H__ + +#include "actor.h" +#include "actorincludes.h" +#include "weapon.h" + +//============================ +// Class SensoryPerception +//============================ +// +// Class used to handle all sensory perception by actors. +// +class SensoryPerception + { + public: + SensoryPerception(); + SensoryPerception(Actor *actor ); + ~SensoryPerception(); + + // Sense functions + void SenseEnemies(); + void SearchForEnemies(); + + + // Stimuli functions + void Stimuli( int stimuli ); + void Stimuli( int stimuli, Entity *ent ); + void Stimuli( int stimuli, const Vector &pos ); + void Stimuli( int stimuli, const Vector &pos, int sound_Type ); + void RespondTo( const str &stimuli_name , qboolean respond ); + void RespondTo( int stimuli, qboolean respond ); + void PermanentlyRespondTo( const str &stimuli_name , qboolean respond ); + qboolean ShouldRespondToStimuli( int new_stimuli ); + + + // Vision functions + qboolean WithinVisionDistance( const Entity *ent ); + qboolean WithinVisionDistance( const Vector &pos ); + qboolean InFOV( const Vector &pos, float check_fov, float check_fovdot ); + qboolean InFOV( const Vector &pos ); + qboolean InFOV( const Entity *ent ); + + // New Vision Functions -- Simplified Vision + qboolean CanSeeEntity( Entity *start , const Entity *target, qboolean useFOV, qboolean useVisionDistance ); + qboolean CanSeeEntity( const Vector &start , const Entity *target, qboolean useFOV, qboolean useVisionDistance ); + + // New Vision Functions -- More Sophisticated Vision + qboolean CanSeeEntityComplex( Entity *start , Entity *target, qboolean useFOV, qboolean useVisionDistance ); + qboolean CanSeeEntityComplex( Vector &start , Entity *target, qboolean useFOV, qboolean useVisionDistance ); + + qboolean CanSeePosition( const Vector &start, const Vector &position, qboolean useFOV, qboolean useVisionDistance ); + + qboolean isInLineOfSight( const Vector &position , const int entNum ); + qboolean checkInLineOfSight( const Vector &position , const int entNum ); + + // Debugging Functions + void ShowInfo(); + + // Accessors and Mutators + void SetNoisePosition( const Vector &pos ); + Vector GetNoisePosition(); + + void SetLastSoundType( int soundtype ); + int GetLastSoundType(); + + void SetNoiseTime( float noisetime ); + float GetNoiseTime(); + + void SetFOV( float fov ); + float GetFOV(); + + void SetFOVdot( float fov_dot ); + float GetFOVdot(); + + void SetVisionDistance( float vision_distance ); + float GetVisionDistance(); + + // Archiving + virtual void Archive( Archiver &arc ); + void DoArchive( Archiver &arc , Actor *actor ); + + private: //Functions + qboolean _CanSeeComplex( Vector &start , Entity *target , qboolean useFOV , qboolean useVisionDistance ); + qboolean _SenseEntity( Entity *ent ); + void _init(); + + private: //Member Variables + + // Stimuli Variables + int _stimuli; + int _permanent_stimuli; + + // Hearing Variables + Vector _noise_position; + int _last_soundType; + float _noise_time; + + // Vision Stuff for "seeing" + float _fov; + float _fovdot; + float _vision_distance; + + float _nextSenseTime; + + LineOfSight_t _lineOfSight; + + Actor *act; + + }; + +#endif /* __ACTOR_SENSORYPERCEPTION_H__ */ diff --git a/dlls/game/actorgamecomponents.cpp b/dlls/game/actorgamecomponents.cpp new file mode 100644 index 0000000..cbcc10f --- /dev/null +++ b/dlls/game/actorgamecomponents.cpp @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actorgamecomponents.cpp $ +// $Revision:: 18 $ +// $Author:: Steven $ +// $Date:: 10/11/02 3:34a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// What I am trying to do here, is encapsulate any game specific pieces for actor. Each game will +// subclass off of the base class, then the Actor class will point to the component class it will +// use. I am hoping this will make life much easier for mod makers as well +// + +#include "_pch_cpp.h" +#include "actorgamecomponents.h" +#include "player.h" + + +// +// Name: CLASS_DECLARATION +// Parameters: Listener -- Parent Class +// ActorGameComponent -- This class +// String -- Name +// Description: Macro +// +CLASS_DECLARATION( Listener , ActorGameComponent, "actor_game_component" ) +{ + { NULL, NULL } +}; + + + +//========================================= +// EFGameComponent Implementation +//========================================= + +// +// Name: CLASS_DECLARATION +// Parameters: ActorGameComponent -- Parent Class +// EFGameComponent -- This class +// String -- Name +// Description: Macro +// +CLASS_DECLARATION( ActorGameComponent, EFGameComponent, "ef_game_component" ) +{ + { NULL, NULL } +}; + + +// +// Name: EFGameComponent() +// Parameters: None +// Description: Constructor +// +EFGameComponent::EFGameComponent() +{ + // Should always use other constructor + gi.Error( ERR_FATAL, "EFGameComponent::EFGameComponent -- Default Constructor Called" ); +} + +// +// Name: EFGameComponent() +// Parameters: Actor *actor +// Description: Constructor +// +EFGameComponent::EFGameComponent( const Actor *actor ) +{ + if ( actor ) + act = (Actor *)actor; +} + +void EFGameComponent::DoArchive( Archiver &arc , const Actor *actor ) +{ + if ( actor ) + act = (Actor *)actor; + else + gi.Error( ERR_FATAL, "EFGameComponnet::DoArchive -- actor is NULL" ); + +} +// +// Name: HandleDeath() +// Parameters: Entity *ent +// Description: Custom Game Death Handler +// +void EFGameComponent::HandleDeath( const Entity *ent ) +{ + +} + + +// +// Name: HandleArmorDamage() +// Parameters: Event *ev +// Description: Custom Game Armor Damage Handler +// +void EFGameComponent::HandleArmorDamage( Event *ev ) +{ + + +} + +// +// Name: HandleThink() +// Parameters: None +// Description: Custom Game Think Handler +// +void EFGameComponent::HandleThink() +{ + + +} + +// +// Name: HandleEvent() +// Parameters: Event *ev +// Description: Event Handler +// +void EFGameComponent::HandleEvent( Event *ev ) +{ + Event *new_event; + new_event = new Event( ev ); + ProcessEvent(new_event); +} + + +// +// Name: DoCheck() +// Parameters: Conditional &condition +// Description: Custom Game conditional check handler +// +qboolean EFGameComponent::DoCheck ( const Conditional &condition ) +{ + return false; +} + diff --git a/dlls/game/actorgamecomponents.h b/dlls/game/actorgamecomponents.h new file mode 100644 index 0000000..67feb20 --- /dev/null +++ b/dlls/game/actorgamecomponents.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actorgamecomponents.h $ +// $Revision:: 12 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// What I am trying to do here, is encapsulate any game specific pieces for actor. Each game will +// subclass off of the base class, then the Actor class will point to the component class it will +// use. I am hoping this will make life much easier for mod makers as well +// +//============================ +// Forward Declarations +//============================ +class ActorGameComponent; +class RedemptionGameComponent; + +#ifndef __ACTORGAMECOMPONENTS_H__ +#define __ACTORGAMECOMPONENTS_H__ + +#include "actor.h" + +//============================ +// Class ActorGameComponent +//============================ +// +// Base class from which all Actor Game Components are derived. +// +class ActorGameComponent : public Listener + { + public: + CLASS_PROTOTYPE( ActorGameComponent ); + + ActorGameComponent() {} + virtual void HandleEvent( Event *ev ) {} + virtual void HandleArmorDamage( Event *ev ) {} + virtual void HandleDeath( const Entity *ent ) {} + virtual void HandleThink() {} + + virtual qboolean DoCheck( const Conditional &condition ) { return false; } + virtual void DoArchive( Archiver &arc, const Actor *act ) {} + + }; + +/* +EF Specific Stuff +*/ +//============================ +// Class EFGameComponent +//============================ +class EFGameComponent : public ActorGameComponent + { + public: + CLASS_PROTOTYPE( EFGameComponent ); + + EFGameComponent(); + EFGameComponent( const Actor *actor ); + + void HandleEvent( Event *ev ); + void HandleArmorDamage( Event *ev ); + void HandleDeath( const Entity *ent); + void HandleThink(); + + qboolean DoCheck( const Conditional &condition ); + void DoArchive( Archiver &arc , const Actor *act ); + + private: + Actor *act; + }; + +#endif /*__ACTORGAMECOMPONENTS_H__*/ diff --git a/dlls/game/actorincludes.h b/dlls/game/actorincludes.h new file mode 100644 index 0000000..925cf50 --- /dev/null +++ b/dlls/game/actorincludes.h @@ -0,0 +1,537 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actorincludes.h $ +// $Revision:: 84 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// Centralized repository for externs, defines, enums, and structs that are required +// by actor and all of actor's helper classes, like sensoryPerception, thinkStrategy, etc... +// + +#ifndef __ACTORINCLUDES_H__ +#define __ACTORINCLUDES_H__ + +//========================================= +// External Events +//========================================= +extern Event EV_Actor_Start; +extern Event EV_Actor_Dead; +extern Event EV_Actor_LookAt; +extern Event EV_Actor_TurnTo; +extern Event EV_Actor_BehaviorFinished ; +extern Event EV_Actor_ControlLost ; +extern Event EV_Actor_EndBehavior; +extern Event EV_Actor_EndHeadBehavior; +extern Event EV_Actor_EndEyeBehavior; +extern Event EV_Actor_NotifyBehavior; +extern Event EV_Actor_NotifyTorsoBehavior; +extern Event EV_Actor_NotifyHeadBehavior; +extern Event EV_Actor_NotifyEyeBehavior; +extern Event EV_Actor_FinishedMove; +extern Event EV_Actor_FinishedAnim; +extern Event EV_Actor_WalkTo; +extern Event EV_Actor_FallToDeath; +extern Event EV_Actor_RunTo; +extern Event EV_Actor_Anim; +extern Event EV_Actor_AttackFinished; +extern Event EV_Actor_Attack; +extern Event EV_Actor_AttackPlayer; +extern Event EV_Actor_AIOn; +extern Event EV_Actor_AIOff; +extern Event EV_Actor_AIDeaf; +extern Event EV_Actor_AIDumb; +extern Event EV_Actor_RespondTo; +extern Event EV_Actor_PermanentlyRespondTo; +extern Event EV_ActorIncomingProjectile; +extern Event EV_Anim_Done; +extern Event EV_ActorOnlyShootable; +extern Event EV_Actor_BounceOff; +extern Event EV_Actor_Push; +extern Event EV_Actor_Statemap; +extern Event EV_Actor_SetTargetable; +extern Event EV_Sentient_StopFire; +extern Event EV_Sentient_Attack; +extern Event EV_Actor_SetUseGravity ; +extern Event EV_Actor_SetSimplifiedThink ; +extern Event EV_Actor_SetStickToGround ; +extern Event EV_Actor_SetMovementMode ; + +//======================================== +// General Defines +//======================================== +#define MAX_ALIAS_NAME_LENGTH 32 +#define MAX_INACTIVE_TIME 30.0 +#define DEFAULT_FOV 150 +#define DEFAULT_VISION_DISTANCE 1536 +#define MIN_SIGHT_DELAY 2 +#define RANDOM_SIGHT_DELAY 1.5 +#define DEFAULT_INITIAL_HATE 100 +#define TURN_SPEED 60 //Used to be 25 +#define HELPER_NODE_MAX_DISTANCE 96.0 +#define HELPER_NODE_ARRIVAL_DISTANCE 24.0 + +#define HACK_PATH_CHECK 1.0 + + + +//======================================== +// Stimuli types +//======================================== +#define STIMULI_ALL -1 +#define STIMULI_NONE 0 +#define STIMULI_SIGHT (1<<0) +#define STIMULI_SOUND (1<<1) +#define STIMULI_PAIN (1<<2) +#define STIMULI_SCRIPT (1<<3) + +//======================================== +// Bones used by actor +//======================================== +#define ACTOR_MOUTH_TAG 0 +#define ACTOR_HEAD_TAG 1 +#define ACTOR_TORSO_TAG 2 +#define ACTOR_LEYE_TAG 3 +#define ACTOR_REYE_TAG 4 + + +//======================================== +// Dialog stuff +//======================================== +#define MAX_DIALOG_PARAMETERS_LENGTH 100 +#define MAX_DIALOG_PARM_LENGTH 64 +#define MAX_DIALOG_PARMS 10 +#define DIALOG_PARM_TYPE_NONE 0 +#define DIALOG_PARM_TYPE_PLAYERHAS 1 +#define DIALOG_PARM_TYPE_PLAYERHASNOT 2 +#define DIALOG_PARM_TYPE_HAS 3 +#define DIALOG_PARM_TYPE_HASNOT 4 +#define DIALOG_PARM_TYPE_DEPENDS 5 +#define DIALOG_PARM_TYPE_DEPENDSNOT 6 +#define DIALOG_PARM_TYPE_DEPENDSINT 7 +#define DIALOG_PARM_TYPE_CONTEXT_INITIATOR 8 +#define DIALOG_PARM_TYPE_CONTEXT_RESPONSE 9 + +//======================================== +// State flags +//======================================== +#define STATE_FLAG_IN_PAIN ( 1<<0 ) +#define STATE_FLAG_MELEE_HIT ( 1<<1 ) +#define STATE_FLAG_TOUCHED ( 1<<2 ) +#define STATE_FLAG_ACTIVATED ( 1<<3 ) +#define STATE_FLAG_USED ( 1<<4 ) +#define STATE_FLAG_TWITCH ( 1<<5 ) +#define STATE_FLAG_BLOCKED_HIT ( 1<<6 ) +#define STATE_FLAG_SMALL_PAIN ( 1<<7 ) +#define STATE_FLAG_OTHER_DIED ( 1<<8 ) +#define STATE_FLAG_STUCK ( 1<<9 ) +#define STATE_FLAG_NO_PATH ( 1<<10 ) +#define STATE_FLAG_TOUCHED_BY_PLAYER ( 1<<11 ) +#define STATE_FLAG_CHANGED_WEAPON ( 1<<12 ) +#define STATE_FLAG_DAMAGE_THRESHOLD_EXCEEDED ( 1<<13 ) +#define STATE_FLAG_ATTACKED ( 1<<14 ) +#define STATE_FLAG_ATTACKED_BY_PLAYER ( 1<<15 ) +#define STATE_FLAG_SHOW_PAIN ( 1<<16 ) +#define STATE_FLAG_IN_THE_WAY ( 1<<17 ) +#define STATE_FLAG_STEERING_FAILED ( 1<<18 ) +#define STATE_FLAG_BLOCKED_BY_ENTITY ( 1<<19 ) +#define STATE_FLAG_ENEMY_PROJECTILE_CLOSE ( 1<<20 ) + +//======================================== +// Combat Subsystem Defines +//======================================== +#define DEFAULT_TRACE_INTERVAL .05f + +#define DEFAULT_PATH_TO_ENEMY_INTERVAL .05f + +//======================================== +// Actor modes +//======================================== +#define ACTOR_MODE_NONE 0 +#define ACTOR_MODE_IDLE 1 +#define ACTOR_MODE_AI 2 +#define ACTOR_MODE_SCRIPT 3 +#define ACTOR_MODE_TALK 4 + +//======================================= +// Pain types +//======================================= +#define PAIN_SMALL 0 +#define PAIN_BIG 1 + +//======================================== +// Save Flags +//======================================== +#define SAVE_FLAG_NEW_ANIM (1<<0) +#define SAVE_FLAG_FORWARD_SPEED (1<<1) +#define SAVE_FLAG_BEHAVIOR (1<<2) +#define SAVE_FLAG_PATH (1<<3) +#define SAVE_FLAG_NOISE (1<<4) +#define SAVE_FLAG_SCRIPT_THREAD (1<<5) +#define SAVE_FLAG_ACTOR_THREAD (1<<6) +#define SAVE_FLAG_KILL_THREAD (1<<7) +#define SAVE_FLAG_STATE (1<<8) +#define SAVE_FLAG_IDLE_THREAD (1<<7) +#define SAVE_FLAG_PARTS (1<<10) +#define SAVE_FLAG_TRIGGER (1<<11) +#define SAVE_FLAG_STATE_FLAGS (1<<12) +#define SAVE_FLAG_COMMAND (1<<13) +#define SAVE_FLAG_STAGE (1<<14) +#define SAVE_FLAG_NUM_OF_SPAWNS (1<<15) +#define SAVE_FLAG_SPAWN_PARENT (1<<16) +#define SAVE_FLAG_DIALOG (1<<17) +#define SAVE_FLAG_SAVED_STUFF (1<<18) +#define SAVE_FLAG_LAST_ANIM_EVENT (1<<19) +#define SAVE_FLAG_PICKUP_ENT (1<<20) +#define SAVE_FLAG_PAIN (1<<21) +#define SAVE_FLAG_SPAWN_ITEMS (1<<22) + +//======================================= +// Rage AI System +//======================================= +#define DEFAULT_EVALUATE_INTERVAL .05f // Was 10.0f +#define DEFAULT_SIGHT_BASED_HATE 5.0f + +//======================================= +// Structures +//======================================= + +//DialogParm_t -- Structure for Dialog Parameters +typedef struct +{ + byte type; + char parm[ MAX_DIALOG_PARM_LENGTH ]; + char parm2[ MAX_DIALOG_PARM_LENGTH ]; +} DialogParm_t; + +typedef enum +{ + DIALOG_TYPE_NORMAL, + DIALOG_TYPE_RADIUS, + DIALOG_TYPE_GREETING, + DIALOG_TYPE_COMBAT, + DIALOG_TYPE_CONTEXT_INITIATOR, + DIALOG_TYPE_CONTEXT_RESPONSE +} DialogType_t; + +//DialogNode_t -- Structure for Dialog Nodes +typedef struct DialogNode_s + { + char alias_name[ MAX_ALIAS_NAME_LENGTH ]; + int random_flag; + int number_of_parms; + float random_percent; + DialogType_t dType; + DialogParm_t parms[ MAX_DIALOG_PARMS ]; + struct DialogNode_s *next; + } DialogNode_t; + +typedef struct + { + int entNum; + float time; + qboolean inLineOfSight; + } LineOfSight_t; + +//HateListEntry_t -- Structure for the hate list +typedef struct +{ + // Information that will need to be persistant, or accesses + // frequently should be placed in this structure + + EntityPtr enemy; //Pointer to the entity + float lastSightTime; //Last time I tried to see this entity + float nextSightTime; //Next time I try and see this entity + qboolean canSee; //Can I see the enemy ( based on last time I tried ) + float damageCaused; //total damage that entity has done to me + float hate; //how much hate I have for this entiy + + float lastDistance; + +} HateListEntry_t; + +typedef struct +{ + str packageName; + str stateFile; +} BehaviorPackageType_t; + +// Helper Node Data +typedef struct +{ + HelperNodePtr node; + int mask; + int nodeID; +} CurrentHelperNodeData_t; + +typedef struct +{ + HelperNodePtr node; + int mask; + int nodeID; +} IgnoreHelperNodeData_t; + +// Follow Target Data +typedef struct +{ + EntityPtr currentFollowTarget; + EntityPtr specifiedFollowTarget; + float maxRangeIdle; + float minRangeIdle; + float maxRangeCombat; + float minRangeCombat; +} FollowTargetData_t; + +typedef struct +{ + int packageIndex; + float currentScore; + float lastScore; + float lastTimeExecuted; + float priority; + +} BehaviorPackageEntry_t; + +typedef struct +{ + int packageIndex; + float tendency; + float lastTendencyCheck; +} PackageTendency_t; + +// We need to modify PackageTendency_t to do +// what struct is going to do, however, I'm 2 days from a +// milestone, so I'm not changing anything right now +typedef struct +{ + str tendencyName; + float tendencyValue; +} Tendency_t; + +// StateVar -- Structure for holding StateVars +typedef struct +{ + str varName; + str varValue; + float varTime; +} StateVar; + +// part_t -- Part stuff +typedef struct +{ + EntityPtr ent; + unsigned int state_flags; +} part_t; + +// threadlist_t -- A Key/Value pair for all the custom threading stuff we're doing +// we will eventually need to convert all those errant actor threads into this. +typedef struct +{ + str threadType; + str threadName; +} threadlist_t; + +//=========================================== +// Enumerations +//=========================================== + +typedef enum{ + MOVEMENT_TYPE_NORMAL, + MOVEMENT_TYPE_ANIM +} MovementType_t; + +//DialogMode_t -- Enumeration of Dialog Modes +typedef enum{ + DIALOG_MODE_ANXIOUS, + DIALOG_MODE_NORMAL, + DIALOG_MODE_IGNORE + } DialogMode_t; + + +// actortype_t -- Enumeration of possible actor types +typedef enum + { + IS_INANIMATE, + IS_MONSTER, + IS_ENEMY, + IS_CIVILIAN, + IS_FRIEND, + IS_ANIMAL, + IS_TEAMMATE, + NUM_ACTORTYPES + } actortype_t; + + + +// targetType_t -- Enumeration of possible target types +typedef enum + { + ATTACK_ANY, + ATTACK_PLAYER_ONLY, + ATTACK_ACTORS_ONLY, + ATTACK_SCRIPTED_ONLY, + ATTACK_LEVEL_INTERACTION + } targetType_t; + +typedef enum + { + DEBUG_NONE, + DEBUG_STATES_ONLY, + DEBUG_STATES_BEHAVIORS, + DEBUG_ALL, + MAX_DEBUG_TYPES + } stateDebugType_t; + +typedef enum + { + TALK_TURNTO, + TALK_HEADWATCH, + TALK_IGNORE + } talkModeStates_t; + +// actorflags -- Enumeration of Actor flags +typedef enum{ + ACTOR_FLAG_NOISE_HEARD, + ACTOR_FLAG_INVESTIGATING, + ACTOR_FLAG_DEATHGIB, + ACTOR_FLAG_DEATHFADE, + ACTOR_FLAG_NOCHATTER, + ACTOR_FLAG_INACTIVE, + ACTOR_FLAG_ANIM_DONE, + ACTOR_FLAG_STATE_DONE_TIME_VALID, + ACTOR_FLAG_MASTER_STATE_DONE_TIME_VALID, + ACTOR_FLAG_AI_ON, + ACTOR_FLAG_LAST_CANSEEENEMY, + ACTOR_FLAG_LAST_CANSEEENEMY_NOFOV, + ACTOR_FLAG_DIALOG_PLAYING, + ACTOR_FLAG_RADIUS_DIALOG_PLAYING, + ACTOR_FLAG_ALLOW_TALK, + ACTOR_FLAG_DAMAGE_ONCE_ON, + ACTOR_FLAG_DAMAGE_ONCE_DAMAGED, + ACTOR_FLAG_BOUNCE_OFF, + ACTOR_FLAG_NOTIFY_OTHERS_AT_DEATH, + ACTOR_FLAG_HAS_THING1, + ACTOR_FLAG_HAS_THING2, + ACTOR_FLAG_HAS_THING3, + ACTOR_FLAG_HAS_THING4, + ACTOR_FLAG_LAST_ATTACK_HIT, + ACTOR_FLAG_STARTED, + ACTOR_FLAG_ALLOW_HANGBACK, + ACTOR_FLAG_USE_GRAVITY, + ACTOR_FLAG_SPAWN_FAILED, + ACTOR_FLAG_FADING_OUT, + ACTOR_FLAG_DEATHSHRINK, + ACTOR_FLAG_DEATHSINK, + ACTOR_FLAG_STAYSOLID, + ACTOR_FLAG_STUNNED, + ACTOR_FLAG_ALLOW_FALL, + ACTOR_FLAG_FINISHED, + ACTOR_FLAG_IN_LIMBO, + ACTOR_FLAG_CAN_WALK_ON_OTHERS, + ACTOR_FLAG_PUSHABLE, + ACTOR_FLAG_LAST_TRY_TALK, + ACTOR_FLAG_TARGETABLE, + ACTOR_FLAG_IMMORTAL, + ACTOR_FLAG_TURNING_HEAD, + ACTOR_FLAG_MOVING_EYES, + ACTOR_FLAG_DIE_COMPLETELY, + ACTOR_FLAG_BLEED_AFTER_DEATH, + ACTOR_FLAG_IGNORE_STUCK_WARNING, + ACTOR_FLAG_IGNORE_OFF_GROUND_WARNING, + ACTOR_FLAG_ALLOWED_TO_KILL, + ACTOR_FLAG_TOUCH_TRIGGERS, + ACTOR_FLAG_IGNORE_WATER, + ACTOR_FLAG_NEVER_IGNORE_SOUNDS, + ACTOR_FLAG_SIMPLE_PATHFINDING, + ACTOR_FLAG_HAVE_MOVED, + ACTOR_FLAG_NO_PAIN_SOUNDS, + ACTOR_FLAG_UPDATE_BOSS_HEALTH, + ACTOR_FLAG_IGNORE_PAIN_FROM_ACTORS, + ACTOR_FLAG_DAMAGE_ALLOWED, + ACTOR_FLAG_AT_COVER_NODE, + ACTOR_FLAG_WAIT_FOR_NEW_ENEMY, + ACTOR_FLAG_TAKE_DAMAGE, + ACTOR_FLAG_USE_DAMAGESKINS, + ACTOR_FLAG_CAPTURED, + ACTOR_FLAG_TURRET_MODE, + ACTOR_FLAG_INCOMING_HITSCAN, + ACTOR_FLAG_RESPONDING_TO_HITSCAN, + ACTOR_FLAG_MELEE_HIT_WORLD, + ACTOR_FLAG_TORSO_ANIM_DONE, + ACTOR_FLAG_WEAPON_READY, + ACTOR_FLAG_DISABLED, + ACTOR_FLAG_IN_ALCOVE, + ACTOR_FLAG_IN_CONE_OF_FIRE, + ACTOR_FLAG_IN_PLAYER_CONE_OF_FIRE, + ACTOR_FLAG_PLAYER_IN_CALL_VOLUME, + ACTOR_FLAG_IN_CALL_VOLUME, + ACTOR_FLAG_OUT_OF_TORSO_RANGE, + ACTOR_FLAG_DUCKED, + ACTOR_FLAG_PRONE, + ACTOR_FLAG_SHOULD_BLINK, + ACTOR_FLAG_CRIPPLED, + ACTOR_FLAG_RETREATING, + ACTOR_FLAG_HIDDEN, + ACTOR_FLAG_FOLLOWING_IN_FORMATION, + ACTOR_FLAG_DISPLAYING_FAILURE_FX, + ACTOR_FLAG_GROUPMEMBER_INJURED, + ACTOR_FLAG_CAN_HEAL_OTHER, + ACTOR_FLAG_STRICTLY_FOLLOW_PATHS, + ACTOR_FLAG_POSTURE_ANIM_DONE, + ACTOR_FLAG_ATTACKING_ENEMY, + ACTOR_FLAG_UPDATE_HATE_WITH_ATTACKERS, + ACTOR_FLAG_LAST_CANSEEPLAYER, + ACTOR_FLAG_LAST_CANSEEPLAYER_NOFOV, + ACTOR_FLAG_MELEE_ALLOWED, + ACTOR_FLAG_PLAYING_DIALOG_ANIM, + ACTOR_FLAG_USING_HUD, + ACTOR_FLAG_FORCE_LIFEBAR, + ACTOR_FLAG_UPDATE_ACTION_LEVEL, + ACTOR_FLAG_CAN_CHANGE_ANIM, + ACTOR_FLAG_USE_FOLLOWRANGE_FOR_NODES, + ACTOR_FLAG_IMMEDIATE_ACTIVATE, + ACTOR_FLAG_CANNOT_DISINTEGRATE, + ACTOR_FLAG_CANNOT_USE, + ACTOR_FLAG_CANNOT_FREEZE, + + ACTOR_FLAG_MAX + + } ActorFlags; + + +typedef enum { + NOTIFY_FLAG_DAMAGED, + NOTIFY_FLAG_KILLED, + NOTIFY_FLAG_SPOTTED_ENEMY, + + NOTIFY_FLAG_MAX + } NotifyFlags; + + +typedef enum { + MOVEMENT_STYLE_NONE, + MOVEMENT_STYLE_WALK, + MOVEMENT_STYLE_RUN, + + MOVEMENT_STYLE_MAX + } MovementStyle; + + +//======================================== +// Global Lists +//======================================== +extern Container SleepList; +extern Container ActiveList; +extern Container TeamMateList; +extern Container PackageList; + +#endif /* __ACTORINCLUDES_H__ */ diff --git a/dlls/game/actorstrategies.cpp b/dlls/game/actorstrategies.cpp new file mode 100644 index 0000000..14f6a86 --- /dev/null +++ b/dlls/game/actorstrategies.cpp @@ -0,0 +1,580 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/actorstrategies.cpp $ +// $Revision:: 46 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:35p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// I am replacing the old way that Actor::Think was done by implementing a group of strategies for it +// instead. This will form the foundation for new flexiblility within the actor class. +// +// What I am trying to achieve is a specialized "think" for different types of actors. A Boss, for instance, +// would use BossThink, an Non-Attacking-NPC could use NPCThink. Using the event system we already have inplace +// it will be easy to change thinking modalities on the fly, allowing us to make a cowardly NPC turn into a +// roaring death machine if he gets shot. +// + +#include "_pch_cpp.h" +#include "actorstrategies.h" +#include "actor.h" +#include "entity.h" + +extern cvar_t *ai_showfailure; +//------------------------- CLASS ------------------------------ +// +// Name: ActorThink +// Base Class: None +// +// Description: Base class from which all Actor Think Strategies +// are derived. +// +// Method of Use: +// Deriving a new class from ActorThink is a good +// to allow Actor to exhibit new behavior. +// +//-------------------------------------------------------------- + +//---------------------------------------------------------------- +// Name: DoArchive +// Class: ActorThink +// +// Description: Archives this instance of ActorThink +// +// Parameters: +// Archiver the class that receives/supplies +// archival info +// +// Returns: None +//---------------------------------------------------------------- +void ActorThink::DoArchive( Archiver &arc ) +{ +} + +//---------------------------------------------------------------- +// Name: ProcessBehaviors +// Class: ActorThink +// +// Description: Processes all the Actors Behaviors +// +// Parameters: +// Actor in question +// +// Returns: None +//---------------------------------------------------------------- +void ActorThink::ProcessBehaviors( Actor &actor ) +{ + bool showFailure; + showFailure = ( ai_showfailure->integer != 0 ); + + // Do the state machine for this creature + actor.ProcessMasterStateMachine(); + actor.ProcessActorStateMachine(); + + // Process the current behavior + + if ( actor.behavior ) + { + actor.behaviorCode = actor.behavior->Evaluate( actor ); + + if ( actor.behaviorCode == BEHAVIOR_FAILED ) + { + actor.behaviorFailureReason = actor.behavior->GetFailureReason(); + } + + if ( actor.behaviorCode != BEHAVIOR_EVALUATING ) + { + if ( stricmp( actor.behavior->getClassname(), "Talk" ) == 0 ) + { + actor.EndBehavior(); + actor.EndMode(); + } + else + { + actor.EndBehavior(); + } + } + + // Process state machine again because the behavior finished + actor.ProcessActorStateMachine(); + } + + // Process the current head behavior + if ( actor.headBehavior ) + { + actor.headBehaviorCode = actor.headBehavior->Evaluate( actor ); + + if ( actor.headBehaviorCode != BEHAVIOR_EVALUATING ) + { + actor.EndHeadBehavior(); + } + + // Process state machine again because the behavior finished + actor.ProcessActorStateMachine(); + } + + // Process the current eye behavior + if ( actor.eyeBehavior ) + { + actor.eyeBehaviorCode = actor.eyeBehavior->Evaluate( actor ); + + if ( actor.eyeBehaviorCode != BEHAVIOR_EVALUATING ) + { + actor.EndEyeBehavior(); + } + + // Process state machine again because the behavior finished + actor.ProcessActorStateMachine(); + } + + // Process the current torso behavior + if ( actor.torsoBehavior ) + { + actor.torsoBehaviorCode = actor.torsoBehavior->Evaluate( actor ); + + if ( actor.torsoBehaviorCode != BEHAVIOR_EVALUATING ) + { + actor.EndTorsoBehavior(); + } + + // Process state machine again because the behavior finished + actor.ProcessActorStateMachine(); + } + + // Reset the animation is done flag + actor.SetActorFlag( ACTOR_FLAG_ANIM_DONE, false ); + actor.SetActorFlag( ACTOR_FLAG_TORSO_ANIM_DONE, false ); + + // Change the animation if necessary + if ( ( actor.newanimnum != -1 ) || ( actor.newTorsoAnimNum != -1 ) ) + actor.ChangeAnim(); + + if ( !showFailure ) + return; + + if ( actor.behaviorCode == BEHAVIOR_FAILED && !actor.GetActorFlag(ACTOR_FLAG_DISPLAYING_FAILURE_FX) ) + { + Event* event; + event = new Event( EV_DisplayEffect ); + event->AddString( "electric" ); + actor.ProcessEvent( event ); + actor.SetActorFlag( ACTOR_FLAG_DISPLAYING_FAILURE_FX , true ); + return; + } + else if ( actor.behaviorCode != BEHAVIOR_FAILED && actor.GetActorFlag(ACTOR_FLAG_DISPLAYING_FAILURE_FX) ) + { + Event* event; + event = new Event( EV_DisplayEffect ); + event->AddString( "noelectric" ); + actor.ProcessEvent( event ); + actor.SetActorFlag( ACTOR_FLAG_DISPLAYING_FAILURE_FX , false ); + return; + } + +} + +//---------------------------------------------------------------- +// Name: DoMove +// Class: ActorThink +// +// Description: Does Movement Stuff for Actor +// +// Parameters: +// Actor in question +// +// Returns: None +//---------------------------------------------------------------- +void ActorThink::DoMove( Actor &actor ) +{ + if ( ( actor.flags & FL_IMMOBILE ) || ( actor.flags & FL_PARTIAL_IMMOBILE ) ) + { + actor.animate->StopAnimating(); + return; + } + + actor.movementSubsystem->CalcMove(); + actor.movementSubsystem->setLastMove( STEPMOVE_STUCK ); + + stepmoveresult_t lastMove; + + if ( actor.flags & FL_SWIM ) + lastMove = actor.movementSubsystem->WaterMove(); + else if ( actor.flags & FL_FLY ) + lastMove = actor.movementSubsystem->AirMove(); + else + lastMove = actor.movementSubsystem->TryMove(); + + actor.movementSubsystem->setLastMove( lastMove ); + + if ( + ( actor.movetype != MOVETYPE_NONE ) && + ( actor.movetype != MOVETYPE_STATIONARY ) && + actor.GetActorFlag( ACTOR_FLAG_TOUCH_TRIGGERS ) && + actor.GetActorFlag( ACTOR_FLAG_HAVE_MOVED ) + ) + G_TouchTriggers( &actor ); + + if ( actor.groundentity && ( actor.groundentity->entity != world ) && !M_CheckBottom( &actor ) ) + actor.flags |= FL_PARTIALGROUND; +} + +//---------------------------------------------------------------- +// Name: UpdateBossHealth +// Class: ActorThink +// +// Description: Handles Boss Specific behavior +// +// Parameters: +// Actor that is the boss in question +// +// Returns: None +//---------------------------------------------------------------- +void ActorThink::UpdateBossHealth( Actor &actor ) +{ + char bosshealth_string[20]; + + sprintf( bosshealth_string, "%.5f", actor.health / actor.max_boss_health ); + gi.cvar_set( "bosshealth", bosshealth_string ); + + gi.cvar_set( "bossname", actor.getName() ); +} + +//---------------------------------------------------------------- +// Name: CheckGround +// Class: ActorThink +// +// Description: Checks that the actor is on the ground, and does +// Falling Damage if necessary +// +// Parameters: +// Actor in question +// +// Returns: None +//---------------------------------------------------------------- +void ActorThink::CheckGround( Actor &actor ) +{ + + if ( actor.GetActorFlag( ACTOR_FLAG_HAVE_MOVED ) || + ( actor.groundentity && actor.groundentity->entity && ( actor.groundentity->entity->entnum != ENTITYNUM_WORLD ) ) ) + actor.CheckGround(); + + // Add Fall Damage if necessary + if ( actor.groundentity ) + { + if ( !actor.Immune( MOD_FALLING ) && !( actor.flags & FL_FLY ) && ( actor.origin.z + 1000.0f < actor.last_ground_z ) ) + actor.Damage( world, world, 1000.0f, actor.origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_FALLING ); + + actor.last_ground_z = actor.origin.z; + } +} + +//---------------------------------------------------------------- +// Name: InanimateObject +// Class: ActorThink +// +// Description: Handles behavior ending, if the Actor is an +// InanimateObject +// +// Parameters: +// Actor in question +// +// Returns: None +//---------------------------------------------------------------- +void ActorThink::InanimateObject( Actor &actor ) +{ + if ( actor.behavior && actor.behavior->Evaluate( actor ) != BEHAVIOR_EVALUATING ) + { + actor.EndBehavior(); + + // stop thinking + actor.turnThinkOff(); + ActiveList.RemoveObject( &actor ); + } + + if ( actor.headBehavior && actor.headBehavior->Evaluate( actor ) != BEHAVIOR_EVALUATING ) + { + actor.EndHeadBehavior(); + + // stop thinking + actor.turnThinkOff(); + ActiveList.RemoveObject( &actor ); + } + + if ( actor.eyeBehavior && actor.eyeBehavior->Evaluate( actor ) != BEHAVIOR_EVALUATING ) + { + actor.EndEyeBehavior(); + + // stop thinking + actor.turnThinkOff(); + ActiveList.RemoveObject( &actor ); + } + + if ( actor.torsoBehavior && actor.torsoBehavior->Evaluate( actor ) != BEHAVIOR_EVALUATING ) + { + actor.EndTorsoBehavior(); + + // stop thinking + actor.turnThinkOff(); + ActiveList.RemoveObject( &actor ); + } + +} + + +//---------------------------------------------------------------- +// Name: TryDrown +// Class: ActorThink +// +// Description: Damages the Actor if they are drowning +// +// Parameters: +// Actor in question +// +// Returns: None +//---------------------------------------------------------------- +void ActorThink::TryDrown( Actor &actor ) +{ + if ( actor.waterlevel == 3 && !( actor.flags & FL_SWIM ) ) + { + // if out of air, start drowning + if ( actor.air_finished < level.time ) + { + // we may have been in a water brush when we spawned, so check our water level again to be sure + actor.movementSubsystem->CheckWater(); + if ( actor.waterlevel < 3 ) + { + // we're ok, so reset our air + actor.air_finished = level.time + 5.0f; + } + else if ( ( actor.next_drown_time < level.time ) && ( actor.health > 0 ) ) + { + // drown! + actor.next_drown_time = level.time + 1.0f; + + //Sound( "snd_uwchoke", CHAN_VOICE ); + actor.BroadcastSound(); + + actor.Damage( world, world, 15.0f, actor.origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_DROWN ); + } + } + } + else + { + actor.air_finished = level.time + 5.0f; + } +} + +//---------------------------------------------------------------- +// Name: ActorStateUpdate +// Class: ActorThink +// +// Description: Updates miscellaneous actor state variables +// +// Parameters: +// Actor in question +// +// Returns: None +//---------------------------------------------------------------- +void ActorThink::ActorStateUpdate( Actor &actor ) +{ + // Update move status + if ( actor.last_origin != actor.origin ) + actor.SetActorFlag( ACTOR_FLAG_HAVE_MOVED, true ); + else + actor.SetActorFlag( ACTOR_FLAG_HAVE_MOVED, false ); + + // Set Origins; + actor.last_origin = actor.origin; + + // Check for the ground + if ( !( actor.flags & FL_SWIM ) && !( actor.flags & FL_FLY ) && actor.GetStickToGround() ) + CheckGround( actor ); + + if ( !actor.deadflag ) + { + // Check to see if stunned + actor.CheckStun(); + + // Handle Game Specific Stuff + actor.gameComponent->HandleThink(); + + // See if can talk to the player + if(actor.DialogMode == DIALOG_MODE_ANXIOUS) + actor.TryTalkToPlayer(); + + // Blink -- Emotions + if ( actor.GetActorFlag( ACTOR_FLAG_SHOULD_BLINK ) ) + actor.TryBlink(); + + if ( actor.getHeadWatchAllowed() ) + actor.headWatcher->HeadWatchTarget(); + + //Update Eyeposition + actor.eyeposition[ 2 ] = actor.maxs[ 2 ] + actor.eyeoffset[ 2 ]; + + // See if we should damage the actor because of waterlevel + TryDrown( actor ); + + if ( actor.groundentity && ( actor.groundentity->entity != world ) && !M_CheckBottom( &actor ) ) + actor.flags |= FL_PARTIALGROUND; + } +} + + +//------------------------- CLASS ------------------------------ +// +// Name: DefaultThink +// Base Class: ActorThink +// +// Description: +// Think class instantiated by most actors -- and +// instantiated in ALL actors by default +// +// Method of Use: +// This is the default behavior for Actor, nothing +// special need be done to use this class +// +//-------------------------------------------------------------- + +//---------------------------------------------------------------- +// Name: Think +// Class: DefaultThink +// +// Description: Main Think Function for Actor +// +// Parameters: +// Actor in question +// +// Returns: None +//---------------------------------------------------------------- +void DefaultThink::Think( Actor &actor ) +{ + if ( actor.flags & FL_IMMOBILE ) + { + // Update boss health if necessary + if ( (actor.GetActorFlag( ACTOR_FLAG_UPDATE_BOSS_HEALTH ) && actor.max_boss_health && ( actor.mode == ACTOR_MODE_AI )) || actor.GetActorFlag(ACTOR_FLAG_FORCE_LIFEBAR) ) + UpdateBossHealth( actor ); + + if ( actor.statemap ) + { + actor.last_time_active = level.time; + return; + } + } + + // Update boss health if necessary + if ( (actor.GetActorFlag( ACTOR_FLAG_UPDATE_BOSS_HEALTH ) && actor.max_boss_health && ( actor.mode == ACTOR_MODE_AI )) || actor.GetActorFlag(ACTOR_FLAG_FORCE_LIFEBAR) ) + UpdateBossHealth( actor ); + + if ( actor.postureController ) + actor.postureController->evaluate(); + + ActorStateUpdate( actor ); + + if ( !actor.deadflag ) + { + // Update the hate list + actor.enemyManager->Update(); + + // Do the movement + DoMove( actor ); + + if ( actor.actortype == IS_INANIMATE ) + { + InanimateObject( actor ); + return; + } + + //Process our behaviors + ProcessBehaviors( actor ); + } + +} + + +//------------------------- CLASS ------------------------------ +// +// Name: SimplifiedThink +// Base Class: ActorThink +// +// Description: +// This is a light weight version of Default Think +// The purpose of this class is to allow Actors that +// move, have behaviors and accept messages without +// the CPU overhead of doing DefaultThink +// +// Method of Use: +// Actors can be given this method of updating via +// the script command "setsimplifiedthink" +// +//-------------------------------------------------------------- +SimplifiedThink::SimplifiedThink( Actor *actor ) : + _actor( actor ), + _previousContents( actor->getContents() ) +{ + actor->setContents( 0 ); +} + +SimplifiedThink::~SimplifiedThink( void ) +{ + _actor->setContents( _previousContents ); +} + +//---------------------------------------------------------------- +// Name: DoMove +// Class: SimplifiedThink +// +// Description: Does Movement Stuff for Actor +// +// Parameters: +// Actor in question +// +// Returns: None +//---------------------------------------------------------------- +void SimplifiedThink::DoMove( Actor &actor ) +{ + actor.last_origin = actor.origin; + actor.movementSubsystem->CalcMove(); + actor.movementSubsystem->setLastMove( actor.movementSubsystem->SimpleMove( actor.GetStickToGround() ) ); +} + +void SimplifiedThink::DoArchive( Archiver &arc ) +{ + ActorThink::DoArchive( arc ); + arc.ArchiveInteger ( &_previousContents ); + arc.ArchiveSafePointer( &_actor ); +} + +//---------------------------------------------------------------- +// Name: Think +// Class: SimplifiedThink +// +// Description: Main Think Function for Actor +// +// Parameters: +// Actor in question +// +// Returns: None +//---------------------------------------------------------------- +void SimplifiedThink::Think( Actor &actor ) +{ + // Update the hate list + actor.enemyManager->TrivialUpdate(); + + if ( actor.GetActorFlag( ACTOR_FLAG_SHOULD_BLINK ) ) + actor.TryBlink(); + + //Process our behaviors + ProcessBehaviors( actor ); + + // Do the movement + DoMove( actor ); +} diff --git a/dlls/game/actorstrategies.h b/dlls/game/actorstrategies.h new file mode 100644 index 0000000..38bad6b --- /dev/null +++ b/dlls/game/actorstrategies.h @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actorstrategies.h $ +// $Revision:: 19 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// I am replacing the old way that Actor::Think was done by implementing a group of strategies for it +// instead. This will form the foundation for new flexiblility within the actor class. +// +// What I am trying to achieve is a specialized "think" for different types of actors. A Boss, for instance, +// would use BossThink, an Non-Attacking-NPC could use NPCThink. Using the event system we already have in place +// it will be easy to change thinking modalities on the fly, allowing us to make a cowardly NPC turn into a +// roaring death machine if he gets shot. +// + +#ifndef __ACTORSTRATEGIES_H__ +#define __ACTORSTRATEGIES_H__ + +//============================ +// Forward Declarations +//============================ + +class Actor; +class Entity; +class Archiver; + +typedef SafePtr ActorPtr; + +//------------------------- CLASS ------------------------------ +// +// Name: ActorThink +// Base Class: None +// +// Description: Base class from which all Actor Think Strategies +// are derived. +// +// Method of Use: +// Deriving a new class from ActorThink is a good +// to allow Actor to exhibit new behavior. +// +//-------------------------------------------------------------- + +class ActorThink +{ +public: + virtual ~ActorThink() { } + virtual void DoArchive( Archiver &arc ); + virtual void Think( Actor & ) = 0; + virtual bool isSimple( void ) { return false; } + +protected: + virtual void ProcessBehaviors( Actor &actor ); + virtual void DoMove( Actor &actor ); + virtual void UpdateBossHealth( Actor &actor ); + virtual void CheckGround( Actor &actor ); + virtual void InanimateObject( Actor &actor ); + virtual void TryDrown( Actor &actor ); + virtual void ActorStateUpdate( Actor &actor ); + +}; + + +//------------------------- CLASS ------------------------------ +// +// Name: DefaultThink +// Base Class: ActorThink +// +// Description: +// Think class instantiated by most actors -- and +// instantiated in ALL actors by default +// +// Method of Use: +// This is the default behavior for Actor, nothing +// special need be done to use this class +// +//-------------------------------------------------------------- + +class DefaultThink : public ActorThink +{ +public: + virtual void Think( Actor &actor ); +}; + +//------------------------- CLASS ------------------------------ +// +// Name: SimplifiedThink +// Base Class: ActorThink +// +// Description: +// This is a light weight version of Default Think +// The purpose of this class is to allow Actors that +// move, have behaviors and accept messages without +// the CPU overhead of doing DefaultThink +// +// Method of Use: +// Actors can be given this method of updating via +// the script command "setsimplifiedthink" +// +//-------------------------------------------------------------- + +class SimplifiedThink : public ActorThink +{ +public: + SimplifiedThink( Actor *actor ); + ~SimplifiedThink( void ); + virtual void Think( Actor &actor ); + virtual void DoArchive( Archiver &arc ); + virtual bool isSimple( void ) { return true; } + +protected: + virtual void DoMove( Actor &actor ); +private: + const SimplifiedThink & operator=( const SimplifiedThink & ); + ActorPtr _actor; + int _previousContents; +}; + +#endif /* __ACTORSTRATEGIES_H__ */ diff --git a/dlls/game/actorutil.cpp b/dlls/game/actorutil.cpp new file mode 100644 index 0000000..a1f8611 --- /dev/null +++ b/dlls/game/actorutil.cpp @@ -0,0 +1,195 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actorutil.cpp $ +// $Revision:: 44 $ +// $Author:: Steven $ +// $Date:: 10/11/02 3:27a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// These classes will be used to help simplify the Actor class, and move some of the large subsystems it contained into +// these smaller classes. +// + +#include "_pch_cpp.h" +#include "actorutil.h" +#include "actor_sensoryperception.h" +#include "actor_enemymanager.h" +#include "player.h" +#include "object.h" + + + + + + + + + + + + + + + + +//=============================================================== +// +// Movement Utility Functions +// +//=============================================================== +qboolean FindMovement::validpath( PathNode *node, int i ) +{ + if ( !StandardMovement::validpath( node, i ) ) + return false; + + return true; + +} + +qboolean FindMovement::done( PathNode *node , const PathNode *end ) +{ + if ( node == end ) + return true; + + return false; +} + +qboolean FindCoverMovement::validpath( PathNode *node, int i ) +{ + PathNodeConnection *path; + PathNode *n; + + path = &node->GetConnection( i ); + + if ( !StandardMovement::validpath( node, i ) ) + { + return false; + } + + n = thePathManager.GetNode( path->targetNodeIndex );; + if ( !n || self->CloseToEnemy( n->origin, 128.0f ) ) + { + return false; + } + + return true; +} + +qboolean FindCoverMovement::done( PathNode *node , const PathNode *end ) +{ + // Get our current enemy + Entity *currentEnemy; + currentEnemy = self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return true; + + if ( node == end ) + { + return true; + } + + if ( !( node->nodeflags & ( AI_DUCK | AI_COVER ) ) ) + { + return false; + } + + if ( self ) + { + return true; + } + + return false; +} + +qboolean FindFleeMovement::validpath( PathNode *node , int i ) +{ + PathNodeConnection *path; + PathNode *n; + + path = &node->GetConnection( i ); + if ( !StandardMovement::validpath( node, i ) ) + { + return false; + } + + n = thePathManager.GetNode( path->targetNodeIndex );; + if ( !n || self->CloseToEnemy( n->origin, 128.0f ) ) + { + return false; + } + + return true; +} + +qboolean FindFleeMovement::done( PathNode *node , const PathNode *end ) +{ + // Get our current enemy + Entity *currentEnemy; + currentEnemy = self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return true; + + + if ( node == end ) + { + return true; + } + + if ( !( node->nodeflags & AI_FLEE ) ) + { + return false; + } + + if ( self ) + { + return true; + } + + return false; +} + +qboolean FindEnemyMovement::done( PathNode *node , const PathNode *end ) +{ + if ( node == end ) + { + return true; + } + + if ( self ) + { + return !true; + } + + return false; +} + + + + + + + + +//====================================== +// Global Functions +//====================================== +qboolean EntityIsValidTarget( const Entity *ent ) +{ + if ( ent && ( ent->flags & FL_NOTARGET ) ) + return false; + + if ( ent && ( ent->entnum == ENTITYNUM_WORLD ) ) + return false; + + return true; +} + + diff --git a/dlls/game/actorutil.h b/dlls/game/actorutil.h new file mode 100644 index 0000000..7d8ce1b --- /dev/null +++ b/dlls/game/actorutil.h @@ -0,0 +1,102 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/actorutil.h $ +// $Revision:: 28 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// Utility Functions and classes for Actor + + +class FindCoverMovement; +class FindFleeMovement; +class FindEnemyMovement; + +#ifndef __ACTORUTIL_H__ +#define __ACTORUTIL_H__ + +#include "actor.h" +#include "actorincludes.h" +#include "weapon.h" + +//============================ +// Global Functions +//============================ +qboolean EntityIsValidTarget( const Entity *ent ); + +//============================ +// Class FindMovement +//============================ +// +// Standard Movement, finds a path from the starting position to the ending position +// +class FindMovement : public StandardMovement + { + public: + Actor *self; + + qboolean validpath ( PathNode *node, int i ); + qboolean done ( PathNode *node, const PathNode *end ); + }; + + + +//============================ +// Class FindCoverMovement +//============================ +// +// Set destination to node with duck or cover set. Class will find a path to that node, or a closer one. +// +class FindCoverMovement : public StandardMovement + { + public: + Actor *self; + + qboolean validpath ( PathNode *node, int i ); + qboolean done ( PathNode *node, const PathNode *end ); + }; + + + +//============================ +// Class FindFleeMovement +//============================ +// +// Set destination to node with flee set. Class will find a path to that node, or a closer one. +// +class FindFleeMovement : public StandardMovement + { + public: + Actor *self; + + qboolean validpath ( PathNode *node, int i ); + qboolean done ( PathNode *node, const PathNode *end ); + + }; + +//============================ +// Class FindEnemyMovement +//============================ +// +// Not sure what this is for at the moment. +// +class FindEnemyMovement : public StandardMovement + { + public: + Actor *self; + + qboolean done ( PathNode *node, const PathNode *end ); + + }; + + +#endif /* __ACTORUTIL_H__ */ diff --git a/dlls/game/ai_chat.cpp b/dlls/game/ai_chat.cpp new file mode 100644 index 0000000..71bc696 --- /dev/null +++ b/dlls/game/ai_chat.cpp @@ -0,0 +1,1276 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_chat.c + * + * desc: Quake3 bot AI + * + * $Archive: /Code/DLLs/game/ai_chat.cpp $ + * $Author: Steven $ + * $Revision: 16 $ + * $Modtime: 10/13/03 9:03a $ + * $Date: 10/13/03 9:11a $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#ifdef MISSIONPACK // bk001205 +#include "botmenudef.h" +#endif + +#define TIME_BETWEENCHATTING 25 + + +/* +================== +BotNumActivePlayers +================== +*/ +int BotNumActivePlayers(void) { + int i, num; + char buf[MAX_INFO_STRING]; +// static int maxclients->integer; + +// if (!maxclients->integer) +// maxclients->integer = gi.Cvar_VariableIntegerValue("sv_maxclients->integer"); + + num = 0; + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || + !strlen(Info_ValueForKey(buf, "name")) + ) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + num++; + } + return num; +} + +/* +================== +BotIsFirstInRankings +================== +*/ +int BotIsFirstInRankings(bot_state_t *bs) { +// int i, score; +// char buf[MAX_INFO_STRING]; +// static int maxclients->integer; +// playerState_t ps; + + return qfalse; + /* FIXME + score = bs->cur_ps.persistant[PERS_SCORE]; + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + gi.getConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "name"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (score < ps.persistant[PERS_SCORE]) return qfalse; + } + return qtrue; + */ +} + +/* +================== +BotIsLastInRankings +================== +*/ +int BotIsLastInRankings(bot_state_t *bs) { +// int i, score; +// char buf[MAX_INFO_STRING]; +// playerState_t ps; + + return qfalse; + /* FIXME + score = bs->cur_ps.persistant[PERS_SCORE]; + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + gi.getConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "name"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (score > ps.persistant[PERS_SCORE]) return qfalse; + } + return qtrue; + + */ +} + +/* +================== +BotFirstClientInRankings +================== +*/ +char *BotFirstClientInRankings(void) { + int i, bestscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + playerState_t ps; + + bestscore = -999999; + bestclient = 0; + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "name"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); +/* FIXME + if (ps.persistant[PERS_SCORE] > bestscore) { + bestscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } +*/ + } + EasyClientName(bestclient, name, 32); + return name; +} + +/* +================== +BotLastClientInRankings +================== +*/ +char *BotLastClientInRankings(void) { + int i, worstscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + playerState_t ps; + + worstscore = 999999; + bestclient = 0; + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "name"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); +/* FIXME + if (ps.persistant[PERS_SCORE] < worstscore) { + worstscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } +*/ + } + EasyClientName(bestclient, name, 32); + return name; +} + +/* +================== +BotRandomOpponentName +================== +*/ +char *BotRandomOpponentName(bot_state_t *bs) { + int i, count; + char buf[MAX_INFO_STRING]; + int opponents[MAX_CLIENTS], numopponents; + static char name[32]; + + numopponents = 0; + opponents[0] = 0; + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + if (i == bs->client) continue; + // + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "name"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + //skip team mates + if (BotSameTeam(bs, i)) continue; + // + opponents[numopponents] = i; + numopponents++; + } + count = (int)(random() * numopponents); + for (i = 0; i < numopponents; i++) { + count--; + if (count <= 0) { + EasyClientName(opponents[i], name, sizeof(name)); + return name; + } + } + EasyClientName(opponents[0], name, sizeof(name)); + return name; +} + +/* +================== +BotMapTitle +================== +*/ + +char *BotMapTitle(void) { + char info[1024]; + static char mapname[128]; + + gi.SV_GetServerinfo(info, sizeof(info)); + + strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); + mapname[sizeof(mapname)-1] = '\0'; + + return mapname; +} + + +/* +================== +BotWeaponNameForMeansOfDeath +================== +*/ + +char *BotWeaponNameForMeansOfDeath( int mod ) +{ + static char meansOfDeathName[ 64 ]; + + strcpy( meansOfDeathName, "$$MOD-" ); + strcat( meansOfDeathName, MOD_NumToName( mod ) ); + strcat( meansOfDeathName, "$$" ); + + return meansOfDeathName; + + /* switch(mod) { + //case MOD_IMOD: return "I-Mod"; + case MOD_EXPLOSION: return "Explosion"; + case MOD_VAPORIZE: + case MOD_VAPORIZE_DISRUPTOR: // fall-through + case MOD_VAPORIZE_COMP: // fall-through + return "Beam blast"; + default: + return "Energy weapon"; + } +// return "Energy weapon"; */ +} + +/* +================== +BotRandomWeaponName +================== +*/ +char *BotRandomWeaponName(void) { + int rnd; + + rnd = rand() % 17; + + switch( rnd ) { + case 0: return "$$Weapon-AttrexianRifle$$"; + case 1: return "$$Weapon-Batleth$$"; + case 2: return "$$Weapon-BurstRifle$$"; + case 3: return "$$Weapon-CompressionRifle$$"; + case 4: return "$$Weapon-DrullStaff$$"; + case 5: return "$$Weapon-FieldAssaultRifle$$"; + case 6: return "$$Weapon-GrenadeLauncher$$"; + case 7: return "$$Weapon-I-Mod$$"; + case 8: return "$$Weapon-Phaser$$"; + case 9: return "$$Weapon-PhotonBurst$$"; + case 10: return "$$Weapon-RomulanDisruptor$$"; + case 11: return "$$Weapon-RomulanRadGun$$"; + case 12: return "$$Weapon-FederationSniperRifle$$"; + case 13: return "$$Weapon-TetryonGatlingGun$$"; + case 14: return "$$Weapon-AttrexianRifle$$"; + case 15: return "$$Weapon-AttrexianRifle$$"; + case 16: return "$$Weapon-AttrexianRifle$$"; + + default: return "$$Weapon-Phaser$$"; + } +} + +/* +================== +BotVisibleEnemies +================== +*/ +int BotVisibleEnemies(bot_state_t *bs) { + float vis; + int i; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + //if the enemy is invisible and not shooting + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + continue; + } + //if on the same team + if (BotSameTeam(bs, i)) continue; + //check if the enemy is visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis > 0) return qtrue; + } + return qfalse; +} + +/* +================== +BotValidChatPosition +================== +*/ +int BotValidChatPosition(bot_state_t *bs) { + vec3_t point, start, end, mins, maxs; + bsp_trace_t trace; + + //if the bot is dead all positions are valid + if (BotIsDead(bs)) return qtrue; + //never start chatting with a powerup + if (bs->inventory[INVENTORY_QUAD] || + bs->inventory[INVENTORY_HASTE] || + bs->inventory[INVENTORY_INVISIBILITY] || + bs->inventory[INVENTORY_REGEN] || + bs->inventory[INVENTORY_FLIGHT]) return qfalse; + //must be on the ground + //if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) return qfalse; + //do not chat if in lava or slime + VectorCopy(bs->origin, point); + point[2] -= 24; + if (gi.pointcontents(point,bs->entitynum) & (CONTENTS_LAVA|CONTENTS_SLIME)) return qfalse; + //do not chat if under water + VectorCopy(bs->origin, point); + point[2] += 32; + if (gi.pointcontents(point,bs->entitynum) & MASK_WATER) return qfalse; + //must be standing on the world entity + VectorCopy(bs->origin, start); + VectorCopy(bs->origin, end); + start[2] += 1; + end[2] -= 10; + gi.AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); + BotAI_Trace(&trace, start, mins, maxs, end, bs->client, MASK_SOLID); + if (trace.ent != ENTITYNUM_WORLD) return qfalse; + //the bot is in a position where it can chat + return qtrue; +} + +/* +================== +BotChat_EnterGame +================== +*/ +int BotChat_EnterGame(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + if (!BotValidChatPosition(bs)) return qfalse; + BotAI_BotInitialChat(bs, "game_enter", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_ExitGame +================== +*/ +int BotChat_ExitGame(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + BotAI_BotInitialChat(bs, "game_exit", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_StartLevel +================== +*/ +int BotChat_StartLevel(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) { + gi.EA_Command(bs->client, "vtaunt"); + return qfalse; + } + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + BotAI_BotInitialChat(bs, "level_start", + EasyClientName(bs->client, name, 32), // 0 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_EndLevel +================== +*/ +int BotChat_EndLevel(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + // teamplay + if (TeamPlayIsOn()) + { + if (BotIsFirstInRankings(bs)) { + gi.EA_Command(bs->client, "vtaunt"); + } + return qtrue; + } + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (BotIsFirstInRankings(bs)) { + BotAI_BotInitialChat(bs, "level_end_victory", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + } + else if (BotIsLastInRankings(bs)) { + BotAI_BotInitialChat(bs, "level_end_lose", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + } + else { + BotAI_BotInitialChat(bs, "level_end", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + } + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_Death +================== +*/ +int BotChat_Death(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + rnd = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_DEATH, 0, 1); + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chatting is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (bs->lastkilledby >= 0 && bs->lastkilledby < maxclients->integer) + EasyClientName(bs->lastkilledby, name, 32); + else + strcpy(name, "[world]"); + // + if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledby)) { + if (bs->lastkilledby == bs->client) return qfalse; + BotAI_BotInitialChat(bs, "death_teammate", name, NULL); + bs->chatto = CHAT_TEAM; + } + else + { + const char *enemyName; + char meansOfDeathName[ 64 ]; + + // Build the chat type + + strcpy( meansOfDeathName, "death_" ); + strcat( meansOfDeathName, MOD_NumToName( bs->botdeathtype ) ); + + // Determine which enemy to char to + + if ( ( bs->botdeathtype == MOD_DROWN ) || + ( bs->botdeathtype == MOD_LAVA ) || + ( bs->botdeathtype == MOD_SUICIDE ) || + ( bs->botdeathtype == MOD_SLIME ) || + ( bs->botdeathtype == MOD_FALLING ) || + ( bs->botdeathtype == MOD_CRUSH ) ) { + enemyName = BotRandomOpponentName( bs ); + } + else { + enemyName = name; + } + + // Try to chat + + if ( G_Random() < 0.25f ) { + gi.EA_Command( bs->client, "taunt 2" ); + } + else if ( gi.BotNumInitialChats( bs->cs, meansOfDeathName ) ) { + BotAI_BotInitialChat( bs, meansOfDeathName, enemyName, BotWeaponNameForMeansOfDeath( bs->botdeathtype ), NULL ); + } + else if ( ( random() < gi.Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1 ) ) && + ( gi.BotNumInitialChats( bs->cs, "death_insult" ) ) ) { + BotAI_BotInitialChat(bs, "death_insult", enemyName, BotWeaponNameForMeansOfDeath( bs->botdeathtype ), NULL); + } + else if ( gi.BotNumInitialChats( bs->cs, "death_praise" ) ) { + BotAI_BotInitialChat(bs, "death_praise", enemyName, BotWeaponNameForMeansOfDeath( bs->botdeathtype ), NULL); + } + + /* + //teamplay +// if (TeamPlayIsOn()) { +// gi.EA_Command(bs->client, "vtaunt"); +// return qtrue; +// } + // + +// if (bs->botdeathtype == MOD_WATER) +// BotAI_BotInitialChat(bs, "death_drown", BotRandomOpponentName(bs), NULL); +// else + if (bs->botdeathtype == MOD_SLIME) + BotAI_BotInitialChat(bs, "death_slime", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_LAVA) + BotAI_BotInitialChat(bs, "death_lava", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_FALLING) + BotAI_BotInitialChat(bs, "death_cratered", BotRandomOpponentName(bs), NULL); + + else if (bs->botsuicide || //all other suicides by own weapon + bs->botdeathtype == MOD_CRUSH || + bs->botdeathtype == MOD_SUICIDE) // || +// bs->botdeathtype == MOD_TARGET_LASER || +// bs->botdeathtype == MOD_TRIGGER_HURT || +// bs->botdeathtype == MOD_UNKNOWN) + BotAI_BotInitialChat(bs, "death_suicide", BotRandomOpponentName(bs), NULL); + else if (bs->botdeathtype == MOD_TELEFRAG) + BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); +#ifdef MISSIONPACKBOTTODO + else if (bs->botdeathtype == MOD_KAMIKAZE && gi.BotNumInitialChats(bs->cs, "death_kamikaze")) + BotAI_BotInitialChat(bs, "death_kamikaze", name, NULL); +#endif + + else { + if ((//bs->botdeathtype == MOD_IMOD || + bs->botdeathtype == MOD_VAPORIZE_DISRUPTOR || + bs->botdeathtype == MOD_VAPORIZE_COMP || + bs->botdeathtype == MOD_VAPORIZE || + bs->botdeathtype == MOD_EXPLOSION) && random() < 0.5) { + + if (bs->botdeathtype == MOD_EXPLOSION) + BotAI_BotInitialChat(bs, "death_explosion", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + else + BotAI_BotInitialChat(bs, "death_vaporize", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + + //choose between insult and praise + else + if (random() < gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { + BotAI_BotInitialChat(bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + else { + BotAI_BotInitialChat(bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + } */ + bs->chatto = CHAT_ALL; + } + bs->lastchat_time = FloatTime(); + return qtrue; + } + +/* +================== +BotChat_Kill +================== +*/ +int BotChat_Kill(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + rnd = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (bs->lastkilledplayer == bs->client) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + EasyClientName(bs->lastkilledplayer, name, 32); + // + bs->chatto = CHAT_ALL; + + if ( TeamPlayIsOn() && BotSameTeam( bs, bs->lastkilledplayer ) ) { + BotAI_BotInitialChat(bs, "kill_teammate", name, NULL); + bs->chatto = CHAT_TEAM; + } + else { + char meansOfDeathName[ 64 ]; + + // Build the chat type + + strcpy( meansOfDeathName, "kill_" ); + strcat( meansOfDeathName, MOD_NumToName( bs->botdeathtype ) ); + + // Try to chat + + if ( G_Random() < 0.25f ) { + gi.EA_Command( bs->client, "taunt 1" ); + } + else if ( gi.BotNumInitialChats( bs->cs, meansOfDeathName ) ) { + BotAI_BotInitialChat( bs, meansOfDeathName, name, NULL ); + } + else if ( ( random() < gi.Characteristic_BFloat( bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1 ) ) && + ( gi.BotNumInitialChats( bs->cs, "kill_insult" ) ) ) { + BotAI_BotInitialChat(bs, "kill_insult", name, NULL); + } + else if ( gi.BotNumInitialChats( bs->cs, "kill_praise" ) ) { + BotAI_BotInitialChat(bs, "kill_praise", name, NULL); + } + + /* if (bs->enemydeathtype == MOD_IMOD) { + BotAI_BotInitialChat(bs, "kill_imod", name, NULL); + } + else if ( (bs->enemydeathtype == MOD_VAPORIZE) || + (bs->enemydeathtype == MOD_VAPORIZE_COMP) || + (bs->enemydeathtype == MOD_VAPORIZE_DISRUPTOR) ) { + BotAI_BotInitialChat(bs, "kill_vaporize", name, NULL); + } + else if (bs->enemydeathtype == MOD_EXPLOSION) { + BotAI_BotInitialChat(bs, "kill_explosion", name, NULL); + } + if (bs->enemydeathtype == MOD_TELEFRAG) { + BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); + } +#ifdef MISSIONPACKBOTTODO + else if (bs->botdeathtype == MOD_KAMIKAZE && gi.BotNumInitialChats(bs->cs, "kill_kamikaze")) + BotAI_BotInitialChat(bs, "kill_kamikaze", name, NULL); +#endif + //choose between insult and praise + else if (random() < gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { + BotAI_BotInitialChat(bs, "kill_insult", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "kill_praise", name, NULL); + } */ + } + bs->lastchat_time = FloatTime(); + return qtrue; +} + +/* +================== +BotChat_EnemySuicide +================== +*/ +int BotChat_EnemySuicide(bot_state_t *bs) { +// char name[32]; +// float rnd; + + return qfalse; + /* FIXME + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + // + rnd = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + if (bs->enemy >= 0) EasyClientName(bs->enemy, name, 32); + else strcpy(name, ""); + BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +*/ +} + +/* +================== +BotChat_HitTalking +================== +*/ +int BotChat_HitTalking(bot_state_t *bs) { +// char name[32], *weap; +// int lasthurt_client; +// float rnd; +/* + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if (!lasthurt_client) return qfalse; + if (lasthurt_client == bs->client) return qfalse; + // + if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; + // + rnd = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITTALKING, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); + // + BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; +*/ + return qtrue; +} + +/* +================== +BotChat_HitNoDeath +================== +*/ +int BotChat_HitNoDeath(bot_state_t *bs) { +// char name[32], *weap; +// float rnd; +// int lasthurt_client; +// aas_entityinfo_t entinfo; +/* + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if (!lasthurt_client) return qfalse; + if (lasthurt_client == bs->client) return qfalse; + // + if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; + // + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + rnd = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNODEATH, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsShooting(&entinfo)) return qfalse; + // + ClientName(lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_mod); + // + BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; +*/ + return qtrue; + } + +/* +================== +BotChat_HitNoKill +================== +*/ +int BotChat_HitNoKill(bot_state_t *bs) { +// char name[32], *weap; +// float rnd; +// aas_entityinfo_t entinfo; + return qfalse; // FIXME +/* + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + rnd = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNOKILL, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsShooting(&entinfo)) return qfalse; + // + ClientName(bs->enemy, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->enemy].client->lasthurt_mod); + // + BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +*/ +} + +/* +================== +BotChat_Random +================== +*/ +int BotChat_Random(bot_state_t *bs) { + float rnd; + char name[32]; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //don't chat when doing something important :) + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_RUSHBASE) return qfalse; + // + rnd = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_RANDOM, 0, 1); + if (random() > bs->thinktime * 0.1) return qfalse; + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + if (random() > 0.25) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + if (bs->lastkilledplayer == bs->client) { + strcpy(name, BotRandomOpponentName(bs)); + } + else { + EasyClientName(bs->lastkilledplayer, name, sizeof(name)); + } + if (TeamPlayIsOn()) { + gi.EA_Command(bs->client, "vtaunt"); + return qfalse; // don't wait + } + // + if (random() < gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_MISC, 0, 1)) { + BotAI_BotInitialChat(bs, "random_misc", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + } + else { + BotAI_BotInitialChat(bs, "random_insult", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + } + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChatTime +================== +*/ +float BotChatTime(bot_state_t *bs) { + int cpm; + + cpm = gi.Characteristic_BInteger(bs->character, CHARACTERISTIC_CHAT_CPM, 1, 4000); + + return 0.0f; //(float) gi.BotChatLength(bs->cs) * 30 / cpm; +} + +/* +================== +BotChatTest +================== +*/ +void BotChatTest(bot_state_t *bs) { + + char name[32]; +// char *weap; + int num, i; + + num = gi.BotNumInitialChats(bs->cs, "game_enter"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "game_enter", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "game_exit"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "game_exit", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "level_start"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_start", + EasyClientName(bs->client, name, 32), // 0 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "level_end_victory"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end_victory", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "level_end_lose"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end_lose", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "level_end"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + EasyClientName(bs->lastkilledby, name, sizeof(name)); + num = gi.BotNumInitialChats(bs->cs, "death_drown"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "death_drown", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "death_slime"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_slime", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "death_lava"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_lava", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "death_cratered"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_cratered", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "death_suicide"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_suicide", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "death_telefrag"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "death_gauntlet"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_gauntlet", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "death_rail"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_rail", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "death_bfg"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_bfg", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "death_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "death_praise"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + // + EasyClientName(bs->lastkilledplayer, name, 32); + // + num = gi.BotNumInitialChats(bs->cs, "kill_gauntlet"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "kill_rail"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_rail", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "kill_telefrag"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "kill_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_insult", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "kill_praise"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_praise", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "enemy_suicide"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } +/* + ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); + num = gi.BotNumInitialChats(bs->cs, "hit_talking"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "hit_nodeath"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "hit_nokill"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } +*/ + // + if (bs->lastkilledplayer == bs->client) { + strcpy(name, BotRandomOpponentName(bs)); + } + else { + EasyClientName(bs->lastkilledplayer, name, sizeof(name)); + } + // + num = gi.BotNumInitialChats(bs->cs, "random_misc"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "random_misc", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = gi.BotNumInitialChats(bs->cs, "random_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "random_insult", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_ALL); + } + +} diff --git a/dlls/game/ai_chat.h b/dlls/game/ai_chat.h new file mode 100644 index 0000000..0a38a0b --- /dev/null +++ b/dlls/game/ai_chat.h @@ -0,0 +1,45 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_chat.h + * + * desc: Quake3 bot AI + * + * $Archive: /Code/DLLs/game/ai_chat.h $ + * $Author: Jwaters $ + * $Revision: 1 $ + * $Modtime: 7/25/02 11:48a $ + * $Date: 7/30/02 1:10p $ + * + *****************************************************************************/ + +// +int BotChat_EnterGame(bot_state_t *bs); +// +int BotChat_ExitGame(bot_state_t *bs); +// +int BotChat_StartLevel(bot_state_t *bs); +// +int BotChat_EndLevel(bot_state_t *bs); +// +int BotChat_HitTalking(bot_state_t *bs); +// +int BotChat_HitNoDeath(bot_state_t *bs); +// +int BotChat_HitNoKill(bot_state_t *bs); +// +int BotChat_Death(bot_state_t *bs); +// +int BotChat_Kill(bot_state_t *bs); +// +int BotChat_EnemySuicide(bot_state_t *bs); +// +int BotChat_Random(bot_state_t *bs); +// time the selected chat takes to type in +float BotChatTime(bot_state_t *bs); +// returns true if the bot can chat at the current position +int BotValidChatPosition(bot_state_t *bs); +// test the initial bot chats +void BotChatTest(bot_state_t *bs); + diff --git a/dlls/game/ai_cmd.cpp b/dlls/game/ai_cmd.cpp new file mode 100644 index 0000000..1c99f8f --- /dev/null +++ b/dlls/game/ai_cmd.cpp @@ -0,0 +1,1976 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_cmd.c + * + * desc: Quake3 bot AI + * + * $Archive: /Code/DLLs/game/ai_cmd.cpp $ + * $Author: Steven $ + * $Revision: 2 $ + * $Modtime: 3/11/03 11:22a $ + * $Date: 3/11/03 11:26a $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "botmenudef.h" + +int notleader[MAX_CLIENTS]; + +#ifdef DEBUG +/* +================== +BotPrintTeamGoal +================== +*/ +void BotPrintTeamGoal(bot_state_t *bs) { + char netname[MAX_NETNAME]; + float t; + + ClientName(bs->client, netname, sizeof(netname)); + t = bs->teamgoal_time - FloatTime(); + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t); + break; + } + case LTG_TEAMACCOMPANY: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t); + break; + } + case LTG_GETFLAG: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t); + break; + } + case LTG_RUSHBASE: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t); + break; + } + case LTG_RETURNFLAG: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t); + break; + } +#ifdef MISSIONPACK + case LTG_ATTACKENEMYBASE: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna attack the enemy base for %1.0f secs\n", netname, t); + break; + } + case LTG_HARVEST: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna harvest for %1.0f secs\n", netname, t); + break; + } +#endif + case LTG_DEFENDKEYAREA: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t); + break; + } + case LTG_GETITEM: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t); + break; + } + case LTG_KILL: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t); + break; + } + case LTG_PATROL: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t); + break; + } + default: + { + if (bs->ctfroam_time > FloatTime()) { + t = bs->ctfroam_time - FloatTime(); + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t); + } + else { + BotAI_Print(PRT_MESSAGE, "%s: I've got a regular goal\n", netname); + } + } + } +} +#endif //DEBUG + +/* +================== +BotGetItemTeamGoal + +FIXME: add stuff like "upper rocket launcher" +"the rl near the railgun", "lower grenade launcher" etc. +================== +*/ +int BotGetItemTeamGoal(char *goalname, bot_goal_t *goal) { + int i; + + if (!strlen(goalname)) return qfalse; + i = -1; + do { + i = gi.BotGetLevelItemGoal(i, goalname, goal); + if (i > 0) { + //do NOT defend dropped items + if (goal->flags & GFL_DROPPED) + continue; + return qtrue; + } + } while(i > 0); + return qfalse; +} + +/* +================== +BotGetMessageTeamGoal +================== +*/ +int BotGetMessageTeamGoal(bot_state_t *bs, char *goalname, bot_goal_t *goal) { + bot_waypoint_t *cp; + + if (BotGetItemTeamGoal(goalname, goal)) return qtrue; + + cp = BotFindWayPoint(bs->checkpoints, goalname); + if (cp) { + memcpy(goal, &cp->goal, sizeof(bot_goal_t)); + return qtrue; + } + return qfalse; +} + +/* +================== +BotGetTime +================== +*/ +float BotGetTime(bot_match_t *match) { + bot_match_t timematch; + char timestring[MAX_MESSAGE_SIZE]; + float t; + + //if the matched string has a time + if (match->subtype & ST_TIME) { + //get the time string + gi.BotMatchVariable(match, TIME, timestring, MAX_MESSAGE_SIZE); + //match it to find out if the time is in seconds or minutes + if (gi.BotFindMatch(timestring, &timematch, MTCONTEXT_TIME)) { + if (timematch.type == MSG_FOREVER) { + t = 99999999.0f; + } + else if (timematch.type == MSG_FORAWHILE) { + t = 10 * 60; // 10 minutes + } + else if (timematch.type == MSG_FORALONGTIME) { + t = 30 * 60; // 30 minutes + } + else { + gi.BotMatchVariable(&timematch, TIME, timestring, MAX_MESSAGE_SIZE); + if (timematch.type == MSG_MINUTES) t = atof(timestring) * 60; + else if (timematch.type == MSG_SECONDS) t = atof(timestring); + else t = 0; + } + //if there's a valid time + if (t > 0) return FloatTime() + t; + } + } + return 0; +} + +/* +================== +FindClientByName +================== +*/ +int FindClientByName(char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = gi.Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + ClientName(i, buf, sizeof(buf)); + if (!Q_stricmp(buf, name)) return i; + } + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + ClientName(i, buf, sizeof(buf)); + if (stristr(buf, name)) return i; + } + return -1; +} + +/* +================== +FindEnemyByName +================== +*/ +int FindEnemyByName(bot_state_t *bs, char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = gi.Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (BotSameTeam(bs, i)) continue; + ClientName(i, buf, sizeof(buf)); + if (!Q_stricmp(buf, name)) return i; + } + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (BotSameTeam(bs, i)) continue; + ClientName(i, buf, sizeof(buf)); + if (stristr(buf, name)) return i; + } + return -1; +} + +/* +================== +NumPlayersOnSameTeam +================== +*/ +int NumPlayersOnSameTeam(bot_state_t *bs) { + int i, num; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = gi.Cvar_VariableIntegerValue("sv_maxclients"); + + num = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + strncpy(buf, gi.getConfigstring(CS_PLAYERS+i), MAX_INFO_STRING); + if (strlen(buf)) { + if (BotSameTeam(bs, i+1)) num++; + } + } + return num; +} + +/* +================== +TeamPlayIsOn +================== +*/ +int BotGetPatrolWaypoints(bot_state_t *bs, bot_match_t *match) { + char keyarea[MAX_MESSAGE_SIZE]; + int patrolflags; + bot_waypoint_t *wp, *newwp, *newpatrolpoints; + bot_match_t keyareamatch; + bot_goal_t goal; + + newpatrolpoints = NULL; + patrolflags = 0; + // + gi.BotMatchVariable(match, KEYAREA, keyarea, MAX_MESSAGE_SIZE); + // + while(1) { + if (!gi.BotFindMatch(keyarea, &keyareamatch, MTCONTEXT_PATROLKEYAREA)) { + gi.EA_SayTeam(bs->client, "what do you say?"); + BotFreeWaypoints(newpatrolpoints); + bs->patrolpoints = NULL; + return qfalse; + } + gi.BotMatchVariable(&keyareamatch, KEYAREA, keyarea, MAX_MESSAGE_SIZE); + if (!BotGetMessageTeamGoal(bs, keyarea, &goal)) { + //BotAI_BotInitialChat(bs, "cannotfind", keyarea, NULL); + //gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotFreeWaypoints(newpatrolpoints); + bs->patrolpoints = NULL; + return qfalse; + } + //create a new waypoint + newwp = BotCreateWayPoint(keyarea, goal.origin, goal.areanum); + if (!newwp) + break; + //add the waypoint to the patrol points + newwp->next = NULL; + for (wp = newpatrolpoints; wp && wp->next; wp = wp->next); + if (!wp) { + newpatrolpoints = newwp; + newwp->prev = NULL; + } + else { + wp->next = newwp; + newwp->prev = wp; + } + // + if (keyareamatch.subtype & ST_BACK) { + patrolflags = PATROL_LOOP; + break; + } + else if (keyareamatch.subtype & ST_REVERSE) { + patrolflags = PATROL_REVERSE; + break; + } + else if (keyareamatch.subtype & ST_MORE) { + gi.BotMatchVariable(&keyareamatch, MORE, keyarea, MAX_MESSAGE_SIZE); + } + else { + break; + } + } + // + if (!newpatrolpoints || !newpatrolpoints->next) { + gi.EA_SayTeam(bs->client, "I need more key points to patrol\n"); + BotFreeWaypoints(newpatrolpoints); + newpatrolpoints = NULL; + return qfalse; + } + // + BotFreeWaypoints(bs->patrolpoints); + bs->patrolpoints = newpatrolpoints; + // + bs->curpatrolpoint = bs->patrolpoints; + bs->patrolflags = patrolflags; + // + return qtrue; +} + +/* +================== +BotAddressedToBot +================== +*/ +int BotAddressedToBot(bot_state_t *bs, bot_match_t *match) { + char addressedto[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char name[MAX_MESSAGE_SIZE]; + char botname[128]; + int client; + bot_match_t addresseematch; + + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientOnSameTeamFromName(bs, netname); + if (client < 0) return qfalse; + //if the message is addressed to someone + if (match->subtype & ST_ADDRESSED) { + gi.BotMatchVariable(match, ADDRESSEE, addressedto, sizeof(addressedto)); + //the name of this bot + ClientName(bs->client, botname, 128); + // + while(gi.BotFindMatch(addressedto, &addresseematch, MTCONTEXT_ADDRESSEE)) { + if (addresseematch.type == MSG_EVERYONE) { + return qtrue; + } + else if (addresseematch.type == MSG_MULTIPLENAMES) { + gi.BotMatchVariable(&addresseematch, TEAMMATE, name, sizeof(name)); + if (strlen(name)) { + if (stristr(botname, name)) return qtrue; + if (stristr(bs->subteam, name)) return qtrue; + } + gi.BotMatchVariable(&addresseematch, MORE, addressedto, MAX_MESSAGE_SIZE); + } + else { + gi.BotMatchVariable(&addresseematch, TEAMMATE, name, MAX_MESSAGE_SIZE); + if (strlen(name)) { + if (stristr(botname, name)) return qtrue; + if (stristr(bs->subteam, name)) return qtrue; + } + break; + } + } + //Com_sprintf(buf, sizeof(buf), "not addressed to me but %s", addressedto); + //gi.EA_Say(bs->client, buf); + return qfalse; + } + else { + bot_match_t tellmatch; + + tellmatch.type = 0; + //if this message wasn't directed solely to this bot + if (!gi.BotFindMatch(match->string, &tellmatch, MTCONTEXT_REPLYCHAT) || + tellmatch.type != MSG_CHATTELL) { + //make sure not everyone reacts to this message + if (random() > (float ) 1.0 / (NumPlayersOnSameTeam(bs)-1)) return qfalse; + } + } + return qtrue; +} + +/* +================== +BotGPSToPosition +================== +*/ +int BotGPSToPosition(char *buf, vec3_t position) { + int i, j = 0; + int num, sign; + + for (i = 0; i < 3; i++) { + num = 0; + while(buf[j] == ' ') j++; + if (buf[j] == '-') { + j++; + sign = -1; + } + else { + sign = 1; + } + while (buf[j]) { + if (buf[j] >= '0' && buf[j] <= '9') { + num = num * 10 + buf[j] - '0'; + j++; + } + else { + j++; + break; + } + } + BotAI_Print(PRT_MESSAGE, "%d\n", sign * num); + position[i] = (float) sign * num; + } + return qtrue; +} + +/* +================== +BotMatch_HelpAccompany +================== +*/ +void BotMatch_HelpAccompany(bot_state_t *bs, bot_match_t *match) { + int client, other, areanum; + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + bot_match_t teammatematch; + aas_entityinfo_t entinfo; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the team mate name + gi.BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + //get the client to help + if (gi.BotFindMatch(teammate, &teammatematch, MTCONTEXT_TEAMMATE) && + //if someone asks for him or herself + teammatematch.type == MSG_ME) { + //get the netname + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + other = qfalse; + } + else { + //asked for someone else + client = FindClientByName(teammate); + //if this is the bot self + if (client == bs->client) { + other = qfalse; + } + else if (!BotSameTeam(bs, client)) { + //FIXME: say "I don't help the enemy" + return; + } + else { + other = qtrue; + } + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if (client < 0) { + if (other) BotAI_BotInitialChat(bs, "whois", teammate, NULL); + else BotAI_BotInitialChat(bs, "whois", netname, NULL); + client = ClientFromName(netname); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + //don't help or accompany yourself + if (client == bs->client) { + return; + } + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) {// && gi.AAS_AreaReachability(areanum)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //if no teamgoal yet + if (bs->teamgoal.entitynum < 0) { + //if near an item + if (match->subtype & ST_NEARITEM) { + //get the match variable + gi.BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //gi.BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + } + } + // + if (bs->teamgoal.entitynum < 0) { + if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); + else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + client = ClientFromName(netname); + gi.BotEnterChat(bs->cs, client, CHAT_TEAM); + return; + } + //the team mate + bs->teammate = client; + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = ClientFromName(netname); + //the team mate who ordered + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //last time the team mate was assumed visible + bs->teammatevisible_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the ltg type + if (match->type == MSG_HELP) { + bs->ltgtype = LTG_TEAMHELP; + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_HELP_TIME; + } + else { + bs->ltgtype = LTG_TEAMACCOMPANY; + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_DefendKeyArea +================== +*/ +void BotMatch_DefendKeyArea(bot_state_t *bs, bot_match_t *match) { + char itemname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the match variable + gi.BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //gi.BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = ClientFromName(netname); + //the team mate who ordered + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + //away from defending + bs->defendaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_GetItem +================== +*/ +void BotMatch_GetItem(bot_state_t *bs, bot_match_t *match) { + char itemname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the match variable + gi.BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //gi.BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientOnSameTeamFromName(bs, netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETITEM; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_GETITEM_TIME; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_Camp +================== +*/ +void BotMatch_Camp(bot_state_t *bs, bot_match_t *match) { + int client, areanum; + char netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + aas_entityinfo_t entinfo; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //asked for someone else + client = FindClientByName(netname); + //if there's no valid client with this name + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", netname, NULL); + gi.BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //get the match variable + gi.BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); + //in CTF it could be the base + if (match->subtype & ST_THERE) { + //camp at the spot the bot is currently standing + bs->teamgoal.entitynum = bs->entitynum; + bs->teamgoal.areanum = bs->areanum; + VectorCopy(bs->origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + else if (match->subtype & ST_HERE) { + //if this is the bot self + if (client == bs->client) return; + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) {// && gi.AAS_AreaReachability(areanum)) { + //NOTE: just assume the bot knows where the person is + //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + //} + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + client = ClientFromName(netname); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + } + else if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //client = ClientFromName(netname); + //gi.BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_CAMPORDER; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; + //not arrived yet + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_Patrol +================== +*/ +void BotMatch_Patrol(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the patrol waypoints + if (!BotGetPatrolWaypoints(bs, match)) return; + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_PATROL; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time if not set already + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_PATROL_TIME; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_GetFlag +================== +*/ +void BotMatch_GetFlag(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + // get an alternate route in ctf + if (gametype == GT_CTF) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_AttackEnemyBase +================== +*/ +void BotMatch_AttackEnemyBase(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + BotMatch_GetFlag(bs, match); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF || gametype == GT_OBELISK || gametype == GT_HARVESTER) { + if (!redobelisk.areanum || !blueobelisk.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + bs->attackaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +#ifdef MISSIONPACK +/* +================== +BotMatch_Harvest +================== +*/ +void BotMatch_Harvest(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_HARVESTER) { + if (!neutralobelisk.areanum || !redobelisk.areanum || !blueobelisk.areanum) + return; + } + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} +#endif + +/* +================== +BotMatch_RushBase +================== +*/ +void BotMatch_RushBase(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF || gametype == GT_HARVESTER) { + if (!redobelisk.areanum || !blueobelisk.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RUSHBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_TaskPreference +================== +*/ +void BotMatch_TaskPreference(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_NETNAME]; + char teammatename[MAX_MESSAGE_SIZE]; + int teammate, preference; + + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + + gi.BotMatchVariable(match, NETNAME, teammatename, sizeof(teammatename)); + teammate = ClientFromName(teammatename); + if (teammate < 0) return; + + preference = BotGetTeamMateTaskPreference(bs, teammate); + switch(match->subtype) + { + case ST_DEFENDER: + { + preference &= ~TEAMTP_ATTACKER; + preference |= TEAMTP_DEFENDER; + break; + } + case ST_ATTACKER: + { + preference &= ~TEAMTP_DEFENDER; + preference |= TEAMTP_ATTACKER; + break; + } + case ST_ROAMER: + { + preference &= ~(TEAMTP_ATTACKER|TEAMTP_DEFENDER); + break; + } + } + BotSetTeamMateTaskPreference(bs, teammate, preference); + // + EasyClientName(teammate, teammatename, sizeof(teammatename)); + BotAI_BotInitialChat(bs, "keepinmind", teammatename, NULL); + gi.BotEnterChat(bs->cs, teammate, CHAT_TELL); + BotVoiceChatOnly(bs, teammate, VOICECHAT_YES); + gi.EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotMatch_ReturnFlag +================== +*/ +void BotMatch_ReturnFlag(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + //if not in CTF mode + if ( + gametype != GT_CTF +#ifdef MISSIONPACK + && gametype != GT_1FCTF +#endif + ) + return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) + return; + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RETURNFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + bs->rushbaseaway_time = 0; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_JoinSubteam +================== +*/ +void BotMatch_JoinSubteam(bot_state_t *bs, bot_match_t *match) { + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the sub team name + gi.BotMatchVariable(match, TEAMNAME, teammate, sizeof(teammate)); + //set the sub team name + strncpy(bs->subteam, teammate, 32); + bs->subteam[31] = '\0'; + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "joinedteam", teammate, NULL); + client = ClientFromName(netname); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_LeaveSubteam(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + if (strlen(bs->subteam)) + { + BotAI_BotInitialChat(bs, "leftteam", bs->subteam, NULL); + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + } //end if + strcpy(bs->subteam, ""); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_WhichTeam(bot_state_t *bs, bot_match_t *match) { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + if (strlen(bs->subteam)) { + BotAI_BotInitialChat(bs, "inteam", bs->subteam, NULL); + } + else { + BotAI_BotInitialChat(bs, "noteam", NULL); + } + gi.BotEnterChat(bs->cs, bs->client, CHAT_TEAM); +} + +/* +================== +BotMatch_CheckPoint +================== +*/ +void BotMatch_CheckPoint(bot_state_t *bs, bot_match_t *match) { + int areanum, client; + char buf[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + vec3_t position; + bot_waypoint_t *cp; + + if (!TeamPlayIsOn()) return; + // + gi.BotMatchVariable(match, POSITION, buf, MAX_MESSAGE_SIZE); + VectorClear(position); + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + //BotGPSToPosition(buf, position); + sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]); + position[2] += 0.5; + areanum = BotPointAreaNum(position); + if (!areanum) { + if (BotAddressedToBot(bs, match)) { + BotAI_BotInitialChat(bs, "checkpoint_invalid", NULL); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + } + return; + } + // + gi.BotMatchVariable(match, NAME, buf, MAX_MESSAGE_SIZE); + //check if there already exists a checkpoint with this name + cp = BotFindWayPoint(bs->checkpoints, buf); + if (cp) { + if (cp->next) cp->next->prev = cp->prev; + if (cp->prev) cp->prev->next = cp->next; + else bs->checkpoints = cp->next; + cp->inuse = qfalse; + } + //create a new check point + cp = BotCreateWayPoint(buf, position, areanum); + //add the check point to the bot's known chech points + cp->next = bs->checkpoints; + if (bs->checkpoints) bs->checkpoints->prev = cp; + bs->checkpoints = cp; + // + if (BotAddressedToBot(bs, match)) { + Com_sprintf(buf, sizeof(buf), "%1.0f %1.0f %1.0f", cp->goal.origin[0], + cp->goal.origin[1], + cp->goal.origin[2]); + + BotAI_BotInitialChat(bs, "checkpoint_confirm", cp->name, buf, NULL); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + } +} + +/* +================== +BotMatch_FormationSpace +================== +*/ +void BotMatch_FormationSpace(bot_state_t *bs, bot_match_t *match) { + char buf[MAX_MESSAGE_SIZE]; + float space; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + gi.BotMatchVariable(match, NUMBER, buf, MAX_MESSAGE_SIZE); + //if it's the distance in feet + if (match->subtype & ST_FEET) space = 0.3048 * 32 * atof(buf); + //else it's in meters + else space = 32 * atof(buf); + //check if the formation intervening space is valid + if (space < 48 || space > 500) space = 100; + bs->formation_dist = space; +} + +/* +================== +BotMatch_Dismiss +================== +*/ +void BotMatch_Dismiss(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + // + bs->decisionmaker = client; + // + bs->ltgtype = 0; + bs->lead_time = 0; + bs->lastgoal_ltgtype = 0; + // + BotAI_BotInitialChat(bs, "dismissed", NULL); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_Suicide +================== +*/ +void BotMatch_Suicide(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + gi.EA_Command(bs->client, "kill"); + // + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + // + BotVoiceChat(bs, client, VOICECHAT_TAUNT); + gi.EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotMatch_StartTeamLeaderShip +================== +*/ +void BotMatch_StartTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //if chats for him or herself + if (match->subtype & ST_I) { + //get the team mate that will be the team leader + gi.BotMatchVariable(match, NETNAME, teammate, sizeof(teammate)); + strncpy(bs->teamleader, teammate, sizeof(bs->teamleader)); + bs->teamleader[sizeof(bs->teamleader) - 1] = '\0'; + } + //chats for someone else + else { + //get the team mate that will be the team leader + gi.BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + client = FindClientByName(teammate); + if (client >= 0) ClientName(client, bs->teamleader, sizeof(bs->teamleader)); + } +} + +/* +================== +BotMatch_StopTeamLeaderShip +================== +*/ +void BotMatch_StopTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //get the team mate that stops being the team leader + gi.BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + //if chats for him or herself + if (match->subtype & ST_I) { + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + } + //chats for someone else + else { + client = FindClientByName(teammate); + } //end else + if (client >= 0) { + if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { + bs->teamleader[0] = '\0'; + notleader[client] = qtrue; + } + } +} + +/* +================== +BotMatch_WhoIsTeamLeader +================== +*/ +void BotMatch_WhoIsTeamLeader(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + + ClientName(bs->client, netname, sizeof(netname)); + //if this bot IS the team leader + if (!Q_stricmp(netname, bs->teamleader)) { + gi.EA_SayTeam(bs->client, "I'm the team leader\n"); + } +} + +/* +================== +BotMatch_WhatAreYouDoing +================== +*/ +void BotMatch_WhatAreYouDoing(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + char goalname[MAX_MESSAGE_SIZE]; + int client; + + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "helping", netname, NULL); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "accompanying", netname, NULL); + break; + } + case LTG_DEFENDKEYAREA: + { + gi.BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_BotInitialChat(bs, "defending", goalname, NULL); + break; + } + case LTG_GETITEM: + { + gi.BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_BotInitialChat(bs, "gettingitem", goalname, NULL); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "killing", netname, NULL); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_BotInitialChat(bs, "camping", NULL); + break; + } + case LTG_PATROL: + { + BotAI_BotInitialChat(bs, "patrolling", NULL); + break; + } + case LTG_GETFLAG: + { + BotAI_BotInitialChat(bs, "capturingflag", NULL); + break; + } + case LTG_RUSHBASE: + { + BotAI_BotInitialChat(bs, "rushingbase", NULL); + break; + } + case LTG_RETURNFLAG: + { + BotAI_BotInitialChat(bs, "returningflag", NULL); + break; + } +#ifdef MISSIONPACK + case LTG_ATTACKENEMYBASE: + { + BotAI_BotInitialChat(bs, "attackingenemybase", NULL); + break; + } + case LTG_HARVEST: + { + BotAI_BotInitialChat(bs, "harvesting", NULL); + break; + } +#endif + default: + { + BotAI_BotInitialChat(bs, "roaming", NULL); + break; + } + } + //chat what the bot is doing + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_WhatIsMyCommand +================== +*/ +void BotMatch_WhatIsMyCommand(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + bs->forceorders = qtrue; +} + +/* +================== +BotNearestVisibleItem +================== +*/ +float BotNearestVisibleItem(bot_state_t *bs, char *itemname, bot_goal_t *goal) { + int i; + char name[64]; + bot_goal_t tmpgoal; + float dist, bestdist; + vec3_t dir; + bsp_trace_t trace; + + bestdist = 999999; + i = -1; + do { + i = gi.BotGetLevelItemGoal(i, itemname, &tmpgoal); + gi.BotGoalName(tmpgoal.number, name, sizeof(name)); + if (Q_stricmp(itemname, name) != 0) + continue; + VectorSubtract(tmpgoal.origin, bs->origin, dir); + dist = VectorLength(dir); + if (dist < bestdist) { + //trace from start to end + BotAI_Trace(&trace, bs->eye, NULL, NULL, tmpgoal.origin, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction >= 1.0) { + bestdist = dist; + memcpy(goal, &tmpgoal, sizeof(bot_goal_t)); + } + } + } while(i > 0); + return bestdist; +} + +/* +================== +BotMatch_WhereAreYou +================== +*/ +void BotMatch_WhereAreYou(bot_state_t *bs, bot_match_t *match) { + float dist, bestdist; + int i, bestitem, redtt, bluett, client; + bot_goal_t goal; + char netname[MAX_MESSAGE_SIZE]; + char *nearbyitems[] = { + "Shotgun", + "Grenade Launcher", + "Rocket Launcher", + "Plasmagun", + "Railgun", + "Lightning Gun", + "BFG10K", + "Quad Damage", + "Regeneration", + "Battle Suit", + "Speed", + "Invisibility", + "Flight", + "Armor", + "Heavy Armor", + "Red Flag", + "Blue Flag", +#ifdef MISSIONPACK + "Nailgun", + "Prox Launcher", + "Chaingun", + "Scout", + "Guard", + "Doubler", + "Ammo Regen", + "Neutral Flag", + "Red Obelisk", + "Blue Obelisk", + "Neutral Obelisk", +#endif + NULL + }; + // + if (!TeamPlayIsOn()) + return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) + return; + + bestitem = -1; + bestdist = 999999; + for (i = 0; nearbyitems[i]; i++) { + dist = BotNearestVisibleItem(bs, nearbyitems[i], &goal); + if (dist < bestdist) { + bestdist = dist; + bestitem = i; + } + } + if (bestitem != -1) { + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + redtt = gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_redflag.areanum, TFL_DEFAULT); + bluett = gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_blueflag.areanum, TFL_DEFAULT); + if (redtt < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); + } + else if (bluett < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); + } + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_OBELISK || gametype == GT_HARVESTER) { + redtt = gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, redobelisk.areanum, TFL_DEFAULT); + bluett = gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, blueobelisk.areanum, TFL_DEFAULT); + if (redtt < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); + } + else if (bluett < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); + } + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + } +#endif + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + } +} + +/* +================== +BotMatch_LeadTheWay +================== +*/ +void BotMatch_LeadTheWay(bot_state_t *bs, bot_match_t *match) { + aas_entityinfo_t entinfo; + char netname[MAX_MESSAGE_SIZE], teammate[MAX_MESSAGE_SIZE]; + int client, areanum, other; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //if someone asks for someone else + if (match->subtype & ST_SOMEONE) { + //get the team mate name + gi.BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + client = FindClientByName(teammate); + //if this is the bot self + if (client == bs->client) { + other = qfalse; + } + else if (!BotSameTeam(bs, client)) { + //FIXME: say "I don't help the enemy" + return; + } + else { + other = qtrue; + } + } + else { + //get the netname + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + other = qfalse; + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", netname, NULL); + gi.BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + // + bs->lead_teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && gi.AAS_AreaReachability(areanum)) { + bs->lead_teamgoal.entitynum = client; + bs->lead_teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); + VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); + VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); + } + } + + if (bs->teamgoal.entitynum < 0) { + if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); + else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + gi.BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + bs->lead_teammate = client; + bs->lead_time = FloatTime() + TEAM_LEAD_TIME; + bs->leadvisible_time = 0; + bs->leadmessage_time = -(FloatTime() + 2 * random()); +} + +/* +================== +BotMatch_Kill +================== +*/ +void BotMatch_Kill(bot_state_t *bs, bot_match_t *match) { + char enemy[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + + gi.BotMatchVariable(match, ENEMY, enemy, sizeof(enemy)); + // + client = FindEnemyByName(bs, enemy); + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", enemy, NULL); + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + bs->teamgoal.entitynum = client; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_KILL; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_KILL_SOMEONE; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_CTF +================== +*/ +void BotMatch_CTF(bot_state_t *bs, bot_match_t *match) { + + char flag[128], netname[MAX_NETNAME]; + + if (gametype == GT_CTF) { + gi.BotMatchVariable(match, FLAG, flag, sizeof(flag)); + if (match->subtype & ST_GOTFLAG) { + if (!Q_stricmp(flag, "red")) { + bs->redflagstatus = 1; + if (BotTeam(bs) == TEAM_BLUE) { + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } + else { + bs->blueflagstatus = 1; + if (BotTeam(bs) == TEAM_RED) { + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } + bs->flagstatuschanged = 1; + bs->lastflagcapture_time = FloatTime(); + } + else if (match->subtype & ST_CAPTUREDFLAG) { + bs->redflagstatus = 0; + bs->blueflagstatus = 0; + bs->flagcarrier = 0; + bs->flagstatuschanged = 1; + } + else if (match->subtype & ST_RETURNEDFLAG) { + if (!Q_stricmp(flag, "red")) bs->redflagstatus = 0; + else bs->blueflagstatus = 0; + bs->flagstatuschanged = 1; + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (match->subtype & ST_1FCTFGOTFLAG) { + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } +#endif +} + +void BotMatch_EnterGame(bot_state_t *bs, bot_match_t *match) { + int client; + char netname[MAX_NETNAME]; + + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + if (client >= 0) { + notleader[client] = qfalse; + } + //NOTE: eliza chats will catch this + //Com_sprintf(buf, sizeof(buf), "heya %s", netname); + //EA_Say(bs->client, buf); +} + +void BotMatch_NewLeader(bot_state_t *bs, bot_match_t *match) { + int client; + char netname[MAX_NETNAME]; + + gi.BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + if (!BotSameTeam(bs, client)) + return; + Q_strncpyz(bs->teamleader, netname, sizeof(bs->teamleader)); +} + +/* +================== +BotMatchMessage +================== +*/ +int BotMatchMessage(bot_state_t *bs, char *message) { + bot_match_t match; + + match.type = 0; + //if it is an unknown message + if (!gi.BotFindMatch(message, &match, MTCONTEXT_MISC + |MTCONTEXT_INITIALTEAMCHAT + |MTCONTEXT_CTF)) { + return qfalse; + } + //react to the found message + switch(match.type) + { + case MSG_HELP: //someone calling for help + case MSG_ACCOMPANY: //someone calling for company + { + BotMatch_HelpAccompany(bs, &match); + break; + } + case MSG_DEFENDKEYAREA: //teamplay defend a key area + { + BotMatch_DefendKeyArea(bs, &match); + break; + } + case MSG_CAMP: //camp somewhere + { + BotMatch_Camp(bs, &match); + break; + } + case MSG_PATROL: //patrol between several key areas + { + BotMatch_Patrol(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_GETFLAG: //ctf get the enemy flag + { + BotMatch_GetFlag(bs, &match); + break; + } +#ifdef MISSIONPACK + //CTF & 1FCTF & Obelisk & Harvester + case MSG_ATTACKENEMYBASE: + { + BotMatch_AttackEnemyBase(bs, &match); + break; + } + //Harvester + case MSG_HARVEST: + { + BotMatch_Harvest(bs, &match); + break; + } +#endif + //CTF & 1FCTF & Harvester + case MSG_RUSHBASE: //ctf rush to the base + { + BotMatch_RushBase(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_RETURNFLAG: + { + BotMatch_ReturnFlag(bs, &match); + break; + } + //CTF & 1FCTF & Obelisk & Harvester + case MSG_TASKPREFERENCE: + { + BotMatch_TaskPreference(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_CTF: + { + BotMatch_CTF(bs, &match); + break; + } + case MSG_GETITEM: + { + BotMatch_GetItem(bs, &match); + break; + } + case MSG_JOINSUBTEAM: //join a sub team + { + BotMatch_JoinSubteam(bs, &match); + break; + } + case MSG_LEAVESUBTEAM: //leave a sub team + { + BotMatch_LeaveSubteam(bs, &match); + break; + } + case MSG_WHICHTEAM: + { + BotMatch_WhichTeam(bs, &match); + break; + } + case MSG_CHECKPOINT: //remember a check point + { + BotMatch_CheckPoint(bs, &match); + break; + } + case MSG_CREATENEWFORMATION: //start the creation of a new formation + { + gi.EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); + break; + } + case MSG_FORMATIONPOSITION: //tell someone his/her position in the formation + { + gi.EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); + break; + } + case MSG_FORMATIONSPACE: //set the formation space + { + BotMatch_FormationSpace(bs, &match); + break; + } + case MSG_DOFORMATION: //form a certain formation + { + break; + } + case MSG_DISMISS: //dismiss someone + { + BotMatch_Dismiss(bs, &match); + break; + } + case MSG_STARTTEAMLEADERSHIP: //someone will become the team leader + { + BotMatch_StartTeamLeaderShip(bs, &match); + break; + } + case MSG_STOPTEAMLEADERSHIP: //someone will stop being the team leader + { + BotMatch_StopTeamLeaderShip(bs, &match); + break; + } + case MSG_WHOISTEAMLAEDER: + { + BotMatch_WhoIsTeamLeader(bs, &match); + break; + } + case MSG_WHATAREYOUDOING: //ask a bot what he/she is doing + { + BotMatch_WhatAreYouDoing(bs, &match); + break; + } + case MSG_WHATISMYCOMMAND: + { + BotMatch_WhatIsMyCommand(bs, &match); + break; + } + case MSG_WHEREAREYOU: + { + BotMatch_WhereAreYou(bs, &match); + break; + } + case MSG_LEADTHEWAY: + { + BotMatch_LeadTheWay(bs, &match); + break; + } + case MSG_KILL: + { + BotMatch_Kill(bs, &match); + break; + } + case MSG_ENTERGAME: //someone entered the game + { + BotMatch_EnterGame(bs, &match); + break; + } + case MSG_NEWLEADER: + { + BotMatch_NewLeader(bs, &match); + break; + } + case MSG_WAIT: + { + break; + } + case MSG_SUICIDE: + { + BotMatch_Suicide(bs, &match); + break; + } + default: + { + BotAI_Print(PRT_MESSAGE, "unknown match type\n"); + break; + } + } + return qtrue; +} diff --git a/dlls/game/ai_cmd.h b/dlls/game/ai_cmd.h new file mode 100644 index 0000000..47909ba --- /dev/null +++ b/dlls/game/ai_cmd.h @@ -0,0 +1,21 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_cmd.h + * + * desc: Quake3 bot AI + * + * $Archive: /Code/DLLs/game/ai_cmd.h $ + * $Author: Jwaters $ + * $Revision: 1 $ + * $Modtime: 7/25/02 11:48a $ + * $Date: 7/30/02 1:10p $ + * + *****************************************************************************/ + +extern int notleader[MAX_CLIENTS]; + +int BotMatchMessage(bot_state_t *bs, char *message); +void BotPrintTeamGoal(bot_state_t *bs); + diff --git a/dlls/game/ai_dmnet.cpp b/dlls/game/ai_dmnet.cpp new file mode 100644 index 0000000..ad1d437 --- /dev/null +++ b/dlls/game/ai_dmnet.cpp @@ -0,0 +1,2605 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_dmnet.c + * + * desc: Quake3 bot AI + * + * $Archive: /Code/DLLs/game/ai_dmnet.cpp $ + * $Author: Steven $ + * $Revision: 8 $ + * $Modtime: 4/17/03 2:50p $ + * $Date: 4/17/03 3:57p $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +//data file headers +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "botmenudef.h" + +//goal flag, see be_ai_goal.h for the other GFL_* +#define GFL_AIR 128 + +int numnodeswitches; +char nodeswitch[MAX_NODESWITCHES+1][144]; + +#define LOOKAHEAD_DISTANCE 300 + +/* +================== +BotResetNodeSwitches +================== +*/ +void BotResetNodeSwitches(void) { + numnodeswitches = 0; +} + +/* +================== +BotDumpNodeSwitches +================== +*/ +void BotDumpNodeSwitches(bot_state_t *bs) { + int i; + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, FloatTime(), MAX_NODESWITCHES); + for (i = 0; i < numnodeswitches; i++) { + BotAI_Print(PRT_MESSAGE, nodeswitch[i]); + } + BotAI_Print(PRT_FATAL, ""); +} + +/* +================== +BotRecordNodeSwitch +================== +*/ +vmCvar_t bot_showstates; + +void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str, char *s) { + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s from %s\n", netname, FloatTime(), node, str, s); + cvar_t *showstates = gi.cvar_get("bot_showstates"); + if ((showstates) && (showstates->integer)) + BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]); + numnodeswitches++; +} + +/* +================== +BotGetAirGoal +================== +*/ +int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) { + bsp_trace_t bsptrace; + vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2}; + int areanum; + + //trace up until we hit solid + VectorCopy(bs->origin, end); + end[2] += 1000; + BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + //trace down until we hit water + VectorCopy(bsptrace.endpos, end); + BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA); + //if we found the water surface + if (bsptrace.fraction > 0) { + areanum = BotPointAreaNum(bsptrace.endpos); + if (areanum) { + VectorCopy(bsptrace.endpos, goal->origin); + goal->origin[2] -= 2; + goal->areanum = areanum; + goal->mins[0] = -15; + goal->mins[1] = -15; + goal->mins[2] = -1; + goal->maxs[0] = 15; + goal->maxs[1] = 15; + goal->maxs[2] = 1; + goal->flags = GFL_AIR; + goal->number = 0; + goal->iteminfo = 0; + goal->entitynum = 0; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotGoForAir +================== +*/ +int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { + bot_goal_t goal; + + //if the bot needs air + if (bs->lastair_time < FloatTime() - 6) { + // +#ifdef DEBUG + //BotAI_Print(PRT_MESSAGE, "going for air\n"); +#endif //DEBUG + //if we can find an air goal + if (BotGetAirGoal(bs, &goal)) { + gi.BotPushGoal(bs->gs, &goal); + return qtrue; + } + else { + //get a nearby goal outside the water + while(gi.BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range)) { + gi.BotGetTopGoal(bs->gs, &goal); + //if the goal is not in water + if (!(gi.AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) { + return qtrue; + } + gi.BotPopGoal(bs->gs); + } + gi.BotResetAvoidGoals(bs->gs); + } + } + return qfalse; +} + +/* +================== +BotNearbyGoal +================== +*/ +int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { + int ret; + + //check if the bot should go for air + if (BotGoForAir(bs, tfl, ltg, range)) return qtrue; + //if the bot is carrying the enemy flag + if (BotCTFCarryingFlag(bs)) { + //if the bot is just a few secs away from the base + if (gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, + bs->teamgoal.areanum, TFL_DEFAULT) < 300) { + //make the range really small + range = 50; + } + } + // + ret = gi.BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range); + /* + if (ret) + { + char buf[128]; + //get the goal at the top of the stack + gi.BotGetTopGoal(bs->gs, &goal); + gi.BotGoalName(goal.number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", FloatTime(), buf); + } + */ + return ret; +} + +/* +================== +BotReachedGoal +================== +*/ +int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) { + if (goal->flags & GFL_ITEM) { + //if touching the goal + if (gi.BotTouchingGoal(bs->origin, goal)) { + if (!(goal->flags & GFL_DROPPED)) { + gi.BotSetAvoidGoalTime(bs->gs, goal->number, -1); + } + return qtrue; + } + //if the goal isn't there + if (gi.BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { + /* + float avoidtime; + int t; + + avoidtime = gi.BotAvoidGoalTime(bs->gs, goal->number); + if (avoidtime > 0) { + t = gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal->areanum, bs->tfl); + if ((float) t * 0.009 < avoidtime) + return qtrue; + } + */ + return qtrue; + } + //if in the goal area and below or above the goal and not swimming + if (bs->areanum == goal->areanum) { + if (bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0]) { + if (bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1]) { + if (!gi.AAS_Swimming(bs->origin)) { + return qtrue; + } + } + } + } + } + else if (goal->flags & GFL_AIR) { + //if touching the goal + if (gi.BotTouchingGoal(bs->origin, goal)) return qtrue; + //if the bot got air + if (bs->lastair_time > FloatTime() - 1) return qtrue; + } + else { + //if touching the goal + if (gi.BotTouchingGoal(bs->origin, goal)) return qtrue; + } + return qfalse; +} + +/* +================== +BotGetItemLongTermGoal +================== +*/ +int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) { + //if the bot has no goal + if (!gi.BotGetTopGoal(bs->gs, goal)) { + //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n"); + bs->ltg_time = 0; + } + //if the bot touches the current goal + else if (BotReachedGoal(bs, goal)) { + BotChooseWeapon(bs); + bs->ltg_time = 0; + } + //if it is time to find a new long term goal + if (bs->ltg_time < FloatTime()) { + //pop the current goal from the stack + gi.BotPopGoal(bs->gs); + //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname))); + //choose a new goal + //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", FloatTime(), bs->client); + if (gi.BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl)) { + /* + char buf[128]; + //get the goal at the top of the stack + gi.BotGetTopGoal(bs->gs, goal); + gi.BotGoalName(goal->number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", FloatTime(), buf); + */ + bs->ltg_time = FloatTime() + 20; + } + else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though + // +#ifdef DEBUG + char netname[128]; + + BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname))); +#endif + //gi.BotDumpAvoidGoals(bs->gs); + //reset the avoid goals and the avoid reach + gi.BotResetAvoidGoals(bs->gs); + gi.BotResetAvoidReach(bs->ms); + } + //get the goal at the top of the stack + return gi.BotGetTopGoal(bs->gs, goal); + } + return qtrue; +} + +/* +================== +BotGetLongTermGoal + +we could also create a seperate AI node for every long term goal type +however this saves us a lot of code +================== +*/ +int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { + vec3_t target, dir, dir2; + char netname[MAX_NETNAME]; + char buf[MAX_MESSAGE_SIZE]; + int areanum; + float croucher; + aas_entityinfo_t entinfo, botinfo; + bot_waypoint_t *wp; + + if (bs->ltgtype == LTG_TEAMHELP && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + gi.EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //if trying to help the team mate for more than a minute + if (bs->teamgoal_time < FloatTime()) + bs->ltgtype = 0; + //if the team mate IS visible for quite some time + if (bs->teammatevisible_time < FloatTime() - 10) bs->ltgtype = 0; + //get entity information of the companion + BotEntityInfo(bs->teammate, &entinfo); + //if the team mate is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { + //if close just stand still there + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(100)) { + gi.BotResetAvoidReach(bs->ms); + return qfalse; + } + } + else { + //last time the bot was NOT visible + bs->teammatevisible_time = FloatTime(); + } + //if the entity information is valid (entity in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && gi.AAS_AreaReachability(areanum)) { + //update team goal + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + return qtrue; + } + //if the bot accompanies someone + if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + gi.EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //if accompanying the companion for 3 minutes + if (bs->teamgoal_time < FloatTime()) { + BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + gi.BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->ltgtype = 0; + } + //get entity information of the companion + BotEntityInfo(bs->teammate, &entinfo); + //if the companion is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { + //update visible time + bs->teammatevisible_time = FloatTime(); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(bs->formation_dist)) { + // + // if the client being followed bumps into this bot then + // the bot should back up + BotEntityInfo(bs->entitynum, &botinfo); + // if the followed client is not standing ontop of the bot + if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2]) { + // if the bounding boxes touch each other + if (botinfo.origin[0] + botinfo.maxs[0] > entinfo.origin[0] + entinfo.mins[0] - 4&& + botinfo.origin[0] + botinfo.mins[0] < entinfo.origin[0] + entinfo.maxs[0] + 4) { + if (botinfo.origin[1] + botinfo.maxs[1] > entinfo.origin[1] + entinfo.mins[1] - 4 && + botinfo.origin[1] + botinfo.mins[1] < entinfo.origin[1] + entinfo.maxs[1] + 4) { + if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2] - 4 && + botinfo.origin[2] + botinfo.mins[2] < entinfo.origin[2] + entinfo.maxs[2] + 4) { + // if the followed client looks in the direction of this bot + AngleVectors(entinfo.angles, dir, NULL, NULL); + dir[2] = 0; + VectorNormalize(dir); + //VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + VectorSubtract(bs->origin, entinfo.origin, dir2); + VectorNormalize(dir2); + if (DotProduct(dir, dir2) > 0.7) { + // back up + BotSetupForMovement(bs); + gi.BotMoveInDirection(bs->ms, dir2, sv_maxspeed->value, MOVE_WALK); + } + } + } + } + } + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if (bs->attackcrouch_time < FloatTime() - 5) { + croucher = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + if (random() < bs->thinktime * croucher) { + bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; + } + } + //don't crouch when swimming + if (gi.AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; + //if not arrived yet or arived some time ago + if (bs->arrive_time < FloatTime() - 2) { + //if not arrived yet + if (!bs->arrive_time) { + gi.EA_Gesture(bs->client); + BotAI_BotInitialChat(bs, "accompany_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + gi.BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->arrive_time = FloatTime(); + } + //if the bot wants to crouch + else if (bs->attackcrouch_time > FloatTime()) { + gi.EA_Crouch(bs->client); + } + //else do some model taunts + else if (random() < bs->thinktime * 0.05) { + //do a gesture :) + gi.EA_Gesture(bs->client); + } + } + //if just arrived look at the companion + if (bs->arrive_time > FloatTime() - 2) { + VectorSubtract(entinfo.origin, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //else look strategically around for enemies + else if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to go for air + if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) { + gi.BotResetLastAvoidReach(bs->ms); + //get the goal at the top of the stack + //gi.BotGetTopGoal(bs->gs, &tmpgoal); + //gi.BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + 8; + AIEnter_Seek_NBG(bs, "BotLongTermGoal: go for air"); + return qfalse; + } + // + gi.BotResetAvoidReach(bs->ms); + return qfalse; + } + } + //if the entity information is valid (entity in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && gi.AAS_AreaReachability(areanum)) { + //update team goal + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //the goal the bot should go for + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //if the companion is NOT visible for too long + if (bs->teammatevisible_time < FloatTime() - 60) { + BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + gi.BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->ltgtype = 0; + // just to make sure the bot won't spam this message + bs->teammatevisible_time = FloatTime(); + } + return qtrue; + } + // + if (bs->ltgtype == LTG_DEFENDKEYAREA) { + if (gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, + bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) { + bs->defendaway_time = 0; + } + } + //if defending a key area + if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat && + bs->defendaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + gi.BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "defend_start", buf, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONDEFENSE); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //stop after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + gi.BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "defend_stop", buf, NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + bs->ltgtype = 0; + } + //if very close... go away for some time + VectorSubtract(goal->origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(70)) { + gi.BotResetAvoidReach(bs->ms); + bs->defendaway_time = FloatTime() + 3 + 3 * random(); + if (BotHasPersistantPowerupAndWeapon(bs)) { + bs->defendaway_range = 100; + } + else { + bs->defendaway_range = 350; + } + } + return qtrue; + } + //going to kill someone + if (bs->ltgtype == LTG_KILL && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "kill_start", buf, NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->teammessage_time = 0; + } + // + if (bs->lastkilledplayer == bs->teamgoal.entitynum) { + EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "kill_done", buf, NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->lastkilledplayer = -1; + bs->ltgtype = 0; + } + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //just roam around + return BotGetItemLongTermGoal(bs, tfl, goal); + } + //get an item + if (bs->ltgtype == LTG_GETITEM && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + gi.BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_start", buf, NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + gi.EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //stop after some time + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + // + if (gi.BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { + gi.BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + else if (BotReachedGoal(bs, goal)) { + gi.BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + return qtrue; + } + //if camping somewhere + if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + gi.EA_Action(bs->client, ACTION_AFFIRMATIVE); + } + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + // + if (bs->teamgoal_time < FloatTime()) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_stop", NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + } + bs->ltgtype = 0; + } + //if really near the camp spot + VectorSubtract(goal->origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(60)) + { + //if not arrived yet + if (!bs->arrive_time) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_INPOSITION); + } + bs->arrive_time = FloatTime(); + } + //look strategically around for enemies + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if (bs->attackcrouch_time < FloatTime() - 5) { + croucher = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + if (random() < bs->thinktime * croucher) { + bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; + } + } + //if the bot wants to crouch + if (bs->attackcrouch_time > FloatTime()) { + gi.EA_Crouch(bs->client); + } + //don't crouch when swimming + if (gi.AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; + //make sure the bot is not gonna drown + if (gi.PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { // ,bs->entitynum + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_stop", NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + // + if (bs->lastgoal_ltgtype == LTG_CAMPORDER) { + bs->lastgoal_ltgtype = 0; + } + } + bs->ltgtype = 0; + } + // + if (bs->camp_range > 0) { + //FIXME: move around a bit + } + // + gi.BotResetAvoidReach(bs->ms); + return qfalse; + } + return qtrue; + } + //patrolling along several waypoints + if (bs->ltgtype == LTG_PATROL && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + strcpy(buf, ""); + for (wp = bs->patrolpoints; wp; wp = wp->next) { + strcat(buf, wp->name); + if (wp->next) strcat(buf, " to "); + } + BotAI_BotInitialChat(bs, "patrol_start", buf, NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + gi.EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + // + if (!bs->curpatrolpoint) { + bs->ltgtype = 0; + return qfalse; + } + //if the bot touches the current goal + if (gi.BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) { + if (bs->patrolflags & PATROL_BACK) { + if (bs->curpatrolpoint->prev) { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + } + else { + bs->curpatrolpoint = bs->curpatrolpoint->next; + bs->patrolflags &= ~PATROL_BACK; + } + } + else { + if (bs->curpatrolpoint->next) { + bs->curpatrolpoint = bs->curpatrolpoint->next; + } + else { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + bs->patrolflags |= PATROL_BACK; + } + } + } + //stop after 5 minutes + if (bs->teamgoal_time < FloatTime()) { + BotAI_BotInitialChat(bs, "patrol_stop", NULL); + gi.BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + if (!bs->curpatrolpoint) { + bs->ltgtype = 0; + return qfalse; + } + memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t)); + return qtrue; + } +#ifdef CTF + if (gametype == GT_CTF) { + //if going for enemy flag + if (bs->ltgtype == LTG_GETFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "captureflag_start", NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); + bs->teammessage_time = 0; + } + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if touching the flag + if (gi.BotTouchingGoal(bs->origin, goal)) { + // make sure the bot knows the flag isn't there anymore + switch(BotTeam(bs)) { + case TEAM_RED: bs->blueflagstatus = 1; break; + case TEAM_BLUE: bs->redflagstatus = 1; break; + } + bs->ltgtype = 0; + } + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < FloatTime()) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if not carrying the flag anymore + if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0; + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) bs->ltgtype = 0; + //if touching the base flag the bot should loose the enemy flag + if (gi.BotTouchingGoal(bs->origin, goal)) { + //if the bot is still carrying the enemy flag then the + //base flag is gone, now just walk near the base a bit + if (BotCTFCarryingFlag(bs)) { + gi.BotResetAvoidReach(bs->ms); + bs->rushbaseaway_time = FloatTime() + 5 + 10 * random(); + //FIXME: add chat to tell the others to get back the flag + } + else { + bs->ltgtype = 0; + } + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //returning flag + if (bs->ltgtype == LTG_RETURNFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "returnflag_start", NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); + bs->teammessage_time = 0; + } + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if touching the flag + if (gi.BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0; + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (bs->ltgtype == LTG_GETFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "captureflag_start", NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); + bs->teammessage_time = 0; + } + memcpy(goal, &ctf_neutralflag, sizeof(bot_goal_t)); + //if touching the flag + if (gi.BotTouchingGoal(bs->origin, goal)) { + bs->ltgtype = 0; + } + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + return qtrue; + } + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if not carrying the flag anymore + if (!Bot1FCTFCarryingFlag(bs)) { + bs->ltgtype = 0; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (gi.BotTouchingGoal(bs->origin, goal)) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //attack the enemy base + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (gi.BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 2 + 5 * random(); + } + return qtrue; + } + //returning flag + if (bs->ltgtype == LTG_RETURNFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "returnflag_start", NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); + bs->teammessage_time = 0; + } + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //just roam around + return BotGetItemLongTermGoal(bs, tfl, goal); + } + } + else if (gametype == GT_OBELISK) { + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if the bot no longer wants to attack the obelisk + if (BotFeelingBad(bs) > 50) { + return BotGetItemLongTermGoal(bs, tfl, goal); + } + //if touching the obelisk + if (gi.BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 3 + 5 * random(); + } + // or very close to the obelisk + VectorSubtract(bs->origin, goal->origin, dir); + if (VectorLengthSquared(dir) < Square(60)) { + bs->attackaway_time = FloatTime() + 3 + 5 * random(); + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + //just move towards the obelisk + return qtrue; + } + } + else if (gametype == GT_HARVESTER) { + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: BotGoHarvest(bs); return qfalse; + } + //if not carrying any cubes + if (!BotHarvesterCarryingCubes(bs)) { + BotGoHarvest(bs); + return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + BotGoHarvest(bs); + return qfalse; + } + //if touching the base flag the bot should loose the enemy flag + if (gi.BotTouchingGoal(bs->origin, goal)) { + BotGoHarvest(bs); + return qfalse; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //attack the enemy base + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (gi.BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 2 + 5 * random(); + } + return qtrue; + } + //harvest cubes + if (bs->ltgtype == LTG_HARVEST && + bs->harvestaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "harvest_start", NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + memcpy(goal, &neutralobelisk, sizeof(bot_goal_t)); + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + // + if (gi.BotTouchingGoal(bs->origin, goal)) { + bs->harvestaway_time = FloatTime() + 4 + 3 * random(); + } + return qtrue; + } + } +#endif + //normal goal stuff + return BotGetItemLongTermGoal(bs, tfl, goal); +} + +/* +================== +BotLongTermGoal +================== +*/ +int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { + aas_entityinfo_t entinfo; + char teammate[MAX_MESSAGE_SIZE]; + float squaredist; + int areanum; + vec3_t dir; + + //FIXME: also have air long term goals? + // + //if the bot is leading someone and not retreating + if (bs->lead_time > 0 && !retreat) { + if (bs->lead_time < FloatTime()) { + BotAI_BotInitialChat(bs, "lead_stop", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + gi.BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->lead_time = 0; + return BotGetLongTermGoal(bs, tfl, retreat, goal); + } + // + if (bs->leadmessage_time < 0 && -bs->leadmessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + gi.BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //get entity information of the companion + BotEntityInfo(bs->lead_teammate, &entinfo); + // + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && gi.AAS_AreaReachability(areanum)) { + //update team goal + bs->lead_teamgoal.entitynum = bs->lead_teammate; + bs->lead_teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); + VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); + VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); + } + } + //if the team mate is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) { + bs->leadvisible_time = FloatTime(); + } + //if the team mate is not visible for 1 seconds + if (bs->leadvisible_time < FloatTime() - 1) { + bs->leadbackup_time = FloatTime() + 2; + } + //distance towards the team mate + VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir); + squaredist = VectorLengthSquared(dir); + //if backing up towards the team mate + if (bs->leadbackup_time > FloatTime()) { + if (bs->leadmessage_time < FloatTime() - 20) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + gi.BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //if very close to the team mate + if (squaredist < Square(100)) { + bs->leadbackup_time = 0; + } + //the bot should go back to the team mate + memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t)); + return qtrue; + } + else { + //if quite distant from the team mate + if (squaredist > Square(500)) { + if (bs->leadmessage_time < FloatTime() - 20) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + gi.BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //look at the team mate + VectorSubtract(entinfo.origin, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + //just wait for the team mate + return qfalse; + } + } + } + return BotGetLongTermGoal(bs, tfl, retreat, goal); +} + +/* +================== +AIEnter_Intermission +================== +*/ +void AIEnter_Intermission(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "intermission", "", s); + //reset the bot state + BotResetState(bs); + //check for end level chat + if (BotChat_EndLevel(bs)) { + gi.BotEnterChat(bs->cs, 0, bs->chatto); + } + bs->ainode = AINode_Intermission; +} + +/* +================== +AINode_Intermission +================== +*/ +int AINode_Intermission(bot_state_t *bs) { + //if the intermission ended + if (!BotIntermission(bs)) { + if (BotChat_StartLevel(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + } + else { + bs->stand_time = FloatTime() + 2; + } + AIEnter_Stand(bs, "intermission: chat"); + } + return qtrue; +} + +/* +================== +AIEnter_Observer +================== +*/ +void AIEnter_Observer(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "observer", "", s); + //reset the bot state + BotResetState(bs); + bs->ainode = AINode_Observer; +} + +/* +================== +AINode_Observer +================== +*/ +int AINode_Observer(bot_state_t *bs) { + //if the bot left observer mode + if (!BotIsObserver(bs)) { + AIEnter_Stand(bs, "observer: left observer"); + } + return qtrue; +} + +/* +================== +AIEnter_Stand +================== +*/ +void AIEnter_Stand(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "stand", "", s); + bs->standfindenemy_time = FloatTime() + 1; + bs->ainode = AINode_Stand; +} + +/* +================== +AINode_Stand +================== +*/ +int AINode_Stand(bot_state_t *bs) { + + //if the bot's health decreased + if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { + if (BotChat_HitTalking(bs)) { + bs->standfindenemy_time = FloatTime() + BotChatTime(bs) + 0.1; + bs->stand_time = FloatTime() + BotChatTime(bs) + 0.1; + } + } + if (bs->standfindenemy_time < FloatTime()) { + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "stand: found enemy"); + return qfalse; + } + bs->standfindenemy_time = FloatTime() + 1; + } + // put up chat icon + gi.EA_Talk(bs->client); + // when done standing + if (bs->stand_time < FloatTime()) { + gi.BotEnterChat(bs->cs, 0, bs->chatto); + AIEnter_Seek_LTG(bs, "stand: time out"); + return qfalse; + } + // + return qtrue; +} + +/* +================== +AIEnter_Respawn +================== +*/ +void AIEnter_Respawn(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "respawn", "", s); + //reset some states + gi.BotResetMoveState(bs->ms); + gi.BotResetGoalState(bs->gs); + gi.BotResetAvoidGoals(bs->gs); + gi.BotResetAvoidReach(bs->ms); + //if the bot wants to chat + if (BotChat_Death(bs)) { + bs->respawn_time = FloatTime() + BotChatTime(bs); + bs->respawnchat_time = FloatTime(); + } + else { + bs->respawn_time = FloatTime() + 1 + random(); + bs->respawnchat_time = 0; + } + //set respawn state + bs->respawn_wait = qfalse; + bs->ainode = AINode_Respawn; +} + +/* +================== +AINode_Respawn +================== +*/ +int AINode_Respawn(bot_state_t *bs) { + // if waiting for the actual respawn + if (bs->respawn_wait) { + if (!BotIsDead(bs)) { + AIEnter_Seek_LTG(bs, "respawn: respawned"); + } + else { + gi.EA_Respawn(bs->client); + } + } + else if (bs->respawn_time < FloatTime()) { + // wait until respawned + bs->respawn_wait = qtrue; + // elementary action respawn + gi.EA_Respawn(bs->client); + // + if (bs->respawnchat_time) { + gi.BotEnterChat(bs->cs, 0, bs->chatto); + bs->enemy = -1; + } + } + if (bs->respawnchat_time && bs->respawnchat_time < FloatTime() - 0.5) { + gi.EA_Talk(bs->client); + } + // + return qtrue; +} + +/* +================== +BotSelectActivateWeapon +================== +*/ +int BotSelectActivateWeapon(bot_state_t *bs) { // BOTLIB I'd say fix these -- optimum weapon choices + // + if (bs->inventory[INVENTORY_MACHINEGUN] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + return WEAPONINDEX_MACHINEGUN; + else if (bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + return WEAPONINDEX_SHOTGUN; + else if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + return WEAPONINDEX_PLASMAGUN; + else if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + return WEAPONINDEX_LIGHTNING; +#ifdef MISSIONPACK + else if (bs->inventory[INVENTORY_CHAINGUN] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + return WEAPONINDEX_CHAINGUN; + else if (bs->inventory[INVENTORY_NAILGUN] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + return WEAPONINDEX_NAILGUN; +#endif + else if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + return WEAPONINDEX_RAILGUN; + else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + return WEAPONINDEX_ROCKET_LAUNCHER; + else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + return WEAPONINDEX_BFG; + else if (bs->inventory[INVENTORY_PHASER]) + return WEAPONINDEX_PHASER; + else { + return -1; + } +} + +/* +================== +BotClearPath + + try to deactivate obstacles like proximity mines on the bot's path +================== +*/ +void BotClearPath(bot_state_t *bs, bot_moveresult_t *moveresult) { + int i, bestmine; + float dist, bestdist; + vec3_t target, dir; +// bsp_trace_t bsptrace; + entityState_t state; + + // if there is a dead body wearing kamikze nearby + if (bs->kamikazebody) { + // if the bot's view angles and weapon are not used for movement + if ( !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { + // + BotAI_GetEntityState(bs->kamikazebody, &state); + VectorCopy(state.pos.trBase, target); + target[2] += 8; + VectorSubtract(target, bs->eye, dir); + vectoangles(dir, moveresult->ideal_viewangles); + // + moveresult->weapon = BotSelectActivateWeapon(bs); + if (moveresult->weapon == -1) { + // FIXME: run away! + moveresult->weapon = 0; + } + if (moveresult->weapon) { + // + moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; + // if holding the right weapon +/* FIXME -- do we have mines? + if (bs->cur_ps.weapon == moveresult->weapon) { + // if the bot is pretty close with it's aim + if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); + // if the mine is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { + // shoot at the mine + gi.EA_Attack(bs->client); + } + } + } + */ + } + } + } + if (moveresult->flags & MOVERESULT_BLOCKEDBYAVOIDSPOT) { + bs->blockedbyavoidspot_time = FloatTime() + 5; + } + // if blocked by an avoid spot and the view angles and weapon are used for movement + if (bs->blockedbyavoidspot_time > FloatTime() && + !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { + bestdist = 300; + bestmine = -1; + for (i = 0; i < bs->numproxmines; i++) { + BotAI_GetEntityState(bs->proxmines[i], &state); + VectorSubtract(state.pos.trBase, bs->origin, dir); + dist = VectorLength(dir); + if (dist < bestdist) { + bestdist = dist; + bestmine = i; + } + } + if (bestmine != -1) { + // + // state->generic1 == TEAM_RED || state->generic1 == TEAM_BLUE + // + // deactivate prox mines in the bot's path by shooting + // rockets or plasma cells etc. at them + BotAI_GetEntityState(bs->proxmines[bestmine], &state); + VectorCopy(state.pos.trBase, target); + target[2] += 2; + VectorSubtract(target, bs->eye, dir); + vectoangles(dir, moveresult->ideal_viewangles); + // if the bot has a weapon that does splash damage + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + moveresult->weapon = WEAPONINDEX_PLASMAGUN; + else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + moveresult->weapon = WEAPONINDEX_ROCKET_LAUNCHER; + else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_PLASMA] > 0) + moveresult->weapon = WEAPONINDEX_BFG; + else { + moveresult->weapon = 0; + } + if (moveresult->weapon) { + // + moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; + // if holding the right weapon +/* + if (bs->cur_ps.weapon == moveresult->weapon) { + // if the bot is pretty close with it's aim + if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); + // if the mine is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { + // shoot at the mine + gi.EA_Attack(bs->client); + } + } + } + */ + } + } + } +} + +/* +================== +AIEnter_Seek_ActivateEntity +================== +*/ +void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "activate entity", "", s); + bs->ainode = AINode_Seek_ActivateEntity; +} + +/* +================== +AINode_Seek_Activate_Entity +================== +*/ +int AINode_Seek_ActivateEntity(bot_state_t *bs) { + bot_goal_t *goal; + vec3_t target, dir;//, ideal_viewangles; + bot_moveresult_t moveresult; + int targetvisible; + bsp_trace_t bsptrace; + aas_entityinfo_t entinfo; + + if (BotIsObserver(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Observer(bs, "active entity: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Intermission(bs, "activate entity: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Respawn(bs, "activate entity: bot dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + // if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // map specific code + BotMapScripts(bs); + // no enemy + bs->enemy = -1; + // if the bot has no activate goal + if (!bs->activatestack) { + BotClearActivateGoalStack(bs); + AIEnter_Seek_NBG(bs, "activate entity: no goal"); + return qfalse; + } + // + goal = &bs->activatestack->goal; + // initialize target being visible to false + targetvisible = qfalse; + // if the bot has to shoot at a target to activate something + if (bs->activatestack->shoot) { + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->activatestack->target, bs->entitynum, MASK_SHOT); + // if the shootable entity is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == goal->entitynum) { + targetvisible = qtrue; + // if holding the right weapon +/* + if (bs->cur_ps.weapon == bs->activatestack->weapon) { + VectorSubtract(bs->activatestack->target, bs->eye, dir); + vectoangles(dir, ideal_viewangles); + // if the bot is pretty close with it's aim + if (InFieldOfVision(bs->viewangles, 20, ideal_viewangles)) { + gi.EA_Attack(bs->client); + } + } + */ + } + } + // if the shoot target is visible + if (targetvisible) { + // get the entity info of the entity the bot is shooting at + BotEntityInfo(goal->entitynum, &entinfo); + // if the entity the bot shoots at moved + if (!VectorCompare(bs->activatestack->origin, entinfo.origin)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "hit shootable button or trigger\n"); +#endif //DEBUG + bs->activatestack->time = 0; + } + // if the activate goal has been activated or the bot takes too long + if (bs->activatestack->time < FloatTime()) { + BotPopFromActivateGoalStack(bs); + // if there are more activate goals on the stack + if (bs->activatestack) { + bs->activatestack->time = FloatTime() + 10; + return qfalse; + } + AIEnter_Seek_NBG(bs, "activate entity: time out"); + return qfalse; + } + memset(&moveresult, 0, sizeof(bot_moveresult_t)); + } + else { + // if the bot has no goal + if (!goal) { + bs->activatestack->time = 0; + } + // if the bot does not have a shoot goal + else if (!bs->activatestack->shoot) { + //if the bot touches the current goal + if (gi.BotTouchingGoal(bs->origin, goal)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "touched button or trigger\n"); +#endif //DEBUG + bs->activatestack->time = 0; + } + } + // if the activate goal has been activated or the bot takes too long + if (bs->activatestack->time < FloatTime()) { + BotPopFromActivateGoalStack(bs); + // if there are more activate goals on the stack + if (bs->activatestack) { + bs->activatestack->time = FloatTime() + 10; + return qfalse; + } + AIEnter_Seek_NBG(bs, "activate entity: activated"); + return qfalse; + } + //predict obstacles + if (BotAIPredictObstacles(bs, goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + gi.BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + gi.BotResetAvoidReach(bs->ms); + // + bs->activatestack->time = 0; + } + //check if the bot is blocked + BotAIBlocked(bs, &moveresult, qtrue); + } + // + BotClearPath(bs, &moveresult); + // if the bot has to shoot to activate + if (bs->activatestack->shoot) { + // if the view angles aren't yet used for the movement + if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEW)) { + VectorSubtract(bs->activatestack->target, bs->eye, dir); + vectoangles(dir, moveresult.ideal_viewangles); + moveresult.flags |= MOVERESULT_MOVEMENTVIEW; + } + // if there's no weapon yet used for the movement + if (!(moveresult.flags & MOVERESULT_MOVEMENTWEAPON)) { + moveresult.flags |= MOVERESULT_MOVEMENTWEAPON; + // + bs->activatestack->weapon = BotSelectActivateWeapon(bs); + if (bs->activatestack->weapon == -1) { + //FIXME: find a decent weapon first + bs->activatestack->weapon = 0; + } + moveresult.weapon = bs->activatestack->weapon; + } + } + // if the ideal view angles are set for movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + // if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (gi.BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + // if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) + bs->weaponnum = moveresult.weapon; + // if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG(bs, "activate entity: found enemy"); + } + else { + gi.BotResetLastAvoidReach(bs->ms); + //empty the goal stack + gi.BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "activate entity: found enemy"); + } + BotClearActivateGoalStack(bs); + } + return qtrue; +} + +/* +================== +AIEnter_Seek_NBG +================== +*/ +void AIEnter_Seek_NBG(bot_state_t *bs, char *s) { + bot_goal_t goal; + char buf[144]; + + if (gi.BotGetTopGoal(bs->gs, &goal)) { + gi.BotGoalName(goal.number, buf, 144); + BotRecordNodeSwitch(bs, "seek NBG", buf, s); + } + else { + BotRecordNodeSwitch(bs, "seek NBG", "no goal", s); + } + bs->ainode = AINode_Seek_NBG; +} + +/* +================== +AINode_Seek_NBG +================== +*/ +int AINode_Seek_NBG(bot_state_t *bs) { + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "seek nbg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "seek nbg: intermision"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "seek nbg: bot dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //no enemy + bs->enemy = -1; + //if the bot has no goal + if (!gi.BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0; + //if the bot touches the current goal + else if (BotReachedGoal(bs, &goal)) { + BotChooseWeapon(bs); + bs->nbg_time = 0; + } + // + if (bs->nbg_time < FloatTime()) { + //pop the current goal from the stack + gi.BotPopGoal(bs->gs); + //check for new nearby items right away + //NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches + bs->check_time = FloatTime() + 0.05; + //go back to seek ltg + AIEnter_Seek_LTG(bs, "seek nbg: time out"); + return qfalse; + } + //predict obstacles + if (BotAIPredictObstacles(bs, &goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + gi.BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + gi.BotResetAvoidReach(bs->ms); + bs->nbg_time = 0; + } + //check if the bot is blocked + BotAIBlocked(bs, &moveresult, qtrue); + // + BotClearPath(bs, &moveresult); + //if the viewangles are used for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + //if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (!gi.BotGetSecondGoal(bs->gs, &goal)) + gi.BotGetTopGoal(bs->gs, &goal); + if (gi.BotMovementViewTarget(bs->ms, &goal, bs->tfl, 900, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + //FIXME: look at cluster portals? + else + vectoangles(moveresult.movedir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG(bs, "seek nbg: found enemy"); + } + else { + gi.BotResetLastAvoidReach(bs->ms); + //empty the goal stack + gi.BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "seek nbg: found enemy"); + } + } + return qtrue; +} + +/* +================== +AIEnter_Seek_LTG +================== +*/ +void AIEnter_Seek_LTG(bot_state_t *bs, char *s) { + bot_goal_t goal; + char buf[144]; + + if (gi.BotGetTopGoal(bs->gs, &goal)) { + gi.BotGoalName(goal.number, buf, 144); + BotRecordNodeSwitch(bs, "seek LTG", buf, s); + } + else { + BotRecordNodeSwitch(bs, "seek LTG", "no goal", s); + } + bs->ainode = AINode_Seek_LTG; +} + +/* +================== +AINode_Seek_LTG +================== +*/ +int AINode_Seek_LTG(bot_state_t *bs) +{ + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + int range; + //char buf[128]; + //bot_goal_t tmpgoal; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "seek ltg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "seek ltg: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "seek ltg: bot dead"); + return qfalse; + } + // + if (BotChat_Random(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "seek ltg: random chat"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //no enemy + bs->enemy = -1; + // + if (bs->killedenemy_time > FloatTime() - 2) { + if (random() < bs->thinktime * 1) { + gi.EA_Gesture(bs->client); + } + } + //if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_Retreat(bs, "seek ltg: found enemy"); + return qfalse; + } + else { + gi.BotResetLastAvoidReach(bs->ms); + //empty the goal stack + gi.BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "seek ltg: found enemy"); + return qfalse; + } + } + // + BotTeamGoals(bs, qfalse); + //get the current long term goal + if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) { + return qtrue; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 0.5; + //check if the bot wants to camp + BotWantsToCamp(bs); + // + if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400; + else range = 150; + // +#ifdef CTF + if (gametype == GT_CTF) { + //if carrying a flag the bot shouldn't be distracted too much + if (BotCTFCarryingFlag(bs)) + range = 50; + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) + range = 50; + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) + range = 80; + } +#endif + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + gi.BotResetLastAvoidReach(bs->ms); + //get the goal at the top of the stack + //gi.BotGetTopGoal(bs->gs, &tmpgoal); + //gi.BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + 4 + range * 0.01; + AIEnter_Seek_NBG(bs, "ltg seek: nbg"); + return qfalse; + } + } + //predict obstacles + if (BotAIPredictObstacles(bs, &goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + gi.BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + gi.BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qtrue); + // + BotClearPath(bs, &moveresult); + //if the viewangles are used for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + //if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (gi.BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { // BOTLIB nasty hardcoded #, this is a fn of turnspeed, maxspeed, and bbox size + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + //FIXME: look at cluster portals? + else if (VectorLengthSquared(moveresult.movedir)) { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + else if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + // + return qtrue; +} + +/* +================== +AIEnter_Battle_Fight +================== +*/ +void AIEnter_Battle_Fight(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle fight", "", s); + gi.BotResetLastAvoidReach(bs->ms); + bs->ainode = AINode_Battle_Fight; +} + +/* +================== +AIEnter_Battle_Fight +================== +*/ +void AIEnter_Battle_SuicidalFight(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle fight", "", s); + gi.BotResetLastAvoidReach(bs->ms); + bs->ainode = AINode_Battle_Fight; + bs->flags |= BFL_FIGHTSUICIDAL; +} + +/* +================== +AINode_Battle_Fight +================== +*/ +int AINode_Battle_Fight(bot_state_t *bs) { + int areanum; + vec3_t target; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle fight: observer"); + return qfalse; + } + + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle fight: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle fight: bot dead"); + return qfalse; + } + //if there is another better enemy + if (BotFindEnemy(bs, bs->enemy)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); +#endif + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle fight: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + //if the enemy is dead + if (bs->enemydeath_time) { + if (bs->enemydeath_time < FloatTime() - 1.0) { + bs->enemydeath_time = 0; + if (bs->enemysuicide) { + BotChat_EnemySuicide(bs); + } + if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "battle fight: enemy dead"); + } + else { + bs->ltg_time = 0; + AIEnter_Seek_LTG(bs, "battle fight: enemy dead"); + } + return qfalse; + } + } + else { + if (EntityIsDead(&entinfo)) { + bs->enemydeath_time = FloatTime(); + } + } + //if the enemy is invisible and not shooting the bot looses track easily + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + if (random() < 0.2) { + AIEnter_Seek_LTG(bs, "battle fight: invisible"); + return qfalse; + } + } + // + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= maxclients->integer) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && gi.AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //if the bot's health decreased +// if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { +// if (BotChat_HitNoDeath(bs)) { +// bs->stand_time = FloatTime();// + BotChatTime(bs); +// AIEnter_Stand(bs, "battle fight: chat health decreased"); +// return qfalse; +// } +// } + //if the bot hit someone +/* FIXME + if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount) { + if (BotChat_HitNoKill(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "battle fight: chat hit someone"); + return qfalse; + } + } + */ + //if the enemy is not visible + if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + if (BotWantsToChase(bs)) { + AIEnter_Battle_Chase(bs, "battle fight: enemy out of sight"); + return qfalse; + } + else { + AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight"); + return qfalse; + } + } + //use holdable items + BotBattleUseItems(bs); + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //choose the best weapon to fight with + BotChooseWeapon(bs); + //do attack movements + moveresult = BotAttackMove(bs, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + gi.BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //aim at the enemy + BotAimAtEnemy(bs); + //attack the enemy if possible + BotCheckAttack(bs); + //if the bot wants to retreat + if (!(bs->flags & BFL_FIGHTSUICIDAL)) { + if (BotWantsToRetreat(bs)) { + AIEnter_Battle_Retreat(bs, "battle fight: wants to retreat"); + return qtrue; + } + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Chase +================== +*/ +void AIEnter_Battle_Chase(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle chase", "", s); + bs->chase_time = FloatTime(); + bs->ainode = AINode_Battle_Chase; +} + +/* +================== +AINode_Battle_Chase +================== +*/ +int AINode_Battle_Chase(bot_state_t *bs) +{ + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + float range; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle chase: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle chase: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle chase: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle chase: no enemy"); + return qfalse; + } + //if the enemy is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + AIEnter_Battle_Fight(bs, "battle chase"); + return qfalse; + } + //if there is another enemy + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "battle chase: better enemy"); + return qfalse; + } + //there is no last enemy area + if (!bs->lastenemyareanum) { + AIEnter_Seek_LTG(bs, "battle chase: no enemy area"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //create the chase goal + goal.entitynum = bs->enemy; + goal.areanum = bs->lastenemyareanum; + VectorCopy(bs->lastenemyorigin, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + //if the last seen enemy spot is reached the enemy could not be found + if (gi.BotTouchingGoal(bs->origin, &goal)) bs->chase_time = 0; + //if there's no chase time left + if (!bs->chase_time || bs->chase_time < FloatTime() - 10) { + AIEnter_Seek_LTG(bs, "battle chase: time out"); + return qfalse; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 1; + range = 150; + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + //the bot gets 5 seconds to pick up the nearby goal item + bs->nbg_time = FloatTime() + 0.1 * range + 1; + gi.BotResetLastAvoidReach(bs->ms); + AIEnter_Battle_NBG(bs, "battle chase: nbg"); + return qfalse; + } + } + // + BotUpdateBattleInventory(bs, bs->enemy); + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + gi.BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + gi.BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + // + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (bs->chase_time > FloatTime() - 2) { + BotAimAtEnemy(bs); + } + else { + if (gi.BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //if the bot is in the area the enemy was last seen in + if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0; + //if the bot wants to retreat (the bot could have been damage during the chase) + if (BotWantsToRetreat(bs)) { + AIEnter_Battle_Retreat(bs, "battle chase: wants to retreat"); + return qtrue; + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Retreat +================== +*/ +void AIEnter_Battle_Retreat(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle retreat", "", s); + bs->ainode = AINode_Battle_Retreat; +} + +/* +================== +AINode_Battle_Retreat +================== +*/ +int AINode_Battle_Retreat(bot_state_t *bs) { + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + vec3_t target, dir; + float attack_skill, range; + int areanum; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle retreat: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle retreat: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle retreat: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle retreat: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsDead(&entinfo)) { + AIEnter_Seek_LTG(bs, "battle retreat: enemy dead"); + return qfalse; + } + //if there is another better enemy + if (BotFindEnemy(bs, bs->enemy)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); +#endif + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + //map specific code + BotMapScripts(bs); + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //if the bot doesn't want to retreat anymore... probably picked up some nice items + if (BotWantsToChase(bs)) { + //empty the goal stack, when chasing, only the enemy is the goal + gi.BotEmptyGoalStack(bs->gs); + //go chase the enemy + AIEnter_Battle_Chase(bs, "battle retreat: wants to chase"); + return qfalse; + } + //update the last time the enemy was visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + bs->enemyvisible_time = FloatTime(); + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= maxclients->integer) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && gi.AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + } + //if the enemy is NOT visible for 4 seconds + if (bs->enemyvisible_time < FloatTime() - 3.0f) { // BOTLIB : this kinda fixes bot wallstuck, but not really + AIEnter_Seek_LTG(bs, "battle retreat: lost enemy"); + return qfalse; + } + //else if the enemy is NOT visible + else if (bs->enemyvisible_time < FloatTime()) { + //if there is another enemy + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "battle retreat: another enemy"); + return qfalse; + } + } + // + BotTeamGoals(bs, qtrue); + //use holdable items + BotBattleUseItems(bs); + //get the current long term goal while retreating + if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) { + AIEnter_Battle_SuicidalFight(bs, "battle retreat: no way out"); + return qfalse; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 1; + range = 150; +#ifdef CTF + if (gametype == GT_CTF) { + //if carrying a flag the bot shouldn't be distracted too much + if (BotCTFCarryingFlag(bs)) + range = 50; + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) + range = 50; + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) + range = 80; + } +#endif + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + gi.BotResetLastAvoidReach(bs->ms); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + range / 100 + 1; + AIEnter_Battle_NBG(bs, "battle retreat: nbg"); + return qfalse; + } + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + gi.BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + gi.BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //choose the best weapon to fight with + BotChooseWeapon(bs); + //if the view is fixed for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) + && !(bs->flags & BFL_IDEALVIEWSET) ) { + attack_skill = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + //if the bot is skilled anough + if (attack_skill > 0.3) { + BotAimAtEnemy(bs); + } + else { + if (gi.BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //attack the enemy if possible + BotCheckAttack(bs); + // + return qtrue; +} + +/* +================== +AIEnter_Battle_NBG +================== +*/ +void AIEnter_Battle_NBG(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle NBG", "", s); + bs->ainode = AINode_Battle_NBG; +} + +/* +================== +AINode_Battle_NBG +================== +*/ +int AINode_Battle_NBG(bot_state_t *bs) { + int areanum; + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + float attack_skill; + vec3_t target, dir; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle nbg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle nbg: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle nbg: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_NBG(bs, "battle nbg: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsDead(&entinfo)) { + AIEnter_Seek_NBG(bs, "battle nbg: enemy dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //update the last time the enemy was visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + bs->enemyvisible_time = FloatTime(); + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= maxclients->integer) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && gi.AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + } + //if the bot has no goal or touches the current goal + if (!gi.BotGetTopGoal(bs->gs, &goal)) { + bs->nbg_time = 0; + } + else if (BotReachedGoal(bs, &goal)) { + bs->nbg_time = 0; + } + // + if (bs->nbg_time < FloatTime()) { + //pop the current goal from the stack + gi.BotPopGoal(bs->gs); + //if the bot still has a goal + if (gi.BotGetTopGoal(bs->gs, &goal)) + AIEnter_Battle_Retreat(bs, "battle nbg: time out"); + else + AIEnter_Battle_Fight(bs, "battle nbg: time out"); + // + return qfalse; + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + gi.BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + gi.BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->nbg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //choose the best weapon to fight with + BotChooseWeapon(bs); + //if the view is fixed for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) + && !(bs->flags & BFL_IDEALVIEWSET)) { + attack_skill = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + //if the bot is skilled anough and the enemy is visible + if ( (attack_skill > 0.3) && BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy) ) + BotAimAtEnemy(bs); +// } + else { + if (gi.BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //attack the enemy if possible + BotCheckAttack(bs); + // + return qtrue; +} + diff --git a/dlls/game/ai_dmnet.h b/dlls/game/ai_dmnet.h new file mode 100644 index 0000000..6fd29d1 --- /dev/null +++ b/dlls/game/ai_dmnet.h @@ -0,0 +1,45 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_dmnet.h + * + * desc: Quake3 bot AI + * + * $Archive: /Code/DLLs/game/ai_dmnet.h $ + * $Author: Jwaters $ + * $Revision: 1 $ + * $Modtime: 7/25/02 11:48a $ + * $Date: 7/30/02 1:10p $ + * + *****************************************************************************/ + +#define MAX_NODESWITCHES 50 + +void AIEnter_Intermission(bot_state_t *bs, char *s); +void AIEnter_Observer(bot_state_t *bs, char *s); +void AIEnter_Respawn(bot_state_t *bs, char *s); +void AIEnter_Stand(bot_state_t *bs, char *s); +void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s); +void AIEnter_Seek_NBG(bot_state_t *bs, char *s); +void AIEnter_Seek_LTG(bot_state_t *bs, char *s); +void AIEnter_Seek_Camp(bot_state_t *bs, char *s); +void AIEnter_Battle_Fight(bot_state_t *bs, char *s); +void AIEnter_Battle_Chase(bot_state_t *bs, char *s); +void AIEnter_Battle_Retreat(bot_state_t *bs, char *s); +void AIEnter_Battle_NBG(bot_state_t *bs, char *s); +int AINode_Intermission(bot_state_t *bs); +int AINode_Observer(bot_state_t *bs); +int AINode_Respawn(bot_state_t *bs); +int AINode_Stand(bot_state_t *bs); +int AINode_Seek_ActivateEntity(bot_state_t *bs); +int AINode_Seek_NBG(bot_state_t *bs); +int AINode_Seek_LTG(bot_state_t *bs); +int AINode_Battle_Fight(bot_state_t *bs); +int AINode_Battle_Chase(bot_state_t *bs); +int AINode_Battle_Retreat(bot_state_t *bs); +int AINode_Battle_NBG(bot_state_t *bs); + +void BotResetNodeSwitches(void); +void BotDumpNodeSwitches(bot_state_t *bs); + diff --git a/dlls/game/ai_dmq3.cpp b/dlls/game/ai_dmq3.cpp new file mode 100644 index 0000000..fa420d9 --- /dev/null +++ b/dlls/game/ai_dmq3.cpp @@ -0,0 +1,5673 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_dmq3.c + * + * desc: Quake3 bot AI + * + * $Archive: /EF2/Code/DLLs/game/ai_dmq3.cpp $ + * $Author: Singlis $ + * $Revision: 35 $ + * $Modtime: 9/24/03 3:01p $ + * $Date: 9/26/03 2:35p $ + * + *****************************************************************************/ + + +#include "player.h" + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "botmenudef.h" // sos001205 - for q3_ui also + +#include "mp_manager.hpp" + +// from aasfile.h +#define AREACONTENTS_MOVER 1024 +#define AREACONTENTS_MODELNUMSHIFT 24 +#define AREACONTENTS_MAXMODELNUM 0xFF +#define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) + +#define IDEAL_ATTACKDIST 140 + +#define MAX_WAYPOINTS 128 +// +bot_waypoint_t botai_waypoints[MAX_WAYPOINTS]; +bot_waypoint_t *botai_freewaypoints; + +//NOTE: not using a cvars which can be updated because the game should be reloaded anyway +int gametype; //game type +//int maxclients; //maximum number of clients + +vmCvar_t bot_grapple; +vmCvar_t bot_rocketjump; +vmCvar_t bot_fastchat; +vmCvar_t bot_nochat; +vmCvar_t bot_testrchat; +vmCvar_t bot_challenge; +vmCvar_t bot_predictobstacles; +vmCvar_t g_spSkill; + +extern vmCvar_t bot_developer; + +vec3_t lastteleport_origin; //last teleport event origin +float lastteleport_time; //last teleport event time +int max_bspmodelindex; //maximum BSP model index + +//CTF flag goals +bot_goal_t ctf_redflag; +bot_goal_t ctf_blueflag; +#ifdef MISSIONPACK +bot_goal_t ctf_neutralflag; +bot_goal_t redobelisk; +bot_goal_t blueobelisk; +bot_goal_t neutralobelisk; +#endif + +#define MAX_ALTROUTEGOALS 32 + +int altroutegoals_setup; +aas_altroutegoal_t red_altroutegoals[MAX_ALTROUTEGOALS]; +int red_numaltroutegoals; +aas_altroutegoal_t blue_altroutegoals[MAX_ALTROUTEGOALS]; +int blue_numaltroutegoals; + +void BotSetEntityNumForGoal(bot_goal_t *goal, char *classname); + + +/* +================== +BotSetUserInfo +================== +*/ +void BotSetUserInfo(bot_state_t *bs, char *key, char *value) { + char userinfo[MAX_INFO_STRING]; + + gi.getUserinfo(bs->client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, key, value); + gi.setUserinfo(bs->client, userinfo); + G_ClientUserinfoChanged( &g_entities[bs->client] , userinfo ); +} + +/* +================== +BotCTFCarryingFlag +================== +*/ +int BotCTFCarryingFlag(bot_state_t *bs) { + if (gametype != GT_CTF) return CTF_FLAG_NONE; + + if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED; + else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE; + return CTF_FLAG_NONE; +} + +/* +================== +BotTeam +================== +*/ +int BotTeam(bot_state_t *bs) { +// char info[1024]; + + if (bs->client < 0 || bs->client >= maxclients->integer) { + //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n"); + return qfalse; + } + gentity_t *bot = &g_entities[bs->entitynum]; + if (multiplayerManager.getPlayersTeam((Player *)bot->entity )->getName() == "Blue") + return TEAM_BLUE; + if (multiplayerManager.getPlayersTeam((Player *)bot->entity )->getName() == "Red") + return TEAM_RED; + return TEAM_FREE; +} + +/* +================== +BotOppositeTeam +================== +*/ +int BotOppositeTeam(bot_state_t *bs) { + switch(BotTeam(bs)) { + case TEAM_RED: return TEAM_BLUE; + case TEAM_BLUE: return TEAM_RED; + default: return TEAM_FREE; + } +} + +/* +================== +BotEnemyFlag +================== +*/ +bot_goal_t *BotEnemyFlag(bot_state_t *bs) { + if (BotTeam(bs) == TEAM_RED) { + return &ctf_blueflag; + } + else { + return &ctf_redflag; + } +} + +/* +================== +BotTeamFlag +================== +*/ +bot_goal_t *BotTeamFlag(bot_state_t *bs) { + if (BotTeam(bs) == TEAM_RED) { + return &ctf_redflag; + } + else { + return &ctf_blueflag; + } +} + + +/* +================== +EntityIsDead +================== +*/ +qboolean EntityIsDead(aas_entityinfo_t *entinfo) { + playerState_t ps; + + if (entinfo->number >= 0 && entinfo->number < maxclients->integer) { + //retrieve the current client state + if (BotAI_GetClientState( entinfo->number, &ps )) { + if (ps.stats[ STAT_HEALTH ] <= 0) + return qtrue; + if (ps.pm_type != PM_NORMAL) return qtrue; + } + // BOTTODO add something here for obelisk health -- since obelisks *always* exist, do we need to worry? + } + return qfalse; +} + +/* +================== +EntityCarriesFlag +================== +*/ +qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) { + // BOTLIB add proper flag checks + // JPW this version only works for players, which may cause problems. not sure when a non-player entity might carry a flag... + + Player *player = (Player *)g_entities[entinfo->number].entity; + + if ( multiplayerManager.doesPlayerHaveItem( player, "ctfflag-red" ) || + multiplayerManager.doesPlayerHaveItem( player, "ctfflag-blue" ) || + multiplayerManager.doesPlayerHaveItem( player, "ctfflag-one" ) ) + return qtrue; + + return qfalse; +} + +/* +================== +EntityIsInvisible +================== +*/ +qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) { + + // the flag is always visible + if (EntityCarriesFlag(entinfo)) + return qfalse; + + if (g_entities[entinfo->number].entity && g_entities[entinfo->number].entity->_affectingViewModes & gi.GetViewModeMask( "forcevisible" ) ) + return qtrue; + return qfalse; +} + +/* +================== +EntityIsShooting +================== +*/ +qboolean EntityIsShooting(aas_entityinfo_t *entinfo) { + Player *plyr = (Player *)g_entities[entinfo->number].entity; + + if (plyr) + if (plyr->GetLastUcmd().buttons & (BUTTON_ATTACKLEFT | BUTTON_ATTACKRIGHT)) + return qtrue; + + return qfalse; +} + +/* +================== +EntityIsChatting +================== +*/ +qboolean EntityIsChatting(aas_entityinfo_t *entinfo) { +// BOTTODO +// if (entinfo->flags & EF_TALK) { +// return qtrue; +// } + + return qfalse; +} + +/* +================== +EntityHasQuad +================== +*/ +qboolean EntityHasQuad(aas_entityinfo_t *entinfo) { +/* BOTTODO + if (entinfo->powerups & (1 << PW_QUAD)) { + return qtrue; + } +*/ + return qfalse; +} + +#ifdef MISSIONPACK +/* +================== +EntityHasKamikze +================== +*/ +qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo) { +/* FIXME + if (entinfo->flags & EF_KAMIKAZE) { + return qtrue; + } +*/ + return qfalse; +} + +/* +================== +EntityCarriesCubes +================== +*/ +qboolean EntityCarriesCubes(aas_entityinfo_t *entinfo) { + entityState_t state; + + if (gametype != GT_HARVESTER) + return qfalse; + //FIXME: get this info from the aas_entityinfo_t ? + BotAI_GetEntityState(entinfo->number, &state); +// if (state.generic1 > 0) +// return qtrue; + return qfalse; +} + +/* +================== +Bot1FCTFCarryingFlag +================== +*/ +int Bot1FCTFCarryingFlag(bot_state_t *bs) { + if (gametype != GT_1FCTF) return qfalse; + + if (bs->inventory[INVENTORY_NEUTRALFLAG] > 0) return qtrue; + return qfalse; +} + +/* +================== +BotHarvesterCarryingCubes +================== +*/ +int BotHarvesterCarryingCubes(bot_state_t *bs) { + if (gametype != GT_HARVESTER) return qfalse; + + if (bs->inventory[INVENTORY_REDCUBE] > 0) return qtrue; + if (bs->inventory[INVENTORY_BLUECUBE] > 0) return qtrue; + return qfalse; +} +#endif + +/* +================== +BotRememberLastOrderedTask +================== +*/ +void BotRememberLastOrderedTask(bot_state_t *bs) { + if (!bs->ordered) { + return; + } + bs->lastgoal_decisionmaker = bs->decisionmaker; + bs->lastgoal_ltgtype = bs->ltgtype; + memcpy(&bs->lastgoal_teamgoal, &bs->teamgoal, sizeof(bot_goal_t)); + bs->lastgoal_teammate = bs->teammate; +} + +/* +================== +BotSetTeamStatus +================== +*/ +void BotSetTeamStatus(bot_state_t *bs) { +#ifdef MISSIONPACK + int teamtask; + aas_entityinfo_t entinfo; + + teamtask = TEAMTASK_PATROL; + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + break; + case LTG_TEAMACCOMPANY: + BotEntityInfo(bs->teammate, &entinfo); + if ( ( (gametype == GT_CTF || gametype == GT_1FCTF) && EntityCarriesFlag(&entinfo)) + || ( gametype == GT_HARVESTER && EntityCarriesCubes(&entinfo)) ) { + teamtask = TEAMTASK_ESCORT; + } + else { + teamtask = TEAMTASK_FOLLOW; + } + break; + case LTG_DEFENDKEYAREA: + teamtask = TEAMTASK_DEFENSE; + break; + case LTG_GETFLAG: + teamtask = TEAMTASK_OFFENSE; + break; + case LTG_RUSHBASE: + teamtask = TEAMTASK_DEFENSE; + break; + case LTG_RETURNFLAG: + teamtask = TEAMTASK_RETRIEVE; + break; + case LTG_CAMP: + case LTG_CAMPORDER: + teamtask = TEAMTASK_CAMP; + break; + case LTG_PATROL: + teamtask = TEAMTASK_PATROL; + break; + case LTG_GETITEM: + teamtask = TEAMTASK_PATROL; + break; + case LTG_KILL: + teamtask = TEAMTASK_PATROL; + break; + case LTG_HARVEST: + teamtask = TEAMTASK_OFFENSE; + break; + case LTG_ATTACKENEMYBASE: + teamtask = TEAMTASK_OFFENSE; + break; + default: + teamtask = TEAMTASK_PATROL; + break; + } + BotSetUserInfo(bs, "teamtask", (char *)va("%d", teamtask)); +#endif +} + +/* +================== +BotSetLastOrderedTask +================== +*/ +int BotSetLastOrderedTask(bot_state_t *bs) { + + if (gametype == GT_CTF) { + // don't go back to returning the flag if it's at the base + if ( bs->lastgoal_ltgtype == LTG_RETURNFLAG ) { + if ( BotTeam(bs) == TEAM_RED ) { + if ( bs->redflagstatus == 0 ) { + bs->lastgoal_ltgtype = 0; + } + } + else { + if ( bs->blueflagstatus == 0 ) { + bs->lastgoal_ltgtype = 0; + } + } + } + } + + if ( bs->lastgoal_ltgtype ) { + bs->decisionmaker = bs->lastgoal_decisionmaker; + bs->ordered = qtrue; + bs->ltgtype = bs->lastgoal_ltgtype; + memcpy(&bs->teamgoal, &bs->lastgoal_teamgoal, sizeof(bot_goal_t)); + bs->teammate = bs->lastgoal_teammate; + bs->teamgoal_time = FloatTime() + 300; + BotSetTeamStatus(bs); + // + if ( gametype == GT_CTF ) { + if ( bs->ltgtype == LTG_GETFLAG ) { + bot_goal_t *tb, *eb; + int tt, et; + + tb = BotTeamFlag(bs); + eb = BotEnemyFlag(bs); + tt = gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, tb->areanum, TFL_DEFAULT); + et = gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, eb->areanum, TFL_DEFAULT); + // if the travel time towards the enemy base is larger than towards our base + if (et > tt) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + } + } + return qtrue; + } + return qfalse; +} + +/* +================== +BotRefuseOrder +================== +*/ +void BotRefuseOrder(bot_state_t *bs) { + if (!bs->ordered) + return; + // if the bot was ordered to do something + if ( bs->order_time && bs->order_time > FloatTime() - 10 ) { + gi.EA_Action(bs->client, ACTION_NEGATIVE); + BotVoiceChat(bs, bs->decisionmaker, VOICECHAT_NO); + bs->order_time = 0; + } +} + +/* +================== +BotCTFSeekGoals +================== +*/ +void BotCTFSeekGoals(bot_state_t *bs) { + float rnd, l1, l2; + int flagstatus, c; + vec3_t dir; + aas_entityinfo_t entinfo; + + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + switch(BotTeam(bs)) { + case TEAM_RED: VectorSubtract(bs->origin, ctf_blueflag.origin, dir); break; + case TEAM_BLUE: VectorSubtract(bs->origin, ctf_redflag.origin, dir); break; + default: VectorSet(dir, 999, 999, 999); break; + } + // if the bot picked up the flag very close to the enemy base + if ( VectorLength(dir) < 128 ) { + // get an alternative route goal through the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } else { + // don't use any alt route goal, just get the hell out of the base + bs->altroutegoal.areanum = 0; + } + BotSetUserInfo(bs, "teamtask", (char *)va("%d", TEAMTASK_OFFENSE)); + BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); + } + else if (bs->rushbaseaway_time > FloatTime()) { + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus; + else flagstatus = bs->blueflagstatus; + //if the flag is back + if (flagstatus == 0) { + bs->rushbaseaway_time = 0; + } + } + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesFlag(&entinfo)) { + bs->ltgtype = 0; + } + } + // + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; + //if our team has the enemy flag and our flag is at the base + if (flagstatus == 1) { + // + if (bs->owndecision_time < FloatTime()) { + //if Not defending the base already + if (!(bs->ltgtype == LTG_DEFENDKEYAREA && + (bs->teamgoal.number == ctf_redflag.number || + bs->teamgoal.number == ctf_blueflag.number))) { + //if there is a visible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0 && + // and not already following the team mate flag carrier + (bs->ltgtype != LTG_TEAMACCOMPANY || bs->teammate != c)) { + // + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + bs->owndecision_time = (int) FloatTime() + 5; + } + } + } + return; + } + //if the enemy has our flag + else if (flagstatus == 2) { + // + if (bs->owndecision_time < FloatTime()) { + //if enemy flag carrier is visible + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + //FIXME: fight enemy flag carrier + } + //if not already doing something important + if (bs->ltgtype != LTG_GETFLAG && + bs->ltgtype != LTG_RETURNFLAG && + bs->ltgtype != LTG_TEAMHELP && + bs->ltgtype != LTG_TEAMACCOMPANY && + bs->ltgtype != LTG_CAMPORDER && + bs->ltgtype != LTG_PATROL && + bs->ltgtype != LTG_GETITEM) { + + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (random() < 0.5) { + //go for the enemy flag + bs->ltgtype = LTG_GETFLAG; + } + else { + bs->ltgtype = LTG_RETURNFLAG; + } + //no team message + bs->teammessage_time = 0; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + bs->owndecision_time = (int) (FloatTime() + 5.0f); + } + } + return; + } + //if both flags Not at their bases + else if (flagstatus == 3) { + // + if (bs->owndecision_time < FloatTime()) { + // if not trying to return the flag and not following the team flag carrier + if ( bs->ltgtype != LTG_RETURNFLAG && bs->ltgtype != LTG_TEAMACCOMPANY ) { + // + c = BotTeamFlagCarrierVisible(bs); + // if there is a visible team mate flag carrier + if (c >= 0) { + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + // + BotSetTeamStatus(bs); + bs->owndecision_time = (int)(FloatTime() + 5.0f); + } + else { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get the enemy flag + bs->teammessage_time = FloatTime() + 2 * random(); + //get the flag + bs->ltgtype = LTG_RETURNFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + bs->owndecision_time = (int)( FloatTime() + 5.0f); + } + } + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + // if the bot decided to do something on it's own and has a last ordered goal + if ( !bs->ordered && bs->lastgoal_ltgtype ) { + bs->ltgtype = 0; + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + // + if (bs->owndecision_time > FloatTime()) + return;; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + bs->ltgtype = LTG_GETFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } + bs->owndecision_time = (int)(FloatTime() + 5.0f); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotCTFRetreatGoals +================== +*/ +void BotCTFRetreatGoals(bot_state_t *bs) { + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotSetTeamStatus(bs); + } + } +} + +#ifdef MISSIONPACK +/* +================== +Bot1FCTFSeekGoals +================== +*/ +void Bot1FCTFSeekGoals(bot_state_t *bs) { + aas_entityinfo_t entinfo; + float rnd, l1, l2; + int c; + + //when carrying a flag in ctf the bot should rush to the base + if (Bot1FCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); + } + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesFlag(&entinfo)) { + bs->ltgtype = 0; + } + } + //our team has the flag + if (bs->neutralflagstatus == 1) { + if (bs->owndecision_time < FloatTime()) { + // if not already following someone + if (bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is a visible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + bs->owndecision_time = (int)(FloatTime() + 5.0f); + return; + } + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + //if not already attacking the enemy base + if (bs->ltgtype != LTG_ATTACKENEMYBASE) { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + BotSetTeamStatus(bs); + bs->owndecision_time = (int)(FloatTime() + 5.0f); + } + } + return; + } + //enemy team has the flag + else if (bs->neutralflagstatus == 2) { + if (bs->owndecision_time < FloatTime()) { + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + //FIXME: attack enemy flag carrier + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_GETITEM) { + return; + } + // if not already defending the base + if (bs->ltgtype != LTG_DEFENDKEYAREA) { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + bs->owndecision_time = (int)(FloatTime() + 5.0f); + } + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + // if the bot decided to do something on it's own and has a last ordered goal + if ( !bs->ordered && bs->lastgoal_ltgtype ) { + bs->ltgtype = 0; + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + // + if (bs->owndecision_time > FloatTime()) + return;; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && ctf_neutralflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + bs->ltgtype = LTG_GETFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + BotSetTeamStatus(bs); + } + else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } + bs->owndecision_time = (int)(FloatTime() + 5.0f); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +Bot1FCTFRetreatGoals +================== +*/ +void Bot1FCTFRetreatGoals(bot_state_t *bs) { + //when carrying a flag in ctf the bot should rush to the enemy base + if (Bot1FCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + } +} + +/* +================== +BotObeliskSeekGoals +================== +*/ +void BotObeliskSeekGoals(bot_state_t *bs) { + float rnd, l1, l2; + + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + //if already a team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the time the bot will stop attacking the enemy base + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + //get an alternate route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } +} + +/* +================== +BotGoHarvest +================== +*/ +void BotGoHarvest(bot_state_t *bs) { + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the time the bot will stop harvesting + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + BotSetTeamStatus(bs); +} + +/* +================== +BotObeliskRetreatGoals +================== +*/ +void BotObeliskRetreatGoals(bot_state_t *bs) { + //nothing special +} + +/* +================== +BotHarvesterSeekGoals +================== +*/ +void BotHarvesterSeekGoals(bot_state_t *bs) { + aas_entityinfo_t entinfo; + float rnd, l1, l2; + int c; + + //when carrying cubes in harvester the bot should rush to the base + if (BotHarvesterCarryingCubes(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesCubes(&entinfo)) { + bs->ltgtype = 0; + } + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + //if not yet doing something + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_HARVEST || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) { + //FIXME: attack enemy cube carrier + } + if (bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is a visible team mate carrying cubes + c = BotTeamCubeCarrierVisible(bs); + if (c >= 0) { + //follow the team mate carrying cubes + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + return; + } + } + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + // + rnd = random(); + if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotGoHarvest(bs); + } + else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } +} + +/* +================== +BotHarvesterRetreatGoals +================== +*/ +void BotHarvesterRetreatGoals(bot_state_t *bs) { + //when carrying cubes in harvester the bot should rush to the base + if (BotHarvesterCarryingCubes(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotSetTeamStatus(bs); + } + return; + } +} +#endif + +/* +================== +BotTeamGoals +================== +*/ +void BotTeamGoals(bot_state_t *bs, int retreat) { + if ((gametype == GT_OBELISK) && (redobelisk.areanum) && (blueobelisk.areanum)) { + BotSetEntityNumForGoal(&redobelisk, "DestructionObject-red"); + BotSetEntityNumForGoal(&blueobelisk, "DestructionObject-blue"); + if (redobelisk.entitynum && blueobelisk.entitynum) { + //gametype = GT_OBELISK; + } + else { + redobelisk.areanum = 0; + blueobelisk.areanum = 0; + } + } + + if (gametype == GT_CTF) + if (multiplayerManager.checkGameType("oneflag")) + gametype = GT_1FCTF; + + if ( retreat ) { + if (gametype == GT_CTF) { + BotCTFRetreatGoals(bs); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + Bot1FCTFRetreatGoals(bs); + } + else if (gametype == GT_OBELISK) { + BotObeliskRetreatGoals(bs); + } + else if (gametype == GT_HARVESTER) { + BotHarvesterRetreatGoals(bs); + } +#endif + } + else { + if (gametype == GT_CTF) { + //decide what to do in CTF mode + BotCTFSeekGoals(bs); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + Bot1FCTFSeekGoals(bs); + } + else if (gametype == GT_OBELISK) { + BotObeliskSeekGoals(bs); + } + else if (gametype == GT_HARVESTER) { + BotHarvesterSeekGoals(bs); + } +#endif + } + // reset the order time which is used to see if + // we decided to refuse an order + bs->order_time = 0; +} + +/* +================== +BotPointAreaNum +================== +*/ +int BotPointAreaNum(vec3_t origin) { + int areanum, numareas, areas[10]; + vec3_t end; + + areanum = gi.AAS_PointAreaNum(origin); + if (areanum) return areanum; + VectorCopy(origin, end); + end[2] += 10; + numareas = gi.AAS_TraceAreas(origin, end, areas, NULL, 10); + if (numareas > 0) return areas[0]; + return 0; +} + +/* +================== +ClientName +================== +*/ +char *ClientName(int client, char *name, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= maxclients->integer) { + BotAI_Print(PRT_ERROR, "ClientName: client out of range\n"); + return "[client out of range]"; + } + strncpy(buf,gi.getConfigstring(CS_PLAYERS+client), sizeof(buf)); + strncpy(name, Info_ValueForKey(buf, "name"), size-1); + name[size-1] = '\0'; + Q_CleanStr( name ); + return name; +} + +/* +================== +ClientSkin +================== +*/ +char *ClientSkin(int client, char *skin, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= maxclients->integer) { + BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n"); + return "[client out of range]"; + } + strncpy(buf,gi.getConfigstring(CS_PLAYERS+client), sizeof(buf)); + strncpy(skin, Info_ValueForKey(buf, "model"), size-1); + skin[size-1] = '\0'; + return skin; +} + +/* +================== +ClientFromName +================== +*/ +int ClientFromName(char *name) { + int i; + char buf[MAX_INFO_STRING]; +// static int maxclients; + +// if (!maxclients) +// maxclients = gi.Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + Q_CleanStr( buf ); + if (!Q_stricmp(Info_ValueForKey(buf, "name"), name)) + return i; + } + return -1; +} + +/* +================== +ClientOnSameTeamFromName +================== +*/ +int ClientOnSameTeamFromName(bot_state_t *bs, char *name) { + int i; + char buf[MAX_INFO_STRING]; +// static int maxclients; + +// if (!maxclients->integer) +// maxclients = gi.Cvar_VariableIntegerValue("sv_maxclients"); + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + if (!BotSameTeam(bs, i)) + continue; + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + Q_CleanStr( buf ); + if (!Q_stricmp(Info_ValueForKey(buf, "ni"), name)) return i; + } + return -1; +} + +/* +================== +stristr +================== +*/ +char *stristr(char *str, char *charset) { + int i; + + while(*str) { + for (i = 0; charset[i] && str[i]; i++) { + if (toupper(charset[i]) != toupper(str[i])) break; + } + if (!charset[i]) return str; + str++; + } + return NULL; +} + +/* +================== +EasyClientName +================== +*/ +char *EasyClientName(int client, char *buf, int size) { + int i; + char *str1, *str2, *ptr, c; + char name[128]; + + strcpy(name, ClientName(client, name, sizeof(name))); + for (i = 0; name[i]; i++) name[i] &= 127; + //remove all spaces + for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) { + memmove(ptr, ptr+1, strlen(ptr+1)+1); + } + //check for [sx] and ]x[ clan names + str1 = strstr(name, "["); + str2 = strstr(name, "]"); + if (str1 && str2) { + if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1); + else memmove(str2, str1+1, strlen(str1+1)+1); + } + //remove Mr prefix + if ((name[0] == 'm' || name[0] == 'M') && + (name[1] == 'r' || name[1] == 'R')) { + memmove(name, name+2, strlen(name+2)+1); + } + //only allow lower case alphabet characters + ptr = name; + while(*ptr) { + c = *ptr; + if ((c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '_') { + ptr++; + } + else if (c >= 'A' && c <= 'Z') { + *ptr += 'a' - 'A'; + ptr++; + } + else { + memmove(ptr, ptr+1, strlen(ptr + 1)+1); + } + } + strncpy(buf, name, size-1); + buf[size-1] = '\0'; + return buf; +} + +/* +================== +BotSynonymContext +================== +*/ +int BotSynonymContext(bot_state_t *bs) { + int context; + + context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES; + // + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_CTFREDTEAM; + else context |= CONTEXT_CTFBLUETEAM; + } +#ifdef MISSIONPACK + else if (gametype == GT_OBELISK) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_OBELISKREDTEAM; + else context |= CONTEXT_OBELISKBLUETEAM; + } + else if (gametype == GT_HARVESTER) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_HARVESTERREDTEAM; + else context |= CONTEXT_HARVESTERBLUETEAM; + } +#endif + return context; +} + +extern Event EV_Player_UseItem; +/* +================== +BotChooseWeapon +================== +*/ +void BotChooseWeapon(bot_state_t *bs) { + int newweaponnum; + weaponinfo_t wi; + /* FIXME + if (bs->cur_ps.weaponstate == WEAPON_RAISING || + bs->cur_ps.weaponstate == WEAPON_DROPPING) { + gi.EA_SelectWeapon(bs->client, bs->weaponnum); + } + else { +*/ + newweaponnum = gi.BotChooseBestFightWeapon(bs->ws, bs->inventory); +/// if (!newweaponnum) +// newweaponnum = 1; + + if (newweaponnum) { + gi.BotGetWeaponInfo(bs->ws, newweaponnum, &wi); + if (bs->weaponnum != newweaponnum) { + bs->weaponchange_time = FloatTime(); + } + bs->lastucmd.weapon = newweaponnum; + + // extra special weapon swap since we can't do it the q3 way + if (bs->weaponnum != bs->lastucmd.weapon) { + gentity_t *ed = &g_entities[ bs->client ]; + Entity *ent = ed->entity; + Player *plyr = (Player *)ent; + if (plyr->useWeapon(wi.name,WEAPON_DUAL)) + bs->weaponnum = bs->lastucmd.weapon; + } + } +} + +/* +================== +BotSetupForMovement +================== +*/ +void BotSetupForMovement(bot_state_t *bs) { + bot_initmove_t initmove; + + memset(&initmove, 0, sizeof(bot_initmove_t)); + VectorCopy(bs->cur_ps.origin, initmove.origin); + VectorCopy(bs->cur_ps.velocity, initmove.velocity); + VectorClear(initmove.viewoffset); + initmove.viewoffset[2] += bs->cur_ps.viewheight; + initmove.entitynum = bs->entitynum; + initmove.client = bs->client; + initmove.thinktime = bs->thinktime; + //set the onground flag + if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) + initmove.or_moveflags |= MFL_ONGROUND; + //set the teleported flag + if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_TELEPORTED; + } + //set the waterjump flag + if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_WATERJUMP; + } + //set presence type + if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH; + else initmove.presencetype = PRESENCE_NORMAL; + // + if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK; + // + VectorCopy(bs->viewangles, initmove.viewangles); + // + gi.BotInitMoveState(bs->ms, &initmove); +} + +/* +================== +BotCheckItemPickup +================== +*/ +void BotCheckItemPickup(bot_state_t *bs, int *oldinventory) { +#ifdef MISSIONPACK + int offence, leader; + + if (gametype <= GT_TEAM) + return; + + offence = -1; + // go into offence if picked up the kamikaze or invulnerability + if (!oldinventory[INVENTORY_KAMIKAZE] && bs->inventory[INVENTORY_KAMIKAZE] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_INVULNERABILITY] && bs->inventory[INVENTORY_INVULNERABILITY] >= 1) { + offence = qtrue; + } + // if not already wearing the kamikaze or invulnerability + if (!bs->inventory[INVENTORY_KAMIKAZE] && !bs->inventory[INVENTORY_INVULNERABILITY]) { + if (!oldinventory[INVENTORY_SCOUT] && bs->inventory[INVENTORY_SCOUT] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_GUARD] && bs->inventory[INVENTORY_GUARD] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_DOUBLER] && bs->inventory[INVENTORY_DOUBLER] >= 1) { + offence = qfalse; + } + if (!oldinventory[INVENTORY_AMMOREGEN] && bs->inventory[INVENTORY_AMMOREGEN] >= 1) { + offence = qfalse; + } + } + + if (offence >= 0) { + leader = ClientFromName(bs->teamleader); + if (offence) { + if (!(bs->teamtaskpreference & TEAMTP_ATTACKER)) { + // if we have a bot team leader + if (BotTeamLeader(bs)) { + // tell the leader we want to be on offence + BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); + //BotAI_BotInitialChat(bs, "wantoffence", NULL); + //gi.BotEnterChat(bs->cs, leader, CHAT_TELL); + } + else if (g_spSkill.integer <= 3) { + if ( bs->ltgtype != LTG_GETFLAG && + bs->ltgtype != LTG_ATTACKENEMYBASE && + bs->ltgtype != LTG_HARVEST ) { + // + if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && + (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { + // tell the leader we want to be on offence + BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); + //BotAI_BotInitialChat(bs, "wantoffence", NULL); + //gi.BotEnterChat(bs->cs, leader, CHAT_TELL); + } + } + bs->teamtaskpreference |= TEAMTP_ATTACKER; + } + } + bs->teamtaskpreference &= ~TEAMTP_DEFENDER; + } + else { + if (!(bs->teamtaskpreference & TEAMTP_DEFENDER)) { + // if we have a bot team leader + if (BotTeamLeader(bs)) { + // tell the leader we want to be on defense + BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); + //BotAI_BotInitialChat(bs, "wantdefence", NULL); + //gi.BotEnterChat(bs->cs, leader, CHAT_TELL); + } + else if (g_spSkill.integer <= 3) { + if ( bs->ltgtype != LTG_DEFENDKEYAREA ) { + // + if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && + (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { + // tell the leader we want to be on defense + BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); + //BotAI_BotInitialChat(bs, "wantdefence", NULL); + //gi.BotEnterChat(bs->cs, leader, CHAT_TELL); + } + } + } + bs->teamtaskpreference |= TEAMTP_DEFENDER; + } + bs->teamtaskpreference &= ~TEAMTP_ATTACKER; + } + } +#endif +} + +/* +================== +BotUpdateInventory +================== +*/ +void BotUpdateInventory(bot_state_t *bs) { + int oldinventory[MAX_ITEMS]; + memcpy(oldinventory, bs->inventory, sizeof(oldinventory)); + + // clear current inventory (so we don't hold old values after respawn etc) + memset(bs->inventory,0,sizeof(int)*MAX_ITEMS); + + if ( ( bs->cur_ps.clientNum < 0 ) || ( bs->cur_ps.clientNum > maxclients->integer ) ) + return; + +// cycle through player inventory and update bot values + Player *player = (Player *)g_entities[bs->cur_ps.clientNum].entity; + if (player) { +// items + Item *item = player->NextItem(NULL); + while (item != NULL) { + bs->inventory[item->GetBotInventoryIndex()] = + (int)item->getAmount(); + item = player->NextItem(item); + } +// armor + item = player->FindBaseArmor(); + if (item) + bs->inventory[INVENTORY_ARMOR] = (int)item->getAmount(); +// ammo + bs->inventory[INVENTORY_PLASMA] = player->AmmoCount( "Plasma" ); + bs->inventory[INVENTORY_FED] = player->AmmoCount( "Fed" ); + bs->inventory[INVENTORY_IDRYLL] = player->AmmoCount( "Idryll" ); + +// holdable item + item = (Item *)player->getHoldableItem(); + if (item) + bs->inventory[item->GetBotInventoryIndex()] = (int)item->getAmount(); + +// flags + bs->inventory[INVENTORY_REDFLAG] = multiplayerManager.doesPlayerHaveItem( player, "ctfflag-red" ); + bs->inventory[INVENTORY_BLUEFLAG] = multiplayerManager.doesPlayerHaveItem( player, "ctfflag-blue" ); + bs->inventory[INVENTORY_NEUTRALFLAG] = multiplayerManager.doesPlayerHaveItem( player, "ctfflag-one" ); + +// last kill info + Player *target; + target = multiplayerManager.getLastKillerOfPlayer(player,&bs->botdeathtype); + if (target) + bs->lastkilledby = target->edict->s.number; + target = multiplayerManager.getLastKilledByPlayer(player,&bs->enemydeathtype); + if (target) + bs->lastkilledplayer = target->edict->s.number; + } +// health + bs->lasthealth = oldinventory[INVENTORY_HEALTH]; + bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; + + // BOTTODO replace this hardcoded crap with different hardcoded crap -- weapons & ammo inventory FIXME JPW + //armor +// bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; + //weapons +/* + bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0; + bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0; + bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0; + bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0; + bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0; + bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0; + bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0; + bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0; + bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0; + bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0;; + bs->inventory[INVENTORY_PROXLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PROX_LAUNCHER)) != 0;; + bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;; +#endif + //ammo + bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN]; + bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN]; + bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; + bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN]; + bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING]; + bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER]; + bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN]; + bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG]; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN]; + bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER]; + bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN]; +#endif +//powerups + + bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER; + bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_KAMIKAZE] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_KAMIKAZE; + bs->inventory[INVENTORY_PORTAL] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_PORTAL; + bs->inventory[INVENTORY_INVULNERABILITY] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_INVULNERABILITY; +#endif + bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0; + bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0; + bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; + bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; + bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0; + bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_SCOUT] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_SCOUT; + bs->inventory[INVENTORY_GUARD] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_GUARD; + bs->inventory[INVENTORY_DOUBLER] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_DOUBLER; + bs->inventory[INVENTORY_AMMOREGEN] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_AMMOREGEN; +#endif + bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; + bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NEUTRALFLAG] = bs->cur_ps.powerups[PW_NEUTRALFLAG] != 0; + if (BotTeam(bs) == TEAM_RED) { + bs->inventory[INVENTORY_REDCUBE] = bs->cur_ps.generic1; + bs->inventory[INVENTORY_BLUECUBE] = 0; + } + else { + bs->inventory[INVENTORY_REDCUBE] = 0; + bs->inventory[INVENTORY_BLUECUBE] = bs->cur_ps.generic1; + } +#endif +*/ + BotCheckItemPickup(bs, oldinventory); +} + +/* +================== +BotUpdateBattleInventory +================== +*/ +void BotUpdateBattleInventory(bot_state_t *bs, int enemy) { + vec3_t dir; + aas_entityinfo_t entinfo; + + BotEntityInfo(enemy, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + bs->inventory[ENEMY_HEIGHT] = (int) dir[2]; + dir[2] = 0; + + bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir); + + /* if ( ( enemy == redobelisk.entitynum ) || ( enemy == blueobelisk.entitynum ) ) + { + if ( bs->inventory[ ENEMY_HORIZONTAL_DIST ] < 300 ) + { + bs->inventory[ ENEMY_HORIZONTAL_DIST ] = 0.0f; + } + } */ + + //FIXME: add num visible enemies and num visible team mates to the inventory +} + +#ifdef MISSIONPACK +/* +================== +BotUseKamikaze +================== +*/ +#define KAMIKAZE_DIST 1024 + +void BotUseKamikaze(bot_state_t *bs) { + int c, teammates, enemies; + aas_entityinfo_t entinfo; + vec3_t dir, target; + bot_goal_t *goal; + bsp_trace_t trace; + + //if the bot has no kamikaze + if (bs->inventory[INVENTORY_KAMIKAZE] <= 0) + return; + if (bs->kamikaze_time > FloatTime()) + return; + bs->kamikaze_time = FloatTime() + 0.2; + if (gametype == GT_CTF) { + //never use kamikaze if the team flag carrier is visible + if (BotCTFCarryingFlag(bs)) + return; + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + gi.EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_1FCTF) { + //never use kamikaze if the team flag carrier is visible + if (Bot1FCTFCarryingFlag(bs)) + return; + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + gi.EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_OBELISK) { + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST * 0.9)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + gi.EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_HARVESTER) { + // + if (BotHarvesterCarryingCubes(bs)) + return; + //never use kamikaze if a team mate carrying cubes is visible + c = BotTeamCubeCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + gi.EA_Use(bs->client); + return; + } + } + } + // + BotVisibleTeamMatesAndEnemies(bs, &teammates, &enemies, KAMIKAZE_DIST); + // + if (enemies > 2 && enemies > teammates+1) { + gi.EA_Use(bs->client); + return; + } +} + +/* +================== +BotUseInvulnerability +================== +*/ +void BotUseInvulnerability(bot_state_t *bs) { + int c; + vec3_t dir, target; + bot_goal_t *goal; + bsp_trace_t trace; + + //if the bot has no invulnerability + if (bs->inventory[INVENTORY_INVULNERABILITY] <= 0) + return; + if (bs->invulnerability_time > FloatTime()) + return; + bs->invulnerability_time = FloatTime() + 0.2; + if (gametype == GT_CTF) { + //never use kamikaze if the team flag carrier is visible + if (BotCTFCarryingFlag(bs)) + return; + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy flag and the flag is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &ctf_blueflag; break; + default: goal = &ctf_redflag; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + gi.EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_1FCTF) { + //never use kamikaze if the team flag carrier is visible + if (Bot1FCTFCarryingFlag(bs)) + return; + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy flag and the flag is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &ctf_blueflag; break; + default: goal = &ctf_redflag; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + gi.EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_OBELISK) { + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(300)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + gi.EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_HARVESTER) { + // + if (BotHarvesterCarryingCubes(bs)) + return; + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy base and enemy base is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + gi.EA_Use(bs->client); + return; + } + } + } +} +#endif + +/* +================== +BotBattleUseItems +================== +*/ +void BotBattleUseItems(bot_state_t *bs) { + + if ( ( bs->cur_ps.clientNum < 0 ) || ( bs->cur_ps.clientNum > maxclients->integer ) ) + return; + + if (bs->inventory[INVENTORY_HEALTH] < 40) { + if (bs->inventory[INVENTORY_TELEPORTER] > 0) { + if (!BotCTFCarryingFlag(bs) +#ifdef MISSIONPACK + && !Bot1FCTFCarryingFlag(bs) + && !BotHarvesterCarryingCubes(bs) +#endif + ) { + Player *player = (Player *)g_entities[bs->cur_ps.clientNum].entity; + player->useHoldableItem(); + // gi.EA_Use(bs->client); + } + } + } + + if ((bs->inventory[INVENTORY_HEALTH] < 30) || (bs->lasthealth > bs->inventory[INVENTORY_HEALTH])) { + if (bs->inventory[INVENTORY_INVULNERABILITY]) { + Player *player = (Player *)g_entities[bs->cur_ps.clientNum].entity; + player->useHoldableItem(); + } + } + + if (bs->inventory[INVENTORY_HEALTH] < 60) { + if (bs->inventory[INVENTORY_MEDKIT] > 0) { + Player *player = (Player *)g_entities[bs->cur_ps.clientNum].entity; + player->useHoldableItem(); +// gi.EA_Use(bs->client); + } + } +#ifdef MISSIONPACK + BotUseKamikaze(bs); + BotUseInvulnerability(bs); +#endif +} + +/* +================== +BotSetTeleportTime +================== +*/ +void BotSetTeleportTime(bot_state_t *bs) { +/* FIXME + if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) { + bs->teleport_time = FloatTime(); + } + bs->last_eFlags = bs->cur_ps.eFlags; +*/ +} + +/* +================== +BotIsDead +================== +*/ +qboolean BotIsDead(bot_state_t *bs) { + return (bs->cur_ps.pm_type == PM_DEAD); +} + +/* +================== +BotIsObserver +================== +*/ +qboolean BotIsObserver(bot_state_t *bs) { +/* char buf[MAX_INFO_STRING]; + if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue; + strncpy(buf,gi.getConfigstring(CS_PLAYERS+bs->client), sizeof(buf)); + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue; +*/ return qfalse; +} + +/* +================== +BotIntermission +================== +*/ +qboolean BotIntermission(bot_state_t *bs) { + //NOTE: we shouldn't be looking at the game code... + if (level.intermissiontime) return qtrue; +// return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION); // FIXME + return qfalse; +} + +/* +================== +BotInLavaOrSlime +================== +*/ +qboolean BotInLavaOrSlime(bot_state_t *bs) { + vec3_t feet; + + VectorCopy(bs->origin, feet); + feet[2] -= 23; + return (gi.AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME)); +} + +/* +================== +BotCreateWayPoint +================== +*/ +bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) { + bot_waypoint_t *wp; + vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8}; + + wp = botai_freewaypoints; + if ( !wp ) { + BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" ); + return NULL; + } + botai_freewaypoints = botai_freewaypoints->next; + + Q_strncpyz( wp->name, name, sizeof(wp->name) ); + VectorCopy(origin, wp->goal.origin); + VectorCopy(waypointmins, wp->goal.mins); + VectorCopy(waypointmaxs, wp->goal.maxs); + wp->goal.areanum = areanum; + wp->next = NULL; + wp->prev = NULL; + return wp; +} + +/* +================== +BotFindWayPoint +================== +*/ +bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) { + bot_waypoint_t *wp; + + for (wp = waypoints; wp; wp = wp->next) { + if (!Q_stricmp(wp->name, name)) return wp; + } + return NULL; +} + +/* +================== +BotFreeWaypoints +================== +*/ +void BotFreeWaypoints(bot_waypoint_t *wp) { + bot_waypoint_t *nextwp; + + for (; wp; wp = nextwp) { + nextwp = wp->next; + wp->next = botai_freewaypoints; + botai_freewaypoints = wp; + } +} + +/* +================== +BotInitWaypoints +================== +*/ +void BotInitWaypoints(void) { + int i; + + botai_freewaypoints = NULL; + for (i = 0; i < MAX_WAYPOINTS; i++) { + botai_waypoints[i].next = botai_freewaypoints; + botai_freewaypoints = &botai_waypoints[i]; + } +} + +/* +================== +TeamPlayIsOn +================== +*/ +int TeamPlayIsOn(void) { + return ( gametype >= GT_TEAM ); +} + +/* +================== +BotAggression +================== +*/ +float BotAggression(bot_state_t *bs) { + //if the bot has quad + if (bs->inventory[INVENTORY_QUAD]) { + //if the bot is not holding the gauntlet or the enemy is really nearby + return 70; + } + //if the enemy is located way higher than the bot + if (bs->inventory[ENEMY_HEIGHT] > 200) return 0; + //if the bot is very low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return 0; + //if the bot is low on health + if (bs->inventory[INVENTORY_HEALTH] < 80) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return 0; + } + //if the bot can use the bfg + if (bs->inventory[INVENTORY_BFG10K] > 0 && + bs->inventory[INVENTORY_PLASMA] > 7) return 100; + //if the bot can use the railgun + if (bs->inventory[INVENTORY_RAILGUN] > 0 && + bs->inventory[INVENTORY_PLASMA] > 5) return 95; + //if the bot can use the lightning gun + if (bs->inventory[INVENTORY_LIGHTNING] > 0 && + bs->inventory[INVENTORY_IDRYLL] > 50) return 90; + //if the bot can use the rocketlauncher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && + bs->inventory[INVENTORY_FED] > 5) return 90; + //if the bot can use the plasmagun + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && + bs->inventory[INVENTORY_PLASMA] > 40) return 85; + //if the bot can use the grenade launcher + if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && + bs->inventory[INVENTORY_FED] > 10) return 80; + //if the bot can use the shotgun + if (bs->inventory[INVENTORY_SHOTGUN] > 0 && + bs->inventory[INVENTORY_PLASMA] > 10) return 60; + //otherwise the bot is not feeling too good + return 0; +} + +/* +================== +BotFeelingBad +================== +*/ +float BotFeelingBad(bot_state_t *bs) { +// if (bs->weaponnum == WP_GAUNTLET) { +// return 100; +// } + if (bs->inventory[INVENTORY_HEALTH] < 40) { + return 100; + } +// if (bs->weaponnum == WP_MACHINEGUN) { +// return 90; +// } + if (bs->inventory[INVENTORY_HEALTH] < 60) { + return 80; + } + return 0; +} + +/* +================== +BotWantsToRetreat +================== +*/ +int BotWantsToRetreat(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + if (gametype == GT_CTF) { + //always retreat when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) + return qtrue; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + //if carrying the flag then always retreat + if (Bot1FCTFCarryingFlag(bs)) + return qtrue; + } + else if (gametype == GT_OBELISK) { + //the bots should be dedicated to attacking the enemy obelisk + if (bs->ltgtype == LTG_ATTACKENEMYBASE) { + if (bs->enemy != redobelisk.entitynum || + bs->enemy != blueobelisk.entitynum) { + return qtrue; + } + } + if (BotFeelingBad(bs) > 50) { + return qtrue; + } + return qfalse; + } + else if (gametype == GT_HARVESTER) { + //if carrying cubes then always retreat + if (BotHarvesterCarryingCubes(bs)) return qtrue; + } +#endif + // + if (bs->enemy >= 0) { + //if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qfalse; + } + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) + return qtrue; + // + if (BotAggression(bs) < 50) + return qtrue; + return qfalse; +} + +/* +================== +BotWantsToChase +================== +*/ +int BotWantsToChase(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + if (gametype == GT_CTF) { + //never chase when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) + return qfalse; + //always chase if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qtrue; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + //never chase if carrying the flag + if (Bot1FCTFCarryingFlag(bs)) + return qfalse; + //always chase if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qtrue; + } + else if (gametype == GT_OBELISK) { + //the bots should be dedicated to attacking the enemy obelisk + if (bs->ltgtype == LTG_ATTACKENEMYBASE) { + if (bs->enemy != redobelisk.entitynum || + bs->enemy != blueobelisk.entitynum) { + return qfalse; + } + } + else if ( ( bs->enemy == redobelisk.entitynum ) || ( bs->enemy == blueobelisk.entitynum ) ) { + return qtrue; + } + } + else if (gametype == GT_HARVESTER) { + //never chase if carrying cubes + if (BotHarvesterCarryingCubes(bs)) + return qfalse; + } +#endif + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) + return qfalse; + // + if (BotAggression(bs) > 50) + return qtrue; + return qfalse; +} + +/* +================== +BotWantsToHelp +================== +*/ +int BotWantsToHelp(bot_state_t *bs) { + return qtrue; +} + +/* +================== +BotCanAndWantsToRocketJump +================== +*/ +int BotCanAndWantsToRocketJump(bot_state_t *bs) { + float rocketjumper; + + //if rocket jumping is disabled + if (!bot_rocketjump.integer) return qfalse; + //if no rocket launcher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse; + //if low on rockets + if (bs->inventory[INVENTORY_PLASMA] < 3) return qfalse; + //never rocket jump with the Quad + if (bs->inventory[INVENTORY_QUAD]) return qfalse; + //if low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; + //if not full health + if (bs->inventory[INVENTORY_HEALTH] < 90) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; + } + rocketjumper = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1); + if (rocketjumper < 0.5) return qfalse; + return qtrue; +} + +/* +================== +BotHasPersistantPowerupAndWeapon +================== +*/ +int BotHasPersistantPowerupAndWeapon(bot_state_t *bs) { // BOTTODO fix these for ef2 pickups +#ifdef MISSIONPACK + // if the bot does not have a persistant powerup + if (!bs->inventory[INVENTORY_SCOUT] && + !bs->inventory[INVENTORY_GUARD] && + !bs->inventory[INVENTORY_DOUBLER] && + !bs->inventory[INVENTORY_AMMOREGEN] ) { + return qfalse; + } +#endif + //if the bot is very low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; + //if the bot is low on health + if (bs->inventory[INVENTORY_HEALTH] < 80) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; + } + //if the bot can use the bfg + if (bs->inventory[INVENTORY_BFG10K] > 0 && + bs->inventory[INVENTORY_PLASMA] > 7) return qtrue; + //if the bot can use the railgun + if (bs->inventory[INVENTORY_RAILGUN] > 0 && + bs->inventory[INVENTORY_PLASMA] > 5) return qtrue; + //if the bot can use the lightning gun + if (bs->inventory[INVENTORY_LIGHTNING] > 0 && + bs->inventory[INVENTORY_PLASMA] > 50) return qtrue; + //if the bot can use the rocketlauncher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && + bs->inventory[INVENTORY_PLASMA] > 5) return qtrue; + // + if (bs->inventory[INVENTORY_NAILGUN] > 0 && + bs->inventory[INVENTORY_PLASMA] > 5) return qtrue; + // + if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 && + bs->inventory[INVENTORY_PLASMA] > 5) return qtrue; + // + if (bs->inventory[INVENTORY_CHAINGUN] > 0 && + bs->inventory[INVENTORY_PLASMA] > 40) return qtrue; + //if the bot can use the plasmagun + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && + bs->inventory[INVENTORY_PLASMA] > 20) return qtrue; + return qfalse; +} + +/* +================== +BotGoCamp +================== +*/ +void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) { + float camper; + + bs->decisionmaker = bs->client; + //set message time to zero so bot will NOT show any message + bs->teammessage_time = 0; + //set the ltg type + bs->ltgtype = LTG_CAMP; + //set the team goal + memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t)); + //get the team goal time + camper = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); + if (camper > 0.99) bs->teamgoal_time = FloatTime() + 99999; + else bs->teamgoal_time = FloatTime() + 120 + 180 * camper + random() * 15; + //set the last time the bot started camping + bs->camp_time = FloatTime(); + //the teammate that requested the camping + bs->teammate = 0; + //do NOT type arrive message + bs->arrive_time = 1; +} + +/* +================== +BotWantsToCamp +================== +*/ +int BotWantsToCamp(bot_state_t *bs) { + float camper; + int cs, traveltime, besttraveltime; + bot_goal_t goal, bestgoal; + + camper = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); + if (camper < 0.1) return qfalse; + //if the bot has a team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_CAMP || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL) { + return qfalse; + } + //if camped recently + if (bs->camp_time > FloatTime() - 60 + 300 * (1-camper)) return qfalse; + // + if (random() > camper) { + bs->camp_time = FloatTime(); + return qfalse; + } + //if the bot isn't healthy anough + if (BotAggression(bs) < 50) return qfalse; + //the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo + if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_FED < 10]) && + (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_IDRYLL] < 10) && + (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_PLASMA] < 10)) { + return qfalse; + } + //find the closest camp spot + besttraveltime = 99999; + for (cs = gi.BotGetNextCampSpotGoal(0, &goal); cs; cs = gi.BotGetNextCampSpotGoal(cs, &goal)) { + traveltime = gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT); + if (traveltime && traveltime < besttraveltime) { + besttraveltime = traveltime; + memcpy(&bestgoal, &goal, sizeof(bot_goal_t)); + } + } + if (besttraveltime > 150) return qfalse; + //ok found a camp spot, go camp there + BotGoCamp(bs, &bestgoal); + bs->ordered = qfalse; + // + return qtrue; +} + +/* +================== +BotDontAvoid +================== +*/ +void BotDontAvoid(bot_state_t *bs, char *itemname) { + bot_goal_t goal; + int num; + + num = gi.BotGetLevelItemGoal(-1, itemname, &goal); + while(num >= 0) { + gi.BotRemoveFromAvoidGoals(bs->gs, goal.number); + num = gi.BotGetLevelItemGoal(num, itemname, &goal); + } +} + +/* +================== +BotGoForPowerups +================== +*/ +void BotGoForPowerups(bot_state_t *bs) { + + //don't avoid any of the powerups anymore + BotDontAvoid(bs, "Quad Damage"); + BotDontAvoid(bs, "Regeneration"); + BotDontAvoid(bs, "Battle Suit"); + BotDontAvoid(bs, "Speed"); + BotDontAvoid(bs, "Invisibility"); + //BotDontAvoid(bs, "Flight"); + //reset the long term goal time so the bot will go for the powerup + //NOTE: the long term goal type doesn't change + bs->ltg_time = 0; +} + +/* +================== +BotRoamGoal +================== +*/ +void BotRoamGoal(bot_state_t *bs, vec3_t goal) { + int pc, i; + float len, rnd; + vec3_t dir, bestorg, belowbestorg; + bsp_trace_t trace; + + for (i = 0; i < 10; i++) { + //start at the bot origin + VectorCopy(bs->origin, bestorg); + rnd = random(); + if (rnd > 0.25) { + //add a random value to the x-coordinate + if (random() < 0.5) bestorg[0] -= 800 * random() + 100; + else bestorg[0] += 800 * random() + 100; + } + if (rnd < 0.75) { + //add a random value to the y-coordinate + if (random() < 0.5) bestorg[1] -= 800 * random() + 100; + else bestorg[1] += 800 * random() + 100; + } + //add a random value to the z-coordinate (NOTE: 48 = maxjump?) + bestorg[2] += 2 * 48 * crandom(); + //trace a line from the origin to the roam target + BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID); + //direction and length towards the roam target + VectorSubtract(trace.endpos, bs->origin, dir); + len = VectorNormalize(dir); + //if the roam target is far away anough + if (len > 200) { + //the roam target is in the given direction before walls + VectorScale(dir, len * trace.fraction - 40, dir); + VectorAdd(bs->origin, dir, bestorg); + //get the coordinates of the floor below the roam target + belowbestorg[0] = bestorg[0]; + belowbestorg[1] = bestorg[1]; + belowbestorg[2] = bestorg[2] - 800; + BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID); + // + if (!trace.startsolid) { + trace.endpos[2]++; + pc = gi.PointContents(trace.endpos);//, bs->entitynum); + if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) { + VectorCopy(bestorg, goal); + return; + } + } + } + } + VectorCopy(bestorg, goal); +} + +/* +================== +BotAttackMove +================== +*/ +bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) { + int movetype, i, attackentity; + float attack_skill, jumper, croucher, dist, strafechange_time; + float attack_dist=0, attack_range=0; + vec3_t forward, backward, sideward, hordir, up = {0, 0, 1}, end; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + bot_goal_t goal; + + attackentity = bs->enemy; + // + if (bs->attackchase_time > FloatTime()) { + //create the chase goal + goal.entitynum = attackentity; + goal.areanum = bs->lastenemyareanum; + VectorCopy(bs->lastenemyorigin, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + gi.BotMoveToGoal(&moveresult, bs->ms, &goal, tfl); + return moveresult; + } + // + memset(&moveresult, 0, sizeof(bot_moveresult_t)); + // + attack_skill = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + jumper = 0; // BOTLIB turned jumpers back off 'cause they look really bad in ef2 // gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1); + croucher = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + //if the bot is really stupid + if (attack_skill < 0.2) return moveresult; + //initialize the movement state + BotSetupForMovement(bs); + //get the enemy entity info + BotEntityInfo(attackentity, &entinfo); + //direction towards the enemy + VectorSubtract(entinfo.origin, bs->origin, forward); + //the distance towards the enemy + dist = VectorNormalize(forward); + VectorNegate(forward, backward); + //walk, crouch or jump + movetype = MOVE_WALK; + // + if (bs->attackcrouch_time < FloatTime() - 1) { + if (random() < jumper) { + movetype = MOVE_JUMP; + } + //wait at least one second before crouching again + else if (bs->attackcrouch_time < FloatTime() - 1 && random() < croucher) { + bs->attackcrouch_time = FloatTime() + croucher * 5; + } + } + if (bs->attackcrouch_time > FloatTime()) movetype = MOVE_CROUCH; + //if the bot should jump + if (movetype == MOVE_JUMP) { + //if jumped last frame + if (bs->attackjump_time > FloatTime()) { + movetype = MOVE_WALK; + } + else { + bs->attackjump_time = FloatTime() + 1; + } + } + // check if bot has melee weapon out and close distance + // BOTTODO *NOTE* this only checks primary, not secondary fire mode, and it doesn't check against + // bot's currently (randomly) selected mode. + Player *bot1 = (Player *)g_entities[bs->client].entity; + if (bot1) { + Weapon *weapon = bot1->GetActiveWeapon( WEAPON_DUAL ); + if (weapon) { + if (weapon->GetFireType(FIRE_MODE1) == FT_MELEE ) { + attack_dist = 0; + attack_range = 0; + } + else { + attack_dist = IDEAL_ATTACKDIST; + attack_range = 40; + } + } + } + else { + attack_dist = IDEAL_ATTACKDIST; + attack_range = 40; + } + //if the bot is stupid + if (attack_skill <= 0.4) { + //just walk to or away from the enemy + if (dist > attack_dist + attack_range) { + if (gi.BotMoveInDirection(bs->ms, forward, sv_maxspeed->value, movetype)) return moveresult; + } + if (dist < attack_dist - attack_range) { + if (gi.BotMoveInDirection(bs->ms, backward, sv_maxspeed->value, movetype)) return moveresult; + } + return moveresult; + } + //increase the strafe time + bs->attackstrafe_time += bs->thinktime; + //get the strafe change time + strafechange_time = 0.4 + (1 - attack_skill) * 0.2; + if (attack_skill > 0.7) strafechange_time += crandom() * 0.2; + //if the strafe direction should be changed + if (bs->attackstrafe_time > strafechange_time) { + //some magic number :) + if (random() > 0.935) { + //flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + } + for (i = 0; i < 2; i++) { + hordir[0] = forward[0]; + hordir[1] = forward[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //get the sideward vector + CrossProduct(hordir, up, sideward); + //reverse the vector depending on the strafe direction + if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward); + + // BOTLIB: added trace to detect wall and flip strafe direction + bsp_trace_t trace; + VectorMA(bs->origin,40,sideward,end); // FIXME hardcoded #, grab from mins + BotAI_Trace(&trace, bs->origin, NULL, NULL, end, bs->client, CONTENTS_SOLID); + if (trace.fraction < 1.0f) { + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + VectorScale(sideward,-1.0f,sideward); + //perform the movement + if (gi.BotMoveInDirection(bs->ms, sideward, sv_maxspeed->value, movetype)) + return moveresult; + } + + + //randomly go back a little + if (random() > 0.9) { + VectorAdd(sideward, backward, sideward); + } + else { + //walk forward or backward to get at the ideal attack distance + if (dist > attack_dist + attack_range) { + VectorAdd(sideward, forward, sideward); + } + else if (dist < attack_dist - attack_range) { + VectorAdd(sideward, backward, sideward); + } + } + //perform the movement + if (gi.BotMoveInDirection(bs->ms, sideward, sv_maxspeed->value, movetype)) + return moveresult; + //movement failed, flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + //bot couldn't do any usefull movement +// bs->attackchase_time = AAS_Time() + 6; + return moveresult; +} + +/* +================== +BotSameTeam +================== +*/ +int BotSameTeam(bot_state_t *bs, int entnum) { +// char info1[1024], info2[1024]; + const char *info1 = NULL, *info2 = NULL; + Team *team; + + if (bs->client < 0 || bs->client >= maxclients->integer) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if (entnum < 0 || entnum >= maxclients->integer) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if ( gametype >= GT_TEAM ) { + Player *bot1 = (Player *)g_entities[bs->client].entity, *bot2 = (Player *)g_entities[entnum].entity; + if (!bot1 || !bot2) + return qfalse; + team = multiplayerManager.getPlayersTeam(bot1 ); + if (team) + info1 = team->getName().c_str(); + else + return qfalse; + team = multiplayerManager.getPlayersTeam(bot2 ); + if (team) + info2 = team->getName().c_str(); + else + return qfalse; + if ((!info1) || (!info2)) + return qfalse; + if (!strcmp(info1,info2)) + return qtrue; + } + return qfalse; +} + +/* +================== +InFieldOfVision +================== +*/ +qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) +{ + int i; + float diff, angle; + + for (i = 0; i < 2; i++) { + angle = AngleMod(viewangles[i]); + angles[i] = AngleMod(angles[i]); + diff = angles[i] - angle; + if (angles[i] > angle) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + if (diff > 0) { + if (diff > fov * 0.5) return qfalse; + } + else { + if (diff < -fov * 0.5) return qfalse; + } + } + return qtrue; +} + +/* +================== +BotEntityVisible + +returns visibility in the range [0, 1] taking fog and water surfaces into account +================== +*/ +float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) { + int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc; + float squaredfogdist, waterfactor, vis, bestvis; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t dir, entangles, start, end, middle; + + //calculate middle of bounding box + BotEntityInfo(ent, &entinfo); + VectorAdd(entinfo.mins, entinfo.maxs, middle); + VectorScale(middle, 0.5, middle); + VectorAdd(entinfo.origin, middle, middle); + //check if entity is within field of vision + VectorSubtract(middle, eye, dir); + vectoangles(dir, entangles); + if (!InFieldOfVision(viewangles, fov, entangles)) return 0; + // + pc = gi.AAS_PointContents(eye); + infog = (pc & CONTENTS_FOG); + inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)); + // + bestvis = 0; + for (i = 0; i < 3; i++) { + //if the point is not in potential visible sight + //if (!AAS_inPVS(eye, middle)) continue; + // + contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP; + passent = viewer; + hitent = ent; + VectorCopy(eye, start); + VectorCopy(middle, end); + //if the entity is in water, lava or slime + if (gi.AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //if eye is in water, lava or slime + if (inwater) { + if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) { + passent = ent; + hitent = viewer; + VectorCopy(middle, start); + VectorCopy(eye, end); + } + contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //trace from start to end + BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask); + //if water was hit + waterfactor = 1.0; + if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + //if the water surface is translucent + if (1) { + //trace through the water + contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask); + waterfactor = 0.5; + } + } + //if a full trace or the hitent was hit + if (trace.fraction >= 1 || trace.ent == hitent) { + //check for fog, assuming there's only one fog brush where + //either the viewer or the entity is in or both are in + otherinfog = (gi.AAS_PointContents(middle) & CONTENTS_FOG); + if (infog && otherinfog) { + VectorSubtract(trace.endpos, eye, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else if (infog) { + VectorCopy(trace.endpos, start); + BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG); + VectorSubtract(eye, trace.endpos, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else if (otherinfog) { + VectorCopy(trace.endpos, end); + BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG); + VectorSubtract(end, trace.endpos, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else { + //if the entity and the viewer are not in fog assume there's no fog in between + squaredfogdist = 0; + } + //decrease visibility with the view distance through fog + vis = 1 / ((squaredfogdist * 0.001) < 1 ? 1 : (squaredfogdist * 0.001)); + //if entering water visibility is reduced + vis *= waterfactor; + // + if (vis > bestvis) bestvis = vis; + //if pretty much no fog + if (bestvis >= 0.95) return bestvis; + } + //check bottom and top of bounding box as well + if (i == 0) middle[2] += entinfo.mins[2]; + else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2]; + } + return bestvis; +} + +/* +================== +BotFindEnemy +================== +*/ +int BotFindEnemy(bot_state_t *bs, int curenemy) { + int i, healthdecrease; + float f, alertness, easyfragger, vis; + float squaredist, cursquaredist; + aas_entityinfo_t entinfo, curenemyinfo; + vec3_t dir, angles; + + alertness = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1); + easyfragger = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1); + //check if the health decreased + healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH]; + //remember the current health value + // + + if (curenemy >= 0) { + + // Make sure not accidently targeting a team member + + if ( BotSameTeam( bs, curenemy ) ) { + bs->enemy = -1; + return qtrue; + } + + BotEntityInfo(curenemy, &curenemyinfo); + + if ( EntityCarriesFlag(&curenemyinfo) ) return qfalse; + + VectorSubtract(curenemyinfo.origin, bs->origin, dir); + cursquaredist = VectorLengthSquared(dir); + } + else { + cursquaredist = 0; + } +#ifdef MISSIONPACK + if (gametype == GT_OBELISK) { + vec3_t target; + bot_goal_t *goal; + bsp_trace_t trace; + + if (BotTeam(bs) == TEAM_RED) + goal = &blueobelisk; + else + goal = &redobelisk; + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + if (goal->entitynum == bs->enemy) { + return qfalse; + } + bs->enemy = goal->entitynum; + bs->enemysight_time = FloatTime(); + bs->enemysuicide = qfalse; + bs->enemydeath_time = 0; + bs->enemyvisible_time = FloatTime(); + return qtrue; + } + } +#endif + // + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + //if it's the current enemy + if (i == curenemy) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + //if the enemy is invisible and not shooting + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + continue; + } + //if not an easy fragger don't shoot at chatting players + if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue; + // + if (lastteleport_time > FloatTime() - 3) { + VectorSubtract(entinfo.origin, lastteleport_origin, dir); + if (VectorLengthSquared(dir) < Square(70)) continue; + } + //calculate the distance towards the enemy + VectorSubtract(entinfo.origin, bs->origin, dir); + squaredist = VectorLengthSquared(dir); + //if this entity is not carrying a flag + if (!EntityCarriesFlag(&entinfo)) + { + //if this enemy is further away than the current one + if (curenemy >= 0 && squaredist > cursquaredist) continue; + } //end if + //if the bot has no + if (squaredist > Square(900.0 + alertness * 4000.0)) continue; + //if on the same team + if (BotSameTeam(bs, i)) continue; + //if the bot's health decreased or the enemy is shooting + if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) + f = 360; + else + f = 90 + 90 - (90 - (squaredist > Square(810) ? Square(810) : squaredist) / (810 * 9)); + //check if the enemy is visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i); + if (vis <= 0) continue; + //if the enemy is quite far away, not shooting and the bot is not damaged + if (curenemy < 0 && squaredist > Square(100) && !healthdecrease && !EntityIsShooting(&entinfo)) + { + //check if we can avoid this enemy + VectorSubtract(bs->origin, entinfo.origin, dir); + vectoangles(dir, angles); + //if the bot isn't in the fov of the enemy + if (!InFieldOfVision(entinfo.angles, 90, angles)) { + //update some stuff for this enemy + BotUpdateBattleInventory(bs, i); + //if the bot doesn't really want to fight + if (BotWantsToRetreat(bs)) continue; + } + } + //found an enemy + bs->enemy = entinfo.number; + if (curenemy >= 0) bs->enemysight_time = FloatTime() - 2; + else bs->enemysight_time = FloatTime(); + bs->enemysuicide = qfalse; + bs->enemydeath_time = 0; + bs->enemyvisible_time = FloatTime(); + return qtrue; + } + return qfalse; +} + +/* +================== +BotTeamFlagCarrierVisible +================== +*/ +int BotTeamFlagCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotTeamFlagCarrier +================== +*/ +int BotTeamFlagCarrier(bot_state_t *bs) { + int i; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotEnemyFlagCarrierVisible +================== +*/ +int BotEnemyFlagCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotVisibleTeamMatesAndEnemies +================== +*/ +void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range) { + int i; + float vis; + aas_entityinfo_t entinfo; + vec3_t dir; + + if (teammates) + *teammates = 0; + if (enemies) + *enemies = 0; + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if not within range + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) > Square(range)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) { + if (teammates) + (*teammates)++; + } + else { + if (enemies) + (*enemies)++; + } + } +} + +#ifdef MISSIONPACK +/* +================== +BotTeamCubeCarrierVisible +================== +*/ +int BotTeamCubeCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) continue; + //if this player is carrying a flag + if (!EntityCarriesCubes(&entinfo)) continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) continue; + // + return i; + } + return -1; +} + +/* +================== +BotEnemyCubeCarrierVisible +================== +*/ +int BotEnemyCubeCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesCubes(&entinfo)) continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} +#endif + +/* +================== +BotAimAtEnemy +================== +*/ +void BotAimAtEnemy(bot_state_t *bs) { + int i, enemyvisible; + float dist, f, aim_skill, aim_accuracy, speed, reactiontime; + vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity; + vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; + weaponinfo_t wi; + + aas_entityinfo_t entinfo; +// bot_goal_t goal; + bsp_trace_t trace; + vec3_t target; + + //if the bot has no enemy + if (bs->enemy < 0) { + return; + } + //get the enemy entity information + BotEntityInfo(bs->enemy, &entinfo); + //if this is not a player (should be an obelisk) + if (bs->enemy >= maxclients->integer) { + //if the obelisk is visible + VectorCopy(entinfo.origin, target); +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 32; + } +#endif + //aim at the obelisk + VectorSubtract(target, bs->eye, dir); + vectoangles(dir, bs->ideal_viewangles); + //set the aim target before trying to attack + VectorCopy(target, bs->aimtarget); + return; + } + // + //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy); + // + aim_skill = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1); + aim_accuracy = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); + // + if (aim_skill > 0.95) { + //don't aim too early + reactiontime = 0.5 * gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); + if (bs->enemysight_time > FloatTime() - reactiontime) return; + if (bs->teleport_time > FloatTime() - reactiontime) return; + } + + //get the weapon information + gi.BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the weapon specific aim accuracy and or aim skill +/* // FIXME non-hardcoded crapass solution, please + if (wi.number == WP_MACHINEGUN) { + aim_accuracy = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1); + } + else if (wi.number == WP_SHOTGUN) { + aim_accuracy = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1); + } + else if (wi.number == WP_GRENADE_LAUNCHER) { + aim_accuracy = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1); + aim_skill = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1); + } + else if (wi.number == WP_ROCKET_LAUNCHER) { + aim_accuracy = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1); + aim_skill = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1); + } + else if (wi.number == WP_LIGHTNING) { + aim_accuracy = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1); + } + else if (wi.number == WP_RAILGUN) { + aim_accuracy = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1); + } + else if (wi.number == WP_PLASMAGUN) { + aim_accuracy = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1); + aim_skill = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1); + } + else if (wi.number == WP_BFG) { + aim_accuracy = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1); + aim_skill = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1); + } +*/ + // + if (aim_accuracy <= 0) aim_accuracy = 0.0001f; + //get the enemy entity information + BotEntityInfo(bs->enemy, &entinfo); + //if the enemy is invisible then shoot crappy most of the time + if (EntityIsInvisible(&entinfo)) { + if (random() > 0.1) aim_accuracy *= 0.4f; + } + // + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity); + VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity); + //enemy origin and velocity is remembered every 0.5 seconds + if (bs->enemyposition_time < FloatTime()) { + // + bs->enemyposition_time = FloatTime() + 0.5; + VectorCopy(enemyvelocity, bs->enemyvelocity); + VectorCopy(entinfo.origin, bs->enemyorigin); + } + //if not extremely skilled + if (aim_skill < 0.9) { + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy moved a bit + if (VectorLengthSquared(dir) > Square(48)) { + //if the enemy changed direction + if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) { + //aim accuracy should be worse now + aim_accuracy *= 0.7f; + } + } + } + //check visibility of enemy + enemyvisible = (int)BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy); + //if the enemy is visible + if (enemyvisible) { + // + VectorCopy(entinfo.origin, bestorigin); + bestorigin[2] += (entinfo.maxs[2] * 0.5); // BOTTODO percent of height to aim at, this is center of mass (0.75) instead of waist (0.5) + //get the start point shooting from + //NOTE: the x and y projectile start offsets are ignored + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + start[2] += wi.offset[2]; + // + BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT); + //if the enemy is NOT hit + if (trace.fraction <= 1 && trace.ent != entinfo.number) { + bestorigin[2] += 16; + } + //if it is not an instant hit weapon the bot might want to predict the enemy + if (wi.speed) { + // + VectorSubtract(bestorigin, bs->origin, dir); + dist = VectorLength(dir); + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy is NOT pretty far away and strafing just small steps left and right + if (!(dist > 100 && VectorLengthSquared(dir) < Square(32))) { + //if skilled anough do exact prediction + if (aim_skill > 0.8) { // && + //if the weapon is ready to fire +// bs->cur_ps.weaponstate == WEAPON_READY) { + aas_clientmove_t move; + vec3_t origin; + + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + // + VectorScale(dir, 1 / entinfo.update_time, dir); + // + VectorCopy(entinfo.origin, origin); + origin[2] += 1; + // + VectorClear(cmdmove); + //AAS_ClearShownDebugLines(); + gi.AAS_PredictClientMovement(&move, bs->enemy, origin, + PRESENCE_CROUCH, qfalse, + dir, cmdmove, 0, + (int)(dist * 10 / wi.speed), 0.1f, 0, 0, qfalse); + VectorCopy(move.endpos, bestorigin); + //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", FloatTime(), VectorLength(dir), dist * 10 / wi.speed); + } + //if not that skilled do linear prediction + else if (aim_skill > 0.4) { + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + dir[2] = 0; + // + speed = VectorNormalize(dir) / entinfo.update_time; + //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed); + //best spot to aim at + VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin); + } + } + } + //if the projectile does radial damage + if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) { + //if the enemy isn't standing significantly higher than the bot + if (entinfo.origin[2] < bs->origin[2] + 16) { + //try to aim at the ground in front of the enemy + VectorCopy(entinfo.origin, end); + end[2] -= 64; + BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT); + // + VectorCopy(bestorigin, groundtarget); + if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16; + else groundtarget[2] = trace.endpos[2] - 8; + //trace a line from projectile start to ground target + BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT); + //if hitpoint is not vertically too far from the ground target + if (fabs(trace.endpos[2] - groundtarget[2]) < 50) { + VectorSubtract(trace.endpos, groundtarget, dir); + //if the hitpoint is near anough the ground target + if (VectorLengthSquared(dir) < Square(60)) { + VectorSubtract(trace.endpos, start, dir); + //if the hitpoint is far anough from the bot + if (VectorLengthSquared(dir) > Square(100)) { + //check if the bot is visible from the ground target + trace.endpos[2] += 1; + BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT); + if (trace.fraction >= 1) { + //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time()); + VectorCopy(groundtarget, bestorigin); + } + } + } + } + } + } + bestorigin[0] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[1] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[2] += 10 * crandom() * (1 - aim_accuracy); + } + else { + // + VectorCopy(bs->lastenemyorigin, bestorigin); + bestorigin[2] += 8; + //if the bot is skilled anough + /* BOTTODO FIXME + if (aim_skill > 0.5) { + //do prediction shots around corners + if (wi.number == WP_BFG || + wi.number == WP_ROCKET_LAUNCHER || + wi.number == WP_GRENADE_LAUNCHER) { + //create the chase goal + goal.entitynum = bs->client; + goal.areanum = bs->areanum; + VectorCopy(bs->eye, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + // + if (gi.BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) { + VectorSubtract(target, bs->eye, dir); + if (VectorLengthSquared(dir) > Square(80)) { + VectorCopy(target, bestorigin); + bestorigin[2] -= 20; + } + } + aim_accuracy = 1; + } + } +*/ + aim_accuracy = 1; + } + // + if (enemyvisible) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT); + VectorCopy(trace.endpos, bs->aimtarget); + } + else { + VectorCopy(bestorigin, bs->aimtarget); + } + //get aim direction + VectorSubtract(bestorigin, bs->eye, dir); + // + +// if (wi.number == WP_MACHINEGUN || // FIXME +// wi.number == WP_SHOTGUN || +// wi.number == WP_LIGHTNING || +// wi.number == WP_RAILGUN) { + //distance towards the enemy + dist = VectorLength(dir); + if (dist > 150) dist = 150; + f = 0.6 + dist / 150 * 0.4; + aim_accuracy *= f; +// } + //add some random stuff to the aim direction depending on the aim accuracy + if (aim_accuracy < 0.8) { + VectorNormalize(dir); + for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy); + } + //set the ideal view angles + vectoangles(dir, bs->ideal_viewangles); + //take the weapon spread into account for lower skilled bots + bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + //if the bots should be really challenging + if (bot_challenge.integer) { + //if the bot is really accurate and has the enemy in view for some time + if (aim_accuracy > 0.9 && bs->enemysight_time < FloatTime() - 1) { + //set the view angles directly + if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; + VectorCopy(bs->ideal_viewangles, bs->viewangles); + gi.EA_View(bs->client, bs->viewangles); + } + } +} + +/* +================== +BotCheckAttack +================== +*/ +void BotCheckAttack(bot_state_t *bs) { + float points, reactiontime, fov, firethrottle; + int attackentity; + bsp_trace_t bsptrace; + //float selfpreservation; + vec3_t forward, right, start, end, dir, angles; + weaponinfo_t wi; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + attackentity = bs->enemy; + // + BotEntityInfo(attackentity, &entinfo); + // if not attacking a player + if (attackentity >= maxclients->integer) { +#ifdef MISSIONPACKBOTTODO + // if attacking an obelisk + if ( entinfo.number == redobelisk.entitynum || + entinfo.number == blueobelisk.entitynum ) { + // if obelisk is respawning return + if ( g_entities[entinfo.number].activator && + g_entities[entinfo.number].activator->s.frame == 2 ) { + return; + } + } +#endif + } + // + reactiontime = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); + if (bs->enemysight_time > FloatTime() - reactiontime) return; + if (bs->teleport_time > FloatTime() - reactiontime) return; + //if changing weapons +// if (bs->weaponchange_time > FloatTime() - 0.1) return; // BOTTODO + //check fire throttle characteristic + if (bs->firethrottlewait_time > FloatTime()) return; + firethrottle = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1); + if (bs->firethrottleshoot_time < FloatTime()) { + if (random() > firethrottle) { + gi.EA_ToggleFireState(bs->client); + bs->firethrottlewait_time = FloatTime() + firethrottle; + bs->firethrottleshoot_time = 0; + } + else { + bs->firethrottleshoot_time = FloatTime() + 1 - firethrottle; + bs->firethrottlewait_time = 0; + } + } + // + // + VectorSubtract(bs->aimtarget, bs->eye, dir); + // +/* FIXME + if (bs->weaponnum == WP_GAUNTLET) { + if (VectorLengthSquared(dir) > Square(60)) { + return; + } + } +*/ + if (VectorLengthSquared(dir) < Square(100)) + fov = 120; + else + fov = 50; + // + vectoangles(dir, angles); + if (!InFieldOfVision(bs->viewangles, fov, angles)) + return; + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (bsptrace.fraction < 1 && bsptrace.ent != attackentity) + return; + + //get the weapon info + gi.BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the start point shooting from + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + AngleVectors(bs->viewangles, forward, right, NULL); + start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; + start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; + start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2]; + //end point aiming at + VectorMA(start, 1000, forward, end); + //a little back to make sure not inside a very close enemy + VectorMA(start, -12, forward, start); + BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT); + //if the entity is a client + if (trace.ent > 0 && trace.ent <= maxclients->integer) { + if (trace.ent != attackentity) { + //if a teammate is hit + if (BotSameTeam(bs, trace.ent)) + return; + } + } + //if won't hit the enemy or not attacking a player (obelisk) + if (trace.ent != attackentity || attackentity >= maxclients->integer) { + //if the projectile does radial damage + if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { + if (trace.fraction * 1000 < wi.proj.radius) { + points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5; + if (points > 0) { + return; + } + } + //FIXME: check if a teammate gets radial damage + } + } + //if fire has to be release to activate weapon + if (wi.flags & WFL_FIRERELEASED) { + if (bs->flags & BFL_ATTACKED) { + gi.EA_Attack(bs->client, wi.primarydangerous, wi.altdangerous); + } + } + else { + gi.EA_Attack(bs->client, wi.primarydangerous, wi.altdangerous); + } + bs->flags ^= BFL_ATTACKED; +} + +/* +================== +BotMapScripts +================== +*/ +void BotMapScripts(bot_state_t *bs) { + char info[1024]; + char mapname[128]; + int i, shootbutton; + float aim_accuracy; + aas_entityinfo_t entinfo; + vec3_t dir; + + gi.SV_GetServerinfo(info, sizeof(info)); + + strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); + mapname[sizeof(mapname)-1] = '\0'; + + if (!Q_stricmp(mapname, "q3tourney6")) { + vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; + vec3_t buttonorg = {304, 352, 920}; + //NOTE: NEVER use the func_bobbing in q3tourney6 + bs->tfl &= ~TFL_FUNCBOB; + //if the bot is below the bounding box + if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) { + if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) { + if (bs->origin[2] < mins[2]) { + return; + } + } + } + shootbutton = qfalse; + //if an enemy is below this bounding box then shoot the button + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + // + if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) { + if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) { + if (entinfo.origin[2] < mins[2]) { + //if there's a team mate below the crusher + if (BotSameTeam(bs, i)) { + shootbutton = qfalse; + break; + } + else { + shootbutton = qtrue; + } + } + } + } + } + if (shootbutton) { + bs->flags |= BFL_IDEALVIEWSET; + VectorSubtract(buttonorg, bs->eye, dir); + vectoangles(dir, bs->ideal_viewangles); + aim_accuracy = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); + bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + // + if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) { + gi.EA_Attack(bs->client,0,0); + } + } + } + else if (!Q_stricmp(mapname, "mpq3tourney6")) { + //NOTE: NEVER use the func_bobbing in mpq3tourney6 + bs->tfl &= ~TFL_FUNCBOB; + } +} + +/* +================== +BotSetMovedir +================== +*/ +// bk001205 - made these static +static vec3_t VEC_UP = {0, -1, 0}; +static vec3_t MOVEDIR_UP = {0, 0, 1}; +static vec3_t VEC_DOWN = {0, -2, 0}; +static vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void BotSetMovedir(vec3_t angles, vec3_t movedir) { + if (VectorCompare(angles, VEC_UP)) { + VectorCopy(MOVEDIR_UP, movedir); + } + else if (VectorCompare(angles, VEC_DOWN)) { + VectorCopy(MOVEDIR_DOWN, movedir); + } + else { + AngleVectors(angles, movedir, NULL, NULL); + } +} + +/* +================== +BotModelMinsMaxs + +this is ugly +================== +*/ +int BotModelMinsMaxs(int modelindex, int eType, int contents, vec3_t mins, vec3_t maxs) { + gentity_t *ent; + int i; + + ent = &g_entities[0]; + for (i = 0; i < globals.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( eType && ent->s.eType != eType) { + continue; + } + if ( contents && ent->contents != contents) { + continue; + } + if (ent->s.modelindex == modelindex) { + if (mins) + VectorAdd(ent->currentOrigin, ent->mins, mins); + if (maxs) + VectorAdd(ent->currentOrigin, ent->maxs, maxs); + return i; + } + } + if (mins) + VectorClear(mins); + if (maxs) + VectorClear(maxs); + return 0; +} + +/* +================== +BotFuncButtonGoal +================== +*/ +int BotFuncButtonActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int i, areas[10], numareas, modelindex, entitynum; + char model[128]; + float lip, dist, health, angle; + vec3_t size, start, end, mins, maxs, angles, points[10]; + vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs; + vec3_t extramins = {1, 1, 1}, extramaxs = {-1, -1, -1}; + bsp_trace_t bsptrace; + + activategoal->shoot = qfalse; + VectorClear(activategoal->target); + //create a bot goal towards the button + gi.AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); + //get the lip of the button + gi.AAS_FloatForBSPEpairKey(bspent, "lip", &lip); + if (!lip) lip = 4; + //get the move direction from the angle + gi.AAS_FloatForBSPEpairKey(bspent, "angle", &angle); + VectorSet(angles, 0, angle, 0); + BotSetMovedir(angles, movedir); + //button size + VectorSubtract(maxs, mins, size); + //button origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + //touch distance of the button + dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2]; + dist *= 0.5; + // + gi.AAS_FloatForBSPEpairKey(bspent, "health", &health); + //if the button is shootable + if (health) { + //calculate the shoot target + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, activategoal->target); + activategoal->shoot = qtrue; + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, goalorigin, bs->entitynum, MASK_SHOT); + // if the button is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == entitynum) { + // + activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable button + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + VectorCopy(bs->origin, activategoal->goal.origin); + activategoal->goal.areanum = bs->areanum; + VectorSet(activategoal->goal.mins, -8, -8, -8); + VectorSet(activategoal->goal.maxs, 8, 8, 8); + // + return qtrue; + } + else { + //create a goal from where the button is visible and shoot at the button from there + //add bounding box size to the dist + gi.AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); + for (i = 0; i < 3; i++) { + if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); + else dist += fabs(movedir[i]) * fabs(bboxmins[i]); + } + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 512; + numareas = gi.AAS_TraceAreas(start, end, areas, points, 10); + // + for (i = numareas-1; i >= 0; i--) { + if (gi.AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < 0) { + // FIXME: trace forward and maybe in other directions to find a valid area + } + if (i >= 0) { + // + VectorCopy(points[i], activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSet(activategoal->goal.mins, 8, 8, 8); + VectorSet(activategoal->goal.maxs, -8, -8, -8); + // + for (i = 0; i < 3; i++) + { + if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); + else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); + } //end for + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + } + return qfalse; + } + else { + //add bounding box size to the dist + gi.AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); + for (i = 0; i < 3; i++) { + if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); + else dist += fabs(movedir[i]) * fabs(bboxmins[i]); + } + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 100; + numareas = gi.AAS_TraceAreas(start, end, areas, NULL, 10); + // + for (i = 0; i < numareas; i++) { + if (gi.AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < numareas) { + // + VectorCopy(origin, activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSubtract(mins, origin, activategoal->goal.mins); + VectorSubtract(maxs, origin, activategoal->goal.maxs); + // + for (i = 0; i < 3; i++) + { + if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); + else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); + } //end for + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotFuncDoorGoal +================== +*/ +int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int modelindex, entitynum; + char model[MAX_INFO_STRING]; + vec3_t mins, maxs, origin, angles; + + //shoot at the shootable door + gi.AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); + //door origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + VectorCopy(origin, activategoal->target); + activategoal->shoot = qtrue; + // + activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable door + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + VectorCopy(bs->origin, activategoal->goal.origin); + activategoal->goal.areanum = bs->areanum; + VectorSet(activategoal->goal.mins, -8, -8, -8); + VectorSet(activategoal->goal.maxs, 8, 8, 8); + return qtrue; +} + +/* +================== +BotTriggerMultipleGoal +================== +*/ +int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int i, areas[10], numareas, modelindex, entitynum; + char model[128]; + vec3_t start, end, mins, maxs, angles; + vec3_t origin, goalorigin; + + activategoal->shoot = qfalse; + VectorClear(activategoal->target); + //create a bot goal towards the trigger + gi.AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_MONSTERCLIP, mins, maxs); // FIXME FIXME this will keep anything from working right + //trigger origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + VectorCopy(origin, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 100; + numareas = gi.AAS_TraceAreas(start, end, areas, NULL, 10); + // + for (i = 0; i < numareas; i++) { + if (gi.AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < numareas) { + VectorCopy(origin, activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSubtract(mins, origin, activategoal->goal.mins); + VectorSubtract(maxs, origin, activategoal->goal.maxs); + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + return qfalse; +} + +/* +================== +BotPopFromActivateGoalStack +================== +*/ +int BotPopFromActivateGoalStack(bot_state_t *bs) { + if (!bs->activatestack) + return qfalse; + BotEnableActivateGoalAreas(bs->activatestack, qtrue); + bs->activatestack->inuse = qfalse; + bs->activatestack->justused_time = FloatTime(); + bs->activatestack = bs->activatestack->next; + return qtrue; +} + +/* +================== +BotPushOntoActivateGoalStack +================== +*/ +int BotPushOntoActivateGoalStack(bot_state_t *bs, bot_activategoal_t *activategoal) { + int i, best; + float besttime; + + best = -1; + besttime = FloatTime() + 9999; + // + for (i = 0; i < MAX_ACTIVATESTACK; i++) { + if (!bs->activategoalheap[i].inuse) { + if (bs->activategoalheap[i].justused_time < besttime) { + besttime = bs->activategoalheap[i].justused_time; + best = i; + } + } + } + if (best != -1) { + memcpy(&bs->activategoalheap[best], activategoal, sizeof(bot_activategoal_t)); + bs->activategoalheap[best].inuse = qtrue; + bs->activategoalheap[best].next = bs->activatestack; + bs->activatestack = &bs->activategoalheap[best]; + return qtrue; + } + return qfalse; +} + +/* +================== +BotClearActivateGoalStack +================== +*/ +void BotClearActivateGoalStack(bot_state_t *bs) { + while(bs->activatestack) + BotPopFromActivateGoalStack(bs); +} + +/* +================== +BotEnableActivateGoalAreas +================== +*/ +void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable) { + int i; + + if (activategoal->areasdisabled == !enable) + return; + for (i = 0; i < activategoal->numareas; i++) + gi.AAS_EnableRoutingArea( activategoal->areas[i], enable ); + activategoal->areasdisabled = !enable; +} + +/* +================== +BotIsGoingToActivateEntity +================== +*/ +int BotIsGoingToActivateEntity(bot_state_t *bs, int entitynum) { + bot_activategoal_t *a; + int i; + + for (a = bs->activatestack; a; a = a->next) { + if (a->time < FloatTime()) + continue; + if (a->goal.entitynum == entitynum) + return qtrue; + } + for (i = 0; i < MAX_ACTIVATESTACK; i++) { + if (bs->activategoalheap[i].inuse) + continue; + // + if (bs->activategoalheap[i].goal.entitynum == entitynum) { + // if the bot went for this goal less than 2 seconds ago + if (bs->activategoalheap[i].justused_time > FloatTime() - 2) + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotGetActivateGoal + + returns the number of the bsp entity to activate + goal->entitynum will be set to the game entity to activate +================== +*/ +//#define OBSTACLEDEBUG + +int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activategoal) { + int i, ent, cur_entities[10], spawnflags, modelindex, areas[MAX_ACTIVATEAREAS*2], numareas, t; + char model[MAX_INFO_STRING], tmpmodel[128]; + char target[128], classname[128]; + float health; + char targetname[10][128]; + aas_entityinfo_t entinfo; + aas_areainfo_t areainfo; + vec3_t origin, angles, absmins, absmaxs; + + memset(activategoal, 0, sizeof(bot_activategoal_t)); + BotEntityInfo(entitynum, &entinfo); + Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex); + for (ent = gi.AAS_NextBSPEntity(0); ent; ent = gi.AAS_NextBSPEntity(ent)) { + if (!gi.AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue; + if (!strcmp(model, tmpmodel)) break; + } + if (!ent) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity found with model %s\n", model); + return 0; + } + gi.AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); + if (!classname) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model %s has no classname\n", model); + return 0; + } + //if it is a door + if (!strcmp(classname, "func_door")) { + if (gi.AAS_FloatForBSPEpairKey(ent, "health", &health)) { + //if the door has health then the door must be shot to open + if (health) { + BotFuncDoorActivateGoal(bs, ent, activategoal); + return ent; + } + } + // + gi.AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + // if the door starts open then just wait for the door to return + if ( spawnflags & 1 ) + return 0; + //get the door origin + if (!gi.AAS_VectorForBSPEpairKey(ent, "origin", origin)) { + VectorClear(origin); + } + //if the door is open or opening already + if (!VectorCompare(origin, entinfo.origin)) + return 0; + // store all the areas the door is in + gi.AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); + if (*model) { + modelindex = atoi(model+1); + if (modelindex) { + VectorClear(angles); + BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs); + // + numareas = gi.AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2); + // store the areas with reachabilities first + for (i = 0; i < numareas; i++) { + if (activategoal->numareas >= MAX_ACTIVATEAREAS) + break; + if ( !gi.AAS_AreaReachability(areas[i]) ) { + continue; + } + gi.AAS_AreaInfo(areas[i], &areainfo); + if (areainfo.contents & AREACONTENTS_MOVER) { + activategoal->areas[activategoal->numareas++] = areas[i]; + } + } + // store any remaining areas + for (i = 0; i < numareas; i++) { + if (activategoal->numareas >= MAX_ACTIVATEAREAS) + break; + if ( gi.AAS_AreaReachability(areas[i]) ) { + continue; + } + gi.AAS_AreaInfo(areas[i], &areainfo); + if (areainfo.contents & AREACONTENTS_MOVER) { + activategoal->areas[activategoal->numareas++] = areas[i]; + } + } + } + } + } + // if the bot is blocked by or standing on top of a button + if (!strcmp(classname, "func_button")) { + return 0; + } + // get the targetname so we can find an entity with a matching target + if (!gi.AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model \"%s\" has no targetname\n", model); + } + return 0; + } + // allow tree-like activation + cur_entities[0] = gi.AAS_NextBSPEntity(0); + for (i = 0; i >= 0 && i < 10;) { + for (ent = cur_entities[i]; ent; ent = gi.AAS_NextBSPEntity(ent)) { + if (!gi.AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue; + if (!strcmp(targetname[i], target)) { + cur_entities[i] = gi.AAS_NextBSPEntity(ent); + break; + } + } + if (!ent) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity with target \"%s\"\n", targetname[i]); + } + i--; + continue; + } + if (!gi.AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with target \"%s\" has no classname\n", targetname[i]); + } + continue; + } + // BSP button model + if (!strcmp(classname, "func_button")) { + // + if (!BotFuncButtonActivateGoal(bs, ent, activategoal)) + continue; + // if the bot tries to activate this button already + if ( bs->activatestack && bs->activatestack->inuse && + bs->activatestack->goal.entitynum == activategoal->goal.entitynum && + bs->activatestack->time > FloatTime() && + bs->activatestack->start_time < FloatTime() - 2) + continue; + // if the bot is in a reachability area + if ( gi.AAS_AreaReachability(bs->areanum) ) { + // disable all areas the blocking entity is in + BotEnableActivateGoalAreas( activategoal, qfalse ); + // + t = gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); + // if the button is not reachable + if (!t) { + continue; + } + activategoal->time = FloatTime() + t * 0.01 + 5; + } + return ent; + } + // invisible trigger multiple box + else if (!strcmp(classname, "trigger_multiple")) { + // + if (!BotTriggerMultipleActivateGoal(bs, ent, activategoal)) + continue; + // if the bot tries to activate this trigger already + if ( bs->activatestack && bs->activatestack->inuse && + bs->activatestack->goal.entitynum == activategoal->goal.entitynum && + bs->activatestack->time > FloatTime() && + bs->activatestack->start_time < FloatTime() - 2) + continue; + // if the bot is in a reachability area + if ( gi.AAS_AreaReachability(bs->areanum) ) { + // disable all areas the blocking entity is in + BotEnableActivateGoalAreas( activategoal, qfalse ); + // + t = gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); + // if the trigger is not reachable + if (!t) { + continue; + } + activategoal->time = FloatTime() + t * 0.01 + 5; + } + return ent; + } + else if (!strcmp(classname, "func_timer")) { + // just skip the func_timer + continue; + } + // the actual button or trigger might be linked through a target_relay or target_delay + else if (!strcmp(classname, "target_relay") || !strcmp(classname, "target_delay")) { + if (gi.AAS_ValueForBSPEpairKey(ent, "targetname", targetname[i+1], sizeof(targetname[0]))) { + i++; + cur_entities[i] = gi.AAS_NextBSPEntity(0); + } + } + } +#ifdef OBSTACLEDEBUG + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no valid activator for entity with target \"%s\"\n", targetname[0]); +#endif + return 0; +} + +/* +================== +BotGoForActivateGoal +================== +*/ +int BotGoForActivateGoal(bot_state_t *bs, bot_activategoal_t *activategoal) { + aas_entityinfo_t activateinfo; + + activategoal->inuse = qtrue; + if (!activategoal->time) + activategoal->time = FloatTime() + 10; + activategoal->start_time = FloatTime(); + BotEntityInfo(activategoal->goal.entitynum, &activateinfo); + VectorCopy(activateinfo.origin, activategoal->origin); + // + if (BotPushOntoActivateGoalStack(bs, activategoal)) { + // enter the activate entity AI node + AIEnter_Seek_ActivateEntity(bs, "BotGoForActivateGoal"); + return qtrue; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(activategoal, qtrue); + return qfalse; + } +} + +/* +================== +BotPrintActivateGoalInfo +================== +*/ +void BotPrintActivateGoalInfo(bot_state_t *bs, bot_activategoal_t *activategoal, int bspent) { + char netname[MAX_NETNAME]; + char classname[128]; + char buf[128]; + + ClientName(bs->client, netname, sizeof(netname)); + gi.AAS_ValueForBSPEpairKey(bspent, "classname", classname, sizeof(classname)); + if (activategoal->shoot) { + Com_sprintf(buf, sizeof(buf), "%s: I have to shoot at a %s from %1.1f %1.1f %1.1f in area %d\n", + netname, classname, + activategoal->goal.origin[0], + activategoal->goal.origin[1], + activategoal->goal.origin[2], + activategoal->goal.areanum); + } + else { + Com_sprintf(buf, sizeof(buf), "%s: I have to activate a %s at %1.1f %1.1f %1.1f in area %d\n", + netname, classname, + activategoal->goal.origin[0], + activategoal->goal.origin[1], + activategoal->goal.origin[2], + activategoal->goal.areanum); + } + gi.EA_Say(bs->client, buf); +} + +/* +================== +BotRandomMove +================== +*/ +void BotRandomMove(bot_state_t *bs, bot_moveresult_t *moveresult) { + vec3_t dir, angles; + + angles[0] = 0; + angles[1] = random() * 360; + angles[2] = 0; + AngleVectors(angles, dir, NULL, NULL); + + gi.BotMoveInDirection(bs->ms, dir, sv_maxspeed->value, MOVE_WALK); + + moveresult->failure = qfalse; + VectorCopy(dir, moveresult->movedir); +} + +/* +================== +BotAIBlocked + +Very basic handling of bots being blocked by other entities. +Check what kind of entity is blocking the bot and try to activate +it. If that's not an option then try to walk around or over the entity. +Before the bot ends in this part of the AI it should predict which doors to +open, which buttons to activate etc. +================== +*/ +void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { + int movetype, bspent; + vec3_t hordir, start, end, mins, maxs, sideward, angles, up = {0, 0, 1}; + aas_entityinfo_t entinfo; + bot_activategoal_t activategoal; + + // if the bot is not blocked by anything + if (!moveresult->blocked) { + bs->notblocked_time = FloatTime(); + return; + } + // if stuck in a solid area + if ( moveresult->type == RESULTTYPE_INSOLIDAREA ) { + // move in a random direction in the hope to get out + BotRandomMove(bs, moveresult); + // + return; + } + // get info for the entity that is blocking the bot + BotEntityInfo(moveresult->blockentity, &entinfo); +#ifdef OBSTACLEDEBUG + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex); +#endif //OBSTACLEDEBUG + // if blocked by a bsp model and the bot wants to activate it + if (activate && entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex) { + // find the bsp entity which should be activated in order to get the blocking entity out of the way + bspent = BotGetActivateGoal(bs, entinfo.number, &activategoal); + if (bspent) { + // + if (bs->activatestack && !bs->activatestack->inuse) + bs->activatestack = NULL; + // if not already trying to activate this entity + if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { + // + BotGoForActivateGoal(bs, &activategoal); + } + // if ontop of an obstacle or + // if the bot is not in a reachability area it'll still + // need some dynamic obstacle avoidance, otherwise return + if (!(moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) && + gi.AAS_AreaReachability(bs->areanum)) + return; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(&activategoal, qtrue); + } + } + // just some basic dynamic obstacle avoidance code + hordir[0] = moveresult->movedir[0]; + hordir[1] = moveresult->movedir[1]; + hordir[2] = 0; + // if no direction just take a random direction + if (VectorNormalize(hordir) < 0.1) { + VectorSet(angles, 0, 360 * random(), 0); + AngleVectors(angles, hordir, NULL, NULL); + } + // + //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP; + //else + movetype = MOVE_WALK; + // if there's an obstacle at the bot's feet and head then + // the bot might be able to crouch through + VectorCopy(bs->origin, start); + start[2] += 18; + VectorMA(start, 5, hordir, end); +// VectorSet(mins, -16, -16, -24); +// VectorSet(maxs, 16, 16, 4); + VectorSet(mins, -22, -22, 0); + VectorSet(maxs, 22, 22, 50); + // + //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID); + //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH; + // get the sideward vector + CrossProduct(hordir, up, sideward); + // + if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward); + // try to crouch straight forward? + if (movetype != MOVE_CROUCH || !gi.BotMoveInDirection(bs->ms, hordir, sv_maxspeed->value, movetype)) { + // perform the movement + if (!gi.BotMoveInDirection(bs->ms, sideward, sv_maxspeed->value, movetype)) { + // flip the avoid direction flag + bs->flags ^= BFL_AVOIDRIGHT; + // flip the direction + // VectorNegate(sideward, sideward); + VectorMA(sideward, -1, hordir, sideward); + // move in the other direction + gi.BotMoveInDirection(bs->ms, sideward, sv_maxspeed->value, movetype); + } + } + // + if (bs->notblocked_time < FloatTime() - 0.4) { + // just reset goals and hope the bot will go into another direction? + // is this still needed?? + if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; + else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; + } +} + +/* +================== +BotAIPredictObstacles + +Predict the route towards the goal and check if the bot +will be blocked by certain obstacles. When the bot has obstacles +on it's path the bot should figure out if they can be removed +by activating certain entities. +================== +*/ +int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal) { + int modelnum, entitynum, bspent; + bot_activategoal_t activategoal; + aas_predictroute_t route; + + if (!bot_predictobstacles.integer) + return qfalse; + + // always predict when the goal change or at regular intervals + if (bs->predictobstacles_goalareanum == goal->areanum && + bs->predictobstacles_time > FloatTime() - 6) { + return qfalse; + } + bs->predictobstacles_goalareanum = goal->areanum; + bs->predictobstacles_time = FloatTime(); + + // predict at most 100 areas or 10 seconds ahead + gi.AAS_PredictRoute(&route, bs->areanum, bs->origin, + goal->areanum, bs->tfl, 100, 1000, + RSE_USETRAVELTYPE|RSE_ENTERCONTENTS, + AREACONTENTS_MOVER, TFL_BRIDGE, 0); + // if bot has to travel through an area with a mover + if (route.stopevent & RSE_ENTERCONTENTS) { + // if the bot will run into a mover + if (route.endcontents & AREACONTENTS_MOVER) { + //NOTE: this only works with bspc 2.1 or higher + modelnum = (route.endcontents & AREACONTENTS_MODELNUM) >> AREACONTENTS_MODELNUMSHIFT; + if (modelnum) { + // + entitynum = BotModelMinsMaxs(modelnum, ET_MOVER, 0, NULL, NULL); + if (entitynum) { + //NOTE: BotGetActivateGoal already checks if the door is open or not + bspent = BotGetActivateGoal(bs, entitynum, &activategoal); + if (bspent) { + // + if (bs->activatestack && !bs->activatestack->inuse) + bs->activatestack = NULL; + // if not already trying to activate this entity + if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { + // + //BotAI_Print(PRT_MESSAGE, "blocked by mover model %d, entity %d ?\n", modelnum, entitynum); + // + BotGoForActivateGoal(bs, &activategoal); + return qtrue; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(&activategoal, qtrue); + } + } + } + } + } + } + else if (route.stopevent & RSE_USETRAVELTYPE) { + if (route.endtravelflags & TFL_BRIDGE) { + //FIXME: check if the bridge is available to travel over + } + } + return qfalse; +} + +/* +================== +BotCheckConsoleMessages +================== +*/ +void BotCheckConsoleMessages(bot_state_t *bs) { + char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr; + float chat_reply; + int context, handle; + bot_consolemessage_t m; + bot_match_t match; + + //the name of this bot + ClientName(bs->client, botname, sizeof(botname)); + // + while((handle = gi.BotNextConsoleMessage(bs->cs, &m)) != 0) { + //if the chat state is flooded with messages the bot will read them quickly + if (gi.BotNumConsoleMessages(bs->cs) < 10) { + //if it is a chat message the bot needs some time to read it + if (m.type == CMS_CHAT && m.time > FloatTime() - (1 + random())) break; + } + // + ptr = m.message; + //if it is a chat message then don't unify white spaces and don't + //replace synonyms in the netname + if (m.type == CMS_CHAT) { + // + if (gi.BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + ptr = m.message + match.variables[MESSAGE].offset; + } + } + //unify the white spaces in the message + gi.UnifyWhiteSpaces(ptr); + //replace synonyms in the right context + context = BotSynonymContext(bs); + gi.BotReplaceSynonyms(ptr, context); + //if there's no match + if (!BotMatchMessage(bs, m.message)) { + //if it is a chat message + if (m.type == CMS_CHAT && !bot_nochat.integer) { + // + if (!gi.BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + gi.BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //don't use eliza chats with team messages + if (match.subtype & ST_TEAM) { + gi.BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + // + gi.BotMatchVariable(&match, NETNAME, netname, sizeof(netname)); + gi.BotMatchVariable(&match, MESSAGE, message, sizeof(message)); + //if this is a message from the bot self + if (bs->client == ClientFromName(netname+1)) { // BOTLIB fixed for ef2 crazy parser + gi.BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //unify the message + gi.UnifyWhiteSpaces(message); + // + gi.Cvar_Update(&bot_testrchat); + if (bot_testrchat.integer) { + // + gi.BotLibVarSet("bot_testrchat", "1"); + //if bot replies with a chat message + if (gi.BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + BotAI_Print(PRT_MESSAGE, "------------------------\n"); + } + else { + BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n"); + } + } + //if at a valid chat position and not chatting already and not in teamplay + else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs) && !TeamPlayIsOn()) { + chat_reply = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1); + if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) { + //if bot replies with a chat message + if (gi.BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + //remove the console message + gi.BotRemoveConsoleMessage(bs->cs, handle); + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "BotCheckConsoleMessages: reply chat"); + //EA_Say(bs->client, bs->cs.chatmessage); + break; + } + } + } + } + } + //remove the console message + gi.BotRemoveConsoleMessage(bs->cs, handle); + } +} + +/* +================== +BotCheckEvents +================== +*/ +void BotCheckForGrenades(bot_state_t *bs, entityState_t *state) { + // if this is not a grenade + if (state->eType != ET_MISSILE)// || state->weapon != WP_GRENADE_LAUNCHER) // FIXME + return; + // try to avoid the grenade + gi.BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); +} + +#ifdef MISSIONPACKBOTTODO +/* +================== +BotCheckForProxMines +================== +*/ +void BotCheckForProxMines(bot_state_t *bs, entityState_t *state) { + // if this is not a prox mine + if (state->eType != ET_MISSILE || state->weapon != WP_PROX_LAUNCHER) + return; + // if this prox mine is from someone on our own team + if (state->generic1 == BotTeam(bs)) + return; + // if the bot doesn't have a weapon to deactivate the mine + if (!(bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) && + !(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) && + !(bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) ) { + return; + } + // try to avoid the prox mine + gi.BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); + // + if (bs->numproxmines >= MAX_PROXMINES) + return; + bs->proxmines[bs->numproxmines] = state->number; + bs->numproxmines++; +} + +/* +================== +BotCheckForKamikazeBody +================== +*/ +void BotCheckForKamikazeBody(bot_state_t *bs, entityState_t *state) { + // if this entity is not wearing the kamikaze + if (!(state->eFlags & EF_KAMIKAZE)) + return; + // if this entity isn't dead + if (!(state->eFlags & EF_DEAD)) + return; + //remember this kamikaze body + bs->kamikazebody = state->number; +} +#endif + +/* +================== +BotCheckEvents +================== +*/ +void BotCheckEvents(bot_state_t *bs, entityState_t *state) { + // BOTTODO -- hook in our event system somehow? +/* + int event; + char buf[128]; +#ifdef MISSIONPACK + aas_entityinfo_t entinfo; +#endif + + //NOTE: this sucks, we're accessing the gentity_t directly + //but there's no other fast way to do it right now + if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) { + return; + } + bs->entityeventTime[state->number] = g_entities[state->number].eventTime; + //if it's an event only entity + if (state->eType > ET_EVENTS) { + event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS; + } + else { + event = state->event & ~EV_EVENT_BITS; + } + // + switch(event) { + //client obituary event + case EV_OBITUARY: + { + int target, attacker, mod; + + target = state->otherEntityNum; + attacker = state->otherEntityNum2; + mod = state->eventParm; + // + if (target == bs->client) { + bs->botdeathtype = mod; + bs->lastkilledby = attacker; + // + if (target == attacker || + target == ENTITYNUM_NONE || + target == ENTITYNUM_WORLD) bs->botsuicide = qtrue; + else bs->botsuicide = qfalse; + // + bs->num_deaths++; + } + //else if this client was killed by the bot + else if (attacker == bs->client) { + bs->enemydeathtype = mod; + bs->lastkilledplayer = target; + bs->killedenemy_time = FloatTime(); + // + bs->num_kills++; + } + else if (attacker == bs->enemy && target == attacker) { + bs->enemysuicide = qtrue; + } + // +#ifdef MISSIONPACK + if (gametype == GT_1FCTF) { + // + BotEntityInfo(target, &entinfo); + if ( entinfo.powerups & ( 1 << PW_NEUTRALFLAG ) ) { + if (!BotSameTeam(bs, target)) { + bs->neutralflagstatus = 3; //enemy dropped the flag + bs->flagstatuschanged = qtrue; + } + } + } +#endif + break; + } + case EV_GLOBAL_SOUND: + { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + strncpy(buf,gi.getConfigstring(CS_SOUNDS + state->eventParm), sizeof(buf)); +#ifdef MISSIONPACK + if (!strcmp(buf, "sound/items/kamikazerespawn.wav" )) { + //the kamikaze respawned so dont avoid it + BotDontAvoid(bs, "Kamikaze"); + } + else +#endif + if (!strcmp(buf, "sound/items/poweruprespawn.wav")) { + //powerup respawned... go get it + BotGoForPowerups(bs); + } + break; + } + case EV_GLOBAL_TEAM_SOUND: + { + if (gametype == GT_CTF) { + switch(state->eventParm) { + case GTS_RED_CAPTURE: + bs->blueflagstatus = 0; + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_BLUE_CAPTURE: + bs->blueflagstatus = 0; + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_RED_RETURN: + //blue flag is returned + bs->blueflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_RETURN: + //red flag is returned + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_TAKEN: + //blue flag is taken + bs->blueflagstatus = 1; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_BLUE_TAKEN: + //red flag is taken + bs->redflagstatus = 1; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + switch(state->eventParm) { + case GTS_RED_CAPTURE: + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_CAPTURE: + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_RETURN: + //flag has returned + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_RETURN: + //flag has returned + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_TAKEN: + bs->neutralflagstatus = BotTeam(bs) == TEAM_RED ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_TAKEN: + bs->neutralflagstatus = BotTeam(bs) == TEAM_BLUE ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c + bs->flagstatuschanged = qtrue; + break; + } + } +#endif + break; + } + case EV_PLAYER_TELEPORT_IN: + { + VectorCopy(state->origin, lastteleport_origin); + lastteleport_time = FloatTime(); + break; + } + case EV_GENERAL_SOUND: + { + //if this sound is played on the bot + if (state->number == bs->client) { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + //check out the sound + strncpy(buf,gi.getConfigstring(CS_SOUNDS + state->eventParm), sizeof(buf)); + //if falling into a death pit + if (!strcmp(buf, "*falling1.wav")) { + //if the bot has a personal teleporter + if (bs->inventory[INVENTORY_TELEPORTER] > 0) { + //use the holdable item + gi.EA_Use(bs->client); + } + } + } + break; + } + case EV_FOOTSTEP: + case EV_FOOTSTEP_METAL: + case EV_FOOTSPLASH: + case EV_FOOTWADE: + case EV_SWIM: + case EV_FALL_SHORT: + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: + case EV_JUMP_PAD: + case EV_JUMP: + case EV_TAUNT: + case EV_WATER_TOUCH: + case EV_WATER_LEAVE: + case EV_WATER_UNDER: + case EV_WATER_CLEAR: + case EV_ITEM_PICKUP: + case EV_GLOBAL_ITEM_PICKUP: + case EV_NOAMMO: + case EV_CHANGE_WEAPON: + case EV_FIRE_WEAPON: + //FIXME: either add to sound queue or mark player as someone making noise + break; + case EV_USE_ITEM0: + case EV_USE_ITEM1: + case EV_USE_ITEM2: + case EV_USE_ITEM3: + case EV_USE_ITEM4: + case EV_USE_ITEM5: + case EV_USE_ITEM6: + case EV_USE_ITEM7: + case EV_USE_ITEM8: + case EV_USE_ITEM9: + case EV_USE_ITEM10: + case EV_USE_ITEM11: + case EV_USE_ITEM12: + case EV_USE_ITEM13: + case EV_USE_ITEM14: + break; + } +*/ +} + +/* +================== +BotCheckSnapshot +================== +*/ +void BotCheckSnapshot(bot_state_t *bs) { + int ent; + entityState_t state; + + //remove all avoid spots + gi.BotAddAvoidSpot(bs->ms, vec3_origin, 0, AVOID_CLEAR); + //reset kamikaze body + bs->kamikazebody = 0; + //reset number of proxmines + bs->numproxmines = 0; + // + ent = 0; + while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) { + //check the entity state for events + BotCheckEvents(bs, &state); + //check for grenades the bot should avoid + BotCheckForGrenades(bs, &state); + // +#ifdef MISSIONPACKBOTTODO + //check for proximity mines which the bot should deactivate + BotCheckForProxMines(bs, &state); + //check for dead bodies with the kamikaze effect which should be gibbed + BotCheckForKamikazeBody(bs, &state); +#endif + } + //check the player state for events + BotAI_GetEntityState(bs->client, &state); + //copy the player state events to the entity state +// state.event = bs->cur_ps.externalEvent; // FIXME +// state.eventParm = bs->cur_ps.externalEventParm; + // + BotCheckEvents(bs, &state); +} + +/* +================== +BotCheckAir +================== +*/ +void BotCheckAir(bot_state_t *bs) { + if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) { + if (gi.AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { + return; + } + } + bs->lastair_time = FloatTime(); +} + +/* +================== +BotAlternateRoute +================== +*/ +bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal) { + int t; + + // if the bot has an alternative route goal + if (bs->altroutegoal.areanum) { + // + if (bs->reachedaltroutegoal_time) + return goal; + // travel time towards alternative route goal + t = gi.AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->altroutegoal.areanum, bs->tfl); + if (t && t < 20) { + //BotAI_Print(PRT_MESSAGE, "reached alternate route goal\n"); + bs->reachedaltroutegoal_time = FloatTime(); + } + memcpy(goal, &bs->altroutegoal, sizeof(bot_goal_t)); + return &bs->altroutegoal; + } + return goal; +} + +/* +================== +BotGetAlternateRouteGoal +================== +*/ +int BotGetAlternateRouteGoal(bot_state_t *bs, int base) { + aas_altroutegoal_t *altroutegoals; + bot_goal_t *goal; + int numaltroutegoals, rnd; + + if (base == TEAM_RED) { + altroutegoals = red_altroutegoals; + numaltroutegoals = red_numaltroutegoals; + } + else { + altroutegoals = blue_altroutegoals; + numaltroutegoals = blue_numaltroutegoals; + } + if (!numaltroutegoals) + return qfalse; + rnd = (int)((float) random() * (float)numaltroutegoals); + if (rnd >= numaltroutegoals) + rnd = numaltroutegoals-1; + goal = &bs->altroutegoal; + goal->areanum = altroutegoals[rnd].areanum; + VectorCopy(altroutegoals[rnd].origin, goal->origin); + VectorSet(goal->mins, -8, -8, -8); + VectorSet(goal->maxs, 8, 8, 8); + goal->entitynum = 0; + goal->iteminfo = 0; + goal->number = 0; + goal->flags = 0; + // + bs->reachedaltroutegoal_time = 0; + return qtrue; +} + +/* +================== +BotSetupAlternateRouteGoals +================== +*/ +void BotSetupAlternativeRouteGoals(void) { + + if (altroutegoals_setup) + return; +#ifdef MISSIONPACK + if (gametype == GT_CTF) { + if (gi.BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) + BotAI_Print(PRT_WARNING, "no alt routes without Neutral Flag\n"); + if (ctf_neutralflag.areanum) { + // + red_numaltroutegoals = gi.AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = gi.AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + } + else if (gametype == GT_1FCTF) { + // + red_numaltroutegoals = gi.AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = gi.AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + else if (gametype == GT_OBELISK) { + if (gi.BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); + // + red_numaltroutegoals = gi.AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = gi.AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + else if (gametype == GT_HARVESTER) { + // + red_numaltroutegoals = gi.AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = gi.AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } +#endif + altroutegoals_setup = qtrue; +} + +/* +================== +BotDeathmatchAI +================== +*/ +void BotDeathmatchAI(bot_state_t *bs, float thinktime) { + char gender[144], name[144], buf[144]; + char userinfo[MAX_INFO_STRING]; + int i; + + //if the bot has just been setup + if (bs->setupcount > 0) { + bs->setupcount--; + if (bs->setupcount > 0) return; + //get the gender characteristic + gi.Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender)); + //set the bot gender + gi.getUserinfo(bs->client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, "sex", gender); + gi.setUserinfo(bs->client, userinfo); + //set the team + if ( !bs->map_restart && g_gametype->integer != GT_TOURNAMENT ) { + Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); + gi.EA_Command(bs->client, buf); + } + //set the chat gender + if (gender[0] == 'm') gi.BotSetChatGender(bs->cs, CHAT_GENDERMALE); + else if (gender[0] == 'f') gi.BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); + else gi.BotSetChatGender(bs->cs, CHAT_GENDERLESS); + //set the chat name + ClientName(bs->client, name, sizeof(name)); + gi.BotSetChatName(bs->cs, name, bs->client); + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; +// bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; // FIXME + // + bs->setupcount = 0; + // + BotSetupAlternativeRouteGoals(); + } + //no ideal view set + bs->flags &= ~BFL_IDEALVIEWSET; + // + if (!BotIntermission(bs)) { + //set the teleport time + BotSetTeleportTime(bs); + //update some inventory values + BotUpdateInventory(bs); + //check out the snapshot + BotCheckSnapshot(bs); + //check for air + BotCheckAir(bs); + } + //check the console messages + BotCheckConsoleMessages(bs); + //if not in the intermission and not in observer mode + if (!BotIntermission(bs) && !BotIsObserver(bs)) { + //do team AI + BotTeamAI(bs); + } + //if the bot has no ai node + if (!bs->ainode) { + AIEnter_Seek_LTG(bs, "BotDeathmatchAI: no ai node"); + } + //if the bot entered the game less than 8 seconds ago + if (!bs->entergamechat && bs->entergame_time > FloatTime() - 8) { + if (BotChat_EnterGame(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "BotDeathmatchAI: chat enter game"); + } + bs->entergamechat = qtrue; + } + //reset the node switches from the previous frame + BotResetNodeSwitches(); + //execute AI nodes + for (i = 0; i < MAX_NODESWITCHES; i++) { + if (bs->ainode(bs)) break; + } + //if the bot removed itself :) + if (!bs->inuse) return; + //if the bot executed too many AI nodes + if (i >= MAX_NODESWITCHES) { + gi.BotDumpGoalStack(bs->gs); + gi.BotDumpAvoidGoals(bs->gs); + BotDumpNodeSwitches(bs); + ClientName(bs->client, name, sizeof(name)); + BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, FloatTime(), MAX_NODESWITCHES); + } + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; +// bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; // FIXME +} + +int G_ModelIndex( const char *name ); +/* +================== +BotSetEntityNumForGoalWithModel +================== +*/ +void BotSetEntityNumForGoalWithModel(bot_goal_t *goal, int eType, char *modelname) { + gentity_t *ent; + int i, modelindex; + vec3_t dir; + + modelindex = G_ModelIndex( modelname ); + ent = &g_entities[0]; + for (i = 0; i < globals.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( eType && ent->s.eType != eType) { + continue; + } + if (ent->s.modelindex != modelindex) { + continue; + } + VectorSubtract(goal->origin, ent->s.origin, dir); + if (VectorLengthSquared(dir) < Square(10)) { + goal->entitynum = i; + return; + } + } +} + +/* +================== +BotSetEntityNumForGoal +================== +*/ +void BotSetEntityNumForGoal(bot_goal_t *goal, char *classname) { + gentity_t *ent; + int i; + vec3_t dir; + + ent = &g_entities[0]; + for (i = 0; i < globals.max_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( Q_stricmp(ent->entname, classname) ) { + continue; + } + VectorSubtract(goal->origin, ent->s.origin, dir); + if (VectorLengthSquared(dir) < Square(10)) { + goal->entitynum = i; + return; + } + } +} + +/* +================== +BotGoalForBSPEntity +================== +*/ +int BotGoalForBSPEntity( char *classname, bot_goal_t *goal ) { + char value[MAX_INFO_STRING]; + vec3_t origin, start, end; + int ent, numareas, areas[10]; + + memset(goal, 0, sizeof(bot_goal_t)); + for (ent = gi.AAS_NextBSPEntity(0); ent; ent = gi.AAS_NextBSPEntity(ent)) { + if (!gi.AAS_ValueForBSPEpairKey(ent, "classname", value, sizeof(value))) + continue; + if (!strcmp(value, classname)) { + if (!gi.AAS_VectorForBSPEpairKey(ent, "origin", origin)) + return qfalse; + VectorCopy(origin, goal->origin); + VectorCopy(origin, start); + start[2] -= 32; + VectorCopy(origin, end); + end[2] += 32; + numareas = gi.AAS_TraceAreas(start, end, areas, NULL, 10); + if (!numareas) + return qfalse; + goal->areanum = areas[0]; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotSetupDeathmatchAI +================== +*/ +void BotSetupDeathmatchAI(void) { + int ent, modelnum; + char model[128]; + + switch( gi.Cvar_VariableIntegerValue( "mp_gametype" ) ) + { + case 0 : + gametype = GT_FFA; + break; + case 1 : + gametype = GT_TEAM; + break; + case 2 : + gametype = GT_CTF; + break ; + case 3 : + gametype = GT_TEAM; + break ; + default : + gametype = GT_FFA; + break ; + } + + if ( gi.Cvar_VariableIntegerValue( "mp_modifier_Destruction" ) ) + { + gametype = GT_OBELISK; + } + +// maxclients = gi.Cvar_VariableIntegerValue("sv_maxclients"); + + gi.Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); + gi.Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); + gi.Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0); + gi.Cvar_Register(&bot_nochat, "bot_nochat", "0", 0); + gi.Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0); + gi.Cvar_Register(&bot_showstates, "bot_showstates","0",0); + gi.Cvar_Register(&bot_challenge, "bot_challenge", "0", 0); + gi.Cvar_Register(&bot_predictobstacles, "bot_predictobstacles", "1", 0); + gi.Cvar_Register(&g_spSkill, "g_spSkill", "2", 0); + // + ctf_neutralflag.areanum = 0; + +#ifdef MISSIONPACK + if (gametype == GT_OBELISK) { + if (gi.BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) + BotAI_Print(PRT_WARNING, "Obelisk without red obelisk\n"); + BotSetEntityNumForGoal(&redobelisk, "item_mp_destructionObject-red"); + if (gi.BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) + BotAI_Print(PRT_WARNING, "Obelisk without blue obelisk\n"); + BotSetEntityNumForGoal(&blueobelisk, "item_mp_destructionObject-blue"); + //if ((redobelisk.areanum) && (blueobelisk.areanum)) + // gametype = GT_OBELISK; + } +#endif + + if (gametype == GT_CTF) + if (multiplayerManager.checkGameType("oneflag")) + gametype = GT_1FCTF; + + if (gametype == GT_CTF) { + if (gi.BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); + if (gi.BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (gi.BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) + BotAI_Print(PRT_WARNING, "One Flag CTF without Neutral Flag\n"); + if (gi.BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); + if (gi.BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); + } + else if (gametype == GT_HARVESTER) { + if (gi.BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without red obelisk\n"); + BotSetEntityNumForGoal(&redobelisk, "item_mp_destructionObject-red"); + if (gi.BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without blue obelisk\n"); + BotSetEntityNumForGoal(&blueobelisk, "item_mp_destructionObject-blue"); + if (gi.BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); + BotSetEntityNumForGoal(&neutralobelisk, "team_neutralobelisk"); + } +#endif + + max_bspmodelindex = 0; + for (ent = gi.AAS_NextBSPEntity(0); ent; ent = gi.AAS_NextBSPEntity(ent)) { + if (!gi.AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue; + if (model[0] == '*') { + modelnum = atoi(model+1); + if (modelnum > max_bspmodelindex) + max_bspmodelindex = modelnum; + } + } + //initialize the waypoint heap + BotInitWaypoints(); +} + +/* +================== +BotShutdownDeathmatchAI +================== +*/ +void BotShutdownDeathmatchAI(void) { + altroutegoals_setup = qfalse; +} + diff --git a/dlls/game/ai_dmq3.h b/dlls/game/ai_dmq3.h new file mode 100644 index 0000000..a1042a6 --- /dev/null +++ b/dlls/game/ai_dmq3.h @@ -0,0 +1,191 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_dmq3.h + * + * desc: Quake3 bot AI + * + * $Archive: /Code/DLLs/game/ai_dmq3.h $ + * $Author: Jwaters $ + * $Revision: 2 $ + * $Modtime: 8/25/02 1:03p $ + * $Date: 8/25/02 6:26p $ + * + *****************************************************************************/ + +//setup the deathmatch AI +void BotSetupDeathmatchAI(void); +//shutdown the deathmatch AI +void BotShutdownDeathmatchAI(void); +//let the bot live within it's deathmatch AI net +void BotDeathmatchAI(bot_state_t *bs, float thinktime); +//free waypoints +void BotFreeWaypoints(bot_waypoint_t *wp); +//choose a weapon +void BotChooseWeapon(bot_state_t *bs); +//setup movement stuff +void BotSetupForMovement(bot_state_t *bs); +//update the inventory +void BotUpdateInventory(bot_state_t *bs); +//update the inventory during battle +void BotUpdateBattleInventory(bot_state_t *bs, int enemy); +//use holdable items during battle +void BotBattleUseItems(bot_state_t *bs); +//return true if the bot is dead +qboolean BotIsDead(bot_state_t *bs); +//returns true if the bot is in observer mode +qboolean BotIsObserver(bot_state_t *bs); +//returns true if the bot is in the intermission +qboolean BotIntermission(bot_state_t *bs); +//returns true if the bot is in lava or slime +qboolean BotInLavaOrSlime(bot_state_t *bs); +//returns true if the entity is dead +qboolean EntityIsDead(aas_entityinfo_t *entinfo); +//returns true if the entity is invisible +qboolean EntityIsInvisible(aas_entityinfo_t *entinfo); +//returns true if the entity is shooting +qboolean EntityIsShooting(aas_entityinfo_t *entinfo); +#ifdef MISSIONPACK +//returns true if this entity has the kamikaze +qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo); +#endif +// set a user info key/value pair +void BotSetUserInfo(bot_state_t *bs, char *key, char *value); +// set the team status (offense, defense etc.) +void BotSetTeamStatus(bot_state_t *bs); +//returns the name of the client +char *ClientName(int client, char *name, int size); +//returns an simplyfied client name +char *EasyClientName(int client, char *name, int size); +//returns the skin used by the client +char *ClientSkin(int client, char *skin, int size); +// returns the appropriate synonym context for the current game type and situation +int BotSynonymContext(bot_state_t *bs); +// set last ordered task +int BotSetLastOrderedTask(bot_state_t *bs); +// selection of goals for teamplay +void BotTeamGoals(bot_state_t *bs, int retreat); +//returns the aggression of the bot in the range [0, 100] +float BotAggression(bot_state_t *bs); +//returns how bad the bot feels +float BotFeelingBad(bot_state_t *bs); +//returns true if the bot wants to retreat +int BotWantsToRetreat(bot_state_t *bs); +//returns true if the bot wants to chase +int BotWantsToChase(bot_state_t *bs); +//returns true if the bot wants to help +int BotWantsToHelp(bot_state_t *bs); +//returns true if the bot can and wants to rocketjump +int BotCanAndWantsToRocketJump(bot_state_t *bs); +// returns true if the bot has a persistant powerup and a weapon +int BotHasPersistantPowerupAndWeapon(bot_state_t *bs); +//returns true if the bot wants to and goes camping +int BotWantsToCamp(bot_state_t *bs); +//the bot will perform attack movements +bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl); +//returns true if the bot and the entity are in the same team +int BotSameTeam(bot_state_t *bs, int entnum); +//returns true if teamplay is on +int TeamPlayIsOn(void); +// returns the client number of the team mate flag carrier (-1 if none) +int BotTeamFlagCarrier(bot_state_t *bs); +//returns visible team mate flag carrier if available +int BotTeamFlagCarrierVisible(bot_state_t *bs); +//returns visible enemy flag carrier if available +int BotEnemyFlagCarrierVisible(bot_state_t *bs); +//get the number of visible teammates and enemies +void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range); +//returns true if within the field of vision for the given angles +qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles); +//returns true and sets the .enemy field when an enemy is found +int BotFindEnemy(bot_state_t *bs, int curenemy); +//returns a roam goal +void BotRoamGoal(bot_state_t *bs, vec3_t goal); +//returns entity visibility in the range [0, 1] +float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent); +//the bot will aim at the current enemy +void BotAimAtEnemy(bot_state_t *bs); +//check if the bot should attack +void BotCheckAttack(bot_state_t *bs); +//AI when the bot is blocked +void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate); +//AI to predict obstacles +int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal); +//enable or disable the areas the blocking entity is in +void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable); +//pop an activate goal from the stack +int BotPopFromActivateGoalStack(bot_state_t *bs); +//clear the activate goal stack +void BotClearActivateGoalStack(bot_state_t *bs); +//returns the team the bot is in +int BotTeam(bot_state_t *bs); +//retuns the opposite team of the bot +int BotOppositeTeam(bot_state_t *bs); +//returns the flag the bot is carrying (CTFFLAG_?) +int BotCTFCarryingFlag(bot_state_t *bs); +//remember the last ordered task +void BotRememberLastOrderedTask(bot_state_t *bs); +//set ctf goals (defend base, get enemy flag) during seek +void BotCTFSeekGoals(bot_state_t *bs); +//set ctf goals (defend base, get enemy flag) during retreat +void BotCTFRetreatGoals(bot_state_t *bs); +// +#ifdef MISSIONPACK +int Bot1FCTFCarryingFlag(bot_state_t *bs); +int BotHarvesterCarryingCubes(bot_state_t *bs); +void Bot1FCTFSeekGoals(bot_state_t *bs); +void Bot1FCTFRetreatGoals(bot_state_t *bs); +void BotObeliskSeekGoals(bot_state_t *bs); +void BotObeliskRetreatGoals(bot_state_t *bs); +void BotGoHarvest(bot_state_t *bs); +void BotHarvesterSeekGoals(bot_state_t *bs); +void BotHarvesterRetreatGoals(bot_state_t *bs); +int BotTeamCubeCarrierVisible(bot_state_t *bs); +int BotEnemyCubeCarrierVisible(bot_state_t *bs); +#endif +//get a random alternate route goal towards the given base +int BotGetAlternateRouteGoal(bot_state_t *bs, int base); +//returns either the alternate route goal or the given goal +bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal); +//create a new waypoint +bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum); +//find a waypoint with the given name +bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name); +//strstr but case insensitive +char *stristr(char *str, char *charset); +//returns the number of the client with the given name +int ClientFromName(char *name); +int ClientOnSameTeamFromName(bot_state_t *bs, char *name); +// +int BotPointAreaNum(vec3_t origin); +// +void BotMapScripts(bot_state_t *bs); + +//ctf flags +#define CTF_FLAG_NONE 0 +#define CTF_FLAG_RED 1 +#define CTF_FLAG_BLUE 2 +//CTF skins +#define CTF_SKIN_REDTEAM "red" +#define CTF_SKIN_BLUETEAM "blue" + +extern int gametype; //game type +//extern int maxclients; //maximum number of clients + +extern vmCvar_t bot_grapple; +extern vmCvar_t bot_rocketjump; +extern vmCvar_t bot_fastchat; +extern vmCvar_t bot_nochat; +extern vmCvar_t bot_testrchat; +extern vmCvar_t bot_challenge; +extern vmCvar_t bot_showstates; + +extern bot_goal_t ctf_redflag; +extern bot_goal_t ctf_blueflag; +#ifdef MISSIONPACK +extern bot_goal_t ctf_neutralflag; +extern bot_goal_t redobelisk; +extern bot_goal_t blueobelisk; +extern bot_goal_t neutralobelisk; +#endif diff --git a/dlls/game/ai_main.cpp b/dlls/game/ai_main.cpp new file mode 100644 index 0000000..ef7efe4 --- /dev/null +++ b/dlls/game/ai_main.cpp @@ -0,0 +1,1747 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_main.c + * + * desc: Quake3 bot AI + * + * $Archive: /EF2/Code/DLLs/game/ai_main.cpp $ + * $Author: Singlis $ + * $Revision: 18 $ + * $Modtime: 9/24/03 3:01p $ + * $Date: 9/26/03 2:35p $ + * + *****************************************************************************/ + + +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" //bot lib interface +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_vcmd.h" + +// +#include "chars.h" +#include "inv.h" +#include "syn.h" + +#include "player.h" + +#define MAX_PATH 144 + + +//bot states +bot_state_t *botstates[MAX_CLIENTS]; +//number of bots +int numbots; +//floating point time +float floattime; +//time to do a regular update +float regularupdate_time; +// +int bot_interbreed; +int bot_interbreedmatchcount; +// +vmCvar_t bot_thinktime; +vmCvar_t bot_memorydump; +vmCvar_t bot_saveroutingcache; +vmCvar_t bot_pause; +vmCvar_t bot_report; +vmCvar_t bot_testsolid; +vmCvar_t bot_testclusters; +vmCvar_t bot_developer; +vmCvar_t bot_interbreedchar; +vmCvar_t bot_interbreedbots; +vmCvar_t bot_interbreedcycle; +vmCvar_t bot_interbreedwrite; + + +/* +================== +BotAI_Print +================== +*/ +void QDECL BotAI_Print(int type, char *fmt, ...) { + char str[2048]; + va_list ap; + + va_start(ap, fmt); + vsprintf(str, fmt, ap); + va_end(ap); + + switch(type) { + case PRT_MESSAGE: { + gi.Printf("%s", str); + break; + } + case PRT_WARNING: { + gi.Printf( S_COLOR_YELLOW "Warning: %s", str ); + break; + } + case PRT_ERROR: { + gi.Printf( S_COLOR_RED "Error: %s", str ); + break; + } + case PRT_FATAL: { + gi.Printf( S_COLOR_RED "Fatal: %s", str ); + break; + } + case PRT_EXIT: { + gi.Printf( S_COLOR_RED "Exit: %s", str ); + break; + } + default: { + gi.Printf( "unknown print type\n" ); + break; + } + } +} + + +/* +================== +BotAI_Trace +================== +*/ +void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { + trace_t trace; + +// AAS_ClearShownDebugLines(); +// AAS_DebugLine(ms->origin, goal->origin, LINECOLOR_RED); + gi.trace(&trace, start, mins, maxs, end, passent, contentmask, qfalse); + //copy the trace information + bsptrace->allsolid = trace.allsolid; + bsptrace->startsolid = trace.startsolid; + bsptrace->fraction = trace.fraction; + VectorCopy(trace.endpos, bsptrace->endpos); + bsptrace->plane.dist = trace.plane.dist; + VectorCopy(trace.plane.normal, bsptrace->plane.normal); + bsptrace->plane.signbits = trace.plane.signbits; + bsptrace->plane.type = trace.plane.type; + bsptrace->surface.value = trace.surfaceFlags; + bsptrace->ent = trace.entityNum; + bsptrace->exp_dist = 0; + bsptrace->sidenum = 0; + bsptrace->contents = 0; +} + +/* +================== +BotAI_GetClientState +================== +*/ +int BotAI_GetClientState( int clientNum, playerState_t *state ) { + gentity_t *ent; + + ent = &g_entities[clientNum]; + if ( !ent->inuse ) { + return qfalse; + } + if ( !ent->client ) { + return qfalse; + } + + memcpy( state, &ent->client->ps, sizeof(playerState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetEntityState +================== +*/ +int BotAI_GetEntityState( int entityNum, entityState_t *state ) { + gentity_t *ent; + + ent = &g_entities[entityNum]; + memset( state, 0, sizeof(entityState_t) ); + if (!ent->inuse) return qfalse; + if (!ent->linked) return qfalse; + if (ent->svflags & SVF_NOCLIENT) return qfalse; + memcpy( state, &ent->s, sizeof(entityState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetSnapshotEntity +================== +*/ +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) { + int entNum; + + entNum = gi.BotGetSnapshotEntity( clientNum, sequence ); + if ( entNum == -1 ) { + memset(state, 0, sizeof(entityState_t)); + return -1; + } + + BotAI_GetEntityState( entNum, state ); + + return sequence + 1; +} + +/* +================== +BotAI_BotInitialChat +================== +*/ +void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) { + int i, mcontext; + va_list ap; + char *p; + char *vars[MAX_MATCHVARIABLES]; + + memset(vars, 0, sizeof(vars)); + va_start(ap, type); + p = va_arg(ap, char *); + for (i = 0; i < MAX_MATCHVARIABLES; i++) { + if( !p ) { + break; + } + vars[i] = p; + p = va_arg(ap, char *); + } + va_end(ap); + + mcontext = BotSynonymContext(bs); + + gi.BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] ); +} + + +/* +================== +BotTestAAS +================== +*/ +void BotTestAAS(vec3_t origin) { + int areanum; + aas_areainfo_t info; + + gi.Cvar_Update(&bot_testsolid); + gi.Cvar_Update(&bot_testclusters); + if (bot_testsolid.integer) { + if (!gi.AAS_Initialized()) return; + areanum = BotPointAreaNum(origin); + if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area"); + else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area"); + } + else if (bot_testclusters.integer) { + if (!gi.AAS_Initialized()) return; + areanum = BotPointAreaNum(origin); + if (!areanum) + BotAI_Print(PRT_MESSAGE, "\r^1Solid! "); + else { + gi.AAS_AreaInfo(areanum, &info); + BotAI_Print(PRT_MESSAGE, "\rarea %d, cluster %d ", areanum, info.cluster); + } + } +} + +/* +================== +BotReportStatus +================== +*/ +void BotReportStatus(bot_state_t *bs) { + char goalname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char *leader, flagstatus[32]; + // + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; + else leader = " "; + + strcpy(flagstatus, " "); + if (gametype == GT_CTF) { + if (BotCTFCarryingFlag(bs)) { + if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); + else strcpy(flagstatus, S_COLOR_BLUE"F "); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) { + if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); + else strcpy(flagstatus, S_COLOR_BLUE"F "); + } + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) { + if (BotTeam(bs) == TEAM_RED) Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_RED"%2d", bs->inventory[INVENTORY_REDCUBE]); + else Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_BLUE"%2d", bs->inventory[INVENTORY_BLUECUBE]); + } + } +#endif + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_DEFENDKEYAREA: + { + gi.BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_GETITEM: + { + gi.BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus); + break; + } + case LTG_PATROL: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus); + break; + } + case LTG_GETFLAG: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus); + break; + } + case LTG_RUSHBASE: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus); + break; + } + case LTG_RETURNFLAG: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus); + break; + } + case LTG_ATTACKENEMYBASE: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: attacking the enemy base\n", netname, leader, flagstatus); + break; + } + case LTG_HARVEST: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: harvesting\n", netname, leader, flagstatus); + break; + } + default: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus); + break; + } + } +} + +/* +================== +BotTeamplayReport +================== +*/ +void BotTeamplayReport(void) { + int i; + char buf[MAX_INFO_STRING]; + + BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) continue; + // + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "name"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) { + BotReportStatus(botstates[i]); + } + } + BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) continue; + // + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "name"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) { + BotReportStatus(botstates[i]); + } + } +} + +/* +================== +BotSetInfoConfigString +================== +*/ +void BotSetInfoConfigString(bot_state_t *bs) { + char goalname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char action[MAX_MESSAGE_SIZE]; + char *leader, carrying[32]; + bot_goal_t goal; + // + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; + else leader = " "; + + strcpy(carrying, " "); + if (gametype == GT_CTF) { + if (BotCTFCarryingFlag(bs)) { + strcpy(carrying, "F "); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) { + strcpy(carrying, "F "); + } + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) { + if (BotTeam(bs) == TEAM_RED) Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_REDCUBE]); + else Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_BLUECUBE]); + } + } +#endif + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "helping %s", goalname); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "accompanying %s", goalname); + break; + } + case LTG_DEFENDKEYAREA: + { + gi.BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "defending %s", goalname); + break; + } + case LTG_GETITEM: + { + gi.BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "getting item %s", goalname); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "killing %s", goalname); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + Com_sprintf(action, sizeof(action), "camping"); + break; + } + case LTG_PATROL: + { + Com_sprintf(action, sizeof(action), "patrolling"); + break; + } + case LTG_GETFLAG: + { + Com_sprintf(action, sizeof(action), "capturing flag"); + break; + } + case LTG_RUSHBASE: + { + Com_sprintf(action, sizeof(action), "rushing base"); + break; + } + case LTG_RETURNFLAG: + { + Com_sprintf(action, sizeof(action), "returning flag"); + break; + } + case LTG_ATTACKENEMYBASE: + { + Com_sprintf(action, sizeof(action), "attacking the enemy base"); + break; + } + case LTG_HARVEST: + { + Com_sprintf(action, sizeof(action), "harvesting"); + break; + } + default: + { + gi.BotGetTopGoal(bs->gs, &goal); + gi.BotGoalName(goal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "roaming %s", goalname); + break; + } + } + gi.setConfigstring (CS_BOTINFO + bs->client, va("l\\%s\\c\\%s\\a\\%s", + leader, + carrying, + action)); +} + +/* +============== +BotUpdateInfoConfigStrings +============== +*/ +void BotUpdateInfoConfigStrings(void) { + int i; + char buf[MAX_INFO_STRING]; + + for (i = 0; i < maxclients->integer && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) + continue; + // + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "name"))) + continue; + BotSetInfoConfigString(botstates[i]); + } +} + +/* +============== +BotInterbreedBots +============== +*/ +void BotInterbreedBots(void) { + float ranks[MAX_CLIENTS]; + int parent1, parent2, child; + int i; + + // get rankings for all the bots + for (i = 0; i < maxclients->integer; i++) { + if ( botstates[i] && botstates[i]->inuse ) { + ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; + } + else { + ranks[i] = -1; + } + } + + if (gi.GeneticParentsAndChildSelection(MAX_CLIENTS, ranks, &parent1, &parent2, &child)) { + gi.BotInterbreedGoalFuzzyLogic(botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs); + gi.BotMutateGoalFuzzyLogic(botstates[child]->gs, 1); + } + // reset the kills and deaths + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + botstates[i]->num_kills = 0; + botstates[i]->num_deaths = 0; + } + } +} + +/* +============== +BotWriteInterbreeded +============== +*/ +void BotWriteInterbreeded(char *filename) { + float rank, bestrank; + int i, bestbot; + + bestrank = 0; + bestbot = -1; + // get the best bot + for (i = 0; i < MAX_CLIENTS; i++) { + if ( botstates[i] && botstates[i]->inuse ) { + rank = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; + } + else { + rank = -1; + } + if (rank > bestrank) { + bestrank = rank; + bestbot = i; + } + } + if (bestbot >= 0) { + //write out the new goal fuzzy logic + gi.BotSaveGoalFuzzyLogic(botstates[bestbot]->gs, filename); + } +} + +/* +============== +BotInterbreedEndMatch + +add link back into ExitLevel? +============== +*/ +void BotInterbreedEndMatch(void) { + + if (!bot_interbreed) return; + bot_interbreedmatchcount++; + if (bot_interbreedmatchcount >= bot_interbreedcycle.integer) { + bot_interbreedmatchcount = 0; + // + gi.Cvar_Update(&bot_interbreedwrite); + if (strlen(bot_interbreedwrite.string)) { + BotWriteInterbreeded(bot_interbreedwrite.string); + gi.cvar_set("bot_interbreedwrite", ""); + } + BotInterbreedBots(); + } +} + +int BotAIShutdownClient(int client, qboolean restart); +void G_ExitLevel(void); +/* +============== +BotInterbreeding +============== +*/ +void BotInterbreeding(void) { + int i; + + gi.Cvar_Update(&bot_interbreedchar); + if (!strlen(bot_interbreedchar.string)) return; + //make sure we are in tournament mode + if (gametype != GT_TOURNAMENT) { + gi.cvar_set("g_gametype", va("%d", GT_TOURNAMENT)); + G_ExitLevel(); + return; + } + //shutdown all the bots + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotAIShutdownClient(botstates[i]->client, qfalse); + } + } + //make sure all item weight configs are reloaded and Not shared + gi.BotLibVarSet("bot_reloadcharacters", "1"); + //add a number of bots using the desired bot character + for (i = 0; i < bot_interbreedbots.integer; i++) { + gi.SendConsoleCommand( va("addbot %s 4 free %i %s%d\n", + bot_interbreedchar.string, i * 50, bot_interbreedchar.string, i) ); // EXEC_INSERT, + } + // + gi.cvar_set("bot_interbreedchar", ""); + bot_interbreed = qtrue; +} + +/* +============== +BotEntityInfo +============== +*/ +void BotEntityInfo(int entnum, aas_entityinfo_t *info) { + gi.AAS_EntityInfo(entnum, info); + + if ( !g_entities[info->number].entity ) + info->valid = qfalse; +} + +/* +============== +NumBots +============== +*/ +int NumBots(void) { + return numbots; +} + +/* +============== +BotTeamLeader +============== +*/ +int BotTeamLeader(bot_state_t *bs) { + int leader; + + leader = ClientFromName(bs->teamleader); + if (leader < 0) return qfalse; + if (!botstates[leader] || !botstates[leader]->inuse) return qfalse; + return qtrue; +} + +/* +============== +AngleDifference +============== +*/ +float AngleDifference(float ang1, float ang2) { + float diff; + + diff = ang1 - ang2; + if (ang1 > ang2) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + return diff; +} + +/* +============== +BotChangeViewAngle +============== +*/ +float BotChangeViewAngle(float angle, float ideal_angle, float speed) { + float move; + + angle = AngleMod(angle); + ideal_angle = AngleMod(ideal_angle); + if (angle == ideal_angle) return angle; + move = ideal_angle - angle; + if (ideal_angle > angle) { + if (move > 180.0) move -= 360.0; + } + else { + if (move < -180.0) move += 360.0; + } + if (move > 0) { + if (move > speed) move = speed; + } + else { + if (move < -speed) move = -speed; + } + return AngleMod(angle + move); +} + +/* +============== +BotChangeViewAngles +============== +*/ +void BotChangeViewAngles(bot_state_t *bs, float thinktime) { + float diff, factor, maxchange, anglespeed, disired_speed; + int i; + + if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; + // + if (bs->enemy >= 0) { + factor = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_FACTOR, 0.01f, 1); + maxchange = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_MAXCHANGE, 1, 1800); + } + else { + factor = 0.05f; + maxchange = 360; + } + if (maxchange < 240) maxchange = 240; + maxchange *= thinktime; + for (i = 0; i < 2; i++) { + // + if (bot_challenge.integer) { + //smooth slowdown view model + diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i])); + anglespeed = diff * factor; + if (anglespeed > maxchange) anglespeed = maxchange; + bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i], + bs->ideal_viewangles[i], anglespeed); + } + else { + //over reaction view model + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]); + diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]); + disired_speed = diff * factor; + bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed); + if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange; + if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange; + anglespeed = bs->viewanglespeed[i]; + if (anglespeed > maxchange) anglespeed = maxchange; + if (anglespeed < -maxchange) anglespeed = -maxchange; + bs->viewangles[i] += anglespeed; + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + //demping + bs->viewanglespeed[i] *= 0.45 * (1 - factor); + } + //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);` + //bs->viewangles[i] = bs->ideal_viewangles[i]; + } + //bs->viewangles[PITCH] = 0; + if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360; + //elementary action: view + gi.EA_View(bs->client, bs->viewangles); +} + +/* +============== +BotInputToUserCommand +============== +*/ +void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time) { + vec3_t angles, forward, right; + short temp; + int j; + + //clear the whole structure + memset(ucmd, 0, sizeof(usercmd_t)); + // + //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); + //the duration for the user command in milli seconds + ucmd->serverTime = time; + // + if (bi->actionflags & ACTION_DELAYEDJUMP) { + bi->actionflags |= ACTION_JUMP; + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } + //set the buttons + + if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = (BUTTON_ATTACKRIGHT | BUTTON_ATTACKLEFT); + if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACKLEFT; + if (bi->actionflags & ACTION_ATTACKRIGHT) ucmd->buttons |= BUTTON_ATTACKRIGHT; + if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK; +// if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE; + if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE; + if (!(bi->actionflags & ACTION_WALK)) + ucmd->buttons |= BUTTON_RUN; +// if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING; // will probably need to hook these back up +// if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE; +// if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE; +// if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG; +// if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE; +// if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL; +// if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME; + // + ucmd->weapon = bi->weapon; + //set the view angles + //NOTE: the ucmd->angles are the angles WITHOUT the delta angles + ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]); + ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]); + ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]); + //subtract the delta angles + for (j = 0; j < 3; j++) { + temp = ucmd->angles[j] - delta_angles[j]; + /*NOTE: disabled because temp should be mod first + if ( j == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) temp = 16000; + else if ( temp < -16000 ) temp = -16000; + } + */ + ucmd->angles[j] = temp; + } + //NOTE: movement is relative to the REAL view angles + //get the horizontal forward and right vector + //get the pitch in the range [-180, 180] + if (bi->dir[2]) + angles[PITCH] = bi->viewangles[PITCH]; + else + angles[PITCH] = 0; + angles[YAW] = bi->viewangles[YAW]; + angles[ROLL] = 0; + AngleVectors(angles, forward, right, NULL); + //bot input speed is in the range [0, 400] + bi->speed = bi->speed * 127 / sv_maxspeed->value; + //set the view independent movement + ucmd->forwardmove = (signed char) (DotProduct(forward, bi->dir) * bi->speed); + ucmd->rightmove = (signed char) (0 - DotProduct(right, bi->dir) * bi->speed); + ucmd->upmove = (signed char) (abs(forward[2]) * bi->dir[2] * bi->speed); + //normal keyboard movement + if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127; + if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127; + if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127; + if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127; + //jump/moveup + if ((bi->actionflags & ACTION_MOVEUP) || (bi->actionflags & ACTION_JUMP)) ucmd->upmove += 127; + //crouch/movedown + if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127; + // + //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); + //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime); +} + +/* +============== +BotUpdateInput +============== +*/ +void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) { + bot_input_t bi; + int j; + + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //change the bot view angles + BotChangeViewAngles(bs, (float) elapsed_time / 1000); + //retrieve the bot input + gi.EA_GetInput(bs->client, (float) time / 1000, &bi); + //respawn hack + if (bi.actionflags & ACTION_RESPAWN) { + if (bs->lastucmd.buttons & (BUTTON_ATTACKRIGHT | BUTTON_ATTACKLEFT)) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK); + } + //convert the bot input to a usercmd + BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } +} + +/* +============== +BotAIRegularUpdate +============== +*/ +void BotAIRegularUpdate(void) { + if (regularupdate_time < FloatTime()) { + gi.BotUpdateEntityItems(); + regularupdate_time = FloatTime() + 0.3; + } +} + +/* +============== +RemoveColorEscapeSequences +============== +*/ +void RemoveColorEscapeSequences( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if (Q_IsColorString(&text[i])) { + i++; + continue; + } + if (text[i] > 0x7E) + continue; + text[l++] = text[i]; + } + text[l] = '\0'; +} + +/* +============== +BotAI +============== +*/ +int BotAI(int client, float thinktime) { + bot_state_t *bs; + char buf[1024], *args,cmd[1024], *ptr, *ptr2; + int j; + vec3_t tmpv; + + // Make sure we are still a valid bot + + if ( !g_entities[ client ].inuse || !g_entities[ client ].entity || !g_entities[ client ].client ) + { + BotAIShutdownClient( client, qfalse ); + return qfalse; + } + + gi.EA_ResetInput(client); + // + bs = botstates[client]; + if (!bs || !bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client); + return qfalse; + } + + //retrieve the current client state + BotAI_GetClientState( client, &bs->cur_ps ); + + //retrieve any waiting server commands + while( gi.BotGetConsoleMessage(client, buf, sizeof(buf)) ) { + //have buf point to the command and args to the command arguments + args = strchr( buf, ':'); + if (!args) continue; + *args++;// = '\0'; + *args++; + + RemoveColorEscapeSequences( args ); + + ptr = args; + args = strchr(args,' '); + strncpy(cmd,ptr,++args - ptr ); + cmd[(args - ptr) - 1] = 0; + +// *args++ = '\0'; + + //remove color espace sequences from the arguments + + if (!Q_stricmp(buf, "cp ")) + { /*CenterPrintf*/ } +#ifdef MISSIONPACK + else if (!strncmp(cmd, "vchat",5)) { + BotVoiceChatCommand(bs, SAY_ALL, ptr); + } + else if (!strncmp(cmd, "vtchat",6)) { + BotVoiceChatCommand(bs, SAY_TEAM, ptr); + } + else if (!strncmp(cmd, "vtell",5)) { + BotVoiceChatCommand(bs, SAY_TELL, ptr); + } +#endif + else if (!Q_stricmp(buf, "cs")) + { /*ConfigStringModified*/ } + else if (!strncmp(buf, "hudprint",8)) { + ptr2= strchr(buf,':'); + memcpy(cmd,ptr2,strlen(ptr2)+1); + memcpy(ptr2+1,cmd,strlen(cmd)+1); + *ptr2 = 25; + gi.BotQueueConsoleMessage(bs->cs, CMS_NORMAL, strchr(buf,'"')); + } + else if (!strncmp(buf, "hudsay",6)) { + ptr2= strchr(buf,':'); + memcpy(cmd,ptr2,strlen(ptr2)+1); + memcpy(ptr2+1,cmd,strlen(cmd)+1); + *ptr2 = 25; + gi.BotQueueConsoleMessage(bs->cs, CMS_CHAT, strchr(buf,'"')); + } + else if (!Q_stricmp(buf, "tchat")) { +// remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + gi.BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); + } + else if (!Q_stricmp(buf, "scores")) + { /*FIXME: parse scores?*/ } + else if (!Q_stricmp(buf, "clientLevelShot")) + { /*ignore*/ } + else if ( stricmp(buf, "disconnect") == 0 ) { + int i = 0; + i++; + } + } + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //increase the local time of the bot + bs->ltime += thinktime; + // + bs->thinktime = thinktime; + //origin of the bot + VectorCopy(bs->cur_ps.origin, bs->origin); + //eye coordinates of the bot + VectorCopy(bs->cur_ps.origin, bs->eye); + bs->eye[2] += (bs->cur_ps.viewheight); + //get the area the bot is in + VectorCopy(bs->origin,tmpv); + tmpv[2] += 20; + bs->areanum = BotPointAreaNum(bs->origin); + //the real AI + BotDeathmatchAI(bs, thinktime); + //set the weapon selection every AI frame + gi.EA_SelectWeapon(bs->client, bs->weaponnum); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //everything was ok + return qtrue; +} + +/* +================== +BotScheduleBotThink +================== +*/ +void BotScheduleBotThink(void) { + int i, botnum; + + botnum = 0; + + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + //initialize the bot think residual time + botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots; + botnum++; + } +} + +/* +============== +BotWriteSessionData +============== +*/ +void BotWriteSessionData(bot_state_t *bs) { + const char *s; + const char *var; + + s = va( + "%i %i %i %i %i %i %i %i" + " %f %f %f" + " %f %f %f" + " %f %f %f", + bs->lastgoal_decisionmaker, + bs->lastgoal_ltgtype, + bs->lastgoal_teammate, + bs->lastgoal_teamgoal.areanum, + bs->lastgoal_teamgoal.entitynum, + bs->lastgoal_teamgoal.flags, + bs->lastgoal_teamgoal.iteminfo, + bs->lastgoal_teamgoal.number, + bs->lastgoal_teamgoal.origin[0], + bs->lastgoal_teamgoal.origin[1], + bs->lastgoal_teamgoal.origin[2], + bs->lastgoal_teamgoal.mins[0], + bs->lastgoal_teamgoal.mins[1], + bs->lastgoal_teamgoal.mins[2], + bs->lastgoal_teamgoal.maxs[0], + bs->lastgoal_teamgoal.maxs[1], + bs->lastgoal_teamgoal.maxs[2] + ); + + var = va( "botsession%i", bs->client ); + + gi.cvar_set( var, s ); +} + +/* +============== +BotReadSessionData +============== +*/ +void BotReadSessionData(bot_state_t *bs) { + char s[MAX_STRING_CHARS]; + const char *var; + + var = va( "botsession%i", bs->client ); + gi.Cvar_VariableStringBuffer( var, s, sizeof(s) ); + + sscanf(s, + "%i %i %i %i %i %i %i %i" + " %f %f %f" + " %f %f %f" + " %f %f %f", + &bs->lastgoal_decisionmaker, + &bs->lastgoal_ltgtype, + &bs->lastgoal_teammate, + &bs->lastgoal_teamgoal.areanum, + &bs->lastgoal_teamgoal.entitynum, + &bs->lastgoal_teamgoal.flags, + &bs->lastgoal_teamgoal.iteminfo, + &bs->lastgoal_teamgoal.number, + &bs->lastgoal_teamgoal.origin[0], + &bs->lastgoal_teamgoal.origin[1], + &bs->lastgoal_teamgoal.origin[2], + &bs->lastgoal_teamgoal.mins[0], + &bs->lastgoal_teamgoal.mins[1], + &bs->lastgoal_teamgoal.mins[2], + &bs->lastgoal_teamgoal.maxs[0], + &bs->lastgoal_teamgoal.maxs[1], + &bs->lastgoal_teamgoal.maxs[2] + ); +} + +/* +============== +BotAISetupClient +============== +*/ +int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { + char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; + bot_state_t *bs; + int errnum; + + if (!botstates[client]) botstates[client] = (bot_state_t *)gi.Malloc(sizeof(bot_state_t)); // G_Alloc + bs = botstates[client]; + + memset(bs,0,sizeof(bot_state_t)); // this isn't right, but gi.Malloc() doesn't zero out memory, FIXME find one that does + if (bs && bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); + return qfalse; + } + + if (!gi.AAS_Initialized()) { + BotAI_Print(PRT_FATAL, "AAS not initialized\n"); + return qfalse; + } + + //load the bot character + bs->character = gi.BotLoadCharacter(settings->characterfile, settings->skill); + if (!bs->character) { + BotAI_Print(PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile); + return qfalse; + } + //copy the settings + memcpy(&bs->settings, settings, sizeof(bot_settings_t)); + //allocate a goal state + bs->gs = gi.BotAllocGoalState(client); + //load the item weights + gi.Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH); + errnum = gi.BotLoadItemWeights(bs->gs, filename); + if (errnum != BLERR_NOERROR) { + gi.BotFreeGoalState(bs->gs); + return qfalse; + } + //allocate a weapon state + bs->ws = gi.BotAllocWeaponState(); + //load the weapon weights + gi.Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH); + errnum = gi.BotLoadWeaponWeights(bs->ws, filename); + if (errnum != BLERR_NOERROR) { + gi.BotFreeGoalState(bs->gs); + gi.BotFreeWeaponState(bs->ws); + return qfalse; + } + //allocate a chat state + bs->cs = gi.BotAllocChatState(); + //load the chat file + gi.Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH); + gi.Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH); + errnum = gi.BotLoadChatFile(bs->cs, filename, name); + if (errnum != BLERR_NOERROR) { + gi.BotFreeChatState(bs->cs); + gi.BotFreeGoalState(bs->gs); + gi.BotFreeWeaponState(bs->ws); + return qfalse; + } + //get the gender characteristic + gi.Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH); + //set the chat gender + if (*gender == 'f' || *gender == 'F') gi.BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); + else if (*gender == 'm' || *gender == 'M') gi.BotSetChatGender(bs->cs, CHAT_GENDERMALE); + else gi.BotSetChatGender(bs->cs, CHAT_GENDERLESS); + + bs->inuse = qtrue; + bs->client = client; + bs->entitynum = client; + bs->setupcount = 4; + bs->entergame_time = FloatTime(); + bs->ms = gi.BotAllocMoveState(); + bs->walker = gi.Characteristic_BFloat(bs->character, CHARACTERISTIC_WALKER, 0, 1); + numbots++; + + if (gi.Cvar_VariableIntegerValue("bot_testichat")) { + gi.BotLibVarSet("bot_testichat", "1"); + BotChatTest(bs); + } + //NOTE: reschedule the bot thinking + BotScheduleBotThink(); + //if interbreeding start with a mutation + if (bot_interbreed) { + gi.BotMutateGoalFuzzyLogic(bs->gs, 1); + } + // if we kept the bot client + if (restart) { + BotReadSessionData(bs); + } + //bot has been setup succesfully + return qtrue; +} + +/* +============== +BotAIShutdownClient +============== +*/ +int BotAIShutdownClient(int client, qboolean restart) { + bot_state_t *bs; + qboolean characterStillBeingUsed; + int i; + + bs = botstates[client]; + if (!bs || !bs->inuse) { + //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client); + return qfalse; + } + + if (restart) { + BotWriteSessionData(bs); + } + + if (BotChat_ExitGame(bs)) { + gi.BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + + gi.BotFreeMoveState(bs->ms); + //free the goal state` + gi.BotFreeGoalState(bs->gs); + //free the chat file + gi.BotFreeChatState(bs->cs); + //free the weapon weights + gi.BotFreeWeaponState(bs->ws); + //free the bot character + + characterStillBeingUsed = qfalse; + + for( i = 0 ; i < maxclients->integer ; i++ ) + { + bot_state_t *otherBotState; + + otherBotState = botstates[ i ]; + + if ( !otherBotState || !otherBotState->inuse || otherBotState == bs ) + continue; + + if ( otherBotState->character == bs->character ) + characterStillBeingUsed = qtrue; + + } + + if ( !characterStillBeingUsed ) + gi.BotFreeCharacter(bs->character); + + // + BotFreeWaypoints(bs->checkpoints); + BotFreeWaypoints(bs->patrolpoints); + //clear activate goal stack + BotClearActivateGoalStack(bs); + //clear the bot state + memset(bs, 0, sizeof(bot_state_t)); + //set the inuse flag to qfalse + bs->inuse = qfalse; + //there's one bot less + numbots--; + //everything went ok + return qtrue; +} + +/* +============== +BotResetState + +called when a bot enters the intermission or observer mode and +when the level is changed +============== +*/ +void BotResetState(bot_state_t *bs) { + int client, entitynum, inuse; + int movestate, goalstate, chatstate, weaponstate; + bot_settings_t settings; + int character; + playerState_t ps; //current player state + float entergame_time; + + //save some things that should not be reset here + memcpy(&settings, &bs->settings, sizeof(bot_settings_t)); + memcpy(&ps, &bs->cur_ps, sizeof(playerState_t)); + inuse = bs->inuse; + client = bs->client; + entitynum = bs->entitynum; + character = bs->character; + movestate = bs->ms; + goalstate = bs->gs; + chatstate = bs->cs; + weaponstate = bs->ws; + entergame_time = bs->entergame_time; + //free checkpoints and patrol points + BotFreeWaypoints(bs->checkpoints); + BotFreeWaypoints(bs->patrolpoints); + //reset the whole state + memset(bs, 0, sizeof(bot_state_t)); + //copy back some state stuff that should not be reset + bs->ms = movestate; + bs->gs = goalstate; + bs->cs = chatstate; + bs->ws = weaponstate; + memcpy(&bs->cur_ps, &ps, sizeof(playerState_t)); + memcpy(&bs->settings, &settings, sizeof(bot_settings_t)); + bs->inuse = inuse; + bs->client = client; + bs->entitynum = entitynum; + bs->character = character; + bs->entergame_time = entergame_time; + //reset several states + if (bs->ms) gi.BotResetMoveState(bs->ms); + if (bs->gs) gi.BotResetGoalState(bs->gs); + if (bs->ws) gi.BotResetWeaponState(bs->ws); + if (bs->gs) gi.BotResetAvoidGoals(bs->gs); + if (bs->ms) gi.BotResetAvoidReach(bs->ms); +} + +/* +============== +BotAILoadMap +============== +*/ +int BotAILoadMap( int restart ) { + int i; + vmCvar_t mapname; + + if (!restart) { + gi.Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + gi.BotLibLoadMap( mapname.string ); + } + + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotResetState( botstates[i] ); + botstates[i]->setupcount = 4; + } + } + + BotSetupDeathmatchAI(); + + return qtrue; +} + +#ifdef MISSIONPACK +void ProximityMine_Trigger( gentity_t *trigger, gentity_t *other, trace_t *trace ); +#endif + +void G_CheckBotSpawn( void ); + + +extern Event EV_Player_UseItem; + +/* +================== +BotAIStartFrame +================== +*/ +static int local_time; +static int botlib_residual; +static int lastbotthink_time; + +int BotAIStartFrame(int time) { + int i; + gentity_t *ent; + bot_entitystate_t state; + int elapsed_time, thinktime; + + G_CheckBotSpawn(); + + gi.Cvar_Update(&bot_rocketjump); + gi.Cvar_Update(&bot_grapple); + gi.Cvar_Update(&bot_fastchat); + gi.Cvar_Update(&bot_nochat); + gi.Cvar_Update(&bot_testrchat); + gi.Cvar_Update(&bot_thinktime); + gi.Cvar_Update(&bot_memorydump); + gi.Cvar_Update(&bot_saveroutingcache); + gi.Cvar_Update(&bot_pause); + gi.Cvar_Update(&bot_report); + + if (bot_report.integer) { +// BotTeamplayReport(); +// gi.cvar_set("bot_report", "0"); + BotUpdateInfoConfigStrings(); + } + + if (bot_pause.integer) { + // execute bot user commands every frame + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + if( g_entities[i].client->pers.enterTime) { // connected != CON_CONNECTED ) { + continue; + } + botstates[i]->lastucmd.forwardmove = 0; + botstates[i]->lastucmd.rightmove = 0; + botstates[i]->lastucmd.upmove = 0; + botstates[i]->lastucmd.buttons = 0; + botstates[i]->lastucmd.serverTime = time; + gi.BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); + } + return qtrue; + } + + if (bot_memorydump.integer) { + gi.BotLibVarSet("memorydump", "1"); + gi.cvar_set("bot_memorydump", "0"); + } + if (bot_saveroutingcache.integer) { + gi.BotLibVarSet("saveroutingcache", "1"); + gi.cvar_set("bot_saveroutingcache", "0"); + } + //check if bot interbreeding is activated + BotInterbreeding(); + //cap the bot think time + if (bot_thinktime.integer > 200) { + gi.cvar_set("bot_thinktime", "200"); + } + //if the bot think time changed we should reschedule the bots + if (bot_thinktime.integer != lastbotthink_time) { + lastbotthink_time = bot_thinktime.integer; + BotScheduleBotThink(); + } + + elapsed_time = time - local_time; + local_time = time; + + botlib_residual += elapsed_time; + + if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time; + else thinktime = bot_thinktime.integer; + + // update the bot library + if ( botlib_residual >= thinktime ) { + botlib_residual -= thinktime; + + gi.BotLibStartFrame((float) time / 1000); + + if (!gi.AAS_Initialized()) return qfalse; + + //update entities in the botlib + for (i = 0; i < MAX_GENTITIES; i++) { + ent = &g_entities[i]; + if (!ent->inuse) { + gi.BotLibUpdateEntity(i, NULL); + continue; + } + if (!ent->linked) { + gi.BotLibUpdateEntity(i, NULL); + continue; + } + if (ent->svflags & SVF_NOCLIENT) { + gi.BotLibUpdateEntity(i, NULL); + continue; + } + // do not update missiles + if (ent->s.eType == ET_MISSILE) { // && ent->s.weapon != WP_GRAPPLING_HOOK) { + gi.BotLibUpdateEntity(i, NULL); + continue; + } + // do not update event only entities + if (ent->s.eType > ET_EVENTS) { + gi.BotLibUpdateEntity(i, NULL); + continue; + } +#ifdef MISSIONPACKBOTTODO + // never link prox mine triggers + if (ent->contents == CONTENTS_TRIGGER) { + if (ent->touch == ProximityMine_Trigger) { + gi.BotLibUpdateEntity(i, NULL); + continue; + } + } +#endif + // + memset(&state, 0, sizeof(bot_entitystate_t)); + // + VectorCopy(ent->currentOrigin, state.origin); + VectorCopy(ent->currentAngles, state.angles); + VectorCopy(ent->s.origin2, state.old_origin); + VectorCopy(ent->mins, state.mins); + VectorCopy(ent->maxs, state.maxs); + state.type = ent->s.eType; + state.flags = ent->s.eFlags; + if (ent->bmodel) state.solid = SOLID_BSP; + else state.solid = SOLID_BBOX; + state.groundent = ent->s.groundEntityNum; + state.modelindex = ent->s.modelindex; +// state.modelindex2 = ent->s.modelindex2; + state.frame = ent->s.frame; +// state.event = ent->s.event; +// state.eventParm = ent->s.eventParm; +// state.powerups = ent->s.powerups; +// state.legsAnim = ent->s.legsAnim; +// state.torsoAnim = ent->s.torsoAnim; +// state.weapon = ent->s.weapon; + // + gi.BotLibUpdateEntity(i, &state); + } + + BotAIRegularUpdate(); + } + + floattime = gi.AAS_Time(); + + // execute scheduled bot AI + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + // + botstates[i]->botthink_residual += elapsed_time; + // + if ( botstates[i]->botthink_residual >= thinktime ) { + botstates[i]->botthink_residual -= thinktime; + + if (!gi.AAS_Initialized()) return qfalse; + + if (g_entities[i].client->pers.enterTime) { // connected == CON_CONNECTED) { + BotAI(i, (float) thinktime / 1000); + } + } + } + + + // execute bot user commands every frame + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + if( !g_entities[i].client->pers.enterTime) { // connected != CON_CONNECTED ) { + continue; + } + + BotUpdateInput(botstates[i], time, elapsed_time); + gi.BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); + } + + return qtrue; +} + +/* +============== +BotInitLibrary +============== +*/ +int BotInitLibrary(void) { + char buf[144]; + + //set the maxclients->integer and maxentities library variables before calling BotSetupLibrary + gi.Cvar_VariableStringBuffer("sv_maxclients->integer", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "8"); + gi.BotLibVarSet("maxclients->integer", buf); + Com_sprintf(buf, sizeof(buf), "%d", MAX_GENTITIES); + gi.BotLibVarSet("maxentities", buf); + //bsp checksum + gi.Cvar_VariableStringBuffer("sv_mapChecksum", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("sv_mapChecksum", buf); + //maximum number of aas links + gi.Cvar_VariableStringBuffer("max_aaslinks", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("max_aaslinks", buf); + //maximum number of items in a level + gi.Cvar_VariableStringBuffer("max_levelitems", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("max_levelitems", buf); + //game type + gi.Cvar_VariableStringBuffer("g_gametype", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "0"); + gi.BotLibVarSet("g_gametype", buf); + //bot developer mode and log file + gi.BotLibVarSet("bot_developer", bot_developer.string); + gi.BotLibVarSet("log", buf); + //no chatting + gi.Cvar_VariableStringBuffer("bot_nochat", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("nochat", "0"); + //visualize jump pads + gi.Cvar_VariableStringBuffer("bot_visualizejumppads", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("bot_visualizejumppads", buf); + //forced clustering calculations + gi.Cvar_VariableStringBuffer("bot_forceclustering", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("forceclustering", buf); + //forced reachability calculations + gi.Cvar_VariableStringBuffer("bot_forcereachability", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("forcereachability", buf); + //force writing of AAS to file + gi.Cvar_VariableStringBuffer("bot_forcewrite", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("forcewrite", buf); + //no AAS optimization + gi.Cvar_VariableStringBuffer("bot_aasoptimize", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("aasoptimize", buf); + // + gi.Cvar_VariableStringBuffer("bot_saveroutingcache", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("saveroutingcache", buf); + //reload instead of cache bot character files + gi.Cvar_VariableStringBuffer("bot_reloadcharacters", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "0"); + gi.BotLibVarSet("bot_reloadcharacters", buf); + //base directory + gi.Cvar_VariableStringBuffer("fs_basepath", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("basedir", buf); + //game directory + gi.Cvar_VariableStringBuffer("fs_game", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("gamedir", buf); + //cd directory + gi.Cvar_VariableStringBuffer("fs_cdpath", buf, sizeof(buf)); + if (strlen(buf)) gi.BotLibVarSet("cddir", buf); + // +#ifdef MISSIONPACKBOTTODO + gi.BotLibDefine("MISSIONPACK"); +#endif + //setup the bot library + return gi.BotLibSetup(); +} + +/* +============== +BotAISetup +============== +*/ +int BotAISetup( int restart ) { + int errnum; + + local_time = 0; + botlib_residual = 0; + lastbotthink_time = 0; + + floattime = 0; + regularupdate_time = 0; + + gi.Cvar_Register(&bot_thinktime, "bot_thinktime", "100", CVAR_CHEAT); + gi.Cvar_Register(&bot_memorydump, "bot_memorydump", "0", CVAR_CHEAT); + gi.Cvar_Register(&bot_saveroutingcache, "bot_saveroutingcache", "0", CVAR_CHEAT); + gi.Cvar_Register(&bot_pause, "bot_pause", "0", CVAR_CHEAT); + gi.Cvar_Register(&bot_report, "bot_report", "0", CVAR_CHEAT); + gi.Cvar_Register(&bot_testsolid, "bot_testsolid", "0", CVAR_CHEAT); + gi.Cvar_Register(&bot_testclusters, "bot_testclusters", "0", CVAR_CHEAT); + gi.Cvar_Register(&bot_developer, "bot_developer", "0", CVAR_CHEAT); + gi.Cvar_Register(&bot_interbreedchar, "bot_interbreedchar", "", 0); + gi.Cvar_Register(&bot_interbreedbots, "bot_interbreedbots", "10", 0); + gi.Cvar_Register(&bot_interbreedcycle, "bot_interbreedcycle", "20", 0); + gi.Cvar_Register(&bot_interbreedwrite, "bot_interbreedwrite", "", 0); + + //if the game is restarted for a tournament + if (restart) { + return qtrue; + } + + //initialize the bot states + memset( botstates, 0, sizeof(botstates) ); + + errnum = BotInitLibrary(); + if (errnum != BLERR_NOERROR) return qfalse; + return qtrue; +} + +/* +============== +BotAIShutdown +============== +*/ +int BotAIShutdown( int restart ) { + + int i; + + //if the game is restarted for a tournament + if ( restart ) { + //shutdown all the bots in the botlib + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotAIShutdownClient(botstates[i]->client, restart); + } + } + //don't shutdown the bot library + } + else { + gi.BotLibShutdown(); + } + return qtrue; +} + diff --git a/dlls/game/ai_main.h b/dlls/game/ai_main.h new file mode 100644 index 0000000..9ce6f24 --- /dev/null +++ b/dlls/game/ai_main.h @@ -0,0 +1,286 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_main.h + * + * desc: Quake3 bot AI + * + * $Archive: /Code/DLLs/game/ai_main.h $ + * $Author: Jwaters $ + * $Revision: 2 $ + * $Modtime: 8/05/02 5:51p $ + * $Date: 8/06/02 6:28p $ + * + *****************************************************************************/ +#ifndef __AI_MAIN__ +#define __AI_MAIN__ + +//#define DEBUG +#define CTF + +//#define MAX_ITEMS 256 // already in q_shared.h +//bot flags +#define BFL_STRAFERIGHT 1 //strafe to the right +#define BFL_ATTACKED 2 //bot has attacked last ai frame +#define BFL_ATTACKJUMPED 4 //bot jumped during attack last frame +#define BFL_AIMATENEMY 8 //bot aimed at the enemy this frame +#define BFL_AVOIDRIGHT 16 //avoid obstacles by going to the right +#define BFL_IDEALVIEWSET 32 //bot has ideal view angles set +#define BFL_FIGHTSUICIDAL 64 //bot is in a suicidal fight +//long term goal types +#define LTG_TEAMHELP 1 //help a team mate +#define LTG_TEAMACCOMPANY 2 //accompany a team mate +#define LTG_DEFENDKEYAREA 3 //defend a key area +#define LTG_GETFLAG 4 //get the enemy flag +#define LTG_RUSHBASE 5 //rush to the base +#define LTG_RETURNFLAG 6 //return the flag +#define LTG_CAMP 7 //camp somewhere +#define LTG_CAMPORDER 8 //ordered to camp somewhere +#define LTG_PATROL 9 //patrol +#define LTG_GETITEM 10 //get an item +#define LTG_KILL 11 //kill someone +#define LTG_HARVEST 12 //harvest skulls +#define LTG_ATTACKENEMYBASE 13 //attack the enemy base +#define LTG_MAKELOVE_UNDER 14 +#define LTG_MAKELOVE_ONTOP 15 +//some goal dedication times +#define TEAM_HELP_TIME 60 //1 minute teamplay help time +#define TEAM_ACCOMPANY_TIME 600 //10 minutes teamplay accompany time +#define TEAM_DEFENDKEYAREA_TIME 600 //10 minutes ctf defend base time +#define TEAM_CAMP_TIME 600 //10 minutes camping time +#define TEAM_PATROL_TIME 600 //10 minutes patrolling time +#define TEAM_LEAD_TIME 600 //10 minutes taking the lead +#define TEAM_GETITEM_TIME 60 //1 minute +#define TEAM_KILL_SOMEONE 180 //3 minute to kill someone +#define TEAM_ATTACKENEMYBASE_TIME 600 //10 minutes +#define TEAM_HARVEST_TIME 120 //2 minutes +#define CTF_GETFLAG_TIME 600 //10 minutes ctf get flag time +#define CTF_RUSHBASE_TIME 120 //2 minutes ctf rush base time +#define CTF_RETURNFLAG_TIME 180 //3 minutes to return the flag +#define CTF_ROAM_TIME 60 //1 minute ctf roam time +//patrol flags +#define PATROL_LOOP 1 +#define PATROL_REVERSE 2 +#define PATROL_BACK 4 +//teamplay task preference +#define TEAMTP_DEFENDER 1 +#define TEAMTP_ATTACKER 2 +//CTF strategy +#define CTFS_AGRESSIVE 1 +//copied from the aas file header +#define PRESENCE_NONE 1 +#define PRESENCE_NORMAL 2 +#define PRESENCE_CROUCH 4 +// +#define MAX_PROXMINES 64 + +//check points +typedef struct bot_waypoint_s +{ + int inuse; + char name[32]; + bot_goal_t goal; + struct bot_waypoint_s *next, *prev; +} bot_waypoint_t; + +#define MAX_ACTIVATESTACK 8 +#define MAX_ACTIVATEAREAS 32 + +typedef struct bot_activategoal_s +{ + int inuse; + bot_goal_t goal; //goal to activate (buttons etc.) + float time; //time to activate something + float start_time; //time starting to activate something + float justused_time; //time the goal was used + int shoot; //true if bot has to shoot to activate + int weapon; //weapon to be used for activation + vec3_t target; //target to shoot at to activate something + vec3_t origin; //origin of the blocking entity to activate + int areas[MAX_ACTIVATEAREAS]; //routing areas disabled by blocking entity + int numareas; //number of disabled routing areas + int areasdisabled; //true if the areas are disabled for the routing + struct bot_activategoal_s *next; //next activate goal on stack +} bot_activategoal_t; + +//bot state +typedef struct bot_state_s +{ + int inuse; //true if this state is used by a bot client + int botthink_residual; //residual for the bot thinks + int client; //client number of the bot + int entitynum; //entity number of the bot + playerState_t cur_ps; //current player state + int last_eFlags; //last ps flags + usercmd_t lastucmd; //usercmd from last frame + int entityeventTime[1024]; //last entity event time + // + bot_settings_t settings; //several bot settings + int (*ainode)(struct bot_state_s *bs); //current AI node + float thinktime; //time the bot thinks this frame + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + int presencetype; //presence type of the bot + vec3_t eye; //eye coordinates of the bot + int areanum; //the number of the area the bot is in + int inventory[256]; // was MAX_INVENTORY, but they have different defs in bot code FIXME //string with items amounts the bot has + int tfl; //the travel flags the bot uses + int flags; //several flags + int respawn_wait; //wait until respawned + int lasthealth; //health value previous frame + int lastkilledplayer; //last killed player + int lastkilledby; //player that last killed this bot + int botdeathtype; //the death type of the bot + int enemydeathtype; //the death type of the enemy + int botsuicide; //true when the bot suicides + int enemysuicide; //true when the enemy of the bot suicides + int setupcount; //true when the bot has just been setup + int map_restart; //true when the map is being restarted + int entergamechat; //true when the bot used an enter game chat + int num_deaths; //number of time this bot died + int num_kills; //number of kills of this bot + int revenge_enemy; //the revenge enemy + int revenge_kills; //number of kills the enemy made + int lastframe_health; //health value the last frame + int lasthitcount; //number of hits last frame + int chatto; //chat to all or team + float walker; //walker charactertic + float ltime; //local bot time + float entergame_time; //time the bot entered the game + float ltg_time; //long term goal time + float nbg_time; //nearby goal time + float respawn_time; //time the bot takes to respawn + float respawnchat_time; //time the bot started a chat during respawn + float chase_time; //time the bot will chase the enemy + float enemyvisible_time; //time the enemy was last visible + float check_time; //time to check for nearby items + float stand_time; //time the bot is standing still + float lastchat_time; //time the bot last selected a chat + float kamikaze_time; //time to check for kamikaze usage + float invulnerability_time; //time to check for invulnerability usage + float standfindenemy_time; //time to find enemy while standing + float attackstrafe_time; //time the bot is strafing in one dir + float attackcrouch_time; //time the bot will stop crouching + float attackchase_time; //time the bot chases during actual attack + float attackjump_time; //time the bot jumped during attack + float enemysight_time; //time before reacting to enemy + float enemydeath_time; //time the enemy died + float enemyposition_time; //time the position and velocity of the enemy were stored + float defendaway_time; //time away while defending + float defendaway_range; //max travel time away from defend area + float rushbaseaway_time; //time away from rushing to the base + float attackaway_time; //time away from attacking the enemy base + float harvestaway_time; //time away from harvesting + float ctfroam_time; //time the bot is roaming in ctf + float killedenemy_time; //time the bot killed the enemy + float arrive_time; //time arrived (at companion) + float lastair_time; //last time the bot had air + float teleport_time; //last time the bot teleported + float camp_time; //last time camped + float camp_range; //camp range + float weaponchange_time; //time the bot started changing weapons + float firethrottlewait_time; //amount of time to wait + float firethrottleshoot_time; //amount of time to shoot + float notblocked_time; //last time the bot was not blocked + float blockedbyavoidspot_time; //time blocked by an avoid spot + float predictobstacles_time; //last time the bot predicted obstacles + int predictobstacles_goalareanum; //last goal areanum the bot predicted obstacles for + vec3_t aimtarget; + vec3_t enemyvelocity; //enemy velocity 0.5 secs ago during battle + vec3_t enemyorigin; //enemy origin 0.5 secs ago during battle + // + int kamikazebody; //kamikaze body + int proxmines[MAX_PROXMINES]; + int numproxmines; + // + int character; //the bot character + int ms; //move state of the bot + int gs; //goal state of the bot + int cs; //chat state of the bot + int ws; //weapon state of the bot + // + int enemy; //enemy entity number + int lastenemyareanum; //last reachability area the enemy was in + vec3_t lastenemyorigin; //last origin of the enemy in the reachability area + int weaponnum; //current weapon number + vec3_t viewangles; //current view angles + vec3_t ideal_viewangles; //ideal view angles + vec3_t viewanglespeed; + // + int ltgtype; //long term goal type + // team goals + int teammate; //team mate involved in this team goal + int decisionmaker; //player who decided to go for this goal + int ordered; //true if ordered to do something + float order_time; //time ordered to do something + int owndecision_time; //time the bot made it's own decision + bot_goal_t teamgoal; //the team goal + bot_goal_t altroutegoal; //alternative route goal + float reachedaltroutegoal_time; //time the bot reached the alt route goal + float teammessage_time; //time to message team mates what the bot is doing + float teamgoal_time; //time to stop helping team mate + float teammatevisible_time; //last time the team mate was NOT visible + int teamtaskpreference; //team task preference + // last ordered team goal + int lastgoal_decisionmaker; + int lastgoal_ltgtype; + int lastgoal_teammate; + bot_goal_t lastgoal_teamgoal; + // for leading team mates + int lead_teammate; //team mate the bot is leading + bot_goal_t lead_teamgoal; //team goal while leading + float lead_time; //time leading someone + float leadvisible_time; //last time the team mate was visible + float leadmessage_time; //last time a messaged was sent to the team mate + float leadbackup_time; //time backing up towards team mate + // + char teamleader[32]; //netname of the team leader + float askteamleader_time; //time asked for team leader + float becometeamleader_time; //time the bot will become the team leader + float teamgiveorders_time; //time to give team orders + float lastflagcapture_time; //last time a flag was captured + int numteammates; //number of team mates + int redflagstatus; //0 = at base, 1 = not at base + int blueflagstatus; //0 = at base, 1 = not at base + int neutralflagstatus; //0 = at base, 1 = our team has flag, 2 = enemy team has flag, 3 = enemy team dropped the flag + int flagstatuschanged; //flag status changed + int forceorders; //true if forced to give orders + int flagcarrier; //team mate carrying the enemy flag + int ctfstrategy; //ctf strategy + char subteam[32]; //sub team name + float formation_dist; //formation team mate intervening space + char formation_teammate[16]; //netname of the team mate the bot uses for relative positioning + float formation_angle; //angle relative to the formation team mate + vec3_t formation_dir; //the direction the formation is moving in + vec3_t formation_origin; //origin the bot uses for relative positioning + bot_goal_t formation_goal; //formation goal + + bot_activategoal_t *activatestack; //first activate goal on the stack + bot_activategoal_t activategoalheap[MAX_ACTIVATESTACK]; //activate goal heap + + bot_waypoint_t *checkpoints; //check points + bot_waypoint_t *patrolpoints; //patrol points + bot_waypoint_t *curpatrolpoint; //current patrol point the bot is going for + int patrolflags; //patrol flags +} bot_state_t; + +//resets the whole bot state +void BotResetState(bot_state_t *bs); +//returns the number of bots in the game +int NumBots(void); +//returns info about the entity +void BotEntityInfo(int entnum, aas_entityinfo_t *info); + +extern float floattime; +#define FloatTime() level.time + +// from the game source +void QDECL BotAI_Print(int type, char *fmt, ...); +void QDECL QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ); +void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); +int BotAI_GetClientState( int clientNum, playerState_t *state ); +int BotAI_GetEntityState( int entityNum, entityState_t *state ); +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ); +int BotTeamLeader(bot_state_t *bs); +#endif // __AI_MAIN__ diff --git a/dlls/game/ai_team.cpp b/dlls/game/ai_team.cpp new file mode 100644 index 0000000..3fba607 --- /dev/null +++ b/dlls/game/ai_team.cpp @@ -0,0 +1,2087 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_team.c + * + * desc: Quake3 bot AI + * + * $Archive: /EF2/Code/DLLs/game/ai_team.cpp $ + * $Author: Singlis $ + * $Revision: 6 $ + * $Modtime: 9/24/03 3:03p $ + * $Date: 9/26/03 2:35p $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +#include "ai_vcmd.h" + +#include "match.h" + +// for the voice chats +#include "botmenudef.h" + +//ctf task preferences for a client +typedef struct bot_ctftaskpreference_s +{ + char name[36]; + int preference; +} bot_ctftaskpreference_t; + +bot_ctftaskpreference_t ctftaskpreferences[MAX_CLIENTS]; + + +/* +================== +BotValidTeamLeader +================== +*/ +int BotValidTeamLeader(bot_state_t *bs) { + if (!strlen(bs->teamleader)) return qfalse; + if (ClientFromName(bs->teamleader) == -1) return qfalse; + return qtrue; +} + +/* +================== +BotNumTeamMates +================== +*/ +int BotNumTeamMates(bot_state_t *bs) { + int i, numplayers; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = gi.Cvar_VariableIntegerValue("sv_maxclients"); + + numplayers = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "name"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + numplayers++; + } + } + return numplayers; +} + +/* +================== +BotClientTravelTimeToGoal +================== +*/ +int BotClientTravelTimeToGoal(int client, bot_goal_t *goal) { + playerState_t ps; + int areanum; + + BotAI_GetClientState(client, &ps); + areanum = BotPointAreaNum(ps.origin); + if (!areanum) return 1; + return gi.AAS_AreaTravelTimeToGoalArea(areanum, ps.origin, goal->areanum, TFL_DEFAULT); +} + +/* +================== +BotSortTeamMatesByBaseTravelTime +================== +*/ +int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxteammates) { + + int i, j, k, numteammates, traveltime; + char buf[MAX_INFO_STRING]; + static int maxclients; + int traveltimes[MAX_CLIENTS]; + bot_goal_t *goal = NULL; + + if (gametype == GT_CTF) { // || gametype == GT_1FCTF) { // FIXME + if (BotTeam(bs) == TEAM_RED) + goal = &ctf_redflag; + else + goal = &ctf_blueflag; + } +#ifdef MISSIONPACK + else { + if (BotTeam(bs) == TEAM_RED) + goal = &redobelisk; + else + goal = &blueobelisk; + } +#endif + if (!maxclients) + maxclients = gi.Cvar_VariableIntegerValue("sv_maxclients"); + + numteammates = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "name"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + // + traveltime = BotClientTravelTimeToGoal(i, goal); + // + for (j = 0; j < numteammates; j++) { + if (traveltime < traveltimes[j]) { + for (k = numteammates; k > j; k--) { + traveltimes[k] = traveltimes[k-1]; + teammates[k] = teammates[k-1]; + } + break; + } + } + traveltimes[j] = traveltime; + teammates[j] = i; + numteammates++; + if (numteammates >= maxteammates) break; + } + } + return numteammates; +} + +/* +================== +BotSetTeamMateTaskPreference +================== +*/ +void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference) { + char teammatename[MAX_NETNAME]; + + ctftaskpreferences[teammate].preference = preference; + ClientName(teammate, teammatename, sizeof(teammatename)); + strcpy(ctftaskpreferences[teammate].name, teammatename); +} + +/* +================== +BotGetTeamMateTaskPreference +================== +*/ +int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate) { + char teammatename[MAX_NETNAME]; + + if (!ctftaskpreferences[teammate].preference) return 0; + ClientName(teammate, teammatename, sizeof(teammatename)); + if (Q_stricmp(teammatename, ctftaskpreferences[teammate].name)) return 0; + return ctftaskpreferences[teammate].preference; +} + +/* +================== +BotSortTeamMatesByTaskPreference +================== +*/ +int BotSortTeamMatesByTaskPreference(bot_state_t *bs, int *teammates, int numteammates) { + int defenders[MAX_CLIENTS], numdefenders; + int attackers[MAX_CLIENTS], numattackers; + int roamers[MAX_CLIENTS], numroamers; + int i, preference; + + numdefenders = numattackers = numroamers = 0; + for (i = 0; i < numteammates; i++) { + preference = BotGetTeamMateTaskPreference(bs, teammates[i]); + if (preference & TEAMTP_DEFENDER) { + defenders[numdefenders++] = teammates[i]; + } + else if (preference & TEAMTP_ATTACKER) { + attackers[numattackers++] = teammates[i]; + } + else { + roamers[numroamers++] = teammates[i]; + } + } + numteammates = 0; + //defenders at the front of the list + memcpy(&teammates[numteammates], defenders, numdefenders * sizeof(int)); + numteammates += numdefenders; + //roamers in the middle + memcpy(&teammates[numteammates], roamers, numroamers * sizeof(int)); + numteammates += numroamers; + //attacker in the back of the list + memcpy(&teammates[numteammates], attackers, numattackers * sizeof(int)); + numteammates += numattackers; + + return numteammates; +} + +/* +================== +BotSayTeamOrders +================== +*/ +void BotSayTeamOrderAlways(bot_state_t *bs, int toclient) { + char teamchat[MAX_MESSAGE_SIZE]; + char buf[MAX_MESSAGE_SIZE]; + char name[MAX_NETNAME]; + + //if the bot is talking to itself + if (bs->client == toclient) { + //don't show the message just put it in the console message queue + gi.BotGetChatMessage(bs->cs, buf, sizeof(buf)); + ClientName(bs->client, name, sizeof(name)); + Com_sprintf(teamchat, sizeof(teamchat), EC"(%s"EC")"EC": %s", name, buf); + gi.BotQueueConsoleMessage(bs->cs, CMS_CHAT, teamchat); + } + else { + gi.BotEnterChat(bs->cs, toclient, CHAT_TELL); + } +} + +/* +================== +BotSayTeamOrders +================== +*/ +void BotSayTeamOrder(bot_state_t *bs, int toclient) { +#ifdef MISSIONPACK + // voice chats only + char buf[MAX_MESSAGE_SIZE]; + + gi.BotGetChatMessage(bs->cs, buf, sizeof(buf)); +#else + BotSayTeamOrderAlways(bs, toclient); +#endif +} + +/* +================== +BotVoiceChat +================== +*/ +void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + if (toclient == -1) + // voice only say team + gi.EA_Command(bs->client, va("vsay_team %s", voicechat)); + else + // voice only tell single player + gi.EA_Command(bs->client, va("vtell %d %s", toclient, voicechat)); +#endif +} + +/* +================== +BotVoiceChatOnly +================== +*/ +void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + if (toclient == -1) + // voice only say team + gi.EA_Command(bs->client, va("vosay_team %s", voicechat)); + else + // voice only tell single player + gi.EA_Command(bs->client, va("votell %d %s", toclient, voicechat)); +#endif +} + +/* +================== +BotSayVoiceTeamOrder +================== +*/ +void BotSayVoiceTeamOrder(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + BotVoiceChat(bs, toclient, voicechat); +#endif +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to attack the enemy base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to accompany the flag carrier + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + } + else { + // + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + } + BotSayTeamOrder(bs, other); + //tell the one furthest from the the base not carrying the flag to get the enemy flag + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_RETURNFLAG); + break; + } + default: + { + defenders = (int) ((float) numteammates * 0.4 + 0.5); + if (defenders > 4) defenders = 4; + attackers = (int) ((float) numteammates * 0.5 + 0.5); + if (attackers > 5) attackers = 5; + if (bs->flagcarrier != -1) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[i], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[i]); + } + } + else { + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_GETFLAG); + BotSayTeamOrder(bs, teammates[i]); + } + } + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_RETURNFLAG); + } + // + break; + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_FlagNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //both will go for the enemy flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //keep one near the base for when the flag is returned + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other two get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //keep some people near the base for when the flag is returned + defenders = (int) ((float) numteammates * 0.3 + 0.5); + if (defenders > 3) defenders = 3; + attackers = (int) ((float) numteammates * 0.7 + 0.5); + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //both will go for the enemy flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //everyone go for the flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //keep some people near the base for when the flag is returned + defenders = (int) ((float) numteammates * 0.2 + 0.5); + if (defenders > 2) defenders = 2; + attackers = (int) ((float) numteammates * 0.7 + 0.5); + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_EnemyFlagNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to defend the base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + + if ( bs->flagcarrier == -1 ) { + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + BotSayTeamOrder(bs, other); + } + else { + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + } + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the other also to defend the base + + if (teammates[2] != bs->flagcarrier) + other = teammates[2]; + else + other = teammates[1]; + + if ( bs->flagcarrier == -1 ) { + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + BotSayTeamOrder(bs, other); + } + else { + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + } + break; + } + default: + { + //60% will defend the base + defenders = (int) ((float) numteammates * 0.6 + 0.5); + if (defenders > 6) defenders = 6; + //30% accompanies the flag carrier + attackers = (int) ((float) numteammates * 0.3 + 0.5); + if (attackers > 3) attackers = 3; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + // if we have a flag carrier + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + else { + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + // + break; + } + } +} + + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + defenders = (int) ((float) numteammates * 0.5 + 0.5); + if (defenders > 5) defenders = 5; + attackers = (int) ((float) numteammates * 0.4 + 0.5); + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + defenders = (int) ((float) numteammates * 0.4 + 0.5); + if (defenders > 4) defenders = 4; + attackers = (int) ((float) numteammates * 0.5 + 0.5); + if (attackers > 5) attackers = 5; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders(bot_state_t *bs) { + int flagstatus; + + // + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; + // + switch(flagstatus) { + case 0: BotCTFOrders_BothFlagsAtBase(bs); break; + case 1: BotCTFOrders_EnemyFlagNotAtBase(bs); break; + case 2: BotCTFOrders_FlagNotAtBase(bs); break; + case 3: BotCTFOrders_BothFlagsNotAtBase(bs); break; + } +} + + +/* +================== +BotCreateGroup +================== +*/ +void BotCreateGroup(bot_state_t *bs, int *teammates, int groupsize) { + char name[MAX_NETNAME], leadername[MAX_NETNAME]; + int i; + + // the others in the group will follow the teammates[0] + ClientName(teammates[0], leadername, sizeof(leadername)); + for (i = 1; i < groupsize; i++) + { + ClientName(teammates[i], name, sizeof(name)); + if (teammates[0] == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, leadername, NULL); + } + BotSayTeamOrderAlways(bs, teammates[i]); + } +} + +/* +================== +BotTeamOrders + + FIXME: defend key areas? +================== +*/ +void BotTeamOrders(bot_state_t *bs) { + int teammates[MAX_CLIENTS]; + int numteammates, i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = gi.Cvar_VariableIntegerValue("sv_maxclients"); + + numteammates = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + strncpy(buf,gi.getConfigstring(CS_PLAYERS+i), sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "name"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + teammates[numteammates] = i; + numteammates++; + } + } + // + switch(numteammates) { + case 1: break; + case 2: + { + //nothing special + break; + } + case 3: + { + //have one follow another and one free roaming + BotCreateGroup(bs, teammates, 2); + break; + } + case 4: + { + BotCreateGroup(bs, teammates, 2); //a group of 2 + BotCreateGroup(bs, &teammates[2], 2); //a group of 2 + break; + } + case 5: + { + BotCreateGroup(bs, teammates, 2); //a group of 2 + BotCreateGroup(bs, &teammates[2], 3); //a group of 3 + break; + } + default: + { + if (numteammates <= 10) { + for (i = 0; i < numteammates / 2; i++) { + BotCreateGroup(bs, &teammates[i*2], 2); //groups of 2 + } + } + break; + } + } +} + +#ifdef MISSIONPACK + +/* +================== +Bot1FCTFOrders_FlagAtCenter + + X% defend the base, Y% get the flag +================== +*/ +void Bot1FCTFOrders_FlagAtCenter(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //50% defend the base + defenders = (int) ((float) numteammates * 0.5 + 0.5); + if (defenders > 5) defenders = 5; + //40% get the flag + attackers = (int) ((float) numteammates * 0.4 + 0.5); + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //30% defend the base + defenders = (int) ((float) numteammates * 0.3 + 0.5); + if (defenders > 3) defenders = 3; + //60% get the flag + attackers = (int) ((float) numteammates * 0.6 + 0.5); + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_TeamHasFlag + + X% towards neutral flag, Y% go towards enemy base and accompany flag carrier if visible +================== +*/ +void Bot1FCTFOrders_TeamHasFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to attack the enemy base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_OFFENSE); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the one furthest from the base not carrying the flag to accompany the flag carrier + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + } + else { + // + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + } + BotSayTeamOrder(bs, other); + break; + } + default: + { + //30% will defend the base + defenders = (int) ((float) numteammates * 0.3 + 0.5); + if (defenders > 3) defenders = 3; + //70% accompanies the flag carrier + attackers = (int) ((float) numteammates * 0.7 + 0.5); + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + if (bs->flagcarrier != -1) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + else { + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to defend the base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the one furthest from the base not carrying the flag to accompany the flag carrier + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, other); + break; + } + default: + { + //20% will defend the base + defenders = (int) ((float) numteammates * 0.2 + 0.5); + if (defenders > 2) defenders = 2; + //80% accompanies the flag carrier + attackers = (int) ((float) numteammates * 0.8 + 0.5); + if (attackers > 8) attackers = 8; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_EnemyHasFlag + + X% defend the base, Y% towards neutral flag +================== +*/ +void Bot1FCTFOrders_EnemyHasFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //both defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will also defend the base + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_DEFEND); + break; + } + default: + { + //80% will defend the base + defenders = (int) ((float) numteammates * 0.8 + 0.5); + if (defenders > 8) defenders = 8; + //10% will try to return the flag + attackers = (int) ((float) numteammates * 0.1 + 0.5); + if (attackers > 2) attackers = 2; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //70% defend the base + defenders = (int) ((float) numteammates * 0.7 + 0.5); + if (defenders > 8) defenders = 8; + //20% try to return the flag + attackers = (int) ((float) numteammates * 0.2 + 0.5); + if (attackers > 2) attackers = 2; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_EnemyDroppedFlag + + X% defend the base, Y% get the flag +================== +*/ +void Bot1FCTFOrders_EnemyDroppedFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //50% defend the base + defenders = (int) ((float) numteammates * 0.5 + 0.5); + if (defenders > 5) defenders = 5; + //40% get the flag + attackers = (int) ((float) numteammates * 0.4 + 0.5); + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //30% defend the base + defenders = (int) ((float) numteammates * 0.3 + 0.5); + if (defenders > 3) defenders = 3; + //60% get the flag + attackers = (int) ((float) numteammates * 0.6 + 0.5); + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_DEFEND); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders +================== +*/ +void Bot1FCTFOrders(bot_state_t *bs) { + switch(bs->neutralflagstatus) { + case 0: Bot1FCTFOrders_FlagAtCenter(bs); break; + case 1: Bot1FCTFOrders_TeamHasFlag(bs); break; + case 2: Bot1FCTFOrders_EnemyHasFlag(bs); break; + case 3: Bot1FCTFOrders_EnemyDroppedFlag(bs); break; + } +} + +/* +================== +BotObeliskOrders + + X% in defence Y% in offence +================== +*/ +void BotObeliskOrders(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the one second closest to the base also defends the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other one attacks the enemy base + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //50% defend the base + defenders = (int) ((float) numteammates * 0.5 + 0.5); + if (defenders > 5) defenders = 5; + //40% attack the enemy base + attackers = (int) ((float) numteammates * 0.4 + 0.5); + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //30% defend the base + defenders = (int) ((float) numteammates * 0.3 + 0.5); + if (defenders > 3) defenders = 3; + //70% attack the enemy base + attackers = (int) ((float) numteammates * 0.7 + 0.5); + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } +} + +/* +================== +BotHarvesterOrders + + X% defend the base, Y% harvest +================== +*/ +void BotHarvesterOrders(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will harvest + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the one second closest to the base also defends the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other one goes harvesting + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //50% defend the base + defenders = (int) ((float) numteammates * 0.5 + 0.5); + if (defenders > 5) defenders = 5; + //40% goes harvesting + attackers = (int) ((float) numteammates * 0.4 + 0.5); + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will harvest + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others go harvesting + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //30% defend the base + defenders = (int) ((float) numteammates * 0.3 + 0.5); + if (defenders > 3) defenders = 3; + //70% go harvesting + attackers = (int) ((float) numteammates * 0.7 + 0.5); + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } +} +#endif + +/* +================== +FindHumanTeamLeader +================== +*/ +int FindHumanTeamLeader(bot_state_t *bs) { +// int i; + // no human team leaders in ef2, 'cause we've got no voicechat commands +/* + for (i = 0; i < MAX_CLIENTS; i++) { + if ( g_entities[i].inuse ) { + // if this player is not a bot + if ( !(g_entities[i].svflags & SVF_BOT) ) { + // if this player is ok with being the leader + if (!notleader[i]) { + // if this player is on the same team + if ( BotSameTeam(bs, i) ) { + ClientName(i, bs->teamleader, sizeof(bs->teamleader)); + // if not yet ordered to do anything + if ( !BotSetLastOrderedTask(bs) ) { + // go on defense by default +// BotVoiceChat_Defend(bs, i, SAY_TELL); // FIXME + } + return qtrue; + } + } + } + } + } + */ + return qfalse; +} + +/* +================== +BotTeamAI +================== +*/ +void BotTeamAI(bot_state_t *bs) { + int numteammates; + char netname[MAX_NETNAME]; + + // + if ( gametype < GT_TEAM ) + return; + // make sure we've got a valid team leader + if (!BotValidTeamLeader(bs)) { + // + if (!FindHumanTeamLeader(bs)) { + // + if (!bs->askteamleader_time && !bs->becometeamleader_time) { + if (bs->entergame_time + 10 > FloatTime()) { + bs->askteamleader_time = FloatTime() + 5 + random() * 10; + } + else { + bs->becometeamleader_time = FloatTime() + 5 + random() * 10; + } + } + if (bs->askteamleader_time && bs->askteamleader_time < FloatTime()) { + // if asked for a team leader and no response + BotAI_BotInitialChat(bs, "whoisteamleader", NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + bs->askteamleader_time = 0; + bs->becometeamleader_time = FloatTime() + 8 + random() * 10; + } + if (bs->becometeamleader_time && bs->becometeamleader_time < FloatTime()) { + BotAI_BotInitialChat(bs, "iamteamleader", NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotSayVoiceTeamOrder(bs, -1, VOICECHAT_STARTLEADER); + ClientName(bs->client, netname, sizeof(netname)); + strncpy(bs->teamleader, netname, sizeof(bs->teamleader)); + bs->teamleader[sizeof(bs->teamleader) - 1] = '\0'; + bs->becometeamleader_time = 0; + } + return; + } + } + bs->askteamleader_time = 0; + bs->becometeamleader_time = 0; + + //return if this bot is NOT the team leader + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + // + numteammates = BotNumTeamMates(bs); + //give orders + switch(gametype) { + case GT_TEAM: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotTeamOrders(bs); + //give orders again after 120 seconds + bs->teamgiveorders_time = FloatTime() + 120; + } + break; + } + case GT_CTF: + { + //if the number of team mates changed or the flag status changed + //or someone wants to know what to do + if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->flagstatuschanged = qfalse; + bs->forceorders = qfalse; + } + //if there were no flag captures the last 3 minutes + if (bs->lastflagcapture_time < FloatTime() - 240) { + bs->lastflagcapture_time = FloatTime(); + //randomly change the CTF strategy + if (random() < 0.4) { + bs->ctfstrategy ^= CTFS_AGRESSIVE; + bs->teamgiveorders_time = FloatTime(); + } + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 3) { + BotCTFOrders(bs); + // + bs->teamgiveorders_time = 0; + } + break; + } +#ifdef MISSIONPACK + case GT_1FCTF: + { + if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->flagstatuschanged = qfalse; + bs->forceorders = qfalse; + } + //if there were no flag captures the last 4 minutes + if (bs->lastflagcapture_time < FloatTime() - 240) { + bs->lastflagcapture_time = FloatTime(); + //randomly change the CTF strategy + if (random() < 0.4) { + bs->ctfstrategy ^= CTFS_AGRESSIVE; + bs->teamgiveorders_time = FloatTime(); + } + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 2) { + Bot1FCTFOrders(bs); + // + bs->teamgiveorders_time = 0; + } + break; + } + case GT_OBELISK: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotObeliskOrders(bs); + //give orders again after 30 seconds + bs->teamgiveorders_time = FloatTime() + 30; + } + break; + } + case GT_HARVESTER: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotHarvesterOrders(bs); + //give orders again after 30 seconds + bs->teamgiveorders_time = FloatTime() + 30; + } + break; + } +#endif + } +} + diff --git a/dlls/game/ai_team.h b/dlls/game/ai_team.h new file mode 100644 index 0000000..361702b --- /dev/null +++ b/dlls/game/ai_team.h @@ -0,0 +1,23 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_team.h + * + * desc: Quake3 bot AI + * + * $Archive: /Code/DLLs/game/ai_team.h $ + * $Author: Jwaters $ + * $Revision: 1 $ + * $Modtime: 7/25/02 11:48a $ + * $Date: 7/30/02 1:10p $ + * + *****************************************************************************/ + +void BotTeamAI(bot_state_t *bs); +int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate); +void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference); +void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat); +void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat); + + diff --git a/dlls/game/ai_vcmd.cpp b/dlls/game/ai_vcmd.cpp new file mode 100644 index 0000000..1d738a7 --- /dev/null +++ b/dlls/game/ai_vcmd.cpp @@ -0,0 +1,532 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_vcmd.c + * + * desc: Quake3 bot AI + * + * $Archive: /Code/DLLs/game/ai_vcmd.cpp $ + * $Author: Singlis $ + * $Revision: 3 $ + * $Modtime: 9/13/02 1:22p $ + * $Date: 9/13/02 4:32p $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +#include "ai_vcmd.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "botmenudef.h" + + +typedef struct voiceCommand_s +{ + char *cmd; + void (*func)(bot_state_t *bs, int client, int mode); +} voiceCommand_t; + +/* +================== +BotVoiceChat_GetFlag +================== +*/ +void BotVoiceChat_GetFlag(bot_state_t *bs, int client, int mode) { + // + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#endif + else { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + // get an alternate route in ctf + if (gametype == GT_CTF) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Offense +================== +*/ +void BotVoiceChat_Offense(bot_state_t *bs, int client, int mode) { + if ( gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + BotVoiceChat_GetFlag(bs, client, mode); + return; + } +#ifdef MISSIONPACK + if (gametype == GT_HARVESTER) { + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } + else +#endif + { + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + bs->attackaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Defend +================== +*/ +void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode) { +#ifdef MISSIONPACK + if ( gametype == GT_OBELISK || gametype == GT_HARVESTER) { + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); break; + default: return; + } + } + else +#endif + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); break; + default: return; + } + } + else { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + //away from defending + bs->defendaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_DefendFlag +================== +*/ +void BotVoiceChat_DefendFlag(bot_state_t *bs, int client, int mode) { + BotVoiceChat_Defend(bs, client, mode); +} + +/* +================== +BotVoiceChat_Patrol +================== +*/ +void BotVoiceChat_Patrol(bot_state_t *bs, int client, int mode) { + // + bs->decisionmaker = client; + // + bs->ltgtype = 0; + bs->lead_time = 0; + bs->lastgoal_ltgtype = 0; + // + BotAI_BotInitialChat(bs, "dismissed", NULL); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONPATROL); + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Camp +================== +*/ +void BotVoiceChat_Camp(bot_state_t *bs, int client, int mode) { + int areanum; + aas_entityinfo_t entinfo; + char netname[MAX_NETNAME]; + + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && gi.AAS_AreaReachability(areanum)) { + //NOTE: just assume the bot knows where the person is + //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + //} + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_CAMPORDER; + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; + //the teammate that requested the camping + bs->teammate = client; + //not arrived yet + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_FollowMe +================== +*/ +void BotVoiceChat_FollowMe(bot_state_t *bs, int client, int mode) { + int areanum; + aas_entityinfo_t entinfo; + char netname[MAX_NETNAME]; + + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && gi.AAS_AreaReachability(areanum)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //the team mate + bs->teammate = client; + //last time the team mate was assumed visible + bs->teammatevisible_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + //set the ltg type + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_FollowFlagCarrier +================== +*/ +void BotVoiceChat_FollowFlagCarrier(bot_state_t *bs, int client, int mode) { + int carrier; + + carrier = BotTeamFlagCarrier(bs); + if (carrier >= 0) + BotVoiceChat_FollowMe(bs, carrier, mode); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_ReturnFlag +================== +*/ +void BotVoiceChat_ReturnFlag(bot_state_t *bs, int client, int mode) { + //if not in CTF mode + if ( + gametype != GT_CTF +#ifdef MISSIONPACK + && gametype != GT_1FCTF +#endif + ) { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RETURNFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + bs->rushbaseaway_time = 0; + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_StartLeader +================== +*/ +void BotVoiceChat_StartLeader(bot_state_t *bs, int client, int mode) { + ClientName(client, bs->teamleader, sizeof(bs->teamleader)); +} + +/* +================== +BotVoiceChat_StopLeader +================== +*/ +void BotVoiceChat_StopLeader(bot_state_t *bs, int client, int mode) { + char netname[MAX_MESSAGE_SIZE]; + + if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { + bs->teamleader[0] = '\0'; + notleader[client] = qtrue; + } +} + +/* +================== +BotVoiceChat_WhoIsLeader +================== +*/ +void BotVoiceChat_WhoIsLeader(bot_state_t *bs, int client, int mode) { + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + + ClientName(bs->client, netname, sizeof(netname)); + //if this bot IS the team leader + if (!Q_stricmp(netname, bs->teamleader)) { + BotAI_BotInitialChat(bs, "iamteamleader", NULL); + gi.BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_STARTLEADER); + } +} + +/* +================== +BotVoiceChat_WantOnDefense +================== +*/ +void BotVoiceChat_WantOnDefense(bot_state_t *bs, int client, int mode) { + char netname[MAX_NETNAME]; + int preference; + + preference = BotGetTeamMateTaskPreference(bs, client); + preference &= ~TEAMTP_ATTACKER; + preference |= TEAMTP_DEFENDER; + BotSetTeamMateTaskPreference(bs, client, preference); + // + EasyClientName(client, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, client, VOICECHAT_YES); + gi.EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotVoiceChat_WantOnOffense +================== +*/ +void BotVoiceChat_WantOnOffense(bot_state_t *bs, int client, int mode) { + char netname[MAX_NETNAME]; + int preference; + + preference = BotGetTeamMateTaskPreference(bs, client); + preference &= ~TEAMTP_DEFENDER; + preference |= TEAMTP_ATTACKER; + BotSetTeamMateTaskPreference(bs, client, preference); + // + EasyClientName(client, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); + gi.BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, client, VOICECHAT_YES); + gi.EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +void BotVoiceChat_Dummy(bot_state_t *bs, int client, int mode) { +} + +voiceCommand_t voiceCommands[] = { + {VOICECHAT_GETFLAG, BotVoiceChat_GetFlag}, + {VOICECHAT_OFFENSE, BotVoiceChat_Offense }, + {VOICECHAT_DEFEND, BotVoiceChat_Defend }, + {VOICECHAT_DEFENDFLAG, BotVoiceChat_DefendFlag }, + {VOICECHAT_PATROL, BotVoiceChat_Patrol }, + {VOICECHAT_CAMP, BotVoiceChat_Camp }, + {VOICECHAT_FOLLOWME, BotVoiceChat_FollowMe }, + {VOICECHAT_FOLLOWFLAGCARRIER, BotVoiceChat_FollowFlagCarrier }, + {VOICECHAT_RETURNFLAG, BotVoiceChat_ReturnFlag }, + {VOICECHAT_STARTLEADER, BotVoiceChat_StartLeader }, + {VOICECHAT_STOPLEADER, BotVoiceChat_StopLeader }, + {VOICECHAT_WHOISLEADER, BotVoiceChat_WhoIsLeader }, + {VOICECHAT_WANTONDEFENSE, BotVoiceChat_WantOnDefense }, + {VOICECHAT_WANTONOFFENSE, BotVoiceChat_WantOnOffense }, + {NULL, BotVoiceChat_Dummy} +}; + +int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voiceChat) { + int i, clientNum; + char *ptr, buf[MAX_MESSAGE_SIZE], *cmd,*cmd2; + + if (!TeamPlayIsOn()) { + return qfalse; + } + + if ( mode == SAY_ALL ) { + return qfalse; + } + + Q_strncpyz(buf, voiceChat, sizeof(buf)); + + cmd = buf; + ptr = strchr(buf,' '); + *ptr++ = 0; + clientNum = atoi(ptr); + cmd2 = ptr+2; + ptr = strchr(cmd2,' '); + *ptr = 0; + + if (!BotSameTeam(bs, clientNum)) { + return qfalse; + } + + for (i = 0; voiceCommands[i].cmd; i++) { + if (!Q_stricmp(cmd2, voiceCommands[i].cmd)) { + voiceCommands[i].func(bs, clientNum, mode); + return qtrue; + } + } + return qfalse; +} diff --git a/dlls/game/ai_vcmd.h b/dlls/game/ai_vcmd.h new file mode 100644 index 0000000..fcf4ed4 --- /dev/null +++ b/dlls/game/ai_vcmd.h @@ -0,0 +1,20 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_vcmd.h + * + * desc: Quake3 bot AI + * + * $Archive: /Code/DLLs/game/ai_vcmd.h $ + * $Author: Jwaters $ + * $Revision: 1 $ + * $Modtime: 7/25/02 11:48a $ + * $Date: 7/30/02 1:10p $ + * + *****************************************************************************/ + +int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voicechat); +void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode); + + diff --git a/dlls/game/ammo.cpp b/dlls/game/ammo.cpp new file mode 100644 index 0000000..9d9d38e --- /dev/null +++ b/dlls/game/ammo.cpp @@ -0,0 +1,175 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/ammo.cpp $ +// $Revision:: 15 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:35p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class for all ammunition for entities derived from the Weapon class. +// +// AmmoEntity is the Class which represents ammo that the player "sees" and " +// picks up" in the game +// +// Ammo is the Class which is used to keep track of how much ammo a player has +// in his inventory + +#include "_pch_cpp.h" +#include "ammo.h" +#include "player.h" + +CLASS_DECLARATION( Item, AmmoEntity, NULL ) +{ + { NULL, NULL } +}; + +AmmoEntity::AmmoEntity() +{ + if ( LoadingSavegame ) + { + // all data will be setup by the archive function + return; + } + setName( "UnknownAmmo" ); + amount = 0; + + _lastPrintTime = 0.0f; +} + +Item *AmmoEntity::ItemPickup( Entity *other, qboolean add_to_inventory, qboolean ) +{ + Sentient *player; + str realname; + int amountUsed; + + if ( !other->isSubclassOf( Player ) ) + return NULL; + + if ( !Pickupable( other ) ) + return NULL; + + player = ( Sentient * )other; + + // Give the ammo to the player + amountUsed = player->GiveAmmo( item_name, (int) amount, true ); + + if ( amountUsed == 0 ) + { + if ( level.time > _lastPrintTime + 1.0f ) + { + _lastPrintTime = level.time; + ((Player *)other)->setItemText( getIcon(), va( "$$CouldNotPickUp$$ $$Ammo-%s$$ $$FullAmmo$$", item_name.c_str() ) ); + //gi.centerprintf ( other->edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$CouldNotPickUp$$ %s $$FullAmmo$$", item_name.c_str() ); + } + + return NULL; + } + + // Play pickup sound + realname = GetRandomAlias( "snd_pickup" ); + if ( realname.length() > 1 ) + player->Sound( realname, CHAN_ITEM ); + + // Cancel some events + CancelEventsOfType( EV_Item_DropToFloor ); + CancelEventsOfType( EV_Item_Respawn ); + CancelEventsOfType( EV_FadeOut ); + + // Hide the model + setSolidType( SOLID_NOT ); + + if ( _missingSkin ) + { + ChangeSkin( _missingSkin, true ); + } + else + { + hideModel(); + } + + // Respawn? + if ( !Respawnable() ) + PostEvent( EV_Remove, FRAMETIME ); + else + PostEvent( EV_Item_Respawn, RespawnTime() ); + + // fire off any pickup_thread's + if ( pickup_thread.length() ) + { + ExecuteThread( pickup_thread ); + } + + return NULL; // This doesn't create any items +} + +void AmmoEntity::cacheStrings( void ) +{ + G_FindConfigstringIndex( va( "$$CouldNotPickUp$$ $$Ammo-%s$$ $$FullAmmo$$", item_name.c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); + G_FindConfigstringIndex( va( "$$PickedUp$$ %d $$Ammo-%s$$\n", (int)amount, item_name.c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); +} + +// This is the Class that is used to keep track of ammo in the player's inventory. +// It is not an entity, just a name and an amount. + +CLASS_DECLARATION( Class, Ammo, NULL ) +{ + {NULL, NULL} +}; + +Ammo::Ammo() +{ + if ( LoadingSavegame ) + { + // all data will be setup by the archive function + return; + } + setName( "UnknownAmmo" ); + setAmount( 0 ); + setMaxAmount( 100 ); +} + +void Ammo::setAmount( int a ) +{ + amount = a; + + if ( ( maxamount > 0 ) && ( amount > maxamount ) ) + amount = maxamount; +} + +int Ammo::getAmount( void ) +{ + return amount; +} + +void Ammo::setMaxAmount( int a ) +{ + maxamount = a; +} + +int Ammo::getMaxAmount( void ) +{ + return maxamount; +} + +void Ammo::setName( const str &n ) +{ + name = n; + name_index = gi.itemindex( name ); +} + +str Ammo::getName( void ) +{ + return name; +} + +int Ammo::getIndex( void ) +{ + return name_index; +} diff --git a/dlls/game/ammo.h b/dlls/game/ammo.h new file mode 100644 index 0000000..28d99cd --- /dev/null +++ b/dlls/game/ammo.h @@ -0,0 +1,89 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/ammo.h $ +// $Revision:: 6 $ +// $Author:: Steven $ +// $Date:: 2/14/03 5:37p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// Base class for all ammunition for entities derived from the Weapon class. +// + +#ifndef __AMMO_H__ +#define __AMMO_H__ + +#include "g_local.h" +#include "item.h" + +class AmmoEntity : public Item + { + private: + float _lastPrintTime; + + public: + CLASS_PROTOTYPE( AmmoEntity ); + + AmmoEntity(); + /* virtual */ Item * ItemPickup( Entity *other, qboolean add_to_inventory, qboolean ); + /* virtual */ void cacheStrings( void ); + /* virtual */ void Archive( Archiver &arc ); + }; + +inline void AmmoEntity::Archive( Archiver &arc ) +{ + Item::Archive( arc ); + + arc.ArchiveFloat( &_lastPrintTime ); +} + +class Ammo : public Class + { + int amount; + int maxamount; + str name; + int name_index; + + public: + CLASS_PROTOTYPE( Ammo ); + + Ammo(); + Ammo(const str &name, int amount, int name_index ); + + void setAmount( int a ); + int getAmount( void ); + void setMaxAmount( int a ); + int getMaxAmount( void ); + void setName( const str &name ); + str getName( void ); + int getIndex( void ); + virtual void Archive( Archiver &arc ); + }; + +inline void Ammo::Archive + ( + Archiver &arc + ) + + { + Class::Archive( arc ); + + arc.ArchiveInteger( &amount ); + arc.ArchiveInteger( &maxamount ); + arc.ArchiveString( &name ); + + // + // name_index not archived, because it is auto-generated by gi.itemindex + // + if ( arc.Loading() ) + { + setName( name ); + } + } + +#endif /* ammo.h */ diff --git a/dlls/game/animate.cpp b/dlls/game/animate.cpp new file mode 100644 index 0000000..b78e796 --- /dev/null +++ b/dlls/game/animate.cpp @@ -0,0 +1,1010 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/animate.cpp $ +// $Revision:: 23 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:35p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: Animate Class +// + +#include "_pch_cpp.h" +#include "animate.h" +#include "player.h" +#include + +// Leg Animation events +Event EV_Anim +( + "anim", + EV_DEFAULT, + "s", + "animName", + "set the legs animation to animName." +); +Event EV_SetFrame +( + "setframe", + EV_CODEONLY, + "iS", + "frameNumber animName", + "Set the frame on the legs, if anim is not specified, current is assumed." +); +Event EV_AnimDone +( + "animdone", + EV_CODEONLY, + NULL, + NULL, + "Legs animation has finished, not for script use." +); +Event EV_FrameDelta +( + "setmovedelta", + EV_CODEONLY, + "v", + "moveDelta", + "movement from animation, not for script use." +); +Event EV_StopAnimating +( + "stopanimating", + EV_CODEONLY, + NULL, + NULL, + "stop the legs from animating. Animation will end at the end of current cycle." +); + +// Torso Animation events +Event EV_Torso_Anim +( + "torso_anim", + EV_CODEONLY, + "s", + "animName", + "set the torso animation to animName." +); +Event EV_Torso_SetFrame +( + "torso_setframe", + EV_CODEONLY, + "iS", + "frameNumber animName", + "Set the frame on the torso, if anim is not specified, current is assumed." +); +Event EV_Torso_AnimDone +( + "torso_animdone", + EV_CODEONLY, + NULL, + NULL, + "Torso animation has finished, not for script use." +); +Event EV_Torso_StopAnimating +( + "torso_stopanimating", + EV_CODEONLY, + NULL, + NULL, + "stop the torso from animating. Animation will end at the end of current cycle." +); +Event EV_NewAnim +( + "animate_newanim", + EV_CODEONLY, + "ii", + "animNum bodyPart", + "Start a new animation, not for script use." +); + +CLASS_DECLARATION( Listener, Animate, "animate" ) +{ + { &EV_Anim, &Animate::Legs_AnimEvent }, + { &EV_SetFrame, &Animate::Legs_SetFrameEvent }, + { &EV_AnimDone, &Animate::Legs_AnimDoneEvent }, + { &EV_StopAnimating, &Animate::Legs_StopAnimating }, + { &EV_FrameDelta, &Animate::FrameDeltaEvent }, + { &EV_NewAnim, &Animate::NewAnimEvent }, + { &EV_Torso_Anim, &Animate::Torso_AnimEvent }, + { &EV_Torso_SetFrame, &Animate::Torso_SetFrameEvent }, + { &EV_Torso_AnimDone, &Animate::Torso_AnimDoneEvent }, + { &EV_Torso_StopAnimating, &Animate::Torso_StopAnimating }, + + { NULL, NULL } +}; + +Animate::Animate() +{ + // Should always use other constructor + + assert( 0 ); +} + +Animate::Animate( Entity *ent ) +{ + // Animation variables + frame_delta = Vector(0, 0, 0); + animDoneEvent = NULL; + torso_animDoneEvent = NULL; + + legs_animtime = 0; + torso_animtime = 0; + + legs_starttime = 0; + torso_starttime = 0; + + legs_numframes = 0; + torso_numframes = 0; + + legs_frametime = 0; + torso_frametime = 0; + + self = ent; + + if ( self ) + { + self->edict->s.animationRate = 1.0f; + self->edict->s.anim |= (ANIM_SERVER_EXITCOMMANDS_PROCESSED); + self->edict->s.torso_anim |= (ANIM_SERVER_EXITCOMMANDS_PROCESSED); + self->edict->s.effectsAnims[ 0 ] = -1 ; + self->edict->s.effectsAnims[ 1 ] = -1 ; + self->edict->s.effectsAnims[ 2 ] = -1 ; + self->edict->s.effectsAnims[ 3 ] = -1 ; + } +} + +Animate::~Animate() +{ + if ( animDoneEvent ) + { + delete animDoneEvent; + animDoneEvent = NULL; + } + + if ( torso_animDoneEvent ) + { + delete torso_animDoneEvent; + torso_animDoneEvent = NULL; + } +} + +void Animate::NewAnim( int animnum, bodypart_t part ) +{ + // + // this is an animating model, we set this here so that non-animating models + // don't have to go through the animating code path + // + if ( self->edict->s.eType == ET_GENERAL ) + { + self->edict->s.eType = ET_MODELANIM; + } + + if ( LoadingSavegame ) + { + // if we are loading a game, we don't need to start animating, that will be automatic + // all these events would mess everything up anyway + return; + } + + + int *anim; + int flags; + float *animtime; + float *starttime; + float *frametime; + int *numframes; + switch( part ) + { + case legs: + anim = &self->edict->s.anim; + self->edict->s.frame &= ~FRAME_EXPLICIT; + flags = EVENT_LEGS_ANIM; + animtime = &legs_animtime; + starttime = &legs_starttime; + frametime = &legs_frametime; + numframes = &legs_numframes; + break; + case torso: + anim = &self->edict->s.torso_anim; + self->edict->s.torso_frame &= ~FRAME_EXPLICIT; + flags = EVENT_TORSO_ANIM; + animtime = &torso_animtime; + starttime = &torso_starttime; + frametime = &torso_frametime; + numframes = &torso_numframes; + break; + default: + warning( "NewAnim", "Unknown body part %d", part ); + return; + break; + } + + int last_anim = *anim & ANIM_MASK; + int last_anim_flags = *anim; + + float lastStartTime = *starttime; + + // + // if the animations were different we need to process the entry and exit events + // + if ( ( last_anim != animnum ) && !( last_anim_flags & ANIM_SERVER_EXITCOMMANDS_PROCESSED ) ) + { + // exit the previous animation + tiki_cmd_t cmds; + if ( gi.Frame_Commands( self->edict->s.modelindex, last_anim, TIKI_FRAME_CMD_EXIT, &cmds ) ) + { + for( int ii = 0; ii < cmds.num_cmds; ii++ ) + { + Event *ev = new Event( cmds.cmds[ ii ].args[ 0 ] ); + + ev->SetSource( EV_FROM_ANIMATION ); + ev->SetAnimationNumber( last_anim ); + ev->SetAnimationFrame( 0 ); + + for( int j = 1; j < cmds.cmds[ ii ].num_args; j++ ) + { + ev->AddToken( cmds.cmds[ ii ].args[ j ] ); + } + self->ProcessEvent( ev ); + } + } + } + + if ( ( animnum >= 0 ) && ( animnum < gi.NumAnims( self->edict->s.modelindex ) ) ) + { + if ( *starttime == level.time ) + { + // don't toggle the togglebit if we've already had an animation set this frame + *anim = ( *anim & ANIM_TOGGLEBIT ) | animnum | ANIM_BLEND; + } + else + { + *anim = ( ( *anim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | animnum | ANIM_BLEND; + } + } + else + { + // bad value + return; + } + + // get rid of old anim events + CancelFlaggedEvents( flags ); + self->CancelFlaggedEvents( flags ); + + // get total time of animation + float totaltime = gi.Anim_Time( self->edict->s.modelindex, animnum ); + totaltime /= self->edict->s.animationRate; + + // set the total time for the animation + *animtime = totaltime; + + // set the start time of the animation + *starttime = level.time; + + // set the number of frames for the animation + *numframes = gi.Anim_NumFrames( self->edict->s.modelindex, animnum ); + + // set the time for each animation frame + *frametime = gi.Frame_Time( self->edict->s.modelindex, animnum, 0 ); + *frametime /= self->edict->s.animationRate; + + bool has_commands = (gi.Anim_HasCommands( self->edict->s.modelindex, animnum ) != 0); + + if ( self->edict->s.eFlags & EF_DONT_PROCESS_COMMANDS ) + has_commands = false; + + if ( ( ( last_anim != animnum ) || ( lastStartTime == 0 ) ) && ( has_commands ) ) + { + ClearAllEffectAnims(); + // enter this animation + tiki_cmd_t cmds; + if ( gi.Frame_Commands( self->edict->s.modelindex, animnum, TIKI_FRAME_CMD_ENTRY, &cmds ) ) + { + for( int ii = 0; ii < cmds.num_cmds; ii++ ) + { + Event *ev = new Event( cmds.cmds[ ii ].args[ 0 ] ); + ev->SetSource( EV_FROM_ANIMATION ); + ev->SetAnimationNumber( animnum ); + ev->SetAnimationFrame( 0 ); + + for( int j = 1; j < cmds.cmds[ ii ].num_args; j++ ) + { + ev->AddToken( cmds.cmds[ ii ].args[ j ] ); + } + self->ProcessEvent( ev ); + } + } + } + + + // initially don't post move delta stuff + bool dodelta = false; + + // if it is the legs, find out if there is a delta and if it is a delta_driven animation + if ( part == legs ) + { + Vector totaldelta; + gi.Anim_AbsoluteDelta( self->edict->s.modelindex, animnum, totaldelta ); + + float length = totaldelta.length(); + if ( length > MINIMUM_DELTA_MOVEMENT ) + { + int flags = gi.Anim_Flags( self->edict->s.modelindex, animnum ); + if ( !( flags & MDL_ANIM_DELTA_DRIVEN ) ) + { + length /= (float)*numframes; + if ( length > MINIMUM_DELTA_MOVEMENT_PER_FRAME ) + { + dodelta = true; + } + } + } + } + + if ( has_commands || dodelta ) + { + float time = 0; + + for( int i = 0; i < *numframes; i++ ) + { + if ( has_commands ) + { + // we want normal frame commands to occur right on the frame + tiki_cmd_t cmds; + if ( gi.Frame_Commands( self->edict->s.modelindex, animnum, i, &cmds ) ) + { + for( int ii = 0; ii < cmds.num_cmds; ii++ ) + { + Event *ev = new Event( cmds.cmds[ ii ].args[ 0 ] ); + + ev->SetSource( EV_FROM_ANIMATION ); + ev->SetAnimationNumber( animnum ); + ev->SetAnimationFrame( i ); + + for( int j = 1; j < cmds.cmds[ ii ].num_args; j++ ) + { + ev->AddToken( cmds.cmds[ ii ].args[ j ] ); + } + self->PostEvent( ev, time, flags ); + } + } + } + + // add to time + time += *frametime; + + // we want deltas to occur at the end of the frame + // only add deltas on the legs + if ( dodelta ) + { + Vector delta; + + // get the current frame delta + gi.Frame_Delta( self->edict->s.modelindex, animnum, i, delta ); + + if ( *frametime > FRAMETIME ) + { + float time_offset; + + VectorScale( delta, ( FRAMETIME / *frametime ), delta ); + + for ( time_offset = 0; time_offset < *frametime; time_offset += FRAMETIME ) + { + Event *ev = new Event( EV_FrameDelta ); + ev->AddVector( delta ); + PostEvent( ev, time + time_offset, flags ); + } + } + else + { + Event *ev = new Event( EV_FrameDelta ); + ev->AddVector( delta ); + ev->AddFloat( *frametime ); + PostEvent( ev, time, flags ); + } + } + + } + } + + // + // if we have a 1 frame animation, which has no commands, + // we aren't a subclass of Sentient and our animation time is the same as frametime + // there is no reason for us to constantly animate, since nothing will change + // lets get the hell out of dodge! + if ( + ( *numframes == 1 ) && + !self->isSubclassOf( Player ) && + !has_commands && + ( *frametime <= FRAMETIME ) + ) + { + switch( part ) + { + case legs: + if ( animDoneEvent ) + { + self->PostEvent( *animDoneEvent, 0.0f ); + } + break; + case torso: + if ( torso_animDoneEvent ) + { + self->PostEvent( *torso_animDoneEvent, 0.0f ); + } + break; + default: + break; + } + return; + } + + // Note: Below we post the ANIMDONE on the last frame of the animation if we're switching + // animations. If we're playing a looped anim, the ANIMDONE event is posted one frame early. + // No idea why it makes a difference, but it removes hitching on the last frame of a looped anim. + switch( part ) + { + case legs: + if ( totaltime > *frametime && last_anim == animnum ) + PostEvent( EV_AnimDone, totaltime - *frametime, flags ); + else + PostEvent( EV_AnimDone, totaltime, flags ); + break; + case torso: + if ( totaltime > *frametime && last_anim == animnum ) + PostEvent( EV_Torso_AnimDone, totaltime - *frametime, flags ); + else + PostEvent( EV_Torso_AnimDone, totaltime, flags ); + break; + default: + warning( "NewAnim", "Unknown body part %d", part ); + return; + break; + } +} + +void Animate::NewAnim( int animnum, Event &newevent, bodypart_t part ) +{ + SetAnimDoneEvent( newevent, part ); + NewAnim( animnum, part ); +} + +void Animate::NewAnim( int animnum, Event *newevent, bodypart_t part ) +{ + SetAnimDoneEvent( newevent, part ); + NewAnim( animnum, part ); +} + +void Animate::FrameDeltaEvent( Event *ev ) +{ + frame_delta = ev->GetVector( 1 ); + self->total_delta += frame_delta * self->edict->s.scale ; +} + +void Animate::EndAnim( bodypart_t part ) +{ + Event * ev; + + switch( part ) + { + case legs: + if ( animDoneEvent ) + { + self->PostEvent( *animDoneEvent, 0.0f ); + } + ev = new Event( EV_NewAnim ); + ev->AddInteger( self->edict->s.anim ); + ev->AddInteger( part ); + + if ( legs_animtime > legs_frametime ) + PostEvent( ev, legs_frametime, EVENT_LEGS_ANIM ); + else + PostEvent( ev, 0.0f, EVENT_LEGS_ANIM ); + break; + case torso: + if ( torso_animDoneEvent ) + { + self->PostEvent( *torso_animDoneEvent, 0.0f ); + } + ev = new Event( EV_NewAnim ); + ev->AddInteger( self->edict->s.torso_anim ); + ev->AddInteger( part ); + + if ( torso_animtime > torso_frametime ) + PostEvent( ev, torso_frametime, EVENT_TORSO_ANIM ); + else + PostEvent( ev, 0.0f, EVENT_TORSO_ANIM ); + break; + default: + warning( "EndAnim", "Unknown body part %d", part ); + return; + break; + } +} + +void Animate::SetAnimDoneEvent( Event *event, bodypart_t part ) +{ + Event **doneevent; + + switch( part ) + { + case legs: + doneevent = &animDoneEvent; + break; + case torso: + doneevent = &torso_animDoneEvent; + break; + default: + warning( "SetAnimDoneEvent", "Unknown body part %d", part ); + return; + break; + } + if ( *doneevent ) + { + delete *doneevent; + } + + *doneevent = event; +} + +void Animate::SetAnimDoneEvent( const Event &event, bodypart_t part ) +{ + SetAnimDoneEvent( new Event( event ), part ); +} + +str Animate::GetName() +{ + return currentAnim; +} + +//=============================================================== +// Name: AddEffectAnim +// Class: Animate +// +// Description: Add an effects anim to be layered about a normal +// anim. +// +// Parameters: const char* -- the anim to whose effects you want to add. +// +// Returns: None +// +//=============================================================== +void Animate::AddEffectAnim( const char *animname ) +{ + int num = gi.Anim_Random( self->edict->s.modelindex, animname ); + if ( num != -1 ) + { + for ( int effectAnimIdx = 0; effectAnimIdx < NUM_EFFECTS_ANIMS; ++effectAnimIdx ) + { + int effectAnimNum = self->edict->s.effectsAnims[ effectAnimIdx ] ; + if ( effectAnimNum == num ) return ; + if ( effectAnimNum == -1 ) + { + self->edict->s.effectsAnims[ effectAnimIdx ] = num ; + return ; + } + } + } +} + + +//=============================================================== +// Name: RemoveEffectAnim +// Class: Animate +// +// Description: Removes an anim effect +// +// Parameters: const char *animname +// +// Returns: None +// +//=============================================================== +void Animate::RemoveEffectAnim( const char *animname ) +{ + int num = gi.Anim_Random( self->edict->s.modelindex, animname ); + if ( num != -1 ) + { + for ( int effectAnimIdx = 0; effectAnimIdx < NUM_EFFECTS_ANIMS; ++effectAnimIdx ) + { + if ( self->edict->s.effectsAnims[ effectAnimIdx ] == num ) + { + self->edict->s.effectsAnims[ effectAnimIdx ] = -1 ; + } + } + } +} + +//=============================================================== +// Name: ClearAllEffectAnims +// Class: Animate +// +// Description: Clears the list of effect anims. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void Animate::ClearAllEffectAnims( void ) +{ + for ( int effectAnimIdx = 0; effectAnimIdx < NUM_EFFECTS_ANIMS; ++effectAnimIdx ) + { + self->edict->s.effectsAnims[ effectAnimIdx ] = -1 ; + } +} + +void Animate::RandomAnimate( const char *animname, Event *endevent, bodypart_t part ) +{ + Event *event_to_post; + int num; + int flags; + qboolean allparts; + + assert( animname ); + if ( !animname ) + { + return; + } + + allparts = false; + if ( part == all ) + { + allparts = true; + } + + do { + if ( allparts ) + { + switch( part ) + { + case all: + part = legs; + break; + case legs: + part = torso; + break; + case torso: + return; + break; + } + } + switch( part ) + { + case legs: + flags = EVENT_LEGS_ANIM; + break; + case torso: + flags = EVENT_TORSO_ANIM; + break; + default: + warning( "RandomAnimate", "Unknown body part %d", part ); + return; + break; + } + num = gi.Anim_Random( self->edict->s.modelindex, animname ); + + currentAnim = animname; + + // + // this is here to ensure that multiple distinct events are posted for each body part + // + if ( allparts && ( part != legs ) ) + { + if ( endevent ) + { + event_to_post = new Event( endevent ); + } + else + { + event_to_post = NULL; + } + } + else + { + event_to_post = endevent; + } + + // + // see if we even have a valid animation at all + // + if ( num == -1 ) + { + if ( event_to_post ) + { + self->PostEvent( event_to_post, FRAMETIME, flags ); + } + } + else + { + SetAnimDoneEvent( event_to_post, part ); + NewAnim( num, part ); + } + } while( allparts ); +} + +void Animate::SetAnimationRate( const float animationRate ) +{ + oldAnimationRate = self->edict->s.animationRate; + self->edict->s.animationRate = animationRate; +} + +void Animate::RestoreAnimationRate( void ) +{ + self->edict->s.animationRate = oldAnimationRate; +} + +void Animate::SetFrame( int framenum, bodypart_t part, int anim ) +{ + int flags; + int numframes; + int *panim; + int *frame; + + switch( part ) + { + case legs: + frame = &self->edict->s.frame; + flags = EVENT_LEGS_ANIM; + numframes = legs_numframes; + panim = &self->edict->s.anim; + break; + case torso: + frame = &self->edict->s.torso_frame; + flags = EVENT_TORSO_ANIM; + numframes = torso_numframes; + panim = &self->edict->s.torso_anim; + break; + default: + warning( "SetFrame", "Unknown body part %d", part ); + return; + break; + } + + if ( anim >= 0 ) + { + numframes = gi.Anim_NumFrames( self->edict->s.modelindex, anim ); + } + + if ( framenum < 0 || ( framenum >= numframes ) ) + { + warning( "SetFrame","Frame %d is out of range on model %s", framenum, self->model.c_str() ); + return; + } + + // get rid of old anim events so we don't animate + CancelFlaggedEvents( flags ); + self->CancelFlaggedEvents( flags ); + + *frame = framenum | FRAME_EXPLICIT; + + // if we have a frame override, make sure to set the animation as well + if ( anim >= 0 ) + { + *panim = anim | ANIM_BLEND; + } +} + +qboolean Animate::HasAnim( const char *animname ) +{ + int num; + + num = gi.Anim_Random( self->edict->s.modelindex, animname ); + return ( num >= 0 ); +} + +void Animate::NewAnimEvent( Event *ev ) +{ + NewAnim( ev->GetInteger( 1 ) & ANIM_MASK, (bodypart_t) ev->GetInteger( 2 ) ); +} + +void Animate::StopAnimating( bodypart_t part ) +{ + int frame; + int anim; + + if ( part == all ) + { + // legs + frame = CurrentFrame( legs ); + anim = CurrentAnim( legs ); + SetFrame( frame, legs, anim ); + // torso + frame = CurrentFrame( torso ); + anim = CurrentAnim( torso ); + SetFrame( frame, torso, anim ); + } + else + { + frame = CurrentFrame( part ); + anim = CurrentAnim( part ); + SetFrame( frame, part, anim ); + } +} + +void Animate::StopAnimatingAtEnd( bodypart_t part ) +{ + int anim; + + if ( part == all ) + { + StopAnimatingAtEnd( legs ); + StopAnimatingAtEnd( torso ); + } + else if ( part == legs ) + { + anim = CurrentAnim( part ); + SetFrame( legs_numframes - 1, part, anim ); + } + else if ( part == torso ) + { + anim = CurrentAnim( part ); + SetFrame( torso_numframes - 1, part, anim ); + } +} + +//////////////////////////// +// +// BODY PART SPECIFIC EVENTS +// +//////////////////////////// + +// Legs + +void Animate::Legs_AnimDoneEvent( Event *ev ) +{ + EndAnim( legs ); +} + +void Animate::Legs_AnimEvent( Event *ev ) +{ + RandomAnimate( ev->GetString( 1 ), NULL, legs ); +} + +void Animate::Legs_SetFrameEvent( Event *ev ) +{ + int framenum; + int animnum; + + framenum = ev->GetInteger( 1 ); + if ( ev->NumArgs() > 1 ) + { + animnum = gi.Anim_NumForName( self->edict->s.modelindex, ev->GetString( 2 ) ); + } + else + { + animnum = -1; + } + + SetFrame( framenum, legs, animnum ); +} + +// HACK HACK HACK +void Animate::Legs_StopAnimating( Event *ev ) +{ + CancelFlaggedEvents( EVENT_LEGS_ANIM ); + self->CancelFlaggedEvents( EVENT_LEGS_ANIM ); +} + +// Torso + +void Animate::Torso_AnimDoneEvent( Event *ev ) +{ + EndAnim( torso ); +} + +void Animate::Torso_AnimEvent( Event *ev ) +{ + RandomAnimate( ev->GetString( 1 ), NULL, torso ); +} + +void Animate::Torso_SetFrameEvent( Event *ev ) +{ + int framenum; + int animnum; + + framenum = ev->GetInteger( 1 ); + if ( ev->NumArgs() > 1 ) + { + animnum = gi.Anim_NumForName( self->edict->s.modelindex, ev->GetString( 2 ) ); + } + else + { + animnum = -1; + } + + SetFrame( framenum, torso, animnum ); +} + +// HACK HACK HACK +void Animate::Torso_StopAnimating( Event *ev ) +{ + CancelFlaggedEvents( EVENT_TORSO_ANIM ); + self->CancelFlaggedEvents( EVENT_TORSO_ANIM ); +} + +void Animate::ClearTorsoAnim( void ) +{ + tiki_cmd_t cmds; + int last_anim; + static qboolean clearing = false; + + last_anim = self->edict->s.torso_anim & ANIM_MASK; + + if ( ( self->edict->s.torso_anim & ANIM_BLEND ) && !( self->edict->s.torso_anim & ANIM_SERVER_EXITCOMMANDS_PROCESSED ) && !clearing ) + { + if ( gi.Frame_Commands( self->edict->s.modelindex, last_anim, TIKI_FRAME_CMD_EXIT, &cmds ) ) + { + int ii, j; + + clearing = true; + for( ii = 0; ii < cmds.num_cmds; ii++ ) + { + Event *ev = new Event( cmds.cmds[ ii ].args[ 0 ] ); + + ev->SetSource( EV_FROM_ANIMATION ); + ev->SetAnimationNumber( last_anim ); + ev->SetAnimationFrame( 0 ); + + for( j = 1; j < cmds.cmds[ ii ].num_args; j++ ) + { + ev->AddToken( cmds.cmds[ ii ].args[ j ] ); + } + self->ProcessEvent( ev ); + } + clearing = false; + } + self->edict->s.torso_anim |= ANIM_SERVER_EXITCOMMANDS_PROCESSED; + } + + CancelFlaggedEvents( EVENT_TORSO_ANIM ); + self->CancelFlaggedEvents( EVENT_TORSO_ANIM ); + self->edict->s.torso_anim &= ~ANIM_BLEND; +} + +void Animate::ClearLegsAnim( void ) +{ + tiki_cmd_t cmds; + int last_anim; + //static qboolean clearing = false; + + if ( self->edict->s.anim & ANIM_SERVER_EXITCOMMANDS_PROCESSED ) + { + last_anim = self->edict->s.anim & ANIM_MASK; + + if ( gi.Frame_Commands( self->edict->s.modelindex, last_anim, TIKI_FRAME_CMD_EXIT, &cmds ) ) + { + int ii, j; + + //clearing = true; + for( ii = 0; ii < cmds.num_cmds; ii++ ) + { + Event *ev = new Event( cmds.cmds[ ii ].args[ 0 ] ); + + ev->SetSource( EV_FROM_ANIMATION ); + ev->SetAnimationNumber( last_anim ); + ev->SetAnimationFrame( 0 ); + + for( j = 1; j < cmds.cmds[ ii ].num_args; j++ ) + { + ev->AddToken( cmds.cmds[ ii ].args[ j ] ); + } + self->ProcessEvent( ev ); + } + //clearing = false; + } + self->edict->s.anim |= ANIM_SERVER_EXITCOMMANDS_PROCESSED; + } + + CancelFlaggedEvents( EVENT_LEGS_ANIM ); + self->CancelFlaggedEvents( EVENT_LEGS_ANIM ); + + self->edict->s.anim &= ~ANIM_BLEND; +} diff --git a/dlls/game/animate.h b/dlls/game/animate.h new file mode 100644 index 0000000..a2a064e --- /dev/null +++ b/dlls/game/animate.h @@ -0,0 +1,330 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/animate.h $ +// $Revision:: 10 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Animate class +// + +#ifndef __ANIMATE_H__ +#define __ANIMATE_H__ + +#include "g_local.h" + +#ifndef __ENTITY_H__ +#include "entity.h" +#endif + +extern Event EV_SetFrame; +extern Event EV_StopAnimating; +extern Event EV_Torso_StopAnimating; + +#define MINIMUM_DELTA_MOVEMENT 8.0f +#define MINIMUM_DELTA_MOVEMENT_PER_FRAME ( MINIMUM_DELTA_MOVEMENT / 20.0f ) + +class Animate; +class Entity; + +typedef SafePtr AnimatePtr; + +class Animate : public Listener + { + private: + Event *animDoneEvent; + Event *torso_animDoneEvent; + + float legs_animtime; + float torso_animtime; + + float legs_starttime; + float torso_starttime; + + float legs_frametime; + float torso_frametime; + + int legs_numframes; + int torso_numframes; + + str currentAnim; + float oldAnimationRate; + + Entity *self; + + void FrameDeltaEvent( Event *ev ); + void EndAnim( bodypart_t part ); + void Legs_AnimDoneEvent( Event *ev ); + void Legs_AnimEvent( Event *ev ); + void Legs_SetFrameEvent( Event *ev ); + void Legs_StopAnimating( Event *ev ); + void Torso_AnimDoneEvent( Event *ev ); + void Torso_AnimEvent( Event *ev ); + void Torso_SetFrameEvent( Event *ev ); + void Torso_StopAnimating( Event *ev ); + void NewAnimEvent( Event *ev ); + public: + + // Animation variables + Vector frame_delta; // current movement from this frame + CLASS_PROTOTYPE( Animate ); + Animate(); + Animate( Entity * ent ); + ~Animate(); + + void RandomAnimate( const char *animname, Event *endevent = NULL, bodypart_t part = legs ); + void RandomAnimate( const char *animname, const Event &endevent, bodypart_t part = legs ); + void SetAnimationRate( const float animationRate ); + void RestoreAnimationRate( void ); + void NewAnim( int animnum, bodypart_t part = legs ); + void NewAnim( int animnum, Event *endevent, bodypart_t part = legs ); + void NewAnim( int animnum, Event &endevent, bodypart_t part = legs ); + void SetFrame( int framenum, bodypart_t part = legs, int anim = -1 ); + qboolean HasAnim( const char *animname ); + Event *AnimDoneEvent( bodypart_t part = legs ); + void SetAnimDoneEvent( const Event &event, bodypart_t part = legs ); + void SetAnimDoneEvent( Event *event, bodypart_t part = legs ); + int NumFrames( bodypart_t part = legs ); + int NumAnims( void ); + const char *AnimName( bodypart_t part = legs ); + float AnimTime( bodypart_t part = legs ); + str GetName(); + + void ClearLegsAnim( void ); + void ClearTorsoAnim( void ); + + virtual void StopAnimating( bodypart_t part = legs ); + virtual void StopAnimatingAtEnd( bodypart_t part = legs ); + + virtual int CurrentAnim( bodypart_t part = legs ); + virtual int CurrentFrame( bodypart_t part = legs ); + + virtual void AddEffectAnim( const char *animName ); + virtual void RemoveEffectAnim( const char *animName ); + virtual void ClearAllEffectAnims( void ); + + virtual void Archive( Archiver &arc ); + }; + +inline void Animate::RandomAnimate + ( + const char *animname, + const Event &endevent, + bodypart_t part + ) + { + Event *ev; + + ev = new Event( endevent ); + RandomAnimate( animname, ev, part ); + } + +inline int Animate::CurrentAnim + ( + bodypart_t part + ) + { + switch( part ) + { + case legs: + if ( self->edict->s.anim & ANIM_BLEND ) + return self->edict->s.anim & ANIM_MASK; + else + return -1; + break; + case torso: + if ( self->edict->s.torso_anim & ANIM_BLEND ) + return self->edict->s.torso_anim & ANIM_MASK; + else + return -1; + break; + default: + warning( "CurrentAnim", "Unknown body part %d", part ); + return -1; + break; + } + } + +inline int Animate::CurrentFrame + ( + bodypart_t part + ) + { + int frame; + + switch( part ) + { + case legs: + if ( self->edict->s.frame & FRAME_EXPLICIT ) + { + frame = self->edict->s.frame & FRAME_MASK; + } + else + { + if ( legs_numframes ) + { + frame = ( int )( ( float )( ( level.time - legs_starttime ) * legs_numframes ) / legs_animtime + 0.5f ); + while ( frame >= legs_numframes ) + frame -= legs_numframes; + } + else + { + frame = 0; + } + } + break; + case torso: + if ( self->edict->s.torso_frame & FRAME_EXPLICIT ) + { + frame = self->edict->s.torso_frame & FRAME_MASK; + } + else + { + if ( torso_numframes ) + { + frame = ( int )( ( float )( ( level.time - torso_starttime ) * torso_numframes ) / torso_animtime + 0.5f ); + while ( frame >= torso_numframes ) + frame -= torso_numframes; + } + else + { + frame = 0; + } + } + break; + default: + warning( "CurrentFrame", "Unknown body part %d", part ); + frame = 0; + break; + } + return frame; + } + +inline int Animate::NumFrames + ( + bodypart_t part + ) + { + switch( part ) + { + case legs: + return legs_numframes; + break; + case torso: + return torso_numframes; + break; + default: + warning( "NumFrames", "Unknown body part %d", part ); + return 0; + break; + } + } + +inline float Animate::AnimTime + ( + bodypart_t part + ) + { + switch( part ) + { + case legs: + return legs_animtime; + break; + case torso: + return torso_animtime; + break; + default: + warning( "AnimTime", "Unknown body part %d", part ); + return 0; + break; + } + } + +inline int Animate::NumAnims + ( + void + ) + + { + return gi.NumAnims( self->edict->s.modelindex ); + } + +inline const char *Animate::AnimName + ( + bodypart_t part + ) + { + switch( part ) + { + case legs: + return gi.Anim_NameForNum( self->edict->s.modelindex, CurrentAnim( part ) ); + break; + case torso: + return gi.Anim_NameForNum( self->edict->s.modelindex, CurrentAnim( part ) ); + break; + default: + warning( "AnimName", "Unknown body part %d", part ); + return NULL; + break; + } + } + +inline Event * Animate::AnimDoneEvent + ( + bodypart_t part + ) + { + switch( part ) + { + case legs: + if ( animDoneEvent ) + return new Event( animDoneEvent ); + else + return NULL; + break; + case torso: + if ( torso_animDoneEvent ) + return new Event( torso_animDoneEvent ); + else + return NULL; + break; + default: + warning( "AnimDoneEvent", "Unknown body part %d", part ); + return NULL; + break; + } + } + +inline void Animate::Archive( Archiver &arc ) +{ + Listener::Archive( arc ); + + arc.ArchiveEventPointer( &animDoneEvent ); + arc.ArchiveEventPointer( &torso_animDoneEvent ); + + arc.ArchiveFloat( &legs_animtime ); + arc.ArchiveFloat( &torso_animtime ); + + arc.ArchiveFloat( &legs_starttime ); + arc.ArchiveFloat( &torso_starttime ); + + arc.ArchiveFloat( &legs_frametime ); + arc.ArchiveFloat( &torso_frametime ); + + arc.ArchiveInteger( &legs_numframes ); + arc.ArchiveInteger( &torso_numframes ); + + arc.ArchiveString( ¤tAnim ); + arc.ArchiveFloat( &oldAnimationRate ); + arc.ArchiveVector( &frame_delta ); +} + +#endif /* animate.h */ diff --git a/dlls/game/archive.cpp b/dlls/game/archive.cpp new file mode 100644 index 0000000..6a8c586 --- /dev/null +++ b/dlls/game/archive.cpp @@ -0,0 +1,1027 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/archive.cpp $ +// $Revision:: 12 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:35p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Class for archiving objects +// +#include "_pch_cpp.h" +#include "archive.h" + +enum +{ + ARC_NULL, ARC_Vector, ARC_Vec3, ARC_Vec4, ARC_Integer, ARC_Unsigned, ARC_Byte, ARC_Char, ARC_Short, ARC_UnsignedShort, + ARC_Float, ARC_Double, ARC_Boolean, ARC_String, ARC_Raw, ARC_Object, ARC_ObjectPointer, + ARC_SafePointer, ARC_Event, ARC_EventPointer, ARC_Quat, ARC_Entity, ARC_Bool, + ARC_NUMTYPES +}; + +static const char *typenames[] = +{ + "NULL", "vector", "vec3", "vec4", "int", "unsigned", "byte", "char", "short", "unsigned short", + "float", "double", "qboolean", "string", "raw data", "object", "objectpointer", + "safepointer", "event", "eventpointer", "quaternion", "entity", "bool" +}; + +#define ArchiveHeader ( *( int * )"EF2" ) +#define ArchiveVersion 2 // This must be changed any time the format changes! +#define ArchiveInfo "EF2 Archive Version 2" // This must be changed any time the format changes! + +CLASS_DECLARATION( Class, FileRead, NULL ) +{ + { NULL, NULL } +}; + +FileRead::FileRead() +{ + length = 0; + buffer = NULL; + pos = 0; +} + +FileRead::~FileRead() +{ + Close(); +} + +void FileRead::Close( void ) +{ + if ( buffer ) + { + gi.Free( ( void * )buffer ); + buffer = NULL; + } + + filename = ""; + length = 0; + pos = 0; +} + +const char *FileRead::Filename( void ) +{ + return filename.c_str(); +} + +size_t FileRead::Length( void ) +{ + return length; +} + +size_t FileRead::Pos( void ) +{ + return pos - buffer; +} + +qboolean FileRead::Seek( size_t newpos ) +{ + if ( !buffer ) + { + return false; + } + + if ( newpos > length ) + { + return false; + } + + pos = buffer + newpos; + + return true; +} + +qboolean FileRead::Open( const char *name ) +{ + byte *tempbuf; + assert( name ); + + assert( !buffer ); + Close(); + + if ( !name ) + { + return false; + } + + length = gi.FS_ReadFile( name, ( void ** )&tempbuf, true ); + if ( length == ( size_t )( -1 ) ) + { + return false; + } + // create our own space + buffer = ( byte * )gi.Malloc( length ); + // copy the file over to our space + memcpy( buffer, tempbuf, length ); + // free the file + gi.FS_FreeFile( tempbuf ); + + filename = name; + pos = buffer; + + return true; +} + +qboolean FileRead::Read( void *dest, size_t size ) +{ + assert( dest ); + assert( buffer ); + assert( pos ); + + if ( !dest ) + { + return false; + } + + if ( size == 0 ) + { + return false; + } + + if ( ( pos + size ) > ( buffer + length ) ) + { + return false; + } + + memcpy( dest, pos, size ); + pos += size; + + return true; +} + +CLASS_DECLARATION( Class, Archiver, NULL ) +{ + { NULL, NULL } +}; + +Archiver::Archiver() +{ + file = 0; + fileerror = false; + harderror = true; + archivemode = ARCHIVE_READ; + numclassespos = 0; + assert( ( sizeof( typenames ) / sizeof( typenames[ 0 ] ) ) == ARC_NUMTYPES ); +} + +Archiver::~Archiver() +{ + if ( file ) + { + Close(); + } + + readfile.Close(); +} + +void Archiver::FileError( const char *fmt, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + fileerror = true; + Close(); + if ( archivemode == ARCHIVE_READ ) + { + if ( harderror ) + { + gi.Error( ERR_DROP, "Error while loading %s : %s\n", filename.c_str(), text ); + } + else + { + gi.WPrintf( "Error while loading %s : %s\n", filename.c_str(), text ); + } + } + else + { + if ( harderror ) + { + gi.Error( ERR_DROP, "Error while writing to %s : %s\n", filename.c_str(), text ); + } + else + { + gi.WPrintf( "Error while writing to %s : %s\n", filename.c_str(), text ); + } + } +} + +void Archiver::Close( void ) +{ + if ( file ) + { + if ( archivemode == ARCHIVE_WRITE ) + { + int numobjects; + + // write out the number of classpointers + gi.FS_FSeek( file, numclassespos, FS_SEEK_SET ); + numclassespos = gi.FS_FTell( file ); + numobjects = classpointerList.NumObjects(); + ArchiveInteger( &numobjects ); + } + + gi.FS_FCloseFile( file ); + file = 0; + } + + readfile.Close(); + + if ( archivemode == ARCHIVE_READ ) + { + int i, num; + Class * classptr; + pointer_fixup_t *fixup; + + num = fixupList.NumObjects(); + for( i = 1; i <= num; i++ ) + { + fixup = fixupList.ObjectAt( i ); + classptr = classpointerList.ObjectAt( fixup->index ); + + // Make sure the ptr isn't screwed up + assert( !( ( classptr == NULL ) && ( fixup->index != ARCHIVE_NULL_POINTER ) ) ); + + if ( fixup->type == pointer_fixup_normal ) + { + Class ** fixupptr; + fixupptr = ( Class ** )fixup->ptr; + *fixupptr = classptr; + } + else if ( fixup->type == pointer_fixup_safe ) + { + SafePtrBase * fixupptr; + fixupptr = ( SafePtrBase * )fixup->ptr; + fixupptr->InitSafePtr( classptr ); + } + delete fixup; + } + fixupList.FreeObjectList(); + classpointerList.FreeObjectList(); + } +} + +/**************************************************************************************** + + File Read/Write functions + +*****************************************************************************************/ + +qboolean Archiver::Read( const char *name, qboolean file_harderror ) +{ + unsigned header; + unsigned version; + str info; + int num; + int i; + Class *null; + + harderror = file_harderror; + + assert( name ); + if ( !name ) + { + FileError( "NULL pointer for filename in Archiver::Read.\n" ); + return false; + } + + fileerror = false; + + archivemode = ARCHIVE_READ; + + filename = name; + + if ( !readfile.Open( filename.c_str() ) ) + { + if ( file_harderror ) + { + FileError( "Couldn't open file." ); + } + return false; + } + + ArchiveUnsigned( &header ); + if ( header != ArchiveHeader ) + { + readfile.Close(); + FileError( "Not a valid archive." ); + return false; + } + + ArchiveUnsigned( &version ); + if ( version > ArchiveVersion ) + { + readfile.Close(); + FileError( "Archive is from version %.2f. Check http://www.ritual.com for an update.", version ); + return false; + } + + if ( version < ArchiveVersion ) + { + readfile.Close(); + FileError( "Archive is out of date." ); + return false; + } + + ArchiveString( &info ); + gi.DPrintf( "%s\n", info.c_str() ); + + // setup out class pointers + ArchiveInteger( &num ); + classpointerList.Resize( num ); + null = NULL; + for( i = 1; i <= num; i++ ) + { + classpointerList.AddObject( null ); + } + + return true; +} + +qboolean Archiver::Create( const char *name, qboolean file_harderror ) +{ + unsigned header; + unsigned version; + str info; + int numZero = 0; + + harderror = file_harderror; + + assert( name ); + if ( !name ) + { + FileError( "NULL pointer for filename in Archiver::Create.\n" ); + return false; + } + + fileerror = false; + + archivemode = ARCHIVE_WRITE; + + filename = name; + + file = gi.FS_FOpenFileWrite( filename.c_str() ); + if ( !file ) + { + FileError( "Couldn't open file." ); + return false; + } + + header = ArchiveHeader; + ArchiveUnsigned( &header ); + version = ArchiveVersion; + ArchiveUnsigned( &version ); + info = ArchiveInfo; + ArchiveString( &info ); + + numclassespos = gi.FS_FTell( file ); + ArchiveInteger( &numZero ); + + return true; +} + +inline void Archiver::CheckRead( void ) +{ + assert( archivemode == ARCHIVE_READ ); + if ( !fileerror && ( archivemode != ARCHIVE_READ ) ) + { + FileError( "File read during a write operation." ); + } +} + +inline void Archiver::CheckWrite( void ) +{ + assert( archivemode == ARCHIVE_WRITE ); + if ( !fileerror && ( archivemode != ARCHIVE_WRITE ) ) + { + FileError( "File write during a read operation." ); + } +} + +inline size_t Archiver::ReadSize( void ) +{ + size_t s; + + s = 0; + if ( !fileerror ) + { + readfile.Read( &s, sizeof( s ) ); + } + + return s; +} + +inline void Archiver::CheckSize( int type, size_t size ) +{ + size_t s; + + if ( !fileerror ) + { + s = ReadSize(); + + if ( size != s ) + { + FileError( "Invalid data size of %d on %s.", s, typenames[ type ] ); + } + } +} + +inline void Archiver::WriteSize( size_t size ) +{ + gi.FS_Write( &size, sizeof( size ), file ); +} + +inline int Archiver::ReadType( void ) +{ + int t; + + if ( !fileerror ) + { + readfile.Read( &t, sizeof( t ) ); + + return t; + } + + return ARC_NULL; +} + +inline void Archiver::WriteType( int type ) +{ + gi.FS_Write( &type, sizeof( type ), file ); +} + +inline void Archiver::CheckType( int type ) +{ + int t; + + assert( ( type >= 0 ) && ( type < ARC_NUMTYPES ) ); + + if ( !fileerror ) + { + t = ReadType(); + if ( t != type ) + { + FileError( "Expecting %s", typenames[ type ] ); + } + } +} + +/**************************************************************************************** + + File Archive functions + +*****************************************************************************************/ + +//#define ARCHIVE_USE_TYPES 1 + +inline void Archiver::ArchiveData( int type, void *data, size_t size ) +{ + if ( archivemode == ARCHIVE_READ ) + { +#ifndef NDEBUG + CheckRead(); +#endif +#ifdef ARCHIVE_USE_TYPES + CheckType( type ); +#endif + + if ( !fileerror && size ) + { + readfile.Read( data, size ); + } + } + else + { +#ifndef NDEBUG + CheckWrite(); +#endif +#ifdef ARCHIVE_USE_TYPES + WriteType( type ); +#endif + + if ( !fileerror && size ) + { + gi.FS_Write( data, size, file ); + } + } +} + + +#define ARCHIVE( func, type ) \ +void Archiver::Archive##func \ + ( \ + type * v \ + ) \ + \ + { \ + ArchiveData( ARC_##func, v, sizeof( type ) ); \ + } + + ARCHIVE( Vector, Vector ); + ARCHIVE( Integer, int ); + ARCHIVE( Unsigned, unsigned ); + ARCHIVE( Byte, byte ); + ARCHIVE( Char, char ); + ARCHIVE( Short, short ); + ARCHIVE( UnsignedShort, unsigned short ); + ARCHIVE( Float, float ); + ARCHIVE( Double, double ); + ARCHIVE( Boolean, qboolean ); + ARCHIVE( Quat, Quat ); + ARCHIVE( Bool, bool ); + +void Archiver::ArchiveVec3( vec3_t vec ) +{ + ArchiveData( ARC_Vec3, vec, sizeof( vec3_t ) ); +} + +void Archiver::ArchiveVec4( vec4_t vec ) +{ + ArchiveData( ARC_Vec4, vec, sizeof( vec4_t ) ); +} + +void Archiver::ArchiveObjectPointer( Class ** ptr ) +{ + int index = 0; + + if ( archivemode == ARCHIVE_READ ) + { + pointer_fixup_t *fixup; + ArchiveData( ARC_ObjectPointer, &index, sizeof( index ) ); + + // Check for a NULL pointer + assert( ptr ); + if ( !ptr ) + { + FileError( "NULL pointer in ArchiveObjectPointer." ); + } + + // + // see if the variable was NULL + // + if ( index == ARCHIVE_NULL_POINTER ) + { + *ptr = NULL; + } + else + { + // init the pointer with NULL until we can fix it + *ptr = NULL; + + fixup = new pointer_fixup_t; + fixup->ptr = ( void ** )ptr; + fixup->index = index; + fixup->type = pointer_fixup_normal; + fixupList.AddObject( fixup ); + } + } + else + { + if ( *ptr ) + { + index = classpointerList.AddUniqueObject( *ptr ); + } + else + { + index = ARCHIVE_NULL_POINTER; + } + ArchiveData( ARC_ObjectPointer, &index, sizeof( index ) ); + } +} + +void Archiver::ArchiveSafePointer( SafePtrBase * ptr ) +{ + int index = 0; + + if ( archivemode == ARCHIVE_READ ) + { + pointer_fixup_t *fixup; + + ArchiveData( ARC_SafePointer, &index, sizeof( &index ) ); + + // Check for a NULL pointer + assert( ptr ); + if ( !ptr ) + { + FileError( "NULL pointer in ReadSafePointer." ); + } + + // + // see if the variable was NULL + // + if ( index == ARCHIVE_NULL_POINTER ) + { + ptr->InitSafePtr( NULL ); + } + else + { + // init the pointer with NULL until we can fix it + ptr->InitSafePtr( NULL ); + + // Add new fixup + fixup = new pointer_fixup_t; + fixup->ptr = ( void ** )ptr; + fixup->index = index; + fixup->type = pointer_fixup_safe; + fixupList.AddObject( fixup ); + } + } + else + { + if ( ptr->Pointer() ) + { + Class * obj; + + obj = ptr->Pointer(); + index = classpointerList.AddUniqueObject( obj ); + } + else + { + index = ARCHIVE_NULL_POINTER; + } + ArchiveData( ARC_SafePointer, &index, sizeof( index ) ); + } +} + +void Archiver::ArchiveEvent( Event * ev ) +{ + if ( archivemode == ARCHIVE_READ ) + { +#ifndef NDEBUG + CheckRead(); +#endif +#ifdef ARCHIVE_USE_TYPES + CheckType( ARC_Event ); +#endif + + if ( !fileerror ) + { + ev->Archive( *this ); + } + } + else + { +#ifndef NDEBUG + CheckWrite(); +#endif +#ifdef ARCHIVE_USE_TYPES + WriteType( ARC_Event ); +#endif + + //FIXME!!!! Make this handle null events + if ( ev == NULL ) + { + NullEvent.Archive( *this ); + } + else + { + ev->Archive( *this ); + } + } +} + +void Archiver::ArchiveEventPointer( Event ** ev ) +{ + int index; + + if ( archivemode == ARCHIVE_READ ) + { +#ifndef NDEBUG + CheckRead(); +#endif +#ifdef ARCHIVE_USE_TYPES + CheckType( ARC_EventPointer ); +#endif + ArchiveInteger( &index ); + + if ( !fileerror ) + { + if ( index == ARCHIVE_POINTER_VALID ) + { + *ev = new Event; + (*ev)->Archive( *this ); + } + else + { + (*ev) = NULL; + } + } + } + else + { +#ifndef NDEBUG + CheckWrite(); +#endif + if ( *ev ) + { + index = ARCHIVE_POINTER_VALID; + } + else + { + index = ARCHIVE_NULL_POINTER; + } + +#ifdef ARCHIVE_USE_TYPES + WriteType( ARC_EventPointer ); +#endif + + ArchiveInteger( &index ); + if ( *ev ) + { + (*ev)->Archive( *this ); + } + } +} + +void Archiver::ArchiveRaw( void *data, size_t size ) +{ + ArchiveData( ARC_Raw, data, size ); +} + +void Archiver::ArchiveString( str * string ) +{ + if ( archivemode == ARCHIVE_READ ) + { + size_t s; + char *data; + +#ifndef NDEBUG + CheckRead(); +#endif +#ifdef ARCHIVE_USE_TYPES + CheckType( ARC_String ); +#endif + + if ( !fileerror ) + { + s = ReadSize(); + if ( !fileerror ) + { + data = new char[ s + 1 ]; + if ( data ) + { + if ( s ) + { + readfile.Read( data, s ); + } + data[ s ] = 0; + + *string = data; + + delete [] data; + } + } + } + } + else + { +#ifndef NDEBUG + CheckWrite(); +#endif +#ifdef ARCHIVE_USE_TYPES + WriteType( ARC_String ); +#endif + WriteSize( string->length() ); + //gi.FS_Write( string->m_data->data, string->length(), file ); + gi.FS_Write( string->c_str(), string->length(), file ); + } +} + +Class * Archiver::ReadObject( void ) +{ + ClassDef *cls; + Class *obj; + str classname; + long objstart; + long endpos; + int index; + size_t size; + qboolean isent; + int type; + + CheckRead(); + + type = ReadType(); + if ( ( type != ARC_Object ) && ( type != ARC_Entity ) ) + { + FileError( "Expecting %s or %s", typenames[ ARC_Object ], typenames[ ARC_Entity ] ); + } + + size = ReadSize(); + ArchiveString( &classname ); + + cls = getClass( classname.c_str() ); + if ( !cls ) + { + FileError( "Invalid class %s.", classname.c_str() ); + } + + isent = checkInheritance( &Entity::ClassInfo, cls ); + if ( type == ARC_Entity ) + { + if ( !isent ) + { + FileError( "Non-Entity class object '%s' saved as an Entity based object.", classname.c_str() ); + } + + ArchiveInteger( &level.spawn_entnum ); + // + // make sure to setup spawnflags properly + // + ArchiveInteger( &level.spawnflags ); + } + else if ( isent ) + { + FileError( "Entity class object '%s' saved as non-Entity based object.", classname.c_str() ); + } + + ArchiveInteger( &index ); + objstart = readfile.Pos(); + + + obj = ( Class * )cls->newInstance(); + if ( !obj ) + { + FileError( "Failed to on new instance of class %s.", classname.c_str() ); + } + else + { + obj->Archive( *this ); + } + + if ( !fileerror ) + { + endpos = readfile.Pos(); + if ( ( endpos - objstart ) > size ) + { + FileError( "Object read past end of object's data" ); + } + else if ( ( endpos - objstart ) < size ) + { + FileError( "Object didn't read entire data from file" ); + } + } + + // + // register this pointer with our list + // + classpointerList.AddObjectAt( index, obj ); + + return obj; +} + +void Archiver::ArchiveObject( Class *obj ) +{ + str classname; + int index; + size_t size; + qboolean isent; + + if ( archivemode == ARCHIVE_READ ) + { + ClassDef *cls; + long objstart; + long endpos; + int type; + + CheckRead(); + type = ReadType(); + if ( ( type != ARC_Object ) && ( type != ARC_Entity ) ) + { + FileError( "Expecting %s or %s", typenames[ ARC_Object ], typenames[ ARC_Entity ] ); + } + + size = ReadSize(); + ArchiveString( &classname ); + + cls = getClass( classname.c_str() ); + if ( !cls ) + { + FileError( "Invalid class %s.", classname.c_str() ); + } + + if ( obj->classinfo() != cls ) + { + FileError( "Archive has a '%s' object, but was expecting a '%s' object.", classname.c_str(), obj->getClassname() ); + } + + isent = obj->isSubclassOf( Entity ); + if ( type == ARC_Entity ) + { + int entnum; + if ( !isent ) + { + FileError( "Non-Entity class object '%s' saved as an Entity based object.", classname.c_str() ); + } + + ArchiveInteger( &entnum ); + ( ( Entity * )obj )->SetEntNum( entnum ); + // + // make sure to setup spawnflags properly + // + ArchiveInteger( &level.spawnflags ); + } + else if ( isent ) + { + FileError( "Entity class object '%s' saved as non-Entity based object.", classname.c_str() ); + } + + ArchiveInteger( &index ); + objstart = readfile.Pos(); + + obj->Archive( *this ); + + if ( !fileerror ) + { + endpos = readfile.Pos(); + if ( ( endpos - objstart ) > size ) + { + FileError( "Object read past end of object's data" ); + } + else if ( ( endpos - objstart ) < size ) + { + FileError( "Object didn't read entire data from file" ); + } + } + + // + // register this pointer with our list + // + classpointerList.AddObjectAt( index, obj ); + } + else + { + long sizepos; + long objstart = 0; + long endpos; + + assert( obj ); + if ( !obj ) + { + FileError( "NULL object in WriteObject" ); + } + + isent = obj->isSubclassOf( Entity ); + + CheckWrite(); + if ( isent ) + { + WriteType( ARC_Entity ); + } + else + { + WriteType( ARC_Object ); + } + + sizepos = gi.FS_FTell( file ); + size = 0; + WriteSize( size ); + + classname = obj->getClassname(); + ArchiveString( &classname ); + + if ( isent ) + { + // Write out the entity number + ArchiveInteger( &( ( Entity * )obj )->entnum ); + // + // make sure to setup spawnflags properly + // + ArchiveInteger( &( ( Entity * )obj )->spawnflags ); + } + + // write out pointer index for this class pointer + index = classpointerList.AddUniqueObject( obj ); + ArchiveInteger( &index ); + + if ( !fileerror ) + { + objstart = gi.FS_FTell( file ); + obj->Archive( *this ); + } + + if ( !fileerror ) + { + endpos = gi.FS_FTell( file ); + size = endpos - objstart; + gi.FS_FSeek( file, sizepos, FS_SEEK_SET ); + WriteSize( size ); + + if ( !fileerror ) + { + gi.FS_FSeek( file, endpos, FS_SEEK_SET ); + } + } + } +} diff --git a/dlls/game/archive.h b/dlls/game/archive.h new file mode 100644 index 0000000..a136a65 --- /dev/null +++ b/dlls/game/archive.h @@ -0,0 +1,489 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/archive.h $ +// $Revision:: 5 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Class for archiving objects +// + +#ifndef __ARCHIVE_H__ +#define __ARCHIVE_H__ + +#include "g_local.h" +#include "class.h" +#include "str.h" + +#define ARCHIVE_NULL_POINTER ( -654321 ) +#define ARCHIVE_POINTER_VALID ( 0 ) +#define ARCHIVE_POINTER_NULL ( ARCHIVE_NULL_POINTER ) +#define ARCHIVE_POINTER_SELF_REFERENTIAL ( -123456 ) + +#define ARCHIVE_WRITE 0 +#define ARCHIVE_READ 1 + +typedef SafePtr EntityPtr; + +enum + { + pointer_fixup_normal, + pointer_fixup_safe + }; + +typedef struct + { + void **ptr; + int index; + int type; + } pointer_fixup_t; + +class FileRead : public Class + { + protected: + str filename; + size_t length; + byte *buffer; + byte *pos; + + public: + CLASS_PROTOTYPE( FileRead ); + + FileRead(); + ~FileRead(); + void Close( void ); + const char *Filename( void ); + size_t Length( void ); + size_t Pos( void ); + qboolean Seek( size_t newpos ); + qboolean Open( const char *name ); + qboolean Read( void *dest, size_t size ); + }; + +class Archiver : public Class + { + private: + Container classpointerList; + Container fixupList; + + protected: + str filename; + qboolean fileerror; + fileHandle_t file; + FileRead readfile; + int archivemode; + int numclassespos; + qboolean harderror; + + void CheckRead( void ); + void CheckType( int type ); + int ReadType( void ); + size_t ReadSize( void ); + void CheckSize( int type, size_t size ); + void ArchiveData( int type, void *data, size_t size ); + + void CheckWrite( void ); + void WriteType( int type ); + void WriteSize( size_t size ); + + public: + CLASS_PROTOTYPE( Archiver ); + + Archiver(); + ~Archiver(); + void FileError( const char *fmt, ... ); + void Close( void ); + + qboolean Read( const str &name, qboolean file_harderror = true ); + qboolean Read( const char *name, qboolean file_harderror = true ); + Class *ReadObject( void ); + + qboolean Create( const str &name, qboolean file_harderror = true ); + qboolean Create( const char *name, qboolean file_harderror = true ); + + qboolean Loading( void ); + qboolean Saving( void ); + qboolean NoErrors( void ); + + void ArchiveVector( Vector * vec ); + void ArchiveQuat( Quat * quat ); + void ArchiveInteger( int * num ); + void ArchiveUnsigned( unsigned * unum); + void ArchiveByte( byte * num ); + void ArchiveChar( char * ch ); + void ArchiveShort( short * num ); + void ArchiveUnsignedShort( unsigned short * num ); + void ArchiveFloat( float * num ); + void ArchiveDouble( double * num ); + void ArchiveBoolean( qboolean * boolean ); + void ArchiveString( str * string ); + void ArchiveObjectPointer( Class ** ptr ); + void ArchiveSafePointer( SafePtrBase * ptr ); + void ArchiveEvent( Event * ev ); + void ArchiveEventPointer( Event ** ev ); + void ArchiveBool( bool * boolean ); + void ArchiveVec3( vec3_t vec ); + void ArchiveVec4( vec4_t vec ); + + void ArchiveRaw( void *data, size_t size ); + void ArchiveObject( Class *obj ); + + }; + +inline qboolean Archiver::Read + ( + const str &name, + qboolean file_harderror + ) + + { + return Read( name.c_str(), file_harderror ); + } + +inline qboolean Archiver::Create + ( + const str &name, + qboolean file_harderror + ) + + { + return Create( name.c_str(), file_harderror ); + } + +inline qboolean Archiver::Loading + ( + void + ) + { + return ( archivemode == ARCHIVE_READ ); + } + +inline qboolean Archiver::Saving + ( + void + ) + { + return ( archivemode == ARCHIVE_WRITE ); + } + +inline qboolean Archiver::NoErrors + ( + void + ) + { + return ( !fileerror ); + } + +inline void Container::Archive + ( + Archiver &arc + ) + { + int i, num; + + if ( arc.Loading() ) + { + ClearObjectList(); + arc.ArchiveInteger( &num ); + Resize( num ); + } + else + { + num = numobjects; + arc.ArchiveInteger( &num ); + } + for( i = 1; i <= num; i++ ) + { + arc.ArchiveString( AddressOfObjectAt( i ) ); + } + } + +inline void Container::Archive + ( + Archiver &arc + ) + { + int i, num; + + if ( arc.Loading() ) + { + ClearObjectList(); + arc.ArchiveInteger( &num ); + Resize( num ); + } + else + { + num = numobjects; + arc.ArchiveInteger( &num ); + } + for( i = 1; i <= num; i++ ) + { + arc.ArchiveVector( AddressOfObjectAt( i ) ); + } + } + +inline void Container::Archive + ( + Archiver &arc + ) + { + int i, num; + + if ( arc.Loading() ) + { + ClearObjectList(); + arc.ArchiveInteger( &num ); + Resize( num ); + } + else + { + num = numobjects; + arc.ArchiveInteger( &num ); + } + for( i = 1; i <= num; i++ ) + { + arc.ArchiveInteger( AddressOfObjectAt( i ) ); + } + } + +inline void Container::Archive + ( + Archiver &arc + ) + { + int i, num; + + if ( arc.Loading() ) + { + ClearObjectList(); + arc.ArchiveInteger( &num ); + Resize( num ); + } + else + { + num = numobjects; + arc.ArchiveInteger( &num ); + } + for( i = 1; i <= num; i++ ) + { + arc.ArchiveFloat( AddressOfObjectAt( i ) ); + } + } + + + + +//=============================================================== +// Name: Archive +// Class: Container +// +// Description: Archive function for a container of Class pointers. +// Note that if the container contains objects deriving +// from Entity, then the Container function +// will get called instead. This will only get called +// if the pointers are to Class or Listener objects. +// +// Parameters: Archiver& -- holds the archive data +// +// Returns: None +// +//=============================================================== +inline void Container::Archive +( + Archiver &arc +) +{ + int numObjects = numobjects ; + arc.ArchiveInteger( &numObjects ); + + if ( arc.Loading() ) + Resize( numObjects ); + + for ( int objectIdx = 1; objectIdx <= numObjects; ++objectIdx ) + { + arc.ArchiveObjectPointer( AddressOfObjectAt( objectIdx ) ); + arc.ArchiveObject( ObjectAt( objectIdx ) ); + } +} + + + +//=============================================================== +// Name: Archive +// Class: Container +// +// Description: Archive function for a contianer of Class objects +// +// Parameters: Archiver& -- holds the archive data +// +// Returns: None +// +//=============================================================== +inline void Container::Archive +( + Archiver &arc +) +{ + int numObjects = numobjects ; + arc.ArchiveInteger( &numObjects ); + + if ( arc.Loading() ) + Resize( numObjects ); + + for ( int objectIdx = 1; objectIdx <= numObjects; ++objectIdx ) + { + arc.ArchiveObject( AddressOfObjectAt( objectIdx ) ); + } +} + + +/* +//=============================================================== +// Name: Archive +// Class: Container +// +// Description: Archive function for a container of Safe pointers. +// +// Parameters: Archiver& -- holds the archive data +// +// Returns: None +// +//=============================================================== +inline void Container< SafePtr >::Archive +( + Archiver &arc +) +{ + int numObjects = numobjects ; + arc.ArchiveInteger( &numObjects ); + + if ( arc.Loading() ) + Resize( numObjects ); + + for ( int objectIdx = 1; objectIdx <= numObjects; ++objectIdx ) + { + SafePtr *safePtr = AddressOfObjectAt( objectIdx ); + arc.ArchiveSafePointer( ObjectAt( objectIdx ) ); + arc.ArchiveObject( ObjectAt( objectIdx ) ); + } +} +*/ + +//=============================================================== +// Name: Archive +// Class: Container +// +// Description: Archive function for a container of Entity pointers. +// +// Parameters: Archiver& -- holds the archive data +// +// Returns: None +// +//=============================================================== +inline void Container::Archive +( + Archiver &arc +) +{ + int numObjects = numobjects ; + arc.ArchiveInteger( &numObjects ); + + if ( arc.Loading() ) + Resize( numObjects ); + + for ( int objectIdx = 1; objectIdx <= numObjects; ++objectIdx ) + { + Entity** entity = AddressOfObjectAt( objectIdx ); + arc.ArchiveObjectPointer( (Class**)entity ); + } +} + +inline void Container::Archive +( + Archiver &arc +) +{ + int i; + int numEntries; + EntityPtr eptr; + EntityPtr *eptrptr; + + if ( arc.Saving() ) + { + numEntries = NumObjects(); + arc.ArchiveInteger( &numEntries ); + for ( i = 1 ; i <= numEntries ; i++ ) + { + eptr = ObjectAt( i ); + arc.ArchiveSafePointer( &eptr ); + } + } + else + { + arc.ArchiveInteger( &numEntries ); + + ClearObjectList(); + Resize( numEntries ); + + for ( i = 1 ; i <= numEntries ; i++ ) + { + AddObject( eptr ); + eptrptr = &ObjectAt( i ); + arc.ArchiveSafePointer( eptrptr ); + } + } +} + +/* +//=============================================================== +// Name: Archive +// Class: Container +// +// Description: Archive function for a container of Entity objects. +// +// Parameters: Archiver& -- holds the archive data +// +// Returns: None +// +//=============================================================== +inline void Container::Archive +( + Archiver &arc +) +{ + int numObjects = numobjects ; + arc.ArchiveInteger( &numObjects ); + + if ( arc.Loading() ) + Resize( numObjects ); + + for ( int objectIdx = 1; objectIdx <= numObjects; ++objectIdx ) + { + arc.ArchiveObject( AddressOfObjectAt( objectIdx ) ); + } +} +*/ + + + + + +#define ArchiveEnum( thing, type ) \ + { \ + int tempInt; \ + \ + tempInt = ( int )( thing ); \ + arc.ArchiveInteger( &tempInt ); \ + ( thing ) = ( type )tempInt; \ + } + + +#endif /* archive.h */ diff --git a/dlls/game/armor.cpp b/dlls/game/armor.cpp new file mode 100644 index 0000000..af5b9e9 --- /dev/null +++ b/dlls/game/armor.cpp @@ -0,0 +1,1077 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/armor.cpp $ +// $Revision:: 63 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:35p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Standard armor that prevents a percentage of damage per hit. +// + +#include "_pch_cpp.h" +#include "armor.h" +#include "player.h" +#include "mp_manager.hpp" + +extern cvar_t *g_armoradaptionlimit; +extern Event EV_SetOriginEveryFrame; + +//------------------------------------------------------------------------- +// Events +//------------------------------------------------------------------------- +Event EV_Armor_SetAdaptionFX +( + "setadaptionfx", + EV_DEFAULT, + "s", + "adaption_fx", + "set the adaption fx model" +); +Event EV_Armor_SetFXLife +( + "setFXlife", + EV_DEFAULT, + "ff", + "Normal_Time Explosive_Time", + "Sets the FX Time" +); +Event EV_Armor_SetActiveStatus +( + "setarmoractivestatus", + EV_DEFAULT, + "b", + "active", + "sets the active status" +); +Event EV_Armor_SetMultiplier +( + "setarmormultiplier", + EV_DEFAULT, + "f", + "multiplier", + "sets the damage multiplier for the armor" +); +Event EV_Armor_AddToNoProtectionList +( + "noprotection", + EV_DEFAULT, + "s", + "MOD", + "Adds a MOD to the No Protection List" +); +Event EV_Armor_UseArmorDirection +( + "usearmordirection", + EV_DEFAULT, + "ff", + "directionAngleMin directionAngleMax", + "Only allows armor to work if attack is within the specifed yaw range" +); + +Event EV_Armor_LoadAdaptionDataFromGameVars +( + "loadadaptiondata", + EV_DEFAULT, + NULL, + NULL, + "Used to control timing on adaption information loads" +); + + +//------------------------------------------------------------------------- +// Armor Implementation +//------------------------------------------------------------------------- +CLASS_DECLARATION( Item, Armor, NULL ) +{ + { NULL, NULL } +}; + + +// +// Name: Armor() +// Class: Armor +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +Armor::Armor() +{ + // all data will be setup by the archive function + if ( LoadingSavegame ) + return; + + if ( multiplayerManager.checkFlag( MP_FLAG_NO_ARMOR ) ) + { + PostEvent( EV_Remove, EV_REMOVE ); + return; + } + + setName( "UnknownArmor" ); + amount = 0; + SetMax ( 200 ); + + _mpItemType = MP_ITEM_TYPE_ARMOR; +} + + + +// +// Name: ~Armor() +// Class: Armor +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +Armor::~Armor() +{ + +} + +// +// Name: ResolveDamage() +// Class: Armor +// +// Description: Resolves the damage. +// +// Parameters: float damage -- The initial damage. +// +// Returns: float newDamage -- The new damage after armor factor is taken into account +// +float Armor::ResolveDamage( float damage , int meansOfDeath , const Vector &direction , const Vector &position , Entity *attacker ) +{ + gi.Error( ERR_FATAL, "Armor::ResolveDamage -- Do Not Use Armor Base Class -- You MUST use a subclassed" ); + return 0.0f; +} + +//-------------------------------------------------------------- +// Name: CanBeDamagedBy() +// Class: Armor +// +// Description: Checks if this armor will aborb 100% of the damage +// for the specified Means Of Death +// +// Parameters: int meansOfDeath +// +// Returns: true or false +//-------------------------------------------------------------- +bool Armor::CanBeDamagedBy( int meansOfDeath ) +{ + gi.Error( ERR_FATAL, "Armor::CanBeDamagedBy -- Do Not Use Armor Base Class -- You MUST use a subclassed" ); + return true; +} + +// +// Name: ItemPickup() +// Class: Armor +// +// Description: Handles Sentients Picking up armor... In this case, however, +// Only Actors are allowed to actually pick up armor +// +// Parameters: Entity *other - The entity doing the picking up +// qboolean add_to_inventory - Here to make parameters match +// +// Returns: NULL -- This function doesn't actually return the armor, instead +// it has the entity picking it up process and armor event +// +Item *Armor::ItemPickup( Entity *other, qboolean add_to_inventory, qboolean ) +{ + Sentient *sent; + str realname; + + // For right now, we only want players to pick up armor + if ( !other->isSubclassOf( Player ) ) + return NULL; + + if ( !Pickupable( other ) ) + return NULL; + + sent = (Sentient*)other; + + // Check if we NEED to pick up the armor, if the armor we are about to pick up is + // less than the armor we have, we don't want to pick it up + if ( !_needThisArmor(sent) ) + return NULL; + + // Handle the actual "Picking Up" + _pickupArmor(sent); + + // Give the Sentient the Armor + Event *armorEvent; + armorEvent = new Event(EV_Sentient_GiveArmor); + armorEvent->AddString( item_name.c_str() ); + armorEvent->AddFloat( amount ); + armorEvent->AddInteger( true ); + + sent->ProcessEvent(armorEvent); + + return NULL; // This doesn't create any items +} + + + +// +// Name: _needThisArmor() +// Class: Armor +// +// Description: Checks if the sentient needs to pick up the +// armor or not +// +// Parameters: Sentient *sentient -- The sentient to check +// +// Returns: true or false +// +qboolean Armor::_needThisArmor( Sentient *sentient ) +{ + int currentArmorValue = sentient->GetArmorValue(); + + if( currentArmorValue >= amount ) + return false; + else + return true; +} + + +// +// Name: _pickupArmor() +// Class: Armor +// +// Description: Takes care of "Picking Up" the armor, by playing the +// appropriate sounds, removing the model... +// +// Parameters: None +// +// Returns: None +// +void Armor::_pickupArmor( Sentient *sentient) +{ + str realname; + + // Play pickup sound + realname = GetRandomAlias( "snd_pickup" ); + if ( realname.length() > 1 ) + sentient->Sound( realname, CHAN_ITEM ); + + // Cancel some events + CancelEventsOfType( EV_Item_DropToFloor ); + CancelEventsOfType( EV_Item_Respawn ); + CancelEventsOfType( EV_FadeOut ); + + // Hide the model + setSolidType( SOLID_NOT ); + + if ( _missingSkin ) + { + ChangeSkin( _missingSkin, true ); + } + else + { + hideModel(); + } + + // Respawn? + if ( !Respawnable() ) + PostEvent( EV_Remove, FRAMETIME ); + else + PostEvent( EV_Item_Respawn, RespawnTime() ); + + // fire off any pickup_thread's + if ( pickup_thread.length() ) + ExecuteThread( pickup_thread ); +} + + +//------------------------------------------------------------------------- +// AdaptiveArmor Implementation +//------------------------------------------------------------------------- +CLASS_DECLARATION( Armor, AdaptiveArmor, NULL ) +{ + { &EV_Armor_SetAdaptionFX, &AdaptiveArmor::SetAdaptionFX }, + { &EV_Armor_LoadAdaptionDataFromGameVars, &AdaptiveArmor::LoadDataFromGameVars}, + { &EV_Armor_SetFXLife, &AdaptiveArmor::SetFXLife }, + { NULL, NULL } +}; + + +// +// Adaptive Armor is Adaption And Cannot Adapt is Global +// +Container AdaptiveArmor::_AdaptionList; +Container AdaptiveArmor::_CannotAdaptToList; +Container AdaptiveArmor::_fxList; + +// +// Name: AdaptiveArmor() +// Class: AdaptiveArmor +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +AdaptiveArmor::AdaptiveArmor() +{ + amount = 0; + SetMax ( 200 ); + + // Add Adaptions to list + _fxList.AddObject( "fx/fx-borg-sheild-01.tik" ); + _fxList.AddObject( "fx/fx-borg-sheild-02.tik" ); + _fxList.AddObject( "fx/fx-borg-sheild-03.tik" ); + + // Set our current Adaption + _AdaptionFX = "fx/fx-borg-sheild-01.tik"; + + // Set our index + _currentFXIndex = 1; + _fxTimeNormal = 0.15; + _fxTimeExplosive = 1.0; + + _AddMODToCannotAdaptList( MOD_IMOD_PRIMARY); + _AddMODToCannotAdaptList( MOD_IMOD_SECONDARY); + _AddMODToCannotAdaptList( MOD_SUICIDE ); + + if ( !LoadingSavegame ) + { + //Try and pull data from the gameVars + Event *loadEvent = new Event ( EV_Armor_LoadAdaptionDataFromGameVars ); + + PostEvent( loadEvent, 3.0 ); + //LoadDataFromGameVars(); + } +} + +// +// Name: ~AdaptiveArmor() +// Class: AdaptiveArmor +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +AdaptiveArmor::~AdaptiveArmor() +{ + + +} + + +// +// Name: ResolveDamage() +// Class: AdaptiveArmor +// +// Description: Resolves the damage +// +// Parameters: float damage -- The initial damage. +// +// Returns: float newDamage -- The new damage after armor factor is taken into account +// +float AdaptiveArmor::ResolveDamage( float damage , int meansOfDeath , const Vector &direction , const Vector &position , Entity *attacker ) +{ + int DamageTotal; + + if (!_CanAdaptTo( meansOfDeath ) ) + return damage; + + if ( attacker->isSubclassOf( Player ) ) + { + if ( !_AdaptionInList( meansOfDeath ) ) + _AddAdaption( meansOfDeath , damage ); + else + _UpdateAdaption( meansOfDeath , damage ); + } + + DamageTotal = (int)_GetDamageTotal( meansOfDeath ); + + _adaptionLimit = g_armoradaptionlimit->value; + if ( DamageTotal > _adaptionLimit ) + { + if ( meansOfDeath == MOD_EXPLOSION || meansOfDeath == MOD_VAPORIZE_COMP ) + _fxTime = _fxTimeExplosive; + else + _fxTime = _fxTimeNormal; + + _PlayAdaptionFX( direction , position ); + return 0.0f; + } + + return damage; +} + + +void AdaptiveArmor::SetAdaptionFX( Event *ev ) +{ + SetAdaptionFX(ev->GetString( 1 ) ); +} + +void AdaptiveArmor::SetAdaptionFX( const str &fxName ) +{ + _AdaptionFX = fxName; +} + +// +// Name: _AddAdaption() +// Class: AdaptiveArmor +// +// Description: Adds and adaption to the adaption list +// +// Parameters: int MOD - The Means Of Death index +// +// Returns: None +// +void AdaptiveArmor::_AddAdaption( int MeansOfDeath , float damage) +{ + MODHitCounter *adaption = NULL; + adaption = new MODHitCounter; + + //We don't want to be able to adapt to all MOD + if ( _AdaptionList.NumObjects() > 2 ) + return; + + //Double Check we're not adding the same MOD + if ( _AdaptionInList( MeansOfDeath ) ) + return; + + if ( adaption ) + { + adaption->MODIndex = MeansOfDeath; + adaption->damage = damage + .1; // Have to add just a bit incase damage = 0 + adaption->adapted = false; + + str MODName = MOD_NumToName( MeansOfDeath ); + AddGameVar ( MODName , adaption->damage ); + _AdaptionList.AddObject( adaption ); + } +} + + +// +// Name: _AdaptionInList() +// Class: AdaptiveArmor +// +// Description: Checks if an adaption for the MOD is in the list +// +// Parameters: int MOD -- the Means Of Death index +// +// Returns: true or false +// +qboolean AdaptiveArmor::_AdaptionInList( int MeansOfDeath ) +{ + int i; + MODHitCounter *adaption; + + for( i=_AdaptionList.NumObjects(); i>0; i-- ) + { + adaption = _AdaptionList.ObjectAt( i ); + if ( adaption->MODIndex == MeansOfDeath ) + return true; + } + + return false; +} + + +// +// Name: _UpdateAdaption() +// Class: AdaptiveArmor +// +// Description: Updates a particular adaption in the adaption list +// +// Parameters: int MOD -- the Means Of Death index +// int count -- the number to add +// +// Returns: None +// +void AdaptiveArmor::_UpdateAdaption( int MeansOfDeath , float damage ) +{ + int i; + MODHitCounter *adaption; + + for( i=_AdaptionList.NumObjects(); i>0; i-- ) + { + adaption = _AdaptionList.ObjectAt( i ); + if ( adaption->MODIndex == MeansOfDeath ) + { + adaption->damage += damage; + + str MODName = MOD_NumToName( MeansOfDeath ); + UpdateGameVar ( MODName , adaption->damage ); + + if ( (adaption->damage >= g_armoradaptionlimit->value) && !adaption->adapted ) + { + adaption->adapted = true; + + Actor *act; + for ( int i=1; i<=ActiveList.NumObjects(); i++ ) + { + act = ActiveList.ObjectAt(i); + if ( !act ) + continue; + + act->InContext( "enemyadapted" , 0 ); + } + + } + return; + } + } +} + + +// +// Name: _GetDamageTotal() +// Class: AdaptiveArmor +// +// Description: Retrieves the number of times shot for a +// particular Means Of Death +// +// Parameters: int MOD -- Means Of Death Index +// +// Returns: int count +// +float AdaptiveArmor::_GetDamageTotal( int MeansOfDeath ) +{ + int i; + MODHitCounter *adaption; + + for( i=_AdaptionList.NumObjects(); i>0; i-- ) + { + adaption = _AdaptionList.ObjectAt( i ); + if ( adaption->MODIndex == MeansOfDeath ) + { + if ( adaption->damage > 0 ) + return adaption->damage; + } + + } + + return 0.0f; +} + +void AdaptiveArmor::_PlayAdaptionFX(const Vector &direction , const Vector &position ) +{ + Vector dir; + Vector newDir; + Vector fxOrigin; + Vector PosDelta; + Entity *theFX; + + + dir = direction; + dir *= -1; + dir.normalize(); + + fxOrigin = dir * 15; + fxOrigin += position; + + dir = dir.toAngles(); + + PosDelta = _currentFXPosition - fxOrigin; + + if ( PosDelta.length() > FX_CHANGE_DELTA ) + { + _changeFX(); + _currentFXPosition = fxOrigin; + } + + theFX = SpawnEffect(_AdaptionFX, fxOrigin , dir , _fxTime ); + theFX->bind( owner ); + + theFX->PostEvent( EV_SetOriginEveryFrame , FRAMETIME ); +} + +qboolean AdaptiveArmor::_CanAdaptTo( int MOD ) +{ + int checkMOD; + int i; + + for( i=_CannotAdaptToList.NumObjects(); i>0; i-- ) + { + checkMOD = _CannotAdaptToList.ObjectAt( i ); + if ( checkMOD == MOD ) + return false; + } + + return true; +} + +void AdaptiveArmor::_AddMODToCannotAdaptList( int MOD ) +{ + _CannotAdaptToList.AddObject( MOD ); +} + +void AdaptiveArmor::_changeFX() +{ + int MaxNumber; + MaxNumber = _fxList.NumObjects(); + + if ( MaxNumber == 0 ) + return; + + _currentFXIndex++; + + if ( _currentFXIndex > MaxNumber ) + _currentFXIndex = 1; + + _AdaptionFX = _fxList.ObjectAt( _currentFXIndex ); + +} + +void AdaptiveArmor::ClearAdaptionList() +{ + + int i; + MODHitCounter *adaption; + + for( i=_AdaptionList.NumObjects(); i>0; i-- ) + { + adaption = _AdaptionList.ObjectAt( i ); + delete adaption; + adaption = NULL; + } + + _AdaptionList.FreeObjectList(); + + +} + +//-------------------------------------------------------------- +// Name: CanBeDamagedBy() +// Class: AdaptiveArmor +// +// Description: Checks if this armor will aborb 100% of the damage +// for the specified Means Of Death +// +// Parameters: int meansOfDeath +// +// Returns: true or false +//-------------------------------------------------------------- +bool AdaptiveArmor::CanBeDamagedBy( int meansOfDeath ) +{ + float DamageTotal; + + if ( !_AdaptionInList( meansOfDeath ) ) + return true; + + DamageTotal = _GetDamageTotal( meansOfDeath ); + if ( DamageTotal > ADAPTION_LIMIT ) + return false; + + return true; +} + + + +void AdaptiveArmor::AddGameVar( const str &name , float value ) +{ + ScriptVariable *script_var; + if ( gameVars.VariableExists( name.c_str() ) ) + return; + + script_var = new ScriptVariable; + script_var->setName( name.c_str() ); + script_var->setFloatValue( value ); + gameVars.AddVariable( script_var ); +} + +void AdaptiveArmor::UpdateGameVar( const str &name , float value ) +{ + ScriptVariable *script_var; + float currentValue; + + if ( !gameVars.VariableExists( name.c_str() ) ) + return; + + script_var = gameVars.GetVariable( name.c_str() ); + currentValue = script_var->floatValue(); + currentValue = currentValue + value; + script_var->setFloatValue( currentValue ); +} + +void AdaptiveArmor::LoadAdaptionData( const str &name ) +{ + ScriptVariable *script_var; + float lastValue; + int i; + unsigned int theMOD; + MODHitCounter *adaption; + + theMOD = MOD_NameToNum( name ); + + if ( gameVars.VariableExists( name.c_str() ) ) + { + script_var = gameVars.GetVariable( name.c_str() ); + lastValue = script_var->floatValue(); + + _AddAdaption( theMOD , lastValue ); + + + if ( lastValue >= g_armoradaptionlimit->value ) + { + for( i=_AdaptionList.NumObjects(); i>0; i-- ) + { + adaption = _AdaptionList.ObjectAt( i ); + if ( adaption->MODIndex == theMOD ) + { + adaption->adapted = true; + } + } + + } + + } +} + +void AdaptiveArmor::LoadDataFromGameVars( Event *ev ) +{ + LoadDataFromGameVars(); +} + +void AdaptiveArmor::LoadDataFromGameVars() +{ + //Sigh, I HATE having to do this, but since we evidently HAVE to have + //the adaptions carry across levels, here we go... + LoadAdaptionData( "phaser" ); + LoadAdaptionData( "vaporize" ); + LoadAdaptionData( "comp_rifle" ); + LoadAdaptionData( "vaporize_comp" ); +} + +void AdaptiveArmor::SetFXLife( Event *ev ) +{ + _fxTimeNormal = ev->GetFloat( 1 ); + _fxTimeExplosive = ev->GetFloat( 2 ); +} + +//------------------------------------------------------------------------- +// BasicArmor Implementation +//------------------------------------------------------------------------- + +const float BasicArmor::_maxAmount = 200.0f; +const float BasicArmor::_normalMaxAmount = 100.0f; +const float BasicArmor::_lossRate = 1.0f; + +const float BasicArmor::_highRangeCutoffSinglePlayer = 66.0f; +const float BasicArmor::_midRangeCutoffSinglePlayer = 33.0f; + +const float BasicArmor::_highRangeCutoffMultiplayer = 100.0f; +const float BasicArmor::_midRangeCutoffMultiplayer = 50.0f; + +const float BasicArmor::_highRangePercent = 0.75f; +const float BasicArmor::_midRangePercent = 0.50f; +const float BasicArmor::_lowRangePercent = 0.25f; + + + +CLASS_DECLARATION( Armor, BasicArmor, NULL ) +{ + { NULL, NULL } +}; + +BasicArmor::BasicArmor() +{ + maximum_amount = _maxAmount; + setName( "BasicArmor" ); + turnThinkOn(); + + _lastAddTime = 0.0f; +} + +// +// Name: ResolveDamage() +// Class: BasicAmor +// +// Description: Resolves the damage. +// +// Parameters: float damage -- The initial damage. +// +// Returns: float newDamage -- The new damage after armor factor is taken into account +// +float BasicArmor::ResolveDamage( float damage , int meansOfDeath , const Vector &direction , const Vector &position , Entity *attacker ) +{ + float tempDamage; + float damageAbsorbed; + float percent; + float highRangeCutoff; + float midRangeCutoff; + + + if ( ( meansOfDeath == MOD_ARMOR_PIERCING ) || ( meansOfDeath == MOD_IMOD_PRIMARY ) || + ( meansOfDeath == MOD_IMOD_SECONDARY ) || ( meansOfDeath == MOD_DEATH_QUAD ) ) + return damage; + + // Determine which cutoff values to use + + if ( multiplayerManager.inMultiplayer() ) + { + highRangeCutoff = _highRangeCutoffMultiplayer; + midRangeCutoff = _midRangeCutoffMultiplayer; + } + else + { + highRangeCutoff = _highRangeCutoffSinglePlayer; + midRangeCutoff = _midRangeCutoffSinglePlayer; + } + + // Figure out amount to absorb + + if ( amount > highRangeCutoff ) + percent = _highRangePercent; + else if ( amount > midRangeCutoff ) + percent = _midRangePercent; + else + percent = _lowRangePercent; + + tempDamage = damage * percent; + + if ( amount > tempDamage ) + damageAbsorbed = tempDamage; + else + damageAbsorbed = amount; + + // Damage the armor + + amount -= damageAbsorbed; + if ( amount < 0.0 ) + amount = 0.0; + + // Return damage left + + return damage - damageAbsorbed; +} + +void BasicArmor::Think( void ) +{ + if ( ( level.time > _lastAddTime + 1.0f ) && ( amount > _normalMaxAmount ) ) + { + amount -= level.frametime * _lossRate; + + if ( amount < _normalMaxAmount ) + { + amount = _normalMaxAmount; + } + } +} + +void BasicArmor::Add( int num ) +{ + Armor::Add( num ); + + _lastAddTime = level.time; +} + +//-------------------------------------------------------------- +// Name: CanBeDamagedBy() +// Class: BasicArmor +// +// Description: Checks if this armor will aborb 100% of the damage +// for the specified Means Of Death +// +// Parameters: int meansOfDeath +// +// Returns: true or false +//-------------------------------------------------------------- +bool BasicArmor::CanBeDamagedBy( int meansOfDeath ) +{ + return true; +} + +//-------------------------------------------------------------- +// Name: _needThisArmor +// Class: BasicArmor +// +// Description: Checks to see if we need more armor +// +// Parameters: Sentient *sentient - sentient that wants to know if it can have more armor +// +// Returns: true or false +//-------------------------------------------------------------- + +qboolean BasicArmor::_needThisArmor( Sentient *sentient ) +{ + int currentArmorValue = sentient->GetArmorValue(); + + if( currentArmorValue < _maxAmount ) + return true; + else + return false; +} + +void BasicArmor::cacheStrings( void ) +{ + G_FindConfigstringIndex( va( "$$PickedUp$$ %d $$Item-Armor$$", (int)amount ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); +} + +//------------------------------------------------------------------------- +// ShieldArmor Implementation +//------------------------------------------------------------------------- +CLASS_DECLARATION( Armor, ShieldArmor, NULL ) +{ + { &EV_Armor_SetActiveStatus, &ShieldArmor::SetActiveStatus }, + { &EV_Armor_SetMultiplier, &ShieldArmor::SetMultiplier }, + { &EV_Armor_AddToNoProtectionList, &ShieldArmor::AddMODToNoProtectionList }, + { &EV_Armor_UseArmorDirection, &ShieldArmor::SetShieldDirection }, + + { NULL, NULL } +}; + +ShieldArmor::ShieldArmor() +{ + maximum_amount = 100.0f; + //amount = 100.0f; + setName( "ShieldArmor" ); + _active = true; + _multiplier = 0.0f; + _directionMin = -90.0; + _directionMax = 90.0; + _useDirection = false; + +} + +void ShieldArmor::SetActiveStatus( Event *ev ) +{ + _active = ev->GetBoolean( 1 ); +} + +void ShieldArmor::SetMultiplier( Event *ev ) +{ + _multiplier = ev->GetFloat( 1 ); +} + +bool ShieldArmor::CanBeUsed() +{ + if ( amount > 0 ) + return true; + + return false; +} + +// +// Name: ResolveDamage() +// Class: BasicAmor +// +// Description: Resolves the damage. +// +// Parameters: float damage -- The initial damage. +// +// Returns: float newDamage -- The new damage after armor factor is taken into account +// +float ShieldArmor::ResolveDamage( float damage , int meansOfDeath , const Vector &direction , const Vector &position , Entity *attacker ) +{ + //Shield Armor basically acts as a "life meter" for a shield. If the armor is actor + //and he have an armor value greater than 0, then we take 0 damage. The as soon as the + //amount is less than 0, then we start taking full damage. + if ( !_active ) return damage; + + int inNoProtectionList = 0; + + inNoProtectionList = _noProtectionList.IndexOfObject( meansOfDeath ); + + if ( inNoProtectionList ) + return damage; + + if ( meansOfDeath == MOD_ARMOR_PIERCING ) + return damage; + + if ( amount <= 0 ) + { + amount = 0.0f; + return damage; + } + + + if ( _useDirection ) + { + Vector directionAngle = direction.toAngles(); + float directionYaw = AngleNormalize180( directionAngle[YAW] ); + if ( (directionYaw > _directionMax) || (directionYaw < _directionMin) ) + { + return damage; + } + } + + amount -= damage; + + if ( amount < 0 ) + amount = 0.0f; + + if ( amount > 0 ) + { + // We blocked so spawn an effect + + str modelName; + Vector effectAngles; + + effectAngles = direction; + effectAngles.toAngles(); + + modelName = "ReflectionModel-"; + modelName += MOD_NumToName( meansOfDeath ); + modelName += ".gdb"; + + SpawnEffect( modelName, position, vec_zero, 1.0f ); + + return (damage * _multiplier); + } + + return damage; +} + +//-------------------------------------------------------------- +// Name: CanBeDamagedBy() +// Class: BasicArmor +// +// Description: Checks if this armor will aborb 100% of the damage +// for the specified Means Of Death +// +// Parameters: int meansOfDeath +// +// Returns: true or false +//-------------------------------------------------------------- +bool ShieldArmor::CanBeDamagedBy( int meansOfDeath ) +{ + return true; +} + + +void ShieldArmor::AddMODToNoProtectionList( Event *ev ) +{ + str MODName = ev->GetString( 1 ); + AddMODToNoProtectionList( MODName ); +} + +void ShieldArmor::AddMODToNoProtectionList(const str &MODName ) +{ + unsigned int theMOD = MOD_NameToNum( MODName.c_str() ); + + _noProtectionList.AddUniqueObject( theMOD ); +} + +void ShieldArmor::SetShieldDirection( Event *ev ) +{ + SetShieldDirection( ev->GetFloat(1) , ev->GetFloat(2) ); +} + +void ShieldArmor::SetShieldDirection( float minAngle, float maxAngle ) +{ + _useDirection = true; + _directionMin = minAngle; + _directionMax = maxAngle; +} + diff --git a/dlls/game/armor.h b/dlls/game/armor.h new file mode 100644 index 0000000..dfa8d76 --- /dev/null +++ b/dlls/game/armor.h @@ -0,0 +1,317 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/armor.h $ +// $Revision:: 38 $ +// $Author:: Sketcher $ +// $Date:: 2/21/03 6:01p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Standard armor that prevents a percentage of damage per hit. +// + +#ifndef __ARMOR_H__ +#define __ARMOR_H__ + +#include "item.h" + + +// +// Defines +// +#define ADAPTION_LIMIT 200 +#define FX_CHANGE_DELTA 25 + +// +// Stucture Name: MODHitCounter +// +// Description: Holds mapping of MODs and counts of hits +// +typedef struct + { + int MODIndex; + float damage; + bool adapted; + } MODHitCounter; + + +// +// Class Name: ArmorEntity +// Base Class: Item +// +// Description: Base Class for all Armor Items +// +class Armor : public Item + { + public: + CLASS_PROTOTYPE( Armor ); + + Armor(); + ~Armor(); + virtual Item *ItemPickup( Entity *other, qboolean add_to_inventory, qboolean ); + virtual float ResolveDamage( float damage , int meansOfDeath , const Vector &direction , const Vector &position , Entity *attacker ); + virtual void Archive( Archiver &arc ); + virtual bool CanBeDamagedBy( int meansOfDeath ); + + protected: + virtual qboolean _needThisArmor( Sentient *sentient ); + virtual void _pickupArmor( Sentient *sentient ); + + }; + +inline void Armor::Archive( Archiver &arc ) +{ + Item::Archive( arc ); +} + +// +// Class Name: AdaptiveArmor +// Base Class: ArmorEntity +// +// Description: Armor that adapts to MOD's +// +class AdaptiveArmor : public Armor + { + public: + CLASS_PROTOTYPE( AdaptiveArmor ); + + AdaptiveArmor(); + ~AdaptiveArmor(); + + float ResolveDamage ( float damage , int meansOfDeath , const Vector &direction , const Vector &position , Entity *attacker ); + void SetAdaptionFX ( Event *ev ); + void SetAdaptionFX ( const str &fxName ); + bool CanBeDamagedBy ( int meansOfDeath ); + void Archive ( Archiver &arc ); + + void SetFXLife ( Event *ev ); + + static void ClearAdaptionList (); + + //Game Var Support Functions + void AddGameVar( const str &name , float value ); + void UpdateGameVar( const str &name , float value ); + void LoadAdaptionData( const str &name ); + void LoadDataFromGameVars( Event *ev ); + void LoadDataFromGameVars(); + + + + + + protected: + void _AddAdaption ( int MeansOfDeath , float damage ); + void _UpdateAdaption ( int MeansOfDeath , float damage ); + qboolean _AdaptionInList ( int MeansOfDeath ); + float _GetDamageTotal ( int MeansOfDeath ); + void _PlayAdaptionFX ( const Vector &direction , const Vector &position ); + void _AddMODToCannotAdaptList ( int MOD ); + qboolean _CanAdaptTo ( int MOD ); + void _changeFX(); + + + private: + static Container _AdaptionList; + static Container _CannotAdaptToList; + static Container _fxList; + int _currentFXIndex; + str _AdaptionFX; + Vector _currentFXPosition; + float _fxTime; + float _fxTimeNormal; + float _fxTimeExplosive; + float _adaptionLimit; + + + }; + +inline void AdaptiveArmor::Archive( Archiver &arc ) +{ + int num; + MODHitCounter* modHitCounter; + int i; + + + Armor::Archive( arc ); + + if ( arc.Loading() ) + { + ClearAdaptionList(); + _CannotAdaptToList.ClearObjectList(); + _fxList.ClearObjectList(); + } + + if ( arc.Saving() ) + { + num = _AdaptionList.NumObjects(); + + arc.ArchiveInteger( &num ); + + for( i = 1 ; i <= num ; i++ ) + { + modHitCounter = _AdaptionList.ObjectAt( i ); + + arc.ArchiveInteger( &modHitCounter->MODIndex ); + arc.ArchiveFloat( &modHitCounter->damage ); + arc.ArchiveBool( &modHitCounter->adapted ); + } + + } + else + { + arc.ArchiveInteger( &num ); + + for( i = 1 ; i <= num ; i++ ) + { + modHitCounter = new MODHitCounter; + + arc.ArchiveInteger( &modHitCounter->MODIndex ); + arc.ArchiveFloat( &modHitCounter->damage ); + arc.ArchiveBool( &modHitCounter->adapted ); + + _AdaptionList.AddObject( modHitCounter ); + } + } + + _CannotAdaptToList.Archive( arc ); + + _fxList.Archive( arc ); + + arc.ArchiveInteger (&_currentFXIndex ); + arc.ArchiveString (&_AdaptionFX ); + arc.ArchiveVector (&_currentFXPosition); + arc.ArchiveFloat (&_fxTime ); + arc.ArchiveFloat (&_fxTimeNormal ); + arc.ArchiveFloat (&_fxTimeExplosive ); + arc.ArchiveFloat (&_adaptionLimit ); + } + +// +// Class Name: BasicArmor +// Base Class: Item +// +// Description: Basic armor +// +class BasicArmor : public Armor + { + protected: + static const float _maxAmount; + static const float _normalMaxAmount; + static const float _lossRate; + + static const float _highRangeCutoffSinglePlayer; + static const float _midRangeCutoffSinglePlayer; + + static const float _highRangeCutoffMultiplayer; + static const float _midRangeCutoffMultiplayer; + + static const float _highRangePercent; + static const float _midRangePercent; + static const float _lowRangePercent; + + float _lastAddTime; + + virtual qboolean _needThisArmor( Sentient *sentient ); + /* virtual */ void Think( void ); + + public: + CLASS_PROTOTYPE( BasicArmor ); + + BasicArmor(); + + virtual float ResolveDamage( float damage , int meansOfDeath , const Vector &direction , const Vector &position , Entity *attacker ); + virtual bool CanBeDamagedBy( int meansOfDeath ); + /* virtual */ void Add( int num ); + /* virtual */ void cacheStrings( void ); + + void Archive ( Archiver &arc ); + }; + +inline void BasicArmor::Archive( Archiver &arc ) +{ + Armor::Archive( arc ); + + arc.ArchiveFloat( &_lastAddTime ); +} + +// +// Class Name: ShieldArmor +// Base Class: Item +// +// Description: Shield armor that can take a specified amount of damage +// +class ShieldArmor : public Armor + { + public: + CLASS_PROTOTYPE ( ShieldArmor ); + + ShieldArmor(); + + void SetActiveStatus( Event *ev ); + void SetMultiplier( Event *ev ); + void AddMODToNoProtectionList( Event *ev ); + void AddMODToNoProtectionList( const str &MODName ); + void SetShieldDirection( Event *ev ); + void SetShieldDirection( float minAngle, float maxAngle ); + bool CanBeUsed(); + virtual float ResolveDamage( float damage, int meansOfDeath, const Vector &direction, const Vector &position , Entity *attacker ); + virtual bool CanBeDamagedBy ( int meansOfDeath ); + void Archive ( Archiver &arc ); + + private: + bool _active; + float _multiplier; + float _directionMin; + float _directionMax; + bool _useDirection; + Container _noProtectionList; + + }; + +inline void ShieldArmor::Archive( Archiver &arc ) +{ + Armor::Archive( arc ); + + arc.ArchiveBool( &_active ); + arc.ArchiveFloat( &_multiplier ); + arc.ArchiveFloat( &_directionMin); + arc.ArchiveFloat( &_directionMax ); + arc.ArchiveBool( &_useDirection ); + + int num , i; + + if ( arc.Saving() ) + { + num = _noProtectionList.NumObjects(); + + arc.ArchiveInteger( &num ); + + for( i = 1 ; i <= num ; i++ ) + { + arc.ArchiveUnsigned ( &_noProtectionList.ObjectAt( i ) ); + } + + } + else + { + arc.ArchiveInteger( &num ); + + for( i = 1 ; i <= num ; i++ ) + { + unsigned int theMod; + arc.ArchiveUnsigned( &theMod ); + _noProtectionList.AddObject( theMod ); + } + } + +} + +typedef SafePtr ArmorPtr; + +#endif /* armor.h */ diff --git a/dlls/game/be_aas.h b/dlls/game/be_aas.h new file mode 100644 index 0000000..b46d305 --- /dev/null +++ b/dlls/game/be_aas.h @@ -0,0 +1,210 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_aas.h + * + * desc: Area Awareness System, stuff exported to the AI + * + * $Archive: /Code/DLLs/game/be_aas.h $ + * $Author: Steven $ + * $Revision: 2 $ + * $Modtime: 10/13/03 9:01a $ + * $Date: 10/13/03 9:11a $ + * + *****************************************************************************/ + +#ifndef MAX_STRINGFIELD +#define MAX_STRINGFIELD 80 +#endif + +//travel flags +#define TFL_INVALID 0x00000001 //traveling temporary not possible +#define TFL_WALK 0x00000002 //walking +#define TFL_CROUCH 0x00000004 //crouching +#define TFL_BARRIERJUMP 0x00000008 //jumping onto a barrier +#define TFL_JUMP 0x00000010 //jumping +#define TFL_LADDER 0x00000020 //climbing a ladder +#define TFL_WALKOFFLEDGE 0x00000080 //walking of a ledge +#define TFL_SWIM 0x00000100 //swimming +#define TFL_WATERJUMP 0x00000200 //jumping out of the water +#define TFL_TELEPORT 0x00000400 //teleporting +#define TFL_ELEVATOR 0x00000800 //elevator +#define TFL_ROCKETJUMP 0x00001000 //rocket jumping +#define TFL_BFGJUMP 0x00002000 //bfg jumping +#define TFL_GRAPPLEHOOK 0x00004000 //grappling hook +#define TFL_DOUBLEJUMP 0x00008000 //double jump +#define TFL_RAMPJUMP 0x00010000 //ramp jump +#define TFL_STRAFEJUMP 0x00020000 //strafe jump +#define TFL_JUMPPAD 0x00040000 //jump pad +#define TFL_AIR 0x00080000 //travel through air +#define TFL_WATER 0x00100000 //travel through water +#define TFL_SLIME 0x00200000 //travel through slime +#define TFL_LAVA 0x00400000 //travel through lava +#define TFL_DONOTENTER 0x00800000 //travel through donotenter area +#define TFL_FUNCBOB 0x01000000 //func bobbing +#define TFL_FLIGHT 0x02000000 //flight +#define TFL_BRIDGE 0x04000000 //move over a bridge +// +#define TFL_NOTTEAM1 0x08000000 //not team 1 +#define TFL_NOTTEAM2 0x10000000 //not team 2 + +//default travel flags +#define TFL_DEFAULT TFL_WALK|TFL_CROUCH|TFL_BARRIERJUMP|\ + TFL_JUMP|TFL_LADDER|\ + TFL_WALKOFFLEDGE|TFL_SWIM|TFL_WATERJUMP|\ + TFL_TELEPORT|TFL_ELEVATOR|\ + TFL_AIR|TFL_WATER|TFL_JUMPPAD|TFL_FUNCBOB + +// already defined in g_public.h in tiki tech, moved to l_util.h so the botlib stuff compiles but the gamecode also compiles +/* +typedef enum +{ + SOLID_NOT, // no interaction with other objects + SOLID_TRIGGER, // only touch when inside, after moving + SOLID_BBOX, // touch on edge + SOLID_BSP // bsp clip, touch on edge +} solid_t; +*/ + +//a trace is returned when a box is swept through the AAS world +typedef struct aas_trace_s +{ + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + int ent; // entity blocking the trace + int lastarea; // last area the trace was in (zero if none) + int area; // area blocking the trace (zero if none) + int planenum; // number of the plane that was hit +} aas_trace_t; + +// Defined in botlib.h + +//bsp_trace_t hit surface +/* +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//a trace is returned when a box is swept through the BSP world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // hit surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; +*/ +// + + +//entity info +typedef struct aas_entityinfo_s +{ + int valid; // true if updated this frame + int type; // entity type + int flags; // entity flags + float ltime; // local time + float update_time; // time between last and current update + int number; // number of the entity + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t lastvisorigin; // last visible origin + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +} aas_entityinfo_t; + +// area info +typedef struct aas_areainfo_s +{ + int contents; + int flags; + int presencetype; + int cluster; + vec3_t mins; + vec3_t maxs; + vec3_t center; +} aas_areainfo_t; + +// client movement prediction stop events, stop as soon as: +#define SE_NONE 0 +#define SE_HITGROUND 1 // the ground is hit +#define SE_LEAVEGROUND 2 // there's no ground +#define SE_ENTERWATER 4 // water is entered +#define SE_ENTERSLIME 8 // slime is entered +#define SE_ENTERLAVA 16 // lava is entered +#define SE_HITGROUNDDAMAGE 32 // the ground is hit with damage +#define SE_GAP 64 // there's a gap +#define SE_TOUCHJUMPPAD 128 // touching a jump pad area +#define SE_TOUCHTELEPORTER 256 // touching teleporter +#define SE_ENTERAREA 512 // the given stoparea is entered +#define SE_HITGROUNDAREA 1024 // a ground face in the area is hit +#define SE_HITBOUNDINGBOX 2048 // hit the specified bounding box +#define SE_TOUCHCLUSTERPORTAL 4096 // touching a cluster portal + +typedef struct aas_clientmove_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + vec3_t velocity; //velocity at the end of movement prediction + aas_trace_t trace; //last trace + int presencetype; //presence type at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + float time; //time predicted ahead + int frames; //number of frames predicted ahead +} aas_clientmove_t; + +// alternate route goals +#define ALTROUTEGOAL_ALL 1 +#define ALTROUTEGOAL_CLUSTERPORTALS 2 +#define ALTROUTEGOAL_VIEWPORTALS 4 + +typedef struct aas_altroutegoal_s +{ + vec3_t origin; + int areanum; + unsigned short starttraveltime; + unsigned short goaltraveltime; + unsigned short extratraveltime; +} aas_altroutegoal_t; + +// route prediction stop events +#define RSE_NONE 0 +#define RSE_NOROUTE 1 //no route to goal +#define RSE_USETRAVELTYPE 2 //stop as soon as on of the given travel types is used +#define RSE_ENTERCONTENTS 4 //stop when entering the given contents +#define RSE_ENTERAREA 8 //stop when entering the given area + +typedef struct aas_predictroute_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + int endtravelflags; //end travel flags + int numareas; //number of areas predicted ahead + int time; //time predicted ahead (in hundreth of a sec) +} aas_predictroute_t; diff --git a/dlls/game/be_ai_char.h b/dlls/game/be_ai_char.h new file mode 100644 index 0000000..b28801b --- /dev/null +++ b/dlls/game/be_ai_char.h @@ -0,0 +1,32 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_char.h + * + * desc: bot characters + * + * $Archive: /Code/DLLs/game/be_ai_char.h $ + * $Author: Jwaters $ + * $Revision: 1 $ + * $Modtime: 5/17/02 11:35a $ + * $Date: 7/31/02 10:45a $ + * + *****************************************************************************/ + +//loads a bot character from a file +int BotLoadCharacter(char *charfile, float skill); +//frees a bot character +void BotFreeCharacter(int character); +//returns a float characteristic +float Characteristic_Float(int character, int index); +//returns a bounded float characteristic +float Characteristic_BFloat(int character, int index, float min, float max); +//returns an integer characteristic +int Characteristic_Integer(int character, int index); +//returns a bounded integer characteristic +int Characteristic_BInteger(int character, int index, int min, int max); +//returns a string characteristic +void Characteristic_String(int character, int index, char *buf, int size); +//free cached bot characters +void BotShutdownCharacters(void); diff --git a/dlls/game/be_ai_chat.h b/dlls/game/be_ai_chat.h new file mode 100644 index 0000000..13f9894 --- /dev/null +++ b/dlls/game/be_ai_chat.h @@ -0,0 +1,97 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: be_ai_chat.h + * + * desc: char AI + * + * $Archive: /Code/DLLs/game/be_ai_chat.h $ + * $Author: Jwaters $ + * $Revision: 1 $ + * $Modtime: 5/17/02 11:35a $ + * $Date: 7/31/02 10:45a $ + * + *****************************************************************************/ + +#define MAX_MESSAGE_SIZE 256 +#define MAX_CHATTYPE_NAME 32 +#define MAX_MATCHVARIABLES 8 + +#define CHAT_GENDERLESS 0 +#define CHAT_GENDERFEMALE 1 +#define CHAT_GENDERMALE 2 + +#define CHAT_ALL 0 +#define CHAT_TEAM 1 +#define CHAT_TELL 2 + +//a console message +typedef struct bot_consolemessage_s +{ + int handle; + float time; //message time + int type; //message type + char message[MAX_MESSAGE_SIZE]; //message + struct bot_consolemessage_s *prev, *next; //prev and next in list +} bot_consolemessage_t; + +//match variable +typedef struct bot_matchvariable_s +{ + char offset; + int length; +} bot_matchvariable_t; +//returned to AI when a match is found +typedef struct bot_match_s +{ + char string[MAX_MESSAGE_SIZE]; + int type; + int subtype; + bot_matchvariable_t variables[MAX_MATCHVARIABLES]; +} bot_match_t; + +//setup the chat AI +int BotSetupChatAI(void); +//shutdown the chat AI +void BotShutdownChatAI(void); +//returns the handle to a newly allocated chat state +int BotAllocChatState(void); +//frees the chatstate +void BotFreeChatState(int handle); +//adds a console message to the chat state +void BotQueueConsoleMessage(int chatstate, int type, char *message); +//removes the console message from the chat state +void BotRemoveConsoleMessage(int chatstate, int handle); +//returns the next console message from the state +int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm); +//returns the number of console messages currently stored in the state +int BotNumConsoleMessages(int chatstate); +//selects a chat message of the given type +void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the number of initial chat messages of the given type +int BotNumInitialChats(int chatstate, char *type); +//find and select a reply for the given message +int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the length of the currently selected chat message +int BotChatLength(int chatstate); +//enters the selected chat message +void BotEnterChat(int chatstate, int clientto, int sendto); +//get the chat message ready to be output +void BotGetChatMessage(int chatstate, char *buf, int size); +//checks if the first string contains the second one, returns index into first string or -1 if not found +int StringContains(char *str1, char *str2, int casesensitive); +//finds a match for the given string using the match templates +int BotFindMatch(char *str, bot_match_t *match, unsigned long int context); +//returns a variable from a match +void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size); +//unify all the white spaces in the string +void UnifyWhiteSpaces(char *string); +//replace all the context related synonyms in the string +void BotReplaceSynonyms(char *string, unsigned long int context); +//loads a chat file for the chat state +int BotLoadChatFile(int chatstate, char *chatfile, char *chatname); +//store the gender of the bot in the chat state +void BotSetChatGender(int chatstate, int gender); +//store the bot name in the chat state +void BotSetChatName(int chatstate, char *name, int client); + diff --git a/dlls/game/be_ai_gen.h b/dlls/game/be_ai_gen.h new file mode 100644 index 0000000..8f0a8e2 --- /dev/null +++ b/dlls/game/be_ai_gen.h @@ -0,0 +1,17 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_gen.h + * + * desc: genetic selection + * + * $Archive: /Code/DLLs/game/be_ai_gen.h $ + * $Author: Jwaters $ + * $Revision: 1 $ + * $Modtime: 5/17/02 11:35a $ + * $Date: 7/31/02 10:45a $ + * + *****************************************************************************/ + +int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child); diff --git a/dlls/game/be_ai_goal.h b/dlls/game/be_ai_goal.h new file mode 100644 index 0000000..cd64e0d --- /dev/null +++ b/dlls/game/be_ai_goal.h @@ -0,0 +1,102 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: be_ai_goal.h + * + * desc: goal AI + * + * $Archive: /Code/DLLs/game/be_ai_goal.h $ + * $Author: Jwaters $ + * $Revision: 1 $ + * $Modtime: 5/17/02 11:35a $ + * $Date: 7/31/02 10:45a $ + * + *****************************************************************************/ + +#define MAX_AVOIDGOALS 256 +#define MAX_GOALSTACK 8 + +#define GFL_NONE 0 +#define GFL_ITEM 1 +#define GFL_ROAM 2 +#define GFL_DROPPED 4 + +//a bot goal +typedef struct bot_goal_s +{ + vec3_t origin; //origin of the goal + int areanum; //area number of the goal + vec3_t mins, maxs; //mins and maxs of the goal + int entitynum; //number of the goal entity + int number; //goal number + int flags; //goal flags + int iteminfo; //item information +} bot_goal_t; + +//reset the whole goal state, but keep the item weights +void BotResetGoalState(int goalstate); +//reset avoid goals +void BotResetAvoidGoals(int goalstate); +//remove the goal with the given number from the avoid goals +void BotRemoveFromAvoidGoals(int goalstate, int number); +//push a goal onto the goal stack +void BotPushGoal(int goalstate, bot_goal_t *goal); +//pop a goal from the goal stack +void BotPopGoal(int goalstate); +//empty the bot's goal stack +void BotEmptyGoalStack(int goalstate); +//dump the avoid goals +void BotDumpAvoidGoals(int goalstate); +//dump the goal stack +void BotDumpGoalStack(int goalstate); +//get the name name of the goal with the given number +void BotGoalName(int number, char *name, int size); +//get the top goal from the stack +int BotGetTopGoal(int goalstate, bot_goal_t *goal); +//get the second goal on the stack +int BotGetSecondGoal(int goalstate, bot_goal_t *goal); +//choose the best long term goal item for the bot +int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags); +//choose the best nearby goal item for the bot +//the item may not be further away from the current bot position than maxtime +//also the travel time from the nearby goal towards the long term goal may not +//be larger than the travel time towards the long term goal from the current bot position +int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime); +//returns true if the bot touches the goal +int BotTouchingGoal(vec3_t origin, bot_goal_t *goal); +//returns true if the goal should be visible but isn't +int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal); +//search for a goal for the given classname, the index can be used +//as a start point for the search when multiple goals are available with that same classname +int BotGetLevelItemGoal(int index, char *classname, bot_goal_t *goal); +//get the next camp spot in the map +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal); +//get the map location with the given name +int BotGetMapLocationGoal(char *name, bot_goal_t *goal); +//returns the avoid goal time +float BotAvoidGoalTime(int goalstate, int number); +//set the avoid goal time +void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime); +//initializes the items in the level +void BotInitLevelItems(void); +//regularly update dynamic entity items (dropped weapons, flags etc.) +void BotUpdateEntityItems(void); +//interbreed the goal fuzzy logic +void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child); +//save the goal fuzzy logic to disk +void BotSaveGoalFuzzyLogic(int goalstate, char *filename); +//mutate the goal fuzzy logic +void BotMutateGoalFuzzyLogic(int goalstate, float range); +//loads item weights for the bot +int BotLoadItemWeights(int goalstate, char *filename); +//frees the item weights of the bot +void BotFreeItemWeights(int goalstate); +//returns the handle of a newly allocated goal state +int BotAllocGoalState(int client); +//free the given goal state +void BotFreeGoalState(int handle); +//setup the goal AI +int BotSetupGoalAI(void); +//shut down the goal AI +void BotShutdownGoalAI(void); diff --git a/dlls/game/be_ai_move.h b/dlls/game/be_ai_move.h new file mode 100644 index 0000000..253ea4c --- /dev/null +++ b/dlls/game/be_ai_move.h @@ -0,0 +1,126 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_move.h + * + * desc: movement AI + * + * $Archive: /Code/DLLs/game/be_ai_move.h $ + * $Author: Jwaters $ + * $Revision: 1 $ + * $Modtime: 5/17/02 11:35a $ + * $Date: 7/31/02 10:45a $ + * + *****************************************************************************/ + +//movement types +#define MOVE_WALK 1 +#define MOVE_CROUCH 2 +#define MOVE_JUMP 4 +#define MOVE_GRAPPLE 8 +#define MOVE_ROCKETJUMP 16 +#define MOVE_BFGJUMP 32 +//move flags +#define MFL_BARRIERJUMP 1 //bot is performing a barrier jump +#define MFL_ONGROUND 2 //bot is in the ground +#define MFL_SWIMMING 4 //bot is swimming +#define MFL_AGAINSTLADDER 8 //bot is against a ladder +#define MFL_WATERJUMP 16 //bot is waterjumping +#define MFL_TELEPORTED 32 //bot is being teleported +#define MFL_GRAPPLEPULL 64 //bot is being pulled by the grapple +#define MFL_ACTIVEGRAPPLE 128 //bot is using the grapple hook +#define MFL_GRAPPLERESET 256 //bot has reset the grapple +#define MFL_WALK 512 //bot should walk slowly +// move result flags +#define MOVERESULT_MOVEMENTVIEW 1 //bot uses view for movement +#define MOVERESULT_SWIMVIEW 2 //bot uses view for swimming +#define MOVERESULT_WAITING 4 //bot is waiting for something +#define MOVERESULT_MOVEMENTVIEWSET 8 //bot has set the view in movement code +#define MOVERESULT_MOVEMENTWEAPON 16 //bot uses weapon for movement +#define MOVERESULT_ONTOPOFOBSTACLE 32 //bot is ontop of obstacle +#define MOVERESULT_ONTOPOF_FUNCBOB 64 //bot is ontop of a func_bobbing +#define MOVERESULT_ONTOPOF_ELEVATOR 128 //bot is ontop of an elevator (func_plat) +#define MOVERESULT_BLOCKEDBYAVOIDSPOT 256 //bot is blocked by an avoid spot +// +#define MAX_AVOIDREACH 1 +#define MAX_AVOIDSPOTS 32 +// avoid spot types +#define AVOID_CLEAR 0 //clear all avoid spots +#define AVOID_ALWAYS 1 //avoid always +#define AVOID_DONTBLOCK 2 //never totally block +// restult types +#define RESULTTYPE_ELEVATORUP 1 //elevator is up +#define RESULTTYPE_WAITFORFUNCBOBBING 2 //waiting for func bobbing to arrive +#define RESULTTYPE_BADGRAPPLEPATH 4 //grapple path is obstructed +#define RESULTTYPE_INSOLIDAREA 8 //stuck in solid area, this is bad + +//structure used to initialize the movement state +//the or_moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP come from the playerstate +typedef struct bot_initmove_s +{ + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t viewoffset; //view offset + int entitynum; //entity number of the bot + int client; //client number of the bot + float thinktime; //time the bot thinks + int presencetype; //presencetype of the bot + vec3_t viewangles; //view angles of the bot + int or_moveflags; //values ored to the movement flags +} bot_initmove_t; + +//NOTE: the ideal_viewangles are only valid if MFL_MOVEMENTVIEW is set +typedef struct bot_moveresult_s +{ + int failure; //true if movement failed all together + int type; //failure or blocked type + int blocked; //true if blocked by an entity + int blockentity; //entity blocking the bot + int traveltype; //last executed travel type + int flags; //result flags + int weapon; //weapon used for movement + vec3_t movedir; //movement direction + vec3_t ideal_viewangles; //ideal viewangles for the movement +} bot_moveresult_t; + +// bk001204: from code/botlib/be_ai_move.c +// TTimo 04/12/2001 was moved here to avoid dup defines +typedef struct bot_avoidspot_s +{ + vec3_t origin; + float radius; + int type; +} bot_avoidspot_t; + +//resets the whole move state +void BotResetMoveState(int movestate); +//moves the bot to the given goal +void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags); +//moves the bot in the specified direction using the specified type of movement +int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type); +//reset avoid reachability +void BotResetAvoidReach(int movestate); +//resets the last avoid reachability +void BotResetLastAvoidReach(int movestate); +//returns a reachability area if the origin is in one +int BotReachabilityArea(vec3_t origin, int client); +//view target based on movement +int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target); +//predict the position of a player based on movement towards a goal +int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target); +//returns the handle of a newly allocated movestate +int BotAllocMoveState(void); +//frees the movestate with the given handle +void BotFreeMoveState(int handle); +//initialize movement state before performing any movement +void BotInitMoveState(int handle, bot_initmove_t *initmove); +//add a spot to avoid (if type == AVOID_CLEAR all spots are removed) +void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type); +//must be called every map change +void BotSetBrushModelTypes(void); +//setup movement AI +int BotSetupMoveAI(void); +//shutdown movement AI +void BotShutdownMoveAI(void); + diff --git a/dlls/game/be_ai_weap.h b/dlls/game/be_ai_weap.h new file mode 100644 index 0000000..e6a3df7 --- /dev/null +++ b/dlls/game/be_ai_weap.h @@ -0,0 +1,90 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_weap.h + * + * desc: weapon AI + * + * $Archive: /Code/DLLs/game/be_ai_weap.h $ + * $Author: Jwaters $ + * $Revision: 4 $ + * $Modtime: 8/19/02 3:51p $ + * $Date: 8/19/02 4:08p $ + * + *****************************************************************************/ + +//projectile flags +#define PFL_WINDOWDAMAGE 1 //projectile damages through window +#define PFL_RETURN 2 //set when projectile returns to owner +//weapon flags +#define WFL_FIRERELEASED 1 //set when projectile is fired with key-up event +//damage types +#define DAMAGETYPE_IMPACT 1 //damage on impact +#define DAMAGETYPE_RADIAL 2 //radial damage +#define DAMAGETYPE_VISIBLE 4 //damage to all entities visible to the projectile + +typedef struct projectileinfo_s +{ + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int flags; + float gravity; + int damage; + float radius; + int visdamage; + int damagetype; + int healthinc; + float push; + float detonation; + float bounce; + float bouncefric; + float bouncestop; +} projectileinfo_t; + +typedef struct weaponinfo_s +{ + int valid; //true if the weapon info is valid + int number; //number of the weapon + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int level; + int weaponindex; + int flags; + char projectile[MAX_STRINGFIELD]; + int numprojectiles; + float hspread; + float vspread; + float speed; + float acceleration; + vec3_t recoil; + vec3_t offset; + vec3_t angleoffset; + float extrazvelocity; + int ammoamount; + int ammoindex; + float activate; + float reload; + float spinup; + float spindown; + int primarydangerous; // if primary and/or alternate fire are REALLY DANGEROUS + int altdangerous; + projectileinfo_t proj; //pointer to the used projectile +} weaponinfo_t; + +//setup the weapon AI +int BotSetupWeaponAI(void); +//shut down the weapon AI +void BotShutdownWeaponAI(void); +//returns the best weapon to fight with +int BotChooseBestFightWeapon(int weaponstate, int *inventory); +//returns the information of the current weapon +void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo); +//loads the weapon weights +int BotLoadWeaponWeights(int weaponstate, char *filename); +//returns a handle to a newly allocated weapon state +int BotAllocWeaponState(void); +//frees the weapon state +void BotFreeWeaponState(int weaponstate); +//resets the whole weapon state +void BotResetWeaponState(int weaponstate); diff --git a/dlls/game/be_ea.h b/dlls/game/be_ea.h new file mode 100644 index 0000000..71e1d83 --- /dev/null +++ b/dlls/game/be_ea.h @@ -0,0 +1,51 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ea.h + * + * desc: elementary actions + * + * $Archive: /Code/DLLs/game/be_ea.h $ + * $Author: Jwaters $ + * $Revision: 2 $ + * $Modtime: 8/08/02 12:17p $ + * $Date: 8/08/02 1:38p $ + * + *****************************************************************************/ + +//ClientCommand elementary actions +void EA_Say(int client, char *str); +void EA_SayTeam(int client, char *str); +void EA_Command(int client, const char *command ); + +void EA_Action(int client, int action); +void EA_Crouch(int client); +void EA_Walk(int client); +void EA_MoveUp(int client); +void EA_MoveDown(int client); +void EA_MoveForward(int client); +void EA_MoveBack(int client); +void EA_MoveLeft(int client); +void EA_MoveRight(int client); +void EA_ToggleFireState(int client); +void EA_Attack(int client, int primarydangerous, int altdangerous); +void EA_Respawn(int client); +void EA_Talk(int client); +void EA_Gesture(int client); +void EA_Use(int client); + +//regular elementary actions +void EA_SelectWeapon(int client, int weapon); +void EA_Jump(int client); +void EA_DelayedJump(int client); +void EA_Move(int client, vec3_t dir, float speed); +void EA_View(int client, vec3_t viewangles); + +//send regular input to the server +void EA_EndRegular(int client, float thinktime); +void EA_GetInput(int client, float thinktime, bot_input_t *input); +void EA_ResetInput(int client); +//setup and shutdown routines +int EA_Setup(void); +void EA_Shutdown(void); diff --git a/dlls/game/beam.cpp b/dlls/game/beam.cpp new file mode 100644 index 0000000..dc8b1cd --- /dev/null +++ b/dlls/game/beam.cpp @@ -0,0 +1,683 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/beam.cpp $ +// $Revision:: 13 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:35p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "beam.h" +#include + +/*****************************************************************************/ +/*QUAKED func_beam (0 0.25 .5) (-8 -8 -8) (8 8 8) START_ON PERSIST WAVE NOISE + +This creates a beam effect from the origin to the target's origin. If no +target is specified, uses angles and projects beam out from there. + +"model" Specifies the model to use as the beam +"overlap" Specifies the amount of overlap each beam link should have. Use this to fill +in the cracks when using electric on beams. (Default is 0) +"minoffset" Minimum amount of electrical variation (Default is 0) +"maxoffset" Maximum amount of electrical variation (Default is 5) +"color" Vector specifiying the red,green, and blue components. (Default is "1 1 1") +"alpha" Alpha of the beam (Default is 1.0) +"damage" Amount of damage the beam inflicts if beam hits someone (Default is 0) +"angles" Sets the angle of the beam if no target is specified. +"life" Sets the life of the beam for use with the persist spawnflag. This is how long a beam will +be displayed. +"numsegments" Number of segments in a beam (Default is 4) +"delay" Delay between beam updates. (i.e. slows the effect of the beam down) +"shader" Set the shader of the beam +"scale" Set the width of the beam + +START_ON - Starts the beam on +PERSIST - Keeps the last few beams around and fades them out over the life of the beam +WAVE - Make the beam follow a sin wave pattern +NOISE - Use a more computationally expensive random effect, but the results are smoother + +If the model field is not set, then a renderer generated beam will be created +using the color, minoffset, maxoffset, scale, and subdivisions fields + +If the targetname is set, it will use the target specified as the endpoint of the beam + +*****************************************************************************/ + +Event EV_FuncBeam_Activate +( + "activate", + EV_SCRIPTONLY, + NULL, + NULL, + "Activate the beam" +); +Event EV_FuncBeam_Deactivate +( + "deactivate", + EV_SCRIPTONLY, + NULL, + NULL, + "Deactivate the beam" +); +Event EV_FuncBeam_Diameter +( + "diameter", + EV_SCRIPTONLY, + "f", + "diameter", + "Set the diameter of the beam" +); +Event EV_FuncBeam_Maxoffset +( + "maxoffset", + EV_SCRIPTONLY, + "f", + "max_offset", + "Set the maximum offset the beam can travel above, below, forward or back of it's endpoints" +); +Event EV_FuncBeam_Minoffset +( + "minoffset", + EV_SCRIPTONLY, + "f", + "min_offset", + "Set the minimun offset the beam can travel above, below, forward or back of it's endpoints" +); +Event EV_FuncBeam_Overlap +( + "overlap", + EV_SCRIPTONLY, + "f", + "beam_overlap", + "Set the amount of overlap the beams have when they are being strung together" +); +Event EV_FuncBeam_Color +( + "color", + EV_SCRIPTONLY, + "v[0,1][0,1][0,1]", + "color_beam", + "Set the color of the beam" +); +Event EV_FuncBeam_SetTarget +( + "target", + EV_SCRIPTONLY, + "s", + "beam_target", + "Set the target of the beam. The beam will be drawn from the origin\n" + "to the origin of the target entity" +); +Event EV_FuncBeam_SetEndPoint +( + "endpoint", + EV_SCRIPTONLY, + "v", + "beam_end_point", + "Set the end point of the beam. The beam will be draw from the origin to\n" + "the end point." +); +Event EV_FuncBeam_SetLife +( + "life", + EV_DEFAULT, + "f", + "beam_life", + "Set the amount of time the beam stays on when activated" +); +Event EV_FuncBeam_Shader +( + "shader", + EV_SCRIPTONLY, + "s", + "beam_shader", + "Set the shader that the beam will use" +); +Event EV_FuncBeam_TileShader +( + "tileshader", + EV_SCRIPTONLY, + "s", + "beam_shader", + "Set the shader that the beam will use. This shader will be tiled." +); +Event EV_FuncBeam_Segments +( + "numsegments", + EV_SCRIPTONLY, + "i", + "numsegments", + "Set the number of segments for the beam" +); +Event EV_FuncBeam_Delay +( + "delay", + EV_SCRIPTONLY, + "f", + "delay", + "Set the amount of delay on the beam updater" +); +Event EV_FuncBeam_NumSphereBeams +( + "numspherebeams", + EV_SCRIPTONLY, + "i", + "num", + "Set the number of beams that will be shot out in a sphere like formation" +); +Event EV_FuncBeam_SphereRadius +( + "radius", + EV_SCRIPTONLY, + "f", + "radius", + "Set the starting radius of the beams if this is a beamsphere" +); +Event EV_FuncBeam_ToggleDelay +( + "toggledelay", + EV_SCRIPTONLY, + "fB", + "time isRandom", + "Causes a beam toggling effect. Sets the time between toggling.\n" + "If the isRandom flag is set, the time will be random from 0 to the time specified." +); +Event EV_FuncBeam_FindEndpoint +( + "findendpoint", + EV_CODEONLY, + NULL, + NULL, + "Find the endpoint of a beam" +); +Event EV_FuncBeam_UpdateEndpoint +( + "updateendpoint", + EV_CODEONLY, + NULL, + NULL, + "Update the endpoint of a beam" +); +Event EV_FuncBeam_UpdateOrigin +( + "updateorigin", + EV_CODEONLY, + NULL, + NULL, + "Update the origin of a beam" +); +Event EV_FuncBeam_EndAlpha +( + "endalpha", + EV_SCRIPTONLY, + "f", + "alpha", + "Set the endpoint alpha value of the beam" +); +Event EV_FuncBeam_Shoot +( + "shoot", + EV_DEFAULT, + NULL, + NULL, + "Make the beam cause damage to entities that get in the way" +); +Event EV_FuncBeam_ShootRadius +( + "shootradius", + EV_SCRIPTONLY, + "f", + "radius", + "Set the radius of the damage area between beam endpoints" +); +Event EV_FuncBeam_Persist +( + "persist", + EV_SCRIPTONLY, + "b", + "bool", + "Set the persist property of the beam" +); + +CLASS_DECLARATION( ScriptSlave, FuncBeam, "func_beam" ) +{ + { &EV_Activate, &FuncBeam::Activate }, + { &EV_FuncBeam_Activate, &FuncBeam::Activate }, + { &EV_FuncBeam_Deactivate, &FuncBeam::Deactivate }, + { &EV_FuncBeam_Maxoffset, &FuncBeam::SetMaxoffset }, + { &EV_FuncBeam_Minoffset, &FuncBeam::SetMinoffset }, + { &EV_FuncBeam_Overlap, &FuncBeam::SetOverlap }, + { &EV_FuncBeam_Color, &FuncBeam::SetColor }, + { &EV_FuncBeam_SetTarget, &FuncBeam::SetTarget }, + { &EV_SetAngle, &FuncBeam::SetAngle }, + { &EV_FuncBeam_Segments, &FuncBeam::SetSegments }, + { &EV_SetAngles, &FuncBeam::SetAngles }, + { &EV_FuncBeam_SetEndPoint, &FuncBeam::SetEndPoint }, + { &EV_Model, &FuncBeam::SetModel }, + { &EV_Damage, &FuncBeam::SetDamage }, + { &EV_FuncBeam_SetLife, &FuncBeam::SetLife }, + { &EV_FuncBeam_Shader, &FuncBeam::SetBeamShader }, + { &EV_FuncBeam_TileShader, &FuncBeam::SetBeamTileShader }, + { &EV_FuncBeam_Delay, &FuncBeam::SetDelay }, + { &EV_FuncBeam_NumSphereBeams, &FuncBeam::SetNumSphereBeams }, + { &EV_FuncBeam_SphereRadius, &FuncBeam::SetSphereRadius }, + { &EV_FuncBeam_ToggleDelay, &FuncBeam::SetToggleDelay }, + { &EV_FuncBeam_ShootRadius, &FuncBeam::SetShootRadius }, + { &EV_FuncBeam_EndAlpha, &FuncBeam::SetEndAlpha }, + { &EV_FuncBeam_Persist, &FuncBeam::SetPersist }, + { &EV_FuncBeam_FindEndpoint, &FuncBeam::FindEndpoint }, + { &EV_FuncBeam_UpdateEndpoint, &FuncBeam::UpdateEndpoint }, + { &EV_FuncBeam_UpdateOrigin, &FuncBeam::UpdateOrigin }, + { &EV_FuncBeam_Shoot, &FuncBeam::Shoot }, + + { NULL, NULL } +}; + +FuncBeam::FuncBeam() +{ + setSolidType( SOLID_NOT ); + setOrigin(); + + damage = 0; + life = 0; + shootradius = 0; + use_angles = false; + + edict->s.renderfx |= RF_BEAM; + edict->s.eType = ET_BEAM; // Entity type beam + edict->s.modelindex = 1; // must be non-zero + SetBeamShader( "beamshader" ); + + if ( !LoadingSavegame ) + { + edict->s.alpha = 1; // alpha + edict->s.surfaces[4] = 4; // num segments + + BEAM_PARM_TO_PKT( 1, edict->s.surfaces[0] ); // life + edict->s.bone_angles[0][1] = 5; // Max offset + edict->s.torso_anim = ENTITYNUM_NONE; + BEAM_PARM_TO_PKT( 1, edict->s.surfaces[9] ); // endalpha + + if ( spawnflags & 0x0001 ) // Start On + PostEvent( EV_Activate, EV_POSTSPAWN ); + else + hideModel(); + edict->s.skinNum = 0; + if ( spawnflags & 0x0002 ) + edict->s.skinNum |= BEAM_PERSIST_EFFECT; + if ( spawnflags & 0x0004 ) + edict->s.skinNum |= BEAM_WAVE_EFFECT; + if ( spawnflags & 0x0008 ) + edict->s.skinNum |= BEAM_USE_NOISE; + + // Try to find the endpoint of this beam after everything has been spawned + PostEvent( EV_FuncBeam_FindEndpoint, EV_LINKBEAMS ); + } +} + +void FuncBeam::SetEndAlpha( Event *ev ) +{ + BEAM_PARM_TO_PKT( ev->GetFloat( 1 ), edict->s.surfaces[9] ); +} + +void FuncBeam::SetToggleDelay( Event *ev ) +{ + edict->s.skinNum |= BEAM_TOGGLE; + + if ( ev->NumArgs() > 2 ) + { + if ( ev->GetBoolean( 2 ) ) + { + edict->s.skinNum |= BEAM_RANDOM_TOGGLEDELAY; + } + BEAM_PARM_TO_PKT( ev->GetFloat( 1 ), edict->s.surfaces[8] ); + } + else + { + BEAM_PARM_TO_PKT( ev->GetFloat( 1 ), edict->s.surfaces[8] ); + } +} + +void FuncBeam::SetSphereRadius( Event *ev ) +{ + edict->s.skinNum |= BEAM_SPHERE_EFFECT; + BEAM_PARM_TO_PKT( ev->GetFloat( 1 ), edict->s.surfaces[7] ); +} + +void FuncBeam::SetNumSphereBeams( Event *ev ) +{ + edict->s.skinNum |= BEAM_SPHERE_EFFECT; + edict->s.surfaces[6] = ev->GetInteger( 1 ); +} + +void FuncBeam::SetAngle( Event *ev ) +{ + Vector movedir; + + movedir = G_GetMovedir( ev->GetFloat( 1 ) ); + setAngles( movedir.toAngles() ); +} + +void FuncBeam::SetAngles( Event *ev ) +{ + setAngles( ev->GetVector( 1 ) ); +} + +// Override setAngles to update the endpoint of the beam if it's rotated +void FuncBeam::setAngles( const Vector &angles ) +{ + trace_t trace; + Vector endpoint; + + ScriptSlave::setAngles( angles ); + + // If there is no target, then use the angles to determine where to put the + // endpoint + if ( !end ) + { + endpoint = origin + ( Vector( orientation[0] ) * WORLD_SIZE ); + + trace = G_Trace( origin, vec_zero, vec_zero, endpoint, this, MASK_SOLID, false, "FuncBeam" ); + + VectorCopy( trace.endpos, edict->s.origin2 ); + + use_angles = true; + } +} + +void FuncBeam::SetEndPoint( Event *ev ) +{ + trace_t trace; + + end_point = ev->GetVector( 1 ); + + trace = G_Trace( origin, vec_zero, vec_zero, end_point, this, MASK_SOLID, false, "FuncBeam" ); + VectorCopy( trace.endpos, edict->s.origin2 ); + + use_angles = false; +} + +void FuncBeam::SetEndPoint( const Vector &endPos ) +{ + end_point = endPos; + VectorCopy( end_point, edict->s.origin2 ); + use_angles = false; +} + +void FuncBeam::SetModel( Event *ev ) +{ + setModel( ev->GetString( 1 ) ); + edict->s.renderfx &= ~RF_BEAM; + edict->s.eType = ET_BEAM; + edict->s.skinNum |= BEAM_USEMODEL; +} + +void FuncBeam::SetDamage( Event *ev ) +{ + damage = ev->GetFloat( 1 ); +} + +void FuncBeam::SetLife( Event *ev ) +{ + BEAM_PARM_TO_PKT( ev->GetFloat( 1 ), edict->s.surfaces[0] ); +} + +void FuncBeam::SetColor( Event *ev ) +{ + Vector color = ev->GetVector( 1 ); + G_SetConstantLight( &edict->s.constantLight, &color[ 0 ], &color[ 1 ], &color[ 2 ], NULL ); +} + +void FuncBeam::SetSegments( Event *ev ) +{ + edict->s.surfaces[4] = ev->GetInteger( 1 ); +} + +void FuncBeam::SetBeamShader( const str &beam_shader ) +{ + str temp_shader; + + shader = beam_shader; + edict->s.tag_num = gi.imageindex( shader ); + + temp_shader = shader + ".spr"; + CacheResource( temp_shader, this ); +} + +void FuncBeam::SetBeamShader( Event *ev ) +{ + SetBeamShader( ev->GetString( 1 ) ); +} + +void FuncBeam::SetBeamTileShader( Event *ev ) +{ + SetBeamShader( ev->GetString( 1 ) ); + edict->s.skinNum |= BEAM_TILESHADER; +} + +void FuncBeam::SetMaxoffset( Event *ev ) +{ + edict->s.bone_angles[0][1] = ev->GetFloat( 1 ); +} + +void FuncBeam::SetMinoffset( Event *ev ) +{ + edict->s.bone_angles[0][0] = ev->GetFloat( 1 ); +} + +void FuncBeam::SetOverlap( Event *ev ) +{ + BEAM_PARM_TO_PKT( ev->GetFloat( 1 ), edict->s.surfaces[3] ); +} + +void FuncBeam::SetDelay( Event *ev ) +{ + if ( ev->NumArgs() > 2 ) + { + str arg = ev->GetString( 1 ); + if ( !arg.icmp( "random" ) ) + { + edict->s.skinNum |= BEAM_RANDOM_DELAY; + } + BEAM_PARM_TO_PKT( ev->GetFloat( 2 ), edict->s.surfaces[5] ); + } + else + { + BEAM_PARM_TO_PKT( ev->GetFloat( 1 ), edict->s.surfaces[5] ); + } +} + +void FuncBeam::Deactivate( Event *ev ) +{ + hideModel(); + + // Cancel all of the events to activate + CancelEventsOfType( EV_FuncBeam_Activate ); + CancelEventsOfType( EV_Activate ); + CancelEventsOfType( EV_FuncBeam_UpdateEndpoint ); + CancelEventsOfType( EV_FuncBeam_UpdateOrigin ); +} + +void FuncBeam::SetShootRadius( Event *ev ) +{ + shootradius = ev->GetFloat( 1 ); +} + +void FuncBeam::SetPersist( Event *ev ) +{ + qboolean persist = ev->GetBoolean( 1 ); + + if ( persist ) + edict->s.skinNum |= BEAM_PERSIST_EFFECT; + else + edict->s.skinNum &= ~BEAM_PERSIST_EFFECT; +} + +void FuncBeam::Shoot( Event *ev ) +{ + trace_t trace; + Vector start, end; + + start = edict->s.origin; + end = edict->s.origin2; + + Vector dir( end - start ); + Vector b1( -shootradius, -shootradius, -shootradius ); + Vector b2( shootradius, shootradius, shootradius ); + + trace = G_Trace( start, b1, b2, end, this, MASK_SHOT, false, "FuncBeam::Activate" ); + + if ( trace.ent && trace.ent->entity && trace.ent->entity->takedamage ) + { + // damage the ent + trace.ent->entity->Damage( this, this, damage, trace.endpos, dir, trace.plane.normal, 0, 0, MOD_BEAM ); + } + + PostEvent( EV_FuncBeam_Shoot, 0.1f ); +} + +void FuncBeam::Activate( Event *ev ) +{ + Vector forward; + trace_t trace; + + showModel(); + + // An entity is targeted + if ( end ) + { + VectorCopy( end->origin, edict->s.origin2 ); + // Post an event so that the beam will update itself every frame + PostEvent( EV_FuncBeam_UpdateEndpoint, FRAMETIME ); + } + else if ( use_angles ) + { + angles.AngleVectors( &forward, NULL, NULL ); + + Vector endpoint( orientation[ 0 ] ); + endpoint *= (float)WORLD_SIZE; + + trace = G_Trace( origin, vec_zero, vec_zero, endpoint, this, MASK_SOLID, false, "FuncBeam::Activate" ); + VectorCopy( trace.endpos, edict->s.origin2 ); + } + else + { + trace = G_Trace( origin, vec_zero, vec_zero, end_point, this, MASK_SOLID, false, "FuncBeam::Activate" ); + VectorCopy( trace.endpos, edict->s.origin2 ); + } + + if ( origin_target ) + { + PostEvent( EV_FuncBeam_UpdateOrigin, FRAMETIME ); + } + + if ( damage ) + { + // Shoot beam and cause damage every frame + ProcessEvent( EV_FuncBeam_Shoot ); + } + + // If life is set, then post a deactivate message + if ( ( life > 0.0f ) && !EventPending( EV_FuncBeam_Deactivate ) ) + { + PostEvent( EV_FuncBeam_Deactivate, life ); + return; + } +} + +void FuncBeam::UpdateEndpoint( Event *ev ) +{ + if ( end ) + { + Event *ev1 = new Event( ev ); + + VectorCopy( end->origin, edict->s.origin2 ); + PostEvent( ev1, FRAMETIME ); + } +} + +void FuncBeam::UpdateOrigin( Event *ev ) +{ + if ( origin_target ) + { + Event *ev1 = new Event( ev ); + + setOrigin( origin_target->centroid ); + PostEvent( ev1, FRAMETIME ); + } +} + +void FuncBeam::FindEndpoint( Event *ev ) +{ + if ( target && strlen( target ) ) + { + end = G_FindTarget( NULL, target ); + if ( end ) + { + VectorCopy( end->origin, edict->s.origin2 ); + } + } +} + +FuncBeam *CreateBeam( const char *model, const char *shader, const Vector &start, const Vector &end, + int numsegments, float scale, float life, float damage, Entity *origin_target ) +{ + FuncBeam *beam; + trace_t trace; + + // set start point + beam = new FuncBeam; + beam->setOrigin( start ); + + if ( origin_target ) + beam->origin_target = origin_target; + + // set endpoint + beam->end_point = end; + trace = G_Trace( start, vec_zero, vec_zero, end, beam, MASK_SOLID, false, "CreateBeam" ); + VectorCopy( trace.endpos, beam->edict->s.origin2 ); + beam->use_angles = false; + + if ( model ) + { + // set the model if we have one + beam->setModel( model ); + beam->edict->s.renderfx &= ~RF_BEAM; + beam->edict->s.eType = ET_BEAM; + beam->edict->s.skinNum |= BEAM_USEMODEL; + } + + if ( shader ) + { + // Set the shader as an image configstring + beam->SetBeamShader( shader ); + } + + // set num segments + beam->edict->s.surfaces[4] = numsegments; + + // set scale + beam->setScale( scale ); + + // set life + BEAM_PARM_TO_PKT( life, beam->edict->s.surfaces[0] ); + beam->life = life; + + // set damage + beam->damage = damage; + + // activate it + beam->ProcessEvent( EV_Activate ); + beam->PostEvent( EV_Remove, life ); + return beam; +} diff --git a/dlls/game/beam.h b/dlls/game/beam.h new file mode 100644 index 0000000..99d7bb2 --- /dev/null +++ b/dlls/game/beam.h @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/beam.h $ +// $Revision:: 6 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// +#ifndef __BEAM_H__ +#define __BEAM_H__ + +#include "g_local.h" +#include "scriptslave.h" + +extern Event EV_FuncBeam_Activate; +extern Event EV_FuncBeam_Deactivate; +extern Event EV_FuncBeam_Diameter; +extern Event EV_FuncBeam_Maxoffset; +extern Event EV_FuncBeam_Minoffset; +extern Event EV_FuncBeam_Overlap; +extern Event EV_FuncBeam_Color; +extern Event EV_FuncBeam_SetTarget; +extern Event EV_FuncBeam_SetAngle; +extern Event EV_FuncBeam_SetEndPoint; +extern Event EV_FuncBeam_SetLife; +extern Event EV_FuncBeam_Shader; +extern Event EV_FuncBeam_Segments; +extern Event EV_FuncBeam_Delay; +extern Event EV_FuncBeam_NumSphereBeams; +extern Event EV_FuncBeam_SphereRadius; +extern Event EV_FuncBeam_ToggleDelay; +extern Event EV_FuncBeam_FindEndpoint; +extern Event EV_FuncBeam_EndAlpha; + +class FuncBeam : public ScriptSlave + { + protected: + EntityPtr end,origin_target; + float damage; + float life; + Vector end_point; + qboolean use_angles; + float shootradius; + str shader; + + public: + CLASS_PROTOTYPE( FuncBeam ); + + FuncBeam(); + + void SetAngle( Event *ev ); + void SetAngles( Event *ev ); + void SetEndPoint( Event *ev ); + void SetModel( Event *ev ); + void SetDamage( Event *ev ); + void SetOverlap( Event *ev ); + void SetBeamStyle( Event *ev ); + void SetLife( Event *ev ); + void Activate( Event *ev ); + void Deactivate( Event *ev ); + void SetDiameter( Event *ev ); + void SetMaxoffset( Event *ev ); + void SetMinoffset( Event *ev ); + void SetColor( Event *ev ); + void SetSegments( Event *ev ); + void SetBeamShader( const str &shader ); + void SetBeamShader( Event *ev ); + void SetBeamTileShader( Event *ev ); + void SetDelay( Event *ev ); + void SetToggleDelay( Event *ev ); + void SetSphereRadius( Event *ev ); + void SetNumSphereBeams( Event *ev ); + void SetEndAlpha( Event *ev ); + void SetShootRadius( Event *ev ); + void SetPersist( Event *ev ); + void FindEndpoint( Event *ev ); + void UpdateEndpoint( Event *ev ); + void UpdateOrigin( Event *ev ); + void Shoot( Event *ev ); + + void SetEndPoint( const Vector &endPos ); + virtual void setAngles( const Vector &ang ); + virtual void Archive( Archiver &arc ); + + friend FuncBeam *CreateBeam( const char *model, const char *shader, const Vector &start, const Vector &end, int numsegments = 4, float scale = 1.0f, float life = 1.0f, float damage = 0.0f, Entity *origin_target=NULL ); + }; + +inline void FuncBeam::Archive + ( + Archiver &arc + ) + { + ScriptSlave::Archive( arc ); + arc.ArchiveSafePointer( &end ); + arc.ArchiveSafePointer( &origin_target ); + arc.ArchiveFloat( &damage ); + arc.ArchiveFloat( &life ); + arc.ArchiveVector( &end_point ); + arc.ArchiveBoolean( &use_angles ); + arc.ArchiveFloat( &shootradius ); + arc.ArchiveString( &shader ); + if ( arc.Loading() ) + { + SetBeamShader( shader ); + } + } + +#endif // __BEAM_H__ diff --git a/dlls/game/behavior.cpp b/dlls/game/behavior.cpp new file mode 100644 index 0000000..11e9291 --- /dev/null +++ b/dlls/game/behavior.cpp @@ -0,0 +1,12928 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/behavior.cpp $ +// $Revision:: 135 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:11a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// Behaviors used by the AI. +// + +#include "_pch_cpp.h" +//#include "g_local.h" +#include "behavior.h" +#include "actor.h" +#include "doors.h" +#include "object.h" +#include "explosion.h" +#include "weaputils.h" +#include "player.h" + +Event EV_Behavior_Args + ( + "args", + EV_CODEONLY, + "SSSSSS", + "token1 token2 token3 token4 token5 token6", + "Gives the current behavior arguments." + ); +Event EV_Behavior_AnimDone + ( + "animdone", + EV_CODEONLY, + NULL, + NULL, + "Tells the behavior that the current animation is done, " + "it is not meant to be called from script." + ); +Event EV_PostureChanged_Completed + ( + "posturechangecompleted", + EV_CODEONLY, + NULL, + NULL, + "Tells the behavior that the requested posture is active" + ); + +Vector ChooseRandomDirection( Actor &self, const Vector &previousdir, float *time, int disallowcontentsmask, qboolean usepitch ); + +/**************************************************************************** + + Behavior Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Listener, Behavior, NULL ) + { + { NULL, NULL } + }; + +Behavior::Behavior() +: _controller( 0 ) // NULL + { + _self = NULL; + } + +void Behavior::ShowInfo + ( + Actor &self + ) + + { + if ( movegoal ) + { + gi.Printf( "movegoal: ( %f, %f, %f ) - '%s'\n", + movegoal->origin.x, movegoal->origin.y, movegoal->origin.z, movegoal->targetname.c_str() ); + } + else + { + gi.Printf( "movegoal: NULL\n" ); + } + } + +void Behavior::Begin + ( + Actor &self + ) + + { + } + +BehaviorReturnCode_t Behavior::Evaluate + ( + Actor &self + ) + + { + return BEHAVIOR_SUCCESS; + } + +void Behavior::End + ( + Actor &self + ) + + { + } + + + +/**************************************************************************** +***************************************************************************** + + General behaviors + +***************************************************************************** +****************************************************************************/ + + + +/**************************************************************************** + + Idle Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Idle, NULL ) + { + { NULL, NULL } + }; + +void Idle::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nnexttwitch : %f\n", nexttwitch ); + } + +void Idle::Begin + ( + Actor &self + ) + + { + nexttwitch = level.time + 10.0f + G_Random( 30.0f ); + self.SetAnim( "idle", EV_Actor_NotifyBehavior ); + } + +BehaviorReturnCode_t Idle::Evaluate + ( + Actor &self + ) + + { + if ( self.enemyManager->GetCurrentEnemy() ) + { + return BEHAVIOR_EVALUATING; + } + + if ( nexttwitch < level.time ) + { + self.chattime += 10.0f; + self.AddStateFlag( STATE_FLAG_TWITCH ); + return BEHAVIOR_EVALUATING; + } + else + { + //self.Chatter( "snd_idle", 1 ); + } + + return BEHAVIOR_EVALUATING; + } + +void Idle::End + ( + Actor &self + ) + + { + + } + +/**************************************************************************** + + Pain Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Pain, NULL ) + { + { &EV_Behavior_AnimDone, &Pain::AnimDone }, + { NULL, NULL } + }; + +void Pain::SetPainAnim + ( + Actor &self, + int new_pain_type, + int new_anim_number + ) + + { + str anim_name; + + // Determine which pain type to play + + if ( new_pain_type == PAIN_SMALL ) + anim_name = "pain_small"; + else + anim_name = "pain"; + + // Try to find the correct anim to play + + anim_name += new_anim_number; + + if ( !self.animate->HasAnim( anim_name.c_str() ) ) + { + if ( new_pain_type == PAIN_SMALL ) + anim_name = "pain_small1"; + else + anim_name = "pain1"; + + pain_anim_number = 1; + } + + anim_done = false; + + // Play the animation if we can + + if ( !self.animate->HasAnim( anim_name.c_str() ) ) + anim_done = true; + else + self.SetAnim( anim_name.c_str(), EV_Actor_NotifyBehavior ); + } + +int Pain::GetNumberOfPainAnims + ( + Actor &self, + int new_pain_type + ) + + { + str anim_name; + str real_anim_name; + int i; + + + // Determine base animation name + + if ( new_pain_type == PAIN_SMALL ) + anim_name = "pain_small"; + else + anim_name = "pain"; + + // Find how many pain animations we have + + for( i = 1 ; i < 10 ; i++ ) + { + real_anim_name = anim_name + i; + + if ( !self.animate->HasAnim( real_anim_name.c_str() ) ) + return( i - 1 ); + } + + return 9; + } + +void Pain::Begin + ( + Actor &self + ) + + { + str anim_name; + int num_pain_anims; + + num_pain_anims = GetNumberOfPainAnims( self, self.pain_type ); + + pain_anim_number = (int)G_Random( num_pain_anims ) + 1; + + // Figure out which type of pain to do + + if ( self.pain_type == PAIN_SMALL ) + SetPainAnim( self, PAIN_SMALL, pain_anim_number ); + else + SetPainAnim( self, PAIN_BIG, pain_anim_number ); + + current_pain_type = self.pain_type; + number_of_pains = 1; + + // Make sure we don't have pain any more + + self.state_flags &= ~STATE_FLAG_SMALL_PAIN; + self.state_flags &= ~STATE_FLAG_IN_PAIN; + + max_pains = (int)G_Random( 4 ) + 4; + } + +void Pain::AnimDone + ( + Event *ev + ) + { + anim_done = true; + } + +BehaviorReturnCode_t Pain::Evaluate + ( + Actor &self + ) + + { + str anim_name; + + if ( self.state_flags & STATE_FLAG_SMALL_PAIN ) + { + // See if we should play another pain animation + + if ( ( self.means_of_death != MOD_FIRE ) && ( self.means_of_death != MOD_ON_FIRE ) && ( self.means_of_death != MOD_FIRE_BLOCKABLE ) ) + { + if ( ( self.pain_type == PAIN_SMALL ) && ( current_pain_type == PAIN_SMALL ) && ( number_of_pains < max_pains ) ) + { + pain_anim_number++; + + number_of_pains++; + + SetPainAnim( self, PAIN_SMALL, pain_anim_number ); + } + else if ( self.pain_type == PAIN_BIG ) + { + pain_anim_number++; + + current_pain_type = PAIN_BIG; + + SetPainAnim( self, PAIN_BIG, pain_anim_number ); + } + } + + // Reset pain stuff + + self.state_flags &= ~STATE_FLAG_SMALL_PAIN; + self.state_flags &= ~STATE_FLAG_IN_PAIN; + } + + // If the pain animation has finished, then we are done + + if ( anim_done ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void Pain::End + ( + Actor &self + ) + + { + self.bullet_hits = 0; + } + +/**************************************************************************** + + Watch Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Watch, NULL ) + { + { &EV_Behavior_Args, &Watch::SetArgs }, + { NULL, NULL } + }; + +Watch::Watch() + { + turn_speed = 360.0f; + old_turn_speed = 0.0f; + } + +void Watch::SetArgs + ( + Event *ev + ) + + { + turn_speed = ev->GetFloat( 1 ); + } + +void Watch::Begin + ( + Actor &self + ) + + { + ent_to_watch = self.enemyManager->GetCurrentEnemy(); + + old_turn_speed = self.movementSubsystem->getTurnSpeed(); + self.movementSubsystem->setTurnSpeed( turn_speed ); + + Vector test; + Vector testAng; + + test = self.movementSubsystem->getMoveDir(); + testAng = test.toAngles(); + } + +BehaviorReturnCode_t Watch::Evaluate + ( + Actor &self + ) + + { + Vector dir; + + Vector testAng; + dir = self.movementSubsystem->getMoveDir(); + testAng = dir.toAngles(); + + if ( !ent_to_watch ) + return BEHAVIOR_SUCCESS; + + if ( self.IsEntityAlive( ent_to_watch ) ) + { + dir = self.movementSubsystem->getMoveDir(); + + testAng = dir.toAngles(); + + self.movementSubsystem->Accelerate( + self.movementSubsystem->SteerTowardsPoint(ent_to_watch->centroid, vec_zero, dir, 1.0f) + ); + } + + return BEHAVIOR_EVALUATING; + } + +void Watch::End + ( + Actor &self + ) + + { + self.movementSubsystem->setTurnSpeed( old_turn_speed ); + } + +/**************************************************************************** + + Turn Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Turn, NULL ) + { + { &EV_Behavior_Args, &Turn::SetArgs }, + { NULL, NULL } + }; + +Turn::Turn() + { + turn_speed = 5; + } + +void Turn::SetArgs + ( + Event *ev + ) + + { + turn_speed = ev->GetFloat( 1 ); + } + +void Turn::Begin + ( + Actor &self + ) + + { + } + +BehaviorReturnCode_t Turn::Evaluate + ( + Actor &self + ) + + { + self.angles[YAW] += turn_speed; + + self.setAngles( self.angles ); + + return BEHAVIOR_EVALUATING; + } + +void Turn::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + Aim Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Aim, NULL ) + { + { NULL, NULL } + }; + +void Aim::SetTarget + ( + Entity *ent + ) + + { + target = ent; + } + +void Aim::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nseek:\n" ); + if ( target ) + { + gi.Printf( "\ntarget : #%d '%s'\n", target->entnum, target->targetname.c_str() ); + } + else + { + gi.Printf( "\ntarget : NULL\n" ); + } + } + +void Aim::Begin + ( + Actor &self + ) + + { + } + +BehaviorReturnCode_t Aim::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector ang; + Vector pos; + + if ( !target ) + { + return BEHAVIOR_SUCCESS; + } + + // + // get my gun pos + // + + // Fixme ? + //pos = self.GunPosition(); + //ang = self.MyGunAngles( pos, false ); + pos = self.centroid; + ang = self.angles; + ang.AngleVectors( &dir, NULL, NULL ); + Vector newSteeringDirection=self.movementSubsystem->SteerTowardsPoint(target->centroid, target->velocity, dir, 1400 + ( skill->value * 600 )); + self.movementSubsystem->Accelerate( newSteeringDirection ); + //self.Accelerate( seek.steeringforce ); + if ( newSteeringDirection.y < 0.25f ) + { + // dead on + return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_EVALUATING; + } + +void Aim::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + TurnTo Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, TurnTo, NULL ) + { + { &EV_Behavior_AnimDone, &TurnTo::AnimDone }, + { NULL, NULL } + }; + +TurnTo::TurnTo() + { + dir = Vector( 1.0f, 0.0f, 0.0f ); + mode = 0; + ent = NULL; + yaw = 0; + anim_done = true; + useTurnAnim = true; + _useAnims = true; + } + +void TurnTo::SetDirection + ( + float new_yaw + ) + + { + Vector ang; + + ang = Vector( 0.0f, new_yaw, 0.0f ); + yaw = anglemod( new_yaw ); + ang.AngleVectors( &dir, NULL, NULL ); + dir *= 100.0f; + mode = 1; + } + +void TurnTo::useAnims( bool useAnims ) +{ + _useAnims = useAnims; +} + +void TurnTo::SetTarget + ( + Entity *target_ent + ) + + { + ent = target_ent; + mode = 2; + } + +void TurnTo::AnimDone + ( + Event *ev + ) + + { + anim_done = true; + } + +void TurnTo::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nseek:\n" ); + + if ( ent ) + { + gi.Printf( "\nent: #%d '%s'\n", ent->entnum, ent->targetname.c_str() ); + } + else + { + gi.Printf( "\nent: NULL\n" ); + } + + gi.Printf( "dir: ( %f, %f, %f )\n", dir.x, dir.y, dir.z ); + gi.Printf( "yaw: %f\n", yaw ); + gi.Printf( "mode: %d\n", mode ); + } + +void TurnTo::Begin( Actor &self ) +{ + extraFrames = 0; + + if ( _useAnims ) + { + self.SetAnim( "idle" ); + } +} + +BehaviorReturnCode_t TurnTo::Evaluate( Actor &self ) +{ + Vector delta; + float ang; + str anim_name; + + Vector targetPosition; + Vector targetVelocity; + + if ( anim_done ) + { + if ( _useAnims ) + { + self.SetAnim( "idle" ); + } + anim_done = false; + } + + switch( mode ) + { + case 1 : + ang = angledist( yaw - self.angles.yaw() ); + if ( fabs( ang ) < 1 ) + { + self.movementSubsystem->Accelerate( Vector( 0.0f, ang, 0.0f ) ); + + // If turned all the way wait for animation to finish + + if ( anim_done || ( self.animname == "idle" ) || !_useAnims ) + return BEHAVIOR_SUCCESS; + else + return BEHAVIOR_EVALUATING; + } + + targetPosition = self.origin + dir ; + targetVelocity = vec_zero ; + break; + + case 2 : + if ( !ent ) + { + return BEHAVIOR_SUCCESS; + } + + delta = ent->origin - self.origin; + yaw = delta.toYaw(); + + targetPosition = ent->origin; + targetVelocity = vec_zero; + break; + + default : + return BEHAVIOR_SUCCESS; + } + + Vector newSteeringForce = + self.movementSubsystem->SteerTowardsPoint( + targetPosition, + targetVelocity, + self.movementSubsystem->getMoveDir(), + self.movementSubsystem->getMoveSpeed() + ); + + if ( _useAnims ) + { + if ( extraFrames < 1 ) + { + if ( useTurnAnim ) + { + if ( newSteeringForce[YAW] > 1.0f ) + { + anim_name = "turn_left"; + anim_done = false; + } + else if ( newSteeringForce[YAW] < -1.0f ) + { + anim_name = "turn_right"; + anim_done = false; + } + else if ( anim_done ) + { + anim_name = "idle"; + } + else + { + anim_name = self.animname; + } + } + else + { + anim_name = "idle"; + } + + if ( gi.Anim_NumForName( self.edict->s.modelindex, anim_name.c_str() ) != -1 ) + { + if ( anim_name != self.animname ) + self.SetAnim( anim_name.c_str(), EV_Actor_NotifyBehavior ); + } + else + { + anim_done = true; + } + } + } + + extraFrames++; + if ( extraFrames > 4 || anim_name == "idle" ) + { + extraFrames = 5; + self.movementSubsystem->Accelerate( newSteeringForce ); + } + + return BEHAVIOR_EVALUATING; +} + +void TurnTo::End( Actor &self ) +{ + if ( _useAnims ) + { + self.SetAnim( "idle" ); + } + + self.movementSubsystem->setMoveSpeed( 1.0 ); +} + +void TurnTo::SetUseTurnAnim( bool useAnim ) + { + useTurnAnim = useAnim; + } + +/**************************************************************************** + + RotateToEnemy Class Definition -- Primarily a convience wrapper for TurnTo + +****************************************************************************/ +CLASS_DECLARATION( Behavior, RotateToEnemy, NULL ) + { + { &EV_Behavior_Args, &RotateToEnemy::SetArgs }, + { NULL, NULL } + }; + +void RotateToEnemy::SetArgs + ( + Event *ev + ) + + { + if (ev->NumArgs() > 0 ) + anim = ev->GetString(1); + else + anim = "idle"; + + if (ev->NumArgs() > 1 ) + turnSpeed = ev->GetFloat(2); + else + turnSpeed = 10; + } + +void RotateToEnemy::Begin + ( + Actor &self + ) + + { + + } + +BehaviorReturnCode_t RotateToEnemy::Evaluate + ( + Actor &self + ) + + { + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + Vector dir = currentEnemy->origin - self.origin; + float yaw_diff = AngleNormalize360( self.origin.toYaw() - dir.toYaw() ); + + if (yaw_diff < 180.0f ) + self.angles[YAW]+=turnSpeed; + + if (yaw_diff > 180.0f ) + self.angles[YAW]-=turnSpeed; + + self.setAngles(self.angles); + + if (self.sensoryPerception->InFOV(currentEnemy) ) + return BEHAVIOR_SUCCESS; + else + return BEHAVIOR_EVALUATING; + } + +void RotateToEnemy::End + ( + Actor &self + ) + + { + + } + +/**************************************************************************** + + HeadWatch Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, HeadWatch, NULL ) + { + { &EV_Behavior_Args, &HeadWatch::SetArgs }, + { NULL, NULL } + }; + +HeadWatch::HeadWatch() + { + max_speed = 15; + forever = true; + usingEyes = false; + } + +void HeadWatch::SetArgs + ( + Event *ev + ) + + { + ent_to_watch = ev->GetEntity( 1 ); + + if ( ev->NumArgs() > 1 ) + max_speed = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + forever = ev->GetBoolean( 3 ); + } + + +void HeadWatch::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void HeadWatch::Begin + ( + Actor &self + ) + + { + self.SetControllerTag( ACTOR_HEAD_TAG, gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Head" ) ); + + current_head_angles = self.GetControllerAngles( ACTOR_HEAD_TAG ); + + self.SetActorFlag( ACTOR_FLAG_TURNING_HEAD, true ); + } + +BehaviorReturnCode_t HeadWatch::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector angles; + int tag_num; + Vector tag_pos; + Vector watch_tag_pos; + Vector angles_diff; + Vector watch_position; + Actor *act = NULL; + Vector change; + + + if ( ent_to_watch ) + { + tag_num = gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Head" ); + + if ( tag_num < 0 ) + return BEHAVIOR_SUCCESS; + + self.GetTag( "Bip01 Head", &tag_pos ); + + if ( ent_to_watch->isSubclassOf( Actor ) ) + act = (Actor *)(Entity *)ent_to_watch; + + if ( act && ( act->watch_offset != vec_zero ) ) + { + MatrixTransformVector( act->watch_offset, ent_to_watch->orientation, watch_position ); + watch_position += ent_to_watch->origin; + } + else + { + tag_num = gi.Tag_NumForName( ent_to_watch->edict->s.modelindex, "Bip01 Head" ); + + if ( tag_num < 0 ) + watch_position = ent_to_watch->centroid; + else + { + ent_to_watch->GetTag( "Bip01 Head", &watch_tag_pos ); + watch_position = watch_tag_pos; + } + } + + dir = watch_position - tag_pos; + angles = dir.toAngles(); + + angles_diff = angles - self.angles; + } + else + { + angles_diff = vec_zero; + } + + angles_diff[YAW] = AngleNormalize180( angles_diff[YAW] ); + angles_diff[PITCH] = AngleNormalize180( angles_diff[PITCH] ); + + if (usingEyes) + { + if (angles_diff[YAW] > (self.minEyeYawAngle + (self.minEyeYawAngle * .5f )) && angles_diff[YAW] < (self.maxEyeYawAngle + (self.maxEyeYawAngle * .5f ))) + angles_diff[YAW] = 0.0f; + if (angles_diff[PITCH] > self.minEyePitchAngle && angles_diff[PITCH] < self.maxEyePitchAngle ) + angles_diff[PITCH] = 0.0f; + } + + // Make sure we don't turn neck too far + + if ( angles_diff[YAW] < -80.0f ) + angles_diff[YAW] = -80.0f; + else if ( angles_diff[YAW] > 80.0f ) + angles_diff[YAW] = 80.0f; + + if ( angles_diff[PITCH] < -45.0f ) + angles_diff[PITCH] = -45.0f; + else if ( angles_diff[PITCH] > 45.0f ) + angles_diff[PITCH] = 45.0f; + + angles_diff[ROLL] = 0.0f; + + // Make sure we don't change our head angles too much at once + + change = angles_diff - current_head_angles; + float absYaw = fabs( change[ YAW ] ); + float absPitch = fabs( change[ PITCH ] ); + + if( ( absYaw < fEpsilon() ) && ( absPitch < fEpsilon() ) ) + { + if( forever ) + { + self.SetControllerAngles( ACTOR_HEAD_TAG, current_head_angles ); + return BEHAVIOR_EVALUATING; + } + else + return BEHAVIOR_SUCCESS; + } + + //change = vec_zero; + + bool isFinished = true; + if( absYaw >= absPitch ) + { + float ratioPitchToYaw = change[ PITCH ] / change[ YAW ]; + + if( change[ YAW ] > max_speed ) + { + change[ YAW ] = max_speed; + isFinished = false; + } + else if( change[ YAW ] < -max_speed ) + { + change[ YAW ] = -max_speed; + isFinished = false; + } + change[ PITCH ] = change[ YAW ] * ratioPitchToYaw; + } + else if( absPitch > absYaw ) + { + float ratioYawToPitch = change[ YAW ] / change[ PITCH ]; + + if( change[ PITCH ] > max_speed ) + { + change[ PITCH ] = max_speed; + isFinished = false; + } + else if( change[ PITCH ] < -max_speed ) + { + change[ PITCH ] = -max_speed; + isFinished = false; + } + change[ YAW ] = change[ PITCH ] * ratioYawToPitch; + } + + angles_diff = current_head_angles + change; + angles_diff[ ROLL ] = 0.0f; + +/* + if ( change[YAW] > max_speed ) + angles_diff[YAW] = current_head_angles[YAW] + max_speed; + else if ( change[YAW] < -max_speed ) + angles_diff[YAW] = current_head_angles[YAW] - max_speed; + + if ( change[PITCH] > max_speed ) + angles_diff[PITCH] = current_head_angles[PITCH] + max_speed; + else if ( change[PITCH] < -max_speed ) + angles_diff[PITCH] = current_head_angles[PITCH] - max_speed; +*/ + self.SetControllerAngles( ACTOR_HEAD_TAG, angles_diff ); + self.real_head_pitch = angles_diff[PITCH]; + + current_head_angles = angles_diff; + + if ( !ent_to_watch && ( current_head_angles == vec_zero ) ) + return BEHAVIOR_SUCCESS; + +// if ( !forever && ( change[YAW] < max_speed ) && ( change[YAW] > -max_speed ) && ( change[PITCH] < max_speed ) && ( change[PITCH] > -max_speed ) ) + if( !forever && isFinished ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void HeadWatch::useEyes + ( + qboolean moveEyes + ) + + { + usingEyes = moveEyes; + } + +void HeadWatch::End + ( + Actor &self + ) + + { + // Snap head back into position if we have lost our target or we are doing a resethead + + self.SetControllerAngles( ACTOR_HEAD_TAG, vec_zero ); + self.real_head_pitch = 0; + + self.SetActorFlag( ACTOR_FLAG_TURNING_HEAD, false ); + } + + + +/**************************************************************************** + + HeadWatchEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, HeadWatchEnemy, NULL ) + { + { &EV_Behavior_Args, &HeadWatchEnemy::SetArgs }, + { NULL, NULL } + }; + +HeadWatchEnemy::HeadWatchEnemy() + { + max_speed = 20; + forever = true; + usingEyes = false; + threshold = 0; + } + +void HeadWatchEnemy::SetArgs + ( + Event *ev + ) + + { + max_speed = ev->GetFloat( 1 ); + + if ( ev->NumArgs() > 1 ) + forever = ev->GetBoolean( 2 ); + + if ( ev->NumArgs() > 2 ) + threshold = ev->GetFloat( 3 ); + } + + +void HeadWatchEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void HeadWatchEnemy::Begin + ( + Actor &self + ) + + { + self.SetControllerTag( ACTOR_HEAD_TAG, gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Head" ) ); + + current_head_angles = self.GetControllerAngles( ACTOR_HEAD_TAG ); + + self.SetActorFlag( ACTOR_FLAG_TURNING_HEAD, true ); + + ent_to_watch = self.enemyManager->GetCurrentEnemy(); + } + +BehaviorReturnCode_t HeadWatchEnemy::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector angles; + int tag_num; + Vector tag_pos; + Vector watch_tag_pos; + Vector angles_diff; + Vector watch_position; + Actor *act = NULL; + Vector change; + + + // Get our Torso Angles + self.SetControllerTag( ACTOR_TORSO_TAG , gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Spine1" ) ); + current_torso_angles = self.GetControllerAngles( ACTOR_TORSO_TAG ); + + //Reset our Controller Tag + self.SetControllerTag( ACTOR_HEAD_TAG, gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Head" ) ); + + + if ( !ent_to_watch ) + { + ent_to_watch = self.enemyManager->GetCurrentEnemy(); + } + + if ( ent_to_watch ) + { + tag_num = gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Head" ); + + if ( tag_num < 0 ) + return BEHAVIOR_SUCCESS; + + self.GetTag( "Bip01 Head", &tag_pos ); + + if ( ent_to_watch->isSubclassOf( Actor ) ) + act = (Actor *)(Entity *)ent_to_watch; + + if ( act && ( act->watch_offset != vec_zero ) ) + { + MatrixTransformVector( act->watch_offset, ent_to_watch->orientation, watch_position ); + watch_position += ent_to_watch->origin; + } + else + { + tag_num = gi.Tag_NumForName( ent_to_watch->edict->s.modelindex, "Bip01 Head" ); + + if ( tag_num < 0 ) + watch_position = ent_to_watch->centroid; + else + { + ent_to_watch->GetTag( "Bip01 Head", &watch_tag_pos ); + watch_position = watch_tag_pos; + } + } + + dir = watch_position - tag_pos; + angles = dir.toAngles(); + + angles_diff = angles - self.angles; + } + else + { + angles_diff = vec_zero; + } + + angles_diff[YAW] = AngleNormalize180( angles_diff[YAW] ); + angles_diff[PITCH] = AngleNormalize180( angles_diff[PITCH] ); + + float yaw_change = angles_diff[YAW]; + float pitch_change = angles_diff[PITCH]; + + if ( threshold && ( yaw_change < threshold ) && ( yaw_change > -threshold ) && ( pitch_change < threshold ) && ( pitch_change > -threshold ) ) + { + if ( forever ) + return BEHAVIOR_EVALUATING; + else + return BEHAVIOR_SUCCESS; + } + + if (usingEyes) + { + if (angles_diff[YAW] > (self.minEyeYawAngle + (self.minEyeYawAngle * .5f )) && angles_diff[YAW] < (self.maxEyeYawAngle + (self.maxEyeYawAngle * .5f ))) + angles_diff[YAW] = 0.0f; + if (angles_diff[PITCH] > self.minEyePitchAngle && angles_diff[PITCH] < self.maxEyePitchAngle ) + angles_diff[PITCH] = 0.0f; + } + + // Make sure we don't turn neck too far + + if ( angles_diff[YAW] < -80.0f ) + angles_diff[YAW] = -80.0f; + else if ( angles_diff[YAW] > 80.0f ) + angles_diff[YAW] = 80.0f; + + if ( angles_diff[PITCH] < -45.0f ) + angles_diff[PITCH] = -45.0f; + else if ( angles_diff[PITCH] > 45.0f ) + angles_diff[PITCH] = 45.0f; + + angles_diff[ROLL] = 0.0f; + + // Make sure we don't change our head angles too much at once + + //current_head_angles[YAW] = current_head_angles[YAW] - current_torso_angles[YAW]; + change = angles_diff - current_head_angles; + + if ( change[YAW] > max_speed ) + angles_diff[YAW] = current_head_angles[YAW] + max_speed; + else if ( change[YAW] < -max_speed ) + angles_diff[YAW] = current_head_angles[YAW] - max_speed; + + if ( change[PITCH] > max_speed ) + angles_diff[PITCH] = current_head_angles[PITCH] + max_speed; + else if ( change[PITCH] < -max_speed ) + angles_diff[PITCH] = current_head_angles[PITCH] - max_speed; + + Vector FinalAngles; + FinalAngles = angles_diff; + FinalAngles[YAW] = angles_diff[YAW] - current_torso_angles[YAW]; + + //self.SetControllerAngles( ACTOR_HEAD_TAG, angles_diff ); + self.SetControllerAngles( ACTOR_HEAD_TAG, FinalAngles ); + self.real_head_pitch = angles_diff[PITCH]; + + current_head_angles = angles_diff; + + /* + if ( !ent_to_watch && ( current_head_angles == vec_zero ) ) + return false; + */ + + if ( !ent_to_watch ) + return BEHAVIOR_EVALUATING; + + if ( !forever && ( change[YAW] < max_speed ) && ( change[YAW] > -max_speed ) && ( change[PITCH] < max_speed ) && ( change[PITCH] > -max_speed ) ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void HeadWatchEnemy::useEyes + ( + qboolean moveEyes + ) + + { + usingEyes = moveEyes; + } + +void HeadWatchEnemy::End + ( + Actor &self + ) + + { + // Snap head back into position if we have lost our target or we are doing a resethead + + self.SetControllerAngles( ACTOR_HEAD_TAG, vec_zero ); + self.real_head_pitch = 0; + + self.SetActorFlag( ACTOR_FLAG_TURNING_HEAD, false ); + } + + +/**************************************************************************** + + EyeWatch Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, EyeWatch, NULL ) + { + { &EV_Behavior_Args, &EyeWatch::SetArgs }, + { NULL, NULL } + }; + +EyeWatch::EyeWatch() + { + ent_to_watch = NULL; + max_speed = 10; + forever = true; + threshold = 0; + } + +void EyeWatch::SetArgs + ( + Event *ev + ) + + { + + ent_to_watch = ev->GetEntity( 1 ); + + if ( ev->NumArgs() > 1 ) + max_speed = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + forever = ev->GetBoolean( 3 ); + + if ( ev->NumArgs() > 3 ) + threshold = ev->GetFloat( 4 ); + } + + +void EyeWatch::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void EyeWatch::Begin + ( + Actor &self + ) + + { + self.SetControllerTag( ACTOR_LEYE_TAG, gi.Tag_NumForName( self.edict->s.modelindex, "Face eyeballL" ) ); + self.SetControllerTag( ACTOR_REYE_TAG, gi.Tag_NumForName( self.edict->s.modelindex, "Face eyeballR" ) ); + + + current_left_eye_angles = self.GetControllerAngles( ACTOR_LEYE_TAG ); + current_right_eye_angles = self.GetControllerAngles( ACTOR_REYE_TAG ); + + self.SetActorFlag( ACTOR_FLAG_MOVING_EYES, true ); + } + +BehaviorReturnCode_t EyeWatch::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector angles; + int tag_num; + Vector tag_pos; + Vector watch_tag_pos; + Vector angles_diff; + Vector watch_position; + Actor *act = NULL; + Vector change; + + if ( ent_to_watch ) + { + tag_num = gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Head" ); + + if ( tag_num < 0 ) + return BEHAVIOR_SUCCESS; + + self.GetTag( "Bip01 Head", &tag_pos ); + + if ( ent_to_watch->isSubclassOf( Actor ) ) + act = (Actor *)(Entity *)ent_to_watch; + + if ( act && ( act->watch_offset != vec_zero ) ) + { + MatrixTransformVector( act->watch_offset, ent_to_watch->orientation, watch_position ); + watch_position += ent_to_watch->origin; + } + else + { + tag_num = gi.Tag_NumForName( ent_to_watch->edict->s.modelindex, "Bip01 Head" ); + + if ( tag_num < 0 ) + watch_position = ent_to_watch->centroid; + else + { + ent_to_watch->GetTag( "Bip01 Head", &watch_tag_pos ); + watch_position = watch_tag_pos; + } + } + + dir = watch_position - tag_pos; + if ( dir.length() < 32.0f ) + { + if ( !forever ) + return BEHAVIOR_SUCCESS; + else + return BEHAVIOR_EVALUATING; + } + + angles = dir.toAngles(); + + angles_diff = angles - self.angles; + } + else + { + angles_diff = vec_zero; + } + + angles_diff[YAW] = AngleNormalize180( angles_diff[YAW] ); + + // Make sure we don't turn eyes too far + if ( angles_diff[YAW] < self.minEyeYawAngle ) + angles_diff[YAW] = self.minEyeYawAngle ; + else if ( angles_diff[YAW] > self.maxEyeYawAngle ) + angles_diff[YAW] = self.maxEyeYawAngle; + + if ( angles_diff[PITCH] < self.minEyePitchAngle ) + angles_diff[PITCH] = self.minEyePitchAngle ; + else if ( angles_diff[PITCH] > self.maxEyePitchAngle ) + angles_diff[PITCH] = self.maxEyePitchAngle; + + angles_diff[ROLL] = 0; + + + // Make sure we don't change our eye angles too much at once + + change = angles_diff - current_left_eye_angles; + + + // check left eye -- both eyes will be the same + if ( change[YAW] > max_speed ) + angles_diff[YAW] = current_left_eye_angles[YAW] + max_speed; + else if ( change[YAW] < -max_speed ) + angles_diff[YAW] = current_left_eye_angles[YAW] - max_speed; + + if ( change[PITCH] > max_speed ) + angles_diff[PITCH] = current_left_eye_angles[PITCH] + max_speed; + else if ( change[PITCH] < -max_speed ) + angles_diff[PITCH] = current_left_eye_angles[PITCH] - max_speed; + + self.SetControllerAngles( ACTOR_LEYE_TAG, angles_diff ); + self.SetControllerAngles( ACTOR_REYE_TAG, angles_diff ); + + current_left_eye_angles = angles_diff; + current_right_eye_angles = angles_diff; + + + if ( !ent_to_watch && ( current_left_eye_angles == vec_zero ) ) + return BEHAVIOR_SUCCESS; + + + if ( !forever && ( change[YAW] < max_speed ) && ( change[YAW] > -max_speed ) && + ( change[PITCH] < max_speed ) && ( change[PITCH] > -max_speed ) ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void EyeWatch::End + ( + Actor &self + ) + + { + // Snap head back into position if we have lost our target or we are doing a resethead + + self.SetControllerAngles( ACTOR_LEYE_TAG, vec_zero ); + self.SetControllerAngles( ACTOR_REYE_TAG, vec_zero ); + + self.SetActorFlag( ACTOR_FLAG_MOVING_EYES, false ); + } + + + + +/**************************************************************************** + + EyeWatchEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, EyeWatchEnemy, NULL ) + { + { &EV_Behavior_Args, &EyeWatchEnemy::SetArgs }, + { NULL, NULL } + }; + +EyeWatchEnemy::EyeWatchEnemy() + { + ent_to_watch = NULL; + max_speed = 10; + forever = true; + threshold = 0; + } + +void EyeWatchEnemy::SetArgs + ( + Event *ev + ) + + { + + max_speed = ev->GetFloat( 1 ); + + if ( ev->NumArgs() > 1 ) + forever = ev->GetBoolean( 2 ); + + if ( ev->NumArgs() > 2 ) + threshold = ev->GetFloat( 3 ); + } + + +void EyeWatchEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void EyeWatchEnemy::Begin + ( + Actor &self + ) + + { + self.SetControllerTag( ACTOR_LEYE_TAG, gi.Tag_NumForName( self.edict->s.modelindex, "Face eyeballL" ) ); + self.SetControllerTag( ACTOR_REYE_TAG, gi.Tag_NumForName( self.edict->s.modelindex, "Face eyeballR" ) ); + + + current_left_eye_angles = self.GetControllerAngles( ACTOR_LEYE_TAG ); + current_right_eye_angles = self.GetControllerAngles( ACTOR_REYE_TAG ); + + self.SetActorFlag( ACTOR_FLAG_MOVING_EYES, true ); + } + +BehaviorReturnCode_t EyeWatchEnemy::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector angles; + int tag_num; + Vector tag_pos; + Vector watch_tag_pos; + Vector angles_diff; + Vector watch_position; + Actor *act = NULL; + Vector change; + + ent_to_watch = self.enemyManager->GetCurrentEnemy(); + + if ( ent_to_watch ) + { + tag_num = gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Head" ); + + if ( tag_num < 0 ) + return BEHAVIOR_SUCCESS; + + self.GetTag( "Bip01 Head", &tag_pos ); + + if ( ent_to_watch->isSubclassOf( Actor ) ) + act = (Actor *)(Entity *)ent_to_watch; + + if ( act && ( act->watch_offset != vec_zero ) ) + { + MatrixTransformVector( act->watch_offset, ent_to_watch->orientation, watch_position ); + watch_position += ent_to_watch->origin; + } + else + { + tag_num = gi.Tag_NumForName( ent_to_watch->edict->s.modelindex, "Bip01 Head" ); + + if ( tag_num < 0 ) + watch_position = ent_to_watch->centroid; + else + { + ent_to_watch->GetTag( "Bip01 Head", &watch_tag_pos ); + watch_position = watch_tag_pos; + } + } + + dir = watch_position - tag_pos; + angles = dir.toAngles(); + + angles_diff = angles - self.angles; + } + else + { + angles_diff = vec_zero; + } + + angles_diff[YAW] = AngleNormalize180( angles_diff[YAW] ); + + + float yaw_change = angles_diff[YAW]; + float pitch_change = angles_diff[PITCH]; + if ( threshold && ( yaw_change < threshold ) && ( yaw_change > -threshold ) && + ( pitch_change < threshold ) && ( pitch_change > -threshold ) ) + { + if ( forever ) + return BEHAVIOR_EVALUATING; + else + return BEHAVIOR_SUCCESS; + } + + + // Make sure we don't turn eyes too far + if ( angles_diff[YAW] < self.minEyeYawAngle ) + angles_diff[YAW] = self.minEyeYawAngle ; + else if ( angles_diff[YAW] > self.maxEyeYawAngle ) + angles_diff[YAW] = self.maxEyeYawAngle; + + if ( angles_diff[PITCH] < self.minEyePitchAngle ) + angles_diff[PITCH] = self.minEyePitchAngle ; + else if ( angles_diff[PITCH] > self.maxEyePitchAngle ) + angles_diff[PITCH] = self.maxEyePitchAngle; + + angles_diff[ROLL] = 0; + + + // Make sure we don't change our eye angles too much at once + + change = angles_diff - current_left_eye_angles; + + + // check left eye -- both eyes will be the same + if ( change[YAW] > max_speed ) + angles_diff[YAW] = current_left_eye_angles[YAW] + max_speed; + else if ( change[YAW] < -max_speed ) + angles_diff[YAW] = current_left_eye_angles[YAW] - max_speed; + + if ( change[PITCH] > max_speed ) + angles_diff[PITCH] = current_left_eye_angles[PITCH] + max_speed; + else if ( change[PITCH] < -max_speed ) + angles_diff[PITCH] = current_left_eye_angles[PITCH] - max_speed; + + self.SetControllerAngles( ACTOR_LEYE_TAG, angles_diff ); + self.SetControllerAngles( ACTOR_REYE_TAG, angles_diff ); + //self.real_head_pitch = angles_diff[PITCH]; + + current_left_eye_angles = angles_diff; + current_right_eye_angles = angles_diff; + //self.eyeposition = current_head_angles; + + if ( !ent_to_watch && ( current_left_eye_angles == vec_zero ) ) + return BEHAVIOR_SUCCESS; + + if ( !forever && ( change[YAW] < max_speed ) && ( change[YAW] > -max_speed ) && + ( change[PITCH] < max_speed ) && ( change[PITCH] > -max_speed ) ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void EyeWatchEnemy::End + ( + Actor &self + ) + + { + // Snap head back into position if we have lost our target or we are doing a resethead + + //if ( !ent_to_watch ) + // { + self.SetControllerAngles( ACTOR_LEYE_TAG, vec_zero ); + self.SetControllerAngles( ACTOR_REYE_TAG, vec_zero ); + + //self.real_head_pitch = 0; + // } + + self.SetActorFlag( ACTOR_FLAG_MOVING_EYES, false ); + } + +/**************************************************************************** + + HeadAndEyeWatch Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, HeadAndEyeWatch, NULL ) + { + { &EV_Behavior_Args, &HeadAndEyeWatch::SetArgs }, + { NULL, NULL } + }; + +HeadAndEyeWatch::HeadAndEyeWatch() + { + } + +void HeadAndEyeWatch::SetArgs + ( + Event *ev + ) + + { + headWatch.SetArgs(ev); + eyeWatch.SetArgs(ev); + + headWatch.useEyes(true); + } + + +void HeadAndEyeWatch::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + headWatch.ShowInfo(self); + eyeWatch.ShowInfo(self); + } + +void HeadAndEyeWatch::Begin + ( + Actor &self + ) + + { + headWatch.Begin(self); + eyeWatch.Begin(self); + } + +BehaviorReturnCode_t HeadAndEyeWatch::Evaluate + ( + Actor &self + ) + + { + // if headWatch returns 0 and eyeWatch returns 0 -- returns 0 + // if headWatch returns 1 and eyeWatch returns 0 -- returns 0 + // if headWatch returns 0 and eyeWatch returns 1 -- returns 0 + // if headWatch returns 1 and eyeWatch returns 1 -- returns 1 + return BEHAVIOR_SUCCESS; + } + +void HeadAndEyeWatch::End + ( + Actor &self + ) + + { + headWatch.End(self); + eyeWatch.End(self); + } + + +/**************************************************************************** + + TorsoTurn Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, TorsoTurn, NULL ) + { + { &EV_Behavior_Args, &TorsoTurn::SetArgs }, + { NULL, NULL } + }; + +void TorsoTurn::SetArgs + ( + Event *ev + ) + + { + turn_towards_enemy = ev->GetInteger( 1 ); + speed = ev->GetFloat( 2 ); + forever = ev->GetInteger( 3 ); + + if ( ev->NumArgs() > 3 ) + tag_name = ev->GetString( 4 ); + + if ( ev->NumArgs() > 4 ) + tolerance = ev->GetFloat( 5 ); + else + tolerance = 0; + + if ( ev->NumArgs() > 5 ) + offset = ev->GetFloat( 6 ); + else + offset = 0; + + if ( ev->NumArgs() > 6 ) + use_pitch = ev->GetBoolean( 7 ); + else + use_pitch = true; + } + +void TorsoTurn::SetRequiredParameters( int TurnTowardsEnemy , int Speed , int Forever ) + { + turn_towards_enemy = TurnTowardsEnemy; + speed = Speed; + forever = Forever; + } + +void TorsoTurn::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void TorsoTurn::Begin + ( + Actor &self + ) + + { + Vector controller_angles; + + self.SetControllerTag( ACTOR_TORSO_TAG, gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Spine1" ) ); + + controller_angles = self.GetControllerAngles( ACTOR_TORSO_TAG ); + + current_yaw = controller_angles[YAW]; + current_pitch = controller_angles[PITCH]; + } + +BehaviorReturnCode_t TorsoTurn::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector angles; + int tag_num; + float yaw_diff; + float pitch_diff; + Vector new_angles; + float yaw_change; + float pitch_change; + Vector tag_pos; + Vector tag_forward; + Vector tag_angles; + + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + tag_num = gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Spine1" ); + + if ( tag_num < 0 ) + return BEHAVIOR_SUCCESS; + + // Determine the angle we want to go to + + if ( turn_towards_enemy ) + { + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + + if ( tag_name.length() ) + { + self.GetTag( tag_name.c_str(), &tag_pos, &tag_forward ); + tag_angles = tag_forward.toAngles(); + dir = currentEnemy->centroid - tag_pos; + } + else + { + dir = currentEnemy->centroid - self.centroid; + } + + + + + angles = dir.toAngles(); + + + + angles[YAW] += offset; + + yaw_diff = AngleNormalize180( angles[YAW] - self.angles[YAW] ); + pitch_diff = AngleNormalize180( angles[PITCH] - self.angles[PITCH] ); + } + else + { + yaw_diff = 0.0f; + pitch_diff = 0.0f; + } + + // Determine the angle change + + yaw_change = AngleNormalize180( yaw_diff - current_yaw ); + pitch_change = AngleNormalize180( pitch_diff - current_pitch ); + + if ( tolerance && ( yaw_change < tolerance ) && ( yaw_change > -tolerance ) && + ( pitch_change < tolerance ) && ( pitch_change > -tolerance ) ) + { + if ( forever ) + return BEHAVIOR_EVALUATING; + else + return BEHAVIOR_SUCCESS; + } + + // Make sure we don't change our torso angles too much at once + + if ( yaw_change > speed ) + yaw_diff = current_yaw + speed; + else if ( yaw_change < -speed ) + yaw_diff = current_yaw - speed; + + if ( pitch_change > speed ) + pitch_diff = current_pitch + speed; + else if ( yaw_change < -speed ) + pitch_diff = current_pitch - speed; + + // Determine our new angles + + new_angles[YAW] = yaw_diff; + + if ( use_pitch ) + new_angles[PITCH] = pitch_diff; + else + new_angles[PITCH] = 0.0f; + + new_angles[ROLL] = 0.0f; + + // Make sure we don't turn too far + + if ( ( new_angles[YAW] > 100.0f ) || ( new_angles[YAW] < -100.0f ) ) + { + if (!forever ) + return BEHAVIOR_SUCCESS; + else + { + self.angles[YAW] = new_angles[YAW]; + self.angles[ROLL] = 0; + self.setAngles( self.angles ); + return BEHAVIOR_EVALUATING; + } + + } + + + if ( new_angles[PITCH] > 45 || new_angles[PITCH] < -45 ) + { + if (!forever ) + return BEHAVIOR_SUCCESS; + else + return BEHAVIOR_EVALUATING; + } + + + // Set our new angles + + self.SetControllerAngles( ACTOR_TORSO_TAG, new_angles ); + + current_yaw = yaw_diff; + current_pitch = pitch_diff; + + // See if we are turned the correct direction now + + if ( !forever && (yaw_change < speed) && (yaw_change > -speed) && (pitch_change < speed) && (pitch_change > -speed) ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void TorsoTurn::End + ( + Actor &self + ) + + { + self.SetControllerAngles( ACTOR_TORSO_TAG, vec_zero ); + } + + + + + +/**************************************************************************** + + TorsoWatchEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, TorsoWatchEnemy, NULL ) + { + { &EV_Behavior_Args, &TorsoWatchEnemy::SetArgs }, + { NULL, NULL } + }; + +void TorsoWatchEnemy::SetArgs + ( + Event *ev + ) + + { + + invert = false; + + speed = ev->GetFloat( 1 ); + forever = ev->GetInteger( 2 ); + threshold = ev->GetFloat( 3 ); + + if ( ev->NumArgs() > 3 ) + offset = ev->GetFloat( 4 ); + else + offset = 0; + + if ( ev->NumArgs() > 4 ) + use_pitch = ev->GetBoolean( 5 ); + else + use_pitch = true; + + if ( ev->NumArgs() > 5 ) + invert = ev->GetBoolean( 6 ); + + if ( ev->NumArgs() > 6 ) + reset = ev->GetBoolean ( 7 ); + else + reset = true; + + + invertLegs = false; + nextFlipTime = 0; + + } + + +void TorsoWatchEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void TorsoWatchEnemy::Begin + ( + Actor &self + ) + + { + Vector controller_angles; + + self.SetControllerTag( ACTOR_TORSO_TAG, gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Spine1" ) ); + + controller_angles = self.GetControllerAngles( ACTOR_TORSO_TAG ); + + current_yaw = controller_angles[YAW]; + current_pitch = controller_angles[PITCH]; + + self.SetActorFlag( ACTOR_FLAG_OUT_OF_TORSO_RANGE , false ); + } + +BehaviorReturnCode_t TorsoWatchEnemy::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector angles; + int tag_num; + float yaw_diff; + float pitch_diff; + Vector new_angles; + float yaw_change; + float pitch_change; + Vector tag_pos; + Vector tag_forward; + Vector tag_angles; + + + + tag_num = gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Spine1" ); + + if ( tag_num < 0 ) + return BEHAVIOR_SUCCESS; + + // Determine the angle we want to go to + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + reset = true; + return BEHAVIOR_EVALUATING; + } + + /* + if ( invertLegs && self.GetActorFlag( ACTOR_FLAG_ANIM_DONE ) ) + { + self.SetAnim( "idle" ); + invertLegs = false; + } + */ + + dir = currentEnemy->centroid - self.centroid; + + if (invert) + dir *= -1.0f; + + angles = dir.toAngles(); + + angles[YAW] += offset; + + /* + Vector controller_angles; + controller_angles = self.GetControllerAngles( ACTOR_TORSO_TAG ); + + current_yaw = controller_angles[YAW]; + current_pitch = controller_angles[PITCH]; + */ + + + yaw_diff = AngleNormalize180( angles[YAW] - self.angles[YAW] ); + pitch_diff = AngleNormalize180( angles[PITCH] - self.angles[PITCH] ); + + //yaw_diff = AngleNormalize180( angles[YAW] - test[YAW] ); + //pitch_diff = AngleNormalize180( angles[PITCH] - test[PITCH] ); + + // Determine the angle change + + yaw_change = AngleNormalize180( yaw_diff - current_yaw ); + pitch_change = AngleNormalize180( pitch_diff - current_pitch ); + + /* + if ( threshold && yaw_change < threshold && yaw_change > -threshold && pitch_change < threshold && pitch_change > -threshold ) + { + if ( forever ) + return true; + else + return false; + + } + */ + // Make sure we don't change our torso angles too much at once + + if ( yaw_change > speed ) + yaw_diff = current_yaw + speed; + else if ( yaw_change < -speed ) + yaw_diff = current_yaw - speed; + + if ( pitch_change > speed ) + pitch_diff = current_pitch + speed; + else if ( pitch_change < -speed ) + pitch_diff = current_pitch - speed; + + // Determine our new angles + + new_angles[YAW] = yaw_diff; + + if ( use_pitch ) + { + new_angles[PITCH] = pitch_diff; + } + else + new_angles[PITCH] = 0.0f; + + new_angles[ROLL] = 0.0f; + + // Make sure we don't turn too far + + if ( ( new_angles[YAW] > 100.0f ) || ( new_angles[YAW] < -100.0f ) ) + { + if ( !forever ) + return BEHAVIOR_SUCCESS; + else + { + if ( new_angles[YAW] < -100.0f || new_angles[YAW] > 100.0f ) + { + if ( nextFlipTime <= level.time ) + { + self.movementSubsystem->flipLegs(); + invertLegs = true; + nextFlipTime = level.time + .75; + } + + return BEHAVIOR_EVALUATING; + } + + } + + } + + if ( ( new_angles[PITCH] > 60.0f ) || ( new_angles[PITCH] < -60 ) ) + { + if ( !forever ) + return BEHAVIOR_SUCCESS; + else + return BEHAVIOR_EVALUATING; + } + + // Set our new angles + + self.SetControllerAngles( ACTOR_TORSO_TAG, new_angles ); + + current_yaw = yaw_diff; + current_pitch = pitch_diff; + + // See if we are turned the correct direction now + + if ( !forever && (yaw_change < speed) && (yaw_change > -speed) && (pitch_change < speed) && (pitch_change > -speed) ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void TorsoWatchEnemy::End + ( + Actor &self + ) + + { + /* + if ( invertLegs ) + self.movementSubsystem->flipLegs(); + */ + + self.movementSubsystem->setMovingBackwards( false ); + if ( reset ) + self.SetControllerAngles( ACTOR_TORSO_TAG, vec_zero ); + } +/**************************************************************************** + + GotoPathNode Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, GotoPathNode, NULL ) + { + { &EV_Behavior_AnimDone, &GotoPathNode::AnimDone }, + { &EV_Behavior_Args, &GotoPathNode::SetArgs }, + { NULL, NULL } + }; + +GotoPathNode::GotoPathNode() + { + chase = NULL; + usevec = false; + movegoal = NULL; + goal = vec_zero; + goalent = NULL; + state = 0; + _followingEntity = false; + } + +void GotoPathNode::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->IsVectorAt( 2 ) ) + { + goal = ev->GetVector( 2 ); + usevec = true; + } + else + { + usevec = false; + movegoal = thePathManager.FindNode( ev->GetString( 2 ) ); + if ( !movegoal ) + { + goalent = ev->GetEntity( 2 ); + } + } + + if ( ev->NumArgs() > 2 ) + entity_to_watch = ev->GetEntity( 3 ); + } + +void GotoPathNode::AnimDone + ( + Event *ev + ) + + { + turnto.ProcessEvent( EV_Behavior_AnimDone ); + } + +void GotoPathNode::SetGoal + ( + PathNode *node + ) + + { + usevec = false; + movegoal = node; + } + +void GotoPathNode::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nturnto:\n" ); + turnto.ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase->ShowInfo( self ); + + gi.Printf( "\nstate: %d\n", state ); + gi.Printf( "usevec: %d\n", usevec ); + //gi.Printf( "time: %f\n", time ); + gi.Printf( "anim: %s\n", anim.c_str() ); + + if ( goalent ) + { + gi.Printf( "\ngoalent: #%d '%s'\n", goalent->entnum, goalent->targetname.c_str() ); + } + else + { + gi.Printf( "\ngoalent: NULL\n" ); + } + + gi.Printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z ); + } + +void GotoPathNode::Begin + ( + Actor &self + ) + + { + Event *ev; + + Vector dir; + dir = self.movementSubsystem->getAnimDir(); + dir = dir.toAngles(); + self.setAngles( dir ); + + turnto.Begin( self ); + float radius=12.0f; + if ( goalent ) + { + FollowPathToEntity *newFollowPath = new FollowPathToEntity(); + newFollowPath->SetGoal( goalent, radius, self ); + chase = newFollowPath; + _followingEntity = true; + } + else if ( movegoal ) + { + FollowPathToPoint *newFollowPath = new FollowPathToPoint(); + newFollowPath->SetGoal( movegoal->origin, radius, self ); + chase = newFollowPath; + } + else + { + FollowPathToPoint *newFollowPath = new FollowPathToPoint(); + newFollowPath->SetGoal( goal, radius, self ); + chase = newFollowPath; + } + + chase->Begin( self ); + + chase->Begin( self ); + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + // Setup head watch stuff + + head_watch.Begin( self ); + + ev = new Event( EV_Behavior_Args ); + ev->AddEntity( entity_to_watch ); + head_watch.ProcessEvent( ev ); + } + +BehaviorReturnCode_t GotoPathNode::Evaluate + ( + Actor &self + ) + + { + float yaw; + + if ( !usevec && !goalent && !movegoal ) + { + gi.DPrintf( "GotoPathNode::No goal\n" ); + return BEHAVIOR_SUCCESS; + } + + if ( entity_to_watch ) + head_watch.Evaluate( self ); + + switch( state ) + { + case 0 : + if ( chase->Evaluate( self ) == Steering::EVALUATING ) + { + break; + } + + state = 1; + self.SetAnim( "idle" ); + + // cascade down to case 1 + // lint -fallthrough + case 1 : + if ( !movegoal ) + { + return BEHAVIOR_SUCCESS; + } + + if ( movegoal->setangles ) + { + yaw = movegoal->angles.yaw(); + turnto.SetDirection( yaw ); + if ( turnto.Evaluate( self ) ) + { + break; + } + } + + if ( movegoal->animname == "" ) + { + self.SetAnim( "idle" ); + return BEHAVIOR_SUCCESS; + } + + self.SetAnim( movegoal->animname, EV_Actor_EndBehavior ); + state = 2; + break; + + case 2 : + break; + } + + return BEHAVIOR_EVALUATING; + } + +void GotoPathNode::End + ( + Actor &self + ) + + { + chase->End( self ); + head_watch.End( self ); + } + +/**************************************************************************** + + Flee Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Flee, NULL ) + { + { &EV_Behavior_Args, &Flee::SetArgs }, + { NULL, NULL } + }; + +void Flee::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + } + +void Flee::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase.ShowInfo( self ); + } + +void Flee::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + chase.Begin( self ); + FindFleeNode( self ); + } + +void Flee::FindFleeNode + ( + Actor &self + ) + { + int i; + PathNode *start_node; + PathNodeConnection *path; + PathNode *current_node; + int max_nodes_to_look_at; + int nodes_looked_at; + qboolean found; + + + // Get closest node + + start_node = thePathManager.NearestNode( self.origin, &self ); + + // Find a random node that is connected to this node + + found = false; + + if ( start_node ) + { + max_nodes_to_look_at = (int)G_Random( 5.0f ) + 10; + nodes_looked_at = 0; + current_node = start_node; + + while( !found ) + { + nodes_looked_at++; + + if ( ( nodes_looked_at >= max_nodes_to_look_at ) && ( current_node != start_node ) ) + { + found = true; + flee_node = current_node; + } + + if ( current_node->NumberOfConnections() == 0 ) + break; + + path = ¤t_node->GetConnection( (int)G_Random( (float)current_node->NumberOfConnections() ) ); + current_node = thePathManager.GetNode( path->targetNodeIndex ); + } + } + + if ( !found ) + { + // If still not found, use old method + + for( i = 0; i < 5; i++ ) + { + flee_node = thePathManager.GetNode( ( int )G_Random( (float)thePathManager.NumNodes() + 1.0f ) ); + if ( flee_node ) + break; + } + } + } + + +BehaviorReturnCode_t Flee::Evaluate + ( + Actor &self + ) + + { + // Make sure we have somewhere to flee to + + if ( !flee_node ) + return BEHAVIOR_SUCCESS; + + float radius=96.0f; + chase.SetGoal( flee_node->origin, radius, self ); + + // Make a racket + + self.Chatter( "snd_panic", 3.0f ); + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + // Try to get to flee node + + if ( chase.Evaluate( self ) != Steering::EVALUATING ) + { + // See if we are done fleeing + + if ( !self.sensoryPerception->CanSeeEntity(&self, currentEnemy , true, true )) + return BEHAVIOR_SUCCESS; + + // Find a new spot to flee to + + FindFleeNode( self ); + } + + return BEHAVIOR_EVALUATING; + } + +void Flee::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +/**************************************************************************** + + PlayAnim Class Definition + +****************************************************************************/ +/* +CLASS_DECLARATION( Behavior, PlayAnim, NULL ) + { + { &EV_Behavior_Args, SetArgs }, + { NULL, NULL } + }; + +void PlayAnim::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + } + +void PlayAnim::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nanim: %s\n", anim.c_str() ); + } + +void PlayAnim::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + { + if ( !self.SetAnim( anim, EV_Actor_EndBehavior ) ) + { + self.PostEvent( EV_Actor_EndBehavior, 0.0f ); + } + } + } + +BehaviorReturnCode_t PlayAnim::Evaluate + ( + Actor &self + ) + + { + return BEHAVIOR_EVALUATING; + } + +void PlayAnim::End + ( + Actor &self + ) + + { + self.RemoveAnimDoneEvent(); + } +*/ + + +/**************************************************************************** + + FindCover Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FindCover, NULL ) + { + { &EV_Behavior_Args, &FindCover::SetArgs }, + { NULL, NULL } + }; + +void FindCover::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + crouch_anim = ev->GetString( 2 ); + } + +PathNode *FindCover::FindCoverNode +( + Actor &self + ) + +{ + PathNode *bestnode; + float bestdist; + PathNode *desperatebestnode; + float desperatebestdist; + FindCoverPath find; + Path *path; + Vector delta; + float dist; + Vector pos; + + pos = self.origin; + + // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + bestdist = 999999999.0f; + bestnode = NULL; + + // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + desperatebestdist = 999999999.0f; + desperatebestnode = NULL; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return NULL; + + PathNode *node = NULL; + for ( int i = 1 ; i <= thePathManager.NumberOfSpecialNodes(); i++ ) + { + node = thePathManager.GetSpecialNode( i ); + + if ( node && ( node->nodeflags & ( AI_DUCK | AI_COVER ) ) && + ( ( node->occupiedTime <= level.time ) || ( node->entnum == self.entnum ) ) ) + { + // get the distance squared (faster than getting real distance) + delta = node->origin - pos; + dist = delta * delta; + + if ( ( dist < bestdist ) && ( !self.sensoryPerception->CanSeeEntity ( node->origin , currentEnemy , true, true ) ||//) )//|| + ( ( node->nodeflags & AI_DUCK ) && !self.sensoryPerception->CanSeeEntity( node->origin - Vector( 0.0f, 0.0f, 32.0f ) , currentEnemy , true , true ) ) ) ) + { + bestnode = node; + bestdist = dist; + } + else if ( ( dist < desperatebestdist ) && ( desperatebestdist > ( 64.0f * 64.0f ) ) ) + { + desperatebestnode = node; + desperatebestdist = dist; + } + } + } + + if ( !bestnode ) + { + bestnode = desperatebestnode; + } + + if ( bestnode ) + { + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + path = find.FindPath( self.origin, bestnode->origin ); + if ( path ) + { + node = path->End(); + + // Mark node as occupied for a short time + node->occupiedTime = level.time + 1.5f; + node->entnum = self.entnum; + + float radius=96.0f; + chase.SetGoal( node->origin, radius, self ); + + delete path; + path = NULL; + + return node; + } + } + + return NULL; +} + +void FindCover::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase.ShowInfo( self ); + + gi.Printf( "\nstate: %d\n", state ); + gi.Printf( "anim: %s\n", anim.c_str() ); + gi.Printf( "nextsearch: %f\n", nextsearch ); + } + +void FindCover::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + if ( !crouch_anim.length() ) + { + crouch_anim = "crouch_down"; + } + + movegoal = NULL; + state = 0; + } + +BehaviorReturnCode_t FindCover::Evaluate + ( + Actor &self + ) + + { + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( !movegoal ) + { + state = 0; + } + + switch( state ) + { + case 0 : + // Checking for cover + chase.Begin( self ); + movegoal = FindCoverNode( self ); + if ( !movegoal ) + { + // Couldn't find any! + return BEHAVIOR_SUCCESS; + } + + // Found cover, going to it + if ( anim.length() && ( ( anim != self.animname ) || self.newanim.length() ) ) + { + self.SetAnim( anim ); + } + + state = 1; + nextsearch = level.time + 1.0f; + + // lint -fallthrough + case 1 : + if ( chase.Evaluate( self ) == Steering::EVALUATING ) + { + if ( nextsearch < level.time ) + { + state = 0; + } + return BEHAVIOR_EVALUATING; + } + + // Reached cover + if ( self.sensoryPerception->CanSeeEntity ( &self , currentEnemy , true , true ) ) + { + state = 0; + } + + if ( movegoal->nodeflags & AI_DUCK ) + { + // ducking + self.SetAnim( crouch_anim.c_str() ); + } + else + { + // standing + self.SetAnim( "idle" ); + } + + chase.End( self ); + return BEHAVIOR_SUCCESS; + break; + } + + return BEHAVIOR_EVALUATING; + } + +void FindCover::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +/**************************************************************************** + + FindFlee Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FindFlee, NULL ) + { + { &EV_Behavior_Args, &FindFlee::SetArgs }, + { NULL, NULL } + }; + +void FindFlee::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + } + +PathNode *FindFlee::FindFleeNode + ( + Actor &self + ) + + { + PathNode *bestnode; + float bestdist; + PathNode *desperatebestnode; + float desperatebestdist; + FindFleePath find; + Path *path; + Vector delta; + float dist; + Vector pos; + + pos = self.origin; + + // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + bestdist = 999999999.0f; + bestnode = NULL; + + // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + desperatebestdist = 999999999.0f; + desperatebestnode = NULL; + + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return NULL; + + PathNode *node = NULL; + for ( int i = 1 ; i <= thePathManager.NumberOfSpecialNodes(); i++ ) + { + node = thePathManager.GetSpecialNode( i ); + + if ( node && ( node->nodeflags & AI_FLEE ) && + ( ( node->occupiedTime <= level.time ) || ( node->entnum == self.entnum ) ) ) + { + // get the distance squared (faster than getting real distance) + delta = node->origin - pos; + dist = delta * delta; + + if ( ( dist < bestdist ) && !self.sensoryPerception->CanSeeEntity( node->origin , currentEnemy , true, true ) ) + { + bestnode = node; + bestdist = dist; + } + else if ( ( dist < desperatebestdist ) && ( desperatebestdist > ( 64.0f * 64.0f ) ) ) + { + desperatebestnode = node; + desperatebestdist = dist; + } + + + } + } + + if ( !bestnode ) + { + bestnode = desperatebestnode; + } + + if ( bestnode ) + { + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + path = find.FindPath( self.origin, bestnode->origin ); + if ( path ) + { + node = path->End(); + + // Mark node as occupied for a short time + node->occupiedTime = level.time + 1.5f; + node->entnum = self.entnum; + + float radius=96.0f; + chase.SetGoal( node->origin, radius, self ); + + delete path; + path = NULL; + + return node; + } + } + + return NULL; + } + +void FindFlee::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase.ShowInfo( self ); + + gi.Printf( "\nstate: %d\n", state ); + gi.Printf( "anim: %s\n", anim.c_str() ); + gi.Printf( "nextsearch: %f\n", nextsearch ); + } + +void FindFlee::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + movegoal = NULL; + state = 0; + } + +BehaviorReturnCode_t FindFlee::Evaluate + ( + Actor &self + ) + + { + if ( !movegoal ) + { + state = 0; + } + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + switch( state ) + { + case 0 : + // Checking for flee node + chase.Begin( self ); + movegoal = FindFleeNode( self ); + if ( !movegoal ) + { + // Couldn't find any! + return BEHAVIOR_SUCCESS; + } + + // Found flee node, going to it + if ( anim.length() && ( anim != self.animname || self.newanim.length() ) ) + { + self.SetAnim( anim ); + } + + state = 1; + nextsearch = level.time + 1.0f; + + // lint -fallthrough + case 1 : + if ( chase.Evaluate( self ) == Steering::EVALUATING ) + { + if ( nextsearch < level.time ) + { + state = 0; + } + return BEHAVIOR_EVALUATING; + } + + if ( self.sensoryPerception ) + { + // Reached cover + if ( self.sensoryPerception->CanSeeEntity( &self , currentEnemy , true , true ) ) + { + state = 0; + } + else + { + // standing + self.SetAnim( "idle" ); + chase.End( self ); + return BEHAVIOR_SUCCESS; + } + } + + break; + } + + return BEHAVIOR_EVALUATING; + } + +void FindFlee::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +/**************************************************************************** + + FindEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FindEnemy, NULL ) + { + { &EV_Behavior_Args, &FindEnemy::SetArgs }, + { NULL, NULL } + }; + +void FindEnemy::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + } + +void FindEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase.ShowInfo( self ); + + gi.Printf( "\nstate: %d\n", state ); + gi.Printf( "nextsearch: %f\n", nextsearch ); + gi.Printf( "anim: %s\n", anim.c_str() ); + } + +void FindEnemy::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + movegoal = NULL; + lastSearchNode = NULL; + state = 0; + } + +PathNode *FindEnemy::FindClosestSightNode + ( + Actor &self + ) + + { + PathNode *bestnode; + float bestdist; + Vector delta; + float dist; + Vector pos; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( currentEnemy ) + { + pos = currentEnemy->origin; + } + else + { + pos = self.origin; + } + + // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + bestdist = 999999999; + bestnode = NULL; + + for ( int i = 1 ; i <= thePathManager.NumberOfSpecialNodes(); i++ ) + { + PathNode *node = thePathManager.GetSpecialNode( i ); + + if ( node && ( ( node->occupiedTime <= level.time ) || ( node->entnum != self.entnum ) ) ) + { + // get the distance squared (faster than getting real distance) + delta = node->origin - pos; + dist = delta * delta; + if ( ( dist < bestdist ) && self.sensoryPerception->CanSeeEntity( node->origin, currentEnemy , true , true ) ) + { + bestnode = node; + bestdist = dist; + } + + } + } + + return bestnode; + } + +BehaviorReturnCode_t FindEnemy::Evaluate + ( + Actor &self + ) + + { + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( nextsearch < level.time ) + { + // check if we should search for the first time + if ( !lastSearchNode ) + { + state = 0; + } + else + { + // search less often if we're far away + nextsearch = self.DistanceTo( currentEnemy ) * ( 1.0f / 512.0f ); + if ( nextsearch < 1.0f ) + { + nextsearch = 1.0f; + } + nextsearch += level.time; + + // don't search again if our enemy hasn't moved very far + if ( currentEnemy->WithinDistance( lastSearchPos, 256.0f ) ) + { + state = 0; + } + } + } + + switch( state ) + { + case 0 : + // Searching for enemy + chase.Begin( self ); + lastSearchPos = currentEnemy->origin; + movegoal = thePathManager.NearestNode( lastSearchPos, &self ); + if ( !movegoal ) + { + movegoal = thePathManager.NearestNode( lastSearchPos, &self, false ); + } + + lastSearchNode = movegoal; + if ( movegoal ) + { + Path *path; + FindEnemyPath find; + PathNode *from; + + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + from = thePathManager.NearestNode( self.origin, &self ); + if ( ( from == movegoal ) && ( self.DistanceTo( from->origin ) < 8.0f ) ) + { + movegoal = NULL; + from = NULL; + } + + if ( from ) + { + path = find.FindPath( from, movegoal ); + if ( path ) + { + float radius=96.0f; + chase.SetGoal( movegoal->origin, radius, self ); + + delete path; + path = NULL; + } + else + { + movegoal = NULL; + } + } + } + + if ( !movegoal ) + { + if ( self.sensoryPerception->CanSeeEntity( &self , currentEnemy , true , true ) || ( !currentEnemy->groundentity && !self.waterlevel ) ) + { + float radius=96.0f; + chase.SetGoal( currentEnemy->origin, radius, self ); + } + else + { + // Couldn't find enemy + // since we can't reach em + // clear out enemy state + self.enemyManager->ClearCurrentEnemy(); + return BEHAVIOR_SUCCESS; + } + } + + // Found enemy, going to it + if ( anim.length() && ( anim != self.animname || self.newanim.length() ) ) + { + self.SetAnim( anim ); + } + + state = 1; + + // lint -fallthrough + case 1 : + if ( self.CanAttack( currentEnemy, false ) ) + { + // Reached enemy + chase.End( self ); + return BEHAVIOR_SUCCESS; + } + + if ( chase.Evaluate( self ) != Steering::EVALUATING ) + { + state = 0; + nextsearch = 0; + } + break; + } + + return BEHAVIOR_EVALUATING; + } + +void FindEnemy::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +/**************************************************************************** + + AimAndShoot Class Definition + +****************************************************************************/ +#define START_AIM 0 +#define AIM 1 +#define FIRE 2 + +CLASS_DECLARATION( Behavior, AimAndShoot, NULL ) + { + { &EV_Behavior_Args, &AimAndShoot::SetArgs }, + { &EV_Behavior_AnimDone, &AimAndShoot::AnimDone }, + { NULL, NULL } + }; + +AimAndShoot::AimAndShoot() + { + maxshots = 1; + numshots = 0; + aim_time = 0.0f; + enemy_health = 0; + animdone = false; + } + +void AimAndShoot::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\naim:\n" ); + aim.ShowInfo( self ); + + gi.Printf( "\nmode: %d\n", mode ); + gi.Printf( "maxshots: %d\n", maxshots ); + gi.Printf( "numshots: %d\n", numshots ); + gi.Printf( "animdone: %d\n", animdone ); + } + +void AimAndShoot::Begin + ( + Actor &self + ) + + { + if ( aimanim.length() ) + { + self.SetAnim( aimanim.c_str() ); + mode = START_AIM; + } + else + { + self.SetAnim( "idle" ); + mode = AIM; + } + } + +void AimAndShoot::SetMaxShots + ( + int num + ) + + { + maxshots = ( num / 2 ) + (int)G_Random( (float)num ); + } + +void AimAndShoot::SetArgs + ( + Event *ev + ) + + { + fireanim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + { + maxshots = ev->GetInteger ( 2 ); + } + + if ( ev->NumArgs() > 2 ) + { + aimanim = ev->GetString ( 3 ); + } + + int turnTowardsEnemy = ev->GetInteger( 3 ); + int speed = ev->GetInteger( 4 ); + + torsoTurn.SetRequiredParameters( turnTowardsEnemy, speed, 0 ); + } + +void AimAndShoot::AnimDone + ( + Event *ev + ) + + { + animdone = true; + } + +BehaviorReturnCode_t AimAndShoot::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector angles; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + switch( mode ) + { + case START_AIM : + + if ( !currentEnemy || !self.CanAttack( currentEnemy, false )) + return BEHAVIOR_SUCCESS; + + animdone = false; + if ( aimanim.length() ) + self.SetAnim( aimanim.c_str() ); + + // save off time, in case we aim for too long + aim_time = level.time + 1.0f; + mode = AIM; + + // lint -fallthrough + case AIM : + + if ( aimanim.length() ) + { + if ( !currentEnemy || ( aim_time < level.time ) ) + return BEHAVIOR_SUCCESS; + + aim.SetTarget( currentEnemy ); + + if ( aim.Evaluate( self ) ) + { + break; + } + } + else + { + if ( self.IsEntityAlive( currentEnemy ) ) + { + dir = currentEnemy->centroid - self.origin; + angles = dir.toAngles(); + + self.angles[YAW] = angles[YAW]; + self.setAngles( self.angles ); + } + } + + // don't go into our firing animation until our weapon is ready, and we are on target + if ( currentEnemy && self.CanAttack( currentEnemy, true ) ) + { + animdone = false; + self.Chatter( "snd_inmysights", 5.0f ); + self.SetAnim( fireanim.c_str(), EV_Actor_NotifyBehavior ); + enemy_health = currentEnemy->health; + mode = 2; + } + else if ( !currentEnemy || currentEnemy->deadflag || + ( currentEnemy->health <= 0.0f ) || !self.CanAttack( currentEnemy, false ) ) + { + // either our enemy is dead, or we can't shoot the enemy from here + return BEHAVIOR_SUCCESS; + } + break; + + case FIRE : + // Fire + if ( animdone ) + { + aim.SetTarget( currentEnemy ); + aim.Evaluate( self ); + + self.times_done++; + + if ( !currentEnemy || ( currentEnemy->health < enemy_health ) ) + { + self.Chatter( "snd_attacktaunt", 4.0f ); + } + else + { + self.Chatter( "snd_missed", 7.0f ); + } + + animdone = false; + numshots++; + + if ( ( numshots >= maxshots ) || !currentEnemy || currentEnemy->deadflag || + ( currentEnemy->health <= 0.0f ) || !self.CanAttack( currentEnemy, false ) ) + { + // either we're out of shots, our enemy is dead, or we can't shoot the enemy from here + + return BEHAVIOR_SUCCESS; + } + else if ( !self.CanAttack( currentEnemy, false ) ) + { + // weapon not ready or not aimed at enemy, so just keep trying to get enemy in our sights + if ( aimanim.length() ) + { + self.SetAnim( aimanim.c_str() ); + } + // + // save off time, in case we aim for too long + // + aim_time = level.time + 1.0f; + mode = 1; + } + else + { + // keep firing + self.SetAnim( fireanim.c_str(), EV_Actor_NotifyBehavior ); + enemy_health = currentEnemy->health; + } + } + break; + } + + return BEHAVIOR_EVALUATING; + } + +void AimAndShoot::End + ( + Actor &self + ) + + { + aim.End( self ); + //self.SetAnim( "idle" ); + } + +/**************************************************************************** + + AimAndMelee Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, AimAndMelee, NULL ) + { + { &EV_Behavior_Args, &AimAndMelee::SetArgs }, + { &EV_Behavior_AnimDone, &AimAndMelee::AnimDone }, + { NULL, NULL } + }; + +AimAndMelee::AimAndMelee() + { + maxshots = 1; + anim_name = "melee"; + mode = 0; + numshots = 0; + animdone = false; + } + +void AimAndMelee::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\naim:\n" ); + aim.ShowInfo( self ); + + gi.Printf( "\nmode: %d\n", mode ); + gi.Printf( "maxshots: %d\n", maxshots ); + gi.Printf( "numshots: %d\n", numshots ); + gi.Printf( "animdone: %d\n", animdone ); + } + +void AimAndMelee::Begin + ( + Actor &self + ) + + { + self.SetAnim( "idle" ); + } + +void AimAndMelee::SetArgs + ( + Event *ev + ) + + { + anim_name = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + { + maxshots = ev->GetInteger( 2 ); + } + } + +void AimAndMelee::AnimDone + ( + Event *ev + ) + + { + animdone = true; + } + +BehaviorReturnCode_t AimAndMelee::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + switch( mode ) + { + case 0 : + if ( !currentEnemy ) + { + return BEHAVIOR_SUCCESS; + } + + if ( self.IsEntityAlive( currentEnemy ) ) + { + dir = currentEnemy->centroid - self.origin; + self.angles[YAW] = dir.toYaw(); + self.setAngles( self.angles ); + } + + numshots++; + animdone = false; + + // melee + self.SetAnim( anim_name.c_str() , EV_Actor_NotifyBehavior ); + self.Chatter( "snd_attacktaunt", 4.0f ); + mode = 1; + + // lint -fallthrough + case 1 : + // finish up the attack + if ( animdone ) + { + self.times_done++; + if ( numshots >= maxshots ) + { + return BEHAVIOR_SUCCESS; + } + + mode = 0; + } + break; + } + + return BEHAVIOR_EVALUATING; + } + +void AimAndMelee::End + ( + Actor &self + ) + + { + aim.End( self ); + } + +/**************************************************************************** + + FallToDeath Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FallToDeath, NULL ) + { + { &EV_Behavior_Args, &FallToDeath::SetArgs }, + { NULL, NULL } + }; + +FallToDeath::FallToDeath() + { + + } + +void FallToDeath::SetArgs + ( + Event *ev + ) + { + // Get the Parameters + forwardmove = ev->GetFloat( 1 ); + sidemove = ev->GetFloat( 2 ); + speed = ev->GetFloat( 3 ); + startAnim = ev->GetString( 4 ); + fallAnim = ev->GetString( 5 ); + deathAnim = ev->GetString( 6 ); + + if ( ev->NumArgs() > 6 ) + impulse_time = ( ev->GetFloat( 7 ) ) + level.time; + else + impulse_time = 0; + + animdone = false; + state = 0; + did_impulse = false; + } + +void FallToDeath::Begin + ( + Actor &self + ) + + { + //Initialize our stuff + + + self.SetAnim( startAnim.c_str() , EV_Anim_Done ); + self.ChangeAnim(); + } + +BehaviorReturnCode_t FallToDeath::Evaluate + ( + Actor &self + ) + + { + if ( !did_impulse && ( level.time >= impulse_time ) ) + { + Vector( 0.0f, self.angles.y, 0.0f ).AngleVectors( &yaw_forward, &yaw_left ); + self.velocity = ( yaw_forward * forwardmove ) - ( yaw_left * sidemove ); + distance = self.velocity.length(); + self.velocity *= speed / distance; + time = distance / speed; + self.velocity[ 2 ] = sv_currentGravity->integer * time * 0.5f; + did_impulse = true; + } + + + animdone = self.GetActorFlag( ACTOR_FLAG_ANIM_DONE ); + self.SetActorFlag( ACTOR_FLAG_ANIM_DONE, false ); + + switch( state ) + { + case 0: + if ( did_impulse ) + state = 1; + // this is here so that we at least hit this function at least once + // this gaves the character the chance to leave the ground, nulling out + // self.groundentity + break; + case 1: + if ( animdone ) + { + animdone = false; + self.SetAnim( fallAnim.c_str(), EV_Anim_Done ); + state = 2; + } + if ( self.groundentity ) + state = 2; + break; + case 2: + // + // wait for the character to hit the ground + // + if ( self.groundentity ) + { + // + // if we have an anim, we go to state 3 + // + animdone = false; + self.SetAnim( deathAnim.c_str(), EV_Actor_Dead ); + state = 3; + } + break; + case 3: + // + // we are on the ground and waiting for our landing animation to finish + // + + if ( animdone ) + { + return BEHAVIOR_SUCCESS; + } + break; + } + + return BEHAVIOR_EVALUATING; + } + +void FallToDeath::End + ( + Actor &self + ) + + { + Event *ev; + ev = new Event( EV_Killed ); + + ev->AddEntity( NULL ); + ev->AddFloat( 250.0f ); + ev->AddEntity( NULL ); + ev->AddInteger( MOD_FALLING ); + ev->AddInteger( 1 ); + + self.PostEvent( ev , 0.0f ); + } + +/**************************************************************************** + + JumpToPathNode Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, JumpToPathNode, NULL ) + { + { &EV_Behavior_Args, &JumpToPathNode::SetArgs }, + { NULL, NULL } + }; + +void JumpToPathNode::SetArgs( Event *ev ) +{ + movegoal = thePathManager.FindNode( ev->GetString( 1 ) ); + + if ( movegoal ) + jump.SetGoal( movegoal->origin ); + else + { + Entity *entity = ev->GetEntity( 1 ); + + if ( entity ) + jump.SetEntity( entity ); + } + + if ( ev->NumArgs() > 1 ) + jump.SetLaunchAngle( ev->GetFloat( 2 ) ); + + +} + +void JumpToPathNode::Begin( Actor &self ) +{ + jump.Begin( self ); +} + +BehaviorReturnCode_t JumpToPathNode::Evaluate( Actor &self ) +{ + if ( jump.Evaluate( self ) != Steering::SUCCESS ) + return BEHAVIOR_EVALUATING; + + return BEHAVIOR_SUCCESS; +} + +void JumpToPathNode::End( Actor &self ) +{ + jump.End( self ); +} + +/**************************************************************************** + + LeapToEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, LeapToEnemy, NULL ) + { + { &EV_Behavior_Args, &LeapToEnemy::SetArgs }, + { NULL, NULL } + }; + +void LeapToEnemy::SetArgs + ( + Event *ev + ) + + { + jump.SetLaunchAngle( ev->GetFloat( 1 ) ); + } + +void LeapToEnemy::Begin + ( + Actor &self + ) + + { + Entity* ent = self.enemyManager->GetCurrentEnemy(); + jump.SetEntity(ent); + jump.Begin(self); + } + +BehaviorReturnCode_t LeapToEnemy::Evaluate + ( + Actor &self + ) + + { + if ( jump.Evaluate( self ) == Steering::EVALUATING ) + return BEHAVIOR_EVALUATING; + + return BEHAVIOR_SUCCESS; + } + +void LeapToEnemy::End + ( + Actor &self + ) + + { + jump.End(self); + } + +/**************************************************************************** + + FlyToPoint Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyToPoint, NULL ) + { + { NULL, NULL } + }; + +FlyToPoint::FlyToPoint() + { + turn_speed = 10.0; + old_turn_speed = turn_speed; + speed = 480.0; + random_allowed = true; + force_goal = false; + adjustYawAndRoll = true; + offsetOrigin = false; + + avoidtime = 0; + stuck = 0; + use_temp_goal = false; + + old_forward_speed = 0; + } + +void FlyToPoint::SetTurnSpeed( float new_turn_speed ) + { + turn_speed = new_turn_speed; + } + +void FlyToPoint::SetGoalPoint( const Vector &goal_point ) + { + if ( goal_point != goal ) + avoidtime = 0; + + goal = goal_point; + } + +void FlyToPoint::SetRandomAllowed( qboolean allowed ) + { + random_allowed = allowed; + } + +void FlyToPoint::SetSpeed( float speed_value ) + { + speed = speed_value; + } + +void FlyToPoint::ForceGoal( void ) + { + force_goal = true; + } + +void FlyToPoint::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyToPoint::Begin + ( + Actor &self + ) + + { + avoidtime = 0; + old_forward_speed = self.movementSubsystem->getForwardSpeed(); + stuck = 0; + use_temp_goal = false; + } + +BehaviorReturnCode_t FlyToPoint::Evaluate + ( + Actor &self + ) + + { + trace_t trace; + Vector dir; + Vector ang; + float time; + float length; + float old_yaw; + float old_pitch; + + float x_offset = 0; + float y_offset = 0; + //float z_offset = 0; + + + if (offsetOrigin) + { + x_offset = self.centroid.x - self.origin.x; + y_offset = self.centroid.y - self.origin.y; + //z_offset = self.centroid.z - self.origin.z; + + self.origin.x+=x_offset; + self.origin.y+=y_offset; + //self.origin.z+=z_offset; + } + + if ( self.movementSubsystem->getLastMove() != STEPMOVE_OK ) + stuck++; + else + stuck = 0; + + + if ( stuck > 1 || ( avoidtime <= level.time ) ) + { + time = G_Random( .3f ) + .3f; + + use_temp_goal = false; + + if ( !force_goal ) + { + trace = G_Trace( self.origin, self.mins, self.maxs, goal, &self, self.edict->clipmask, false, "FlyToPoint" ); + + if ( ( trace.fraction < 0.5f ) || ( stuck > 2 ) ) + { + old_turn_speed = self.movementSubsystem->getTurnSpeed(); + self.movementSubsystem->setTurnSpeed( 60 ); + temp_goal = ChooseRandomDirection( self, goal, &time, MASK_WATER, false ); + + self.movementSubsystem->setTurnSpeed( old_turn_speed ); + use_temp_goal = true; + avoidtime = level.time + time; + + stuck = 0; + } + else + { + goal = trace.endpos; + avoidtime = level.time + time; + } + } + else + { + avoidtime = level.time + time; + } + + if ( use_temp_goal ) + dir = temp_goal - self.origin; + else + dir = goal - self.origin; + + length = dir.length(); + dir.normalize(); + + ang = dir.toAngles(); + + if ( ( length > 150.0f ) && random_allowed && !use_temp_goal ) + { + //ang[YAW] += G_Random( 20 ) - 5.0; + //ang[PITCH] += G_Random( 20 ) - 5.0; + } + + target_angle = ang; + + target_angle[YAW] = AngleNormalize360( target_angle[YAW] ); + target_angle[PITCH] = AngleNormalize360( target_angle[PITCH] ); + } + + if ( (self.angles[YAW] != target_angle[YAW]) || (self.angles[PITCH] != target_angle[PITCH]) ) + { + self.movementSubsystem->setForwardSpeed( speed * 0.8f ); + } + else + { + self.movementSubsystem->setForwardSpeed( speed ); + } + + old_yaw = self.angles[YAW]; + old_pitch = self.angles[PITCH]; + + ang[YAW] = LerpAngle( self.angles[YAW], target_angle[YAW], turn_speed ); + ang[PITCH] = LerpAngle( self.angles[PITCH], target_angle[PITCH], turn_speed ); + ang[ROLL] = 0; + + if( adjustYawAndRoll ) + { + + if ( ( AngleDelta( ang[YAW], old_yaw ) > 0.0f ) && ( ( ang[ROLL] > 315.0f ) || ( ang[ROLL] <= 45.0f ) ) ) + { + ang[ROLL] += 5.0f; + } + else if ( ( AngleDelta( ang[YAW], old_yaw ) < 0.0f ) && ( ( ang[ROLL] < 45.0f ) || ( ang[ROLL] >= 315.0f ) ) ) + { + ang[ROLL] -= 5.0f; + } + else + { + if ( ang[ROLL] != 0.0f ) + { + + if ( ang[ROLL] < 5.0f || ang[ROLL] > 355.0f ) + { + ang[ROLL] = 0.0f; + } + else + { + if ( ang[ROLL] < 180.0f ) + ang[ROLL] += 5.0f; + else + ang[ROLL] -= 5.0f; + } + } + } + } + + ang[YAW] = AngleNormalize360( ang[YAW] ); + ang[PITCH] = AngleNormalize360( ang[PITCH] ); + ang[ROLL] = AngleNormalize360( ang[ROLL] ); + + // Don't get stuck if still turning + + if ( ( AngleDelta( ang[YAW], old_yaw ) > .5 ) || ( AngleDelta( ang[YAW], old_yaw ) < -.5 ) || + ( AngleDelta( ang[PITCH], old_pitch ) > .5 ) || ( AngleDelta( ang[PITCH], old_pitch ) < -.5 ) ) + { + stuck = 0; + } + + self.setAngles( ang ); + + if (offsetOrigin) + { + self.origin.x-=x_offset; + self.origin.y-=y_offset; + //self.origin.z-=z_offset; + } + + return BEHAVIOR_EVALUATING; + } + +float FlyToPoint::LerpAngle( float old_angle, float new_angle, float lerp_amount ) + { + float diff; + float abs_diff; + float lerp_angle; + + new_angle = AngleNormalize360( new_angle ); + old_angle = AngleNormalize360( old_angle ); + + diff = new_angle - old_angle; + + if ( diff > 180.0f ) + { + diff -= 360.0f; + } + + if ( diff < -180.0f ) + { + diff += 360.0f; + } + + lerp_angle = old_angle; + + abs_diff = diff; + + if ( abs_diff < 0.0f ) + { + abs_diff = -abs_diff; + } + + if ( abs_diff < lerp_amount ) + { + lerp_amount = abs_diff; + } + + if ( diff < 0.0f ) + { + lerp_angle -= lerp_amount; + } + else if ( diff > 0.0f ) + { + lerp_angle += lerp_amount; + } + + lerp_angle = AngleNormalize360( lerp_angle ); + + return lerp_angle; + } + +void FlyToPoint::End + ( + Actor &self + ) + + { + self.movementSubsystem->setForwardSpeed( old_forward_speed ); + } + +/**************************************************************************** + + FlyCloseToEnemy Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyCloseToEnemy, NULL ) + { + { &EV_Behavior_Args, &FlyCloseToEnemy::SetArgs }, + { NULL, NULL } + }; + +FlyCloseToEnemy::FlyCloseToEnemy() + { + speed = 0; + turn_speed = 10.0; + anim = "fly"; + adjustPitch = true; + next_goal_time = 0; + } + +void FlyCloseToEnemy::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + turn_speed = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + speed = ev->GetFloat( 3 ); + + if (ev->NumArgs() > 3 ) + fly.setAdjustYawAndRoll( ev->GetBoolean( 4 ) ); + + if (ev->NumArgs() > 4 ) + adjustPitch = ev->GetBoolean( 5 ); + } + + +void FlyCloseToEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyCloseToEnemy::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + fly.Begin( self ); + fly.SetTurnSpeed( turn_speed ); + + if ( speed ) + fly.SetSpeed( speed ); + } + +BehaviorReturnCode_t FlyCloseToEnemy::Evaluate + ( + Actor &self + ) + + { + Vector goal; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( !self.IsEntityAlive( currentEnemy ) ) + return BEHAVIOR_SUCCESS; + + if ( next_goal_time <= level.time ) + { + goal = currentEnemy->centroid; + + if (!adjustPitch) + { + goal.z = self.origin.z; + } + + //goal[0] += G_Random( 30 ) - 15.0; + //goal[1] += G_Random( 30 ) - 15.0; + //goal[2] += G_Random( 60 ) - 30.0; + + fly.SetGoalPoint( goal ); + + next_goal_time = level.time + .5f; + } + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +void FlyCloseToEnemy::End + ( + Actor &self + ) + + { + fly.End( self ); + } + +/**************************************************************************** + + FlyCloseToPlayer Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyCloseToPlayer, NULL ) + { + { &EV_Behavior_Args, &FlyCloseToPlayer::SetArgs }, + { NULL, NULL } + }; + +FlyCloseToPlayer::FlyCloseToPlayer() + { + speed = 0; + turn_speed = 10.0; + anim = "fly"; + next_goal_time = 0; + } + +void FlyCloseToPlayer::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + turn_speed = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + speed = ev->GetFloat( 3 ); + } + +void FlyCloseToPlayer::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyCloseToPlayer::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + fly.Begin( self ); + fly.SetTurnSpeed( turn_speed ); + + if ( speed ) + fly.SetSpeed( speed ); + } + +BehaviorReturnCode_t FlyCloseToPlayer::Evaluate + ( + Actor &self + ) + + { + Vector goal; + Entity *player; + + + player = g_entities[ 0 ].entity; + + + if ( !self.IsEntityAlive( player ) ) + return BEHAVIOR_SUCCESS; + + if ( next_goal_time <= level.time ) + { + goal = player->centroid; + + //goal[0] += G_Random( 30 ) - 15.0; + //goal[1] += G_Random( 30 ) - 15.0; + //goal[2] += G_Random( 60 ) - 30.0; + + fly.SetGoalPoint( goal ); + + next_goal_time = level.time + .5f; + } + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +void FlyCloseToPlayer::End + ( + Actor &self + ) + + { + fly.End( self ); + } + + +/**************************************************************************** + + FlyCloseToParent Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyCloseToParent, NULL ) + { + { &EV_Behavior_Args, &FlyCloseToParent::SetArgs }, + { NULL, NULL } + }; + +FlyCloseToParent::FlyCloseToParent() + { + speed = 0.0f; + turn_speed = 10.0f; + anim = "fly"; + next_goal_time = 0.0f; + } + +void FlyCloseToParent::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + turn_speed = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + speed = ev->GetFloat( 3 ); + } + +void FlyCloseToParent::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyCloseToParent::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + fly.Begin( self ); + fly.SetTurnSpeed( turn_speed ); + + if ( speed ) + fly.SetSpeed( speed ); + } + +BehaviorReturnCode_t FlyCloseToParent::Evaluate + ( + Actor &self + ) + + { + Vector goal; + + + if ( !self.spawnparent || !self.spawnparent->isSubclassOf( Actor ) ) + return BEHAVIOR_SUCCESS; + + if ( next_goal_time <= level.time ) + { + Actor* act; + + act = (Actor*)self.spawnparent; + goal = act->centroid; + + fly.SetGoalPoint( goal ); + + next_goal_time = level.time + .5f; + } + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +void FlyCloseToParent::End + ( + Actor &self + ) + + { + fly.End( self ); + } + + +/**************************************************************************** + + FlyDescend Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FlyDescend, NULL ) + { + { &EV_Behavior_Args, &FlyDescend::SetArgs }, + { NULL, NULL } + }; + +FlyDescend::FlyDescend() + { + anim = "fly"; + height = 500; + speed = 0; + next_height_check = level.time + 2.0f; + last_check_height = 0.0f; + } + +void FlyDescend::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + height = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + speed = ev->GetFloat( 3 ); + } + +void FlyDescend::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyDescend::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + fly.Begin( self ); + + height = currentEnemy->origin.z; + + goal = self.origin; + goal.z = height; + + fly.SetTurnSpeed( 1.0f ); + + fly.SetGoalPoint( goal ); + + last_check_height = self.origin.z; + + if ( speed ) + fly.SetSpeed( speed ); + } + +BehaviorReturnCode_t FlyDescend::Evaluate + ( + Actor &self + ) + + { + if ( self.origin.z <= height || !self.enemyManager->GetCurrentEnemy()) + { + return BEHAVIOR_SUCCESS; + } + + if ( next_height_check < level.time ) + { + if ( self.origin.z < ( last_check_height + 25.0f ) ) + return BEHAVIOR_SUCCESS; + + next_height_check = level.time + 2.0f; + last_check_height = self.origin.z; + } + + if ( self.movementSubsystem->getLastMove() == STEPMOVE_OK ) + fly.SetGoalPoint( goal ); + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +void FlyDescend::End + ( + Actor &self + ) + + { + fly.End( self ); + } + + + +/**************************************************************************** + + FlyWander Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyWander, NULL ) + { + { &EV_Behavior_Args, &FlyWander::SetArgs }, + { NULL, NULL } + }; + +FlyWander::FlyWander() + { + turn_speed = 10.0; + anim = "fly"; + change_course_time = 5.0; + speed = 200; + try_to_go_up = false; + next_change_course_time = 0; + original_z = 0; + } + +void FlyWander::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + speed = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + turn_speed = ev->GetFloat( 3 ); + + if ( ev->NumArgs() > 3 ) + change_course_time = ev->GetFloat( 4 ); + + if ( ev->NumArgs() > 4 ) + try_to_go_up = ev->GetBoolean( 5 ); + + if (ev->NumArgs() > 5 ) + fly.setAdjustYawAndRoll( ev->GetBoolean( 6 ) ); + + if (ev->NumArgs() > 6 ) + fly.setOffsetOrigin( ev->GetBoolean ( 7 ) ); + + } + +void FlyWander::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyWander::Begin + ( + Actor &self + ) + + { + original_z = self.origin.z; + + if ( anim.length() ) + self.SetAnim( anim ); + + fly.Begin( self ); + fly.SetTurnSpeed( turn_speed ); + fly.SetSpeed( speed ); + } + +BehaviorReturnCode_t FlyWander::Evaluate + ( + Actor &self + ) + + { + trace_t trace; + Vector dir; + float length; + int goal_try; + Vector temp_goal; + float max_dist = 0.0f; + + + dir = goal - self.origin; + length = dir.length(); + + if ( ( next_change_course_time <= level.time ) || ( length < 100.0f ) ) //self.lastmove != STEPMOVE_OK ) + { + for( goal_try = 0 ; goal_try < 5 ; goal_try++ ) + { + temp_goal = self.origin; + + temp_goal[0] += G_Random( 10000.0f ) - 5000.0f; + temp_goal[1] += G_Random( 10000.0f ) - 5000.0f; + + if ( try_to_go_up ) + temp_goal[2] += G_Random( 1000.0f ) - 250.0f; + else + temp_goal[2] += G_Random( 100.0f ) - 50.0f; + + trace = G_Trace( self.origin, self.mins, self.maxs, temp_goal, &self, self.edict->clipmask, false, "FlyWander" ); + + temp_goal = trace.endpos; + + dir = temp_goal - self.origin; + length = dir.length(); + + if ( length > max_dist ) + { + max_dist = length; + goal = temp_goal; + + if ( length > 1000.0f ) + break; + } + } + + fly.SetGoalPoint( goal ); + + next_change_course_time = level.time + change_course_time; + } + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +void FlyWander::End + ( + Actor &self + ) + + { + fly.End( self ); + } + +/**************************************************************************** + + FlyToNode Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyToNode, NULL ) + { + { &EV_Behavior_Args, &FlyToNode::SetArgs }, + { NULL, NULL } + }; + +FlyToNode::FlyToNode() + { + turn_speed = 10.0; + anim = "fly"; + speed = 200; + NumberOfNodes = 0; + } + +void FlyToNode::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + { + speed = ev->GetFloat( 2 ); + } + + if ( ev->NumArgs() > 2 ) + { + turn_speed = ev->GetFloat( 3 ); + } + + if ( ev->NumArgs() > 3 ) + { + NodeType = ev->GetString( 4 ); + } + + if ( ev->NumArgs() > 4 ) + { + NumberOfNodes = ev->GetInteger( 5 ); + } + } + +void FlyToNode::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyToNode::Begin + ( + Actor &self + ) + + { + + if ( anim.length() ) + self.SetAnim( anim ); + + fly.Begin( self ); + fly.SetTurnSpeed( turn_speed ); + fly.SetSpeed( speed ); + } + +BehaviorReturnCode_t FlyToNode::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector angles; + str pathnode_name; + PathNode *goal; + Vector teleport_position; + Vector attack_position; + + int bestNode = 0; + float bestDistance = -1; + + for(int i = 0; i <= NumberOfNodes; i++) + { + pathnode_name = NodeType + i; + + goal = thePathManager.FindNode( pathnode_name ); + + if( goal ) + { + float distanceTest = self.origin.length() - goal->origin.length(); + + if ( bestDistance < 0.0f ) + { + bestDistance = distanceTest; + bestNode = i; + } + else if (distanceTest < bestDistance ) + { + bestDistance = distanceTest; + bestNode = i; + } + } + } + + pathnode_name = NodeType + bestNode; + goal = thePathManager.FindNode( pathnode_name ); + + if ( !goal ) + { + gi.WDPrintf( "Can't find position %s\n", pathnode_name.c_str() ); + return BEHAVIOR_SUCCESS; + } + + fly.SetGoalPoint( goal->origin ); + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +void FlyToNode::End + ( + Actor &self + ) + + { + fly.End( self ); + } + +/**************************************************************************** + + FlyToRandomNode Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyToRandomNode, NULL ) + { + { &EV_Behavior_Args, &FlyToRandomNode::SetArgs }, + { NULL, NULL } + }; + +FlyToRandomNode::FlyToRandomNode() + { + turn_speed = 10.0; + anim = "fly"; + speed = 200; + CurrentNode = -1; + NeedNextNode = true; + NumberOfNodes = 0; + goal = NULL; + } + +void FlyToRandomNode::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + { + speed = ev->GetFloat( 2 ); + } + + if ( ev->NumArgs() > 2 ) + { + turn_speed = ev->GetFloat( 3 ); + } + + if ( ev->NumArgs() > 3 ) + { + NodeType = ev->GetString( 4 ); + } + + if ( ev->NumArgs() > 4 ) + { + NumberOfNodes = ev->GetInteger( 5 ); + } + } + +void FlyToRandomNode::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyToRandomNode::Begin + ( + Actor &self + ) + + { + + if ( anim.length() ) + self.SetAnim( anim ); + + fly.Begin( self ); + fly.SetTurnSpeed( turn_speed ); + fly.SetSpeed( speed ); + } + +BehaviorReturnCode_t FlyToRandomNode::Evaluate + ( + Actor &self + ) + + { + str pathnode_name; + Vector dir; + + if(NeedNextNode) + { + int x = (int)G_Random( (float)NumberOfNodes ); + if (x == CurrentNode) + x++; + + CurrentNode = x; + + pathnode_name = NodeType + CurrentNode; + + goal = thePathManager.FindNode( pathnode_name ); + + NeedNextNode = false; + } + + if ( !goal ) + { + gi.WDPrintf( "Can't find position %s\n", pathnode_name.c_str() ); + return BEHAVIOR_SUCCESS; + } + + fly.SetGoalPoint( goal->origin ); + fly.Evaluate( self ); + + dir = self.origin - goal->origin; + + if ( dir.length() < 100.0f ) + { + NeedNextNode = true; + } + + return BEHAVIOR_EVALUATING; + } + +void FlyToRandomNode::End + ( + Actor &self + ) + + { + fly.End( self ); + } + +/**************************************************************************** + + FlyToNodeNearestPlayer Class Definition + +****************************************************************************/ +CLASS_DECLARATION( Behavior, FlyToNodeNearestPlayer, NULL ) + { + { &EV_Behavior_Args, &FlyToNodeNearestPlayer::SetArgs }, + { NULL, NULL } + }; + +FlyToNodeNearestPlayer::FlyToNodeNearestPlayer() + { + turn_speed = 10.0; + anim = "fly"; + speed = 200; + CurrentNode = -1; + NeedNextNode = true; + NumberOfNodes = 0; + goal = NULL; + } + +void FlyToNodeNearestPlayer::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + { + speed = ev->GetFloat( 2 ); + } + + if ( ev->NumArgs() > 2 ) + { + turn_speed = ev->GetFloat( 3 ); + } + + if ( ev->NumArgs() > 3 ) + { + NodeType = ev->GetString( 4 ); + } + + if ( ev->NumArgs() > 4 ) + { + NumberOfNodes = ev->GetInteger( 5 ); + } + } + +void FlyToNodeNearestPlayer::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyToNodeNearestPlayer::Begin + ( + Actor &self + ) + + { + + if ( anim.length() ) + self.SetAnim( anim ); + + fly.Begin( self ); + fly.SetTurnSpeed( turn_speed ); + fly.SetSpeed( speed ); + } + +BehaviorReturnCode_t FlyToNodeNearestPlayer::Evaluate + ( + Actor &self + ) + + { + str pathnode_name; + Vector dir; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if(NeedNextNode) + { + int ClosestNode = 0; + float CheckLen = -1; + PathNode* Pnode = 0; + + for ( int i = 1; i <= NumberOfNodes; i++ ) + { + pathnode_name = NodeType + i; + Pnode = thePathManager.FindNode(pathnode_name); + + if ( !Pnode ) + { + gi.WDPrintf( "Can't find Pathnode %s\n", pathnode_name.c_str() ); + return BEHAVIOR_SUCCESS; + } + + if( currentEnemy) + { + dir = Pnode->origin - currentEnemy->centroid; + } + else + { + Entity* player = g_entities[0].entity; + dir = Pnode->origin - player->centroid; + } + + if ( CheckLen < 0.0f ) + { + CheckLen = dir.length(); + } + else + { + if(dir.length() < CheckLen) + { + CheckLen = dir.length(); + ClosestNode = i; + } + } + } + + pathnode_name = NodeType + ClosestNode; + + goal = thePathManager.FindNode( pathnode_name ); + + NeedNextNode = false; + } + + fly.SetGoalPoint( goal->origin ); + fly.Evaluate( self ); + + dir = self.origin - goal->origin; + + if ( dir.length() < 100.0f ) + { + NeedNextNode = true; + } + + return BEHAVIOR_EVALUATING; + } + +void FlyToNodeNearestPlayer::End + ( + Actor &self + ) + + { + fly.End( self ); + } + +/**************************************************************************** + + FlyNodePath Class Definition + +****************************************************************************/ +CLASS_DECLARATION( Behavior, FlyNodePath, NULL ) + { + { &EV_Behavior_Args, &FlyNodePath::SetArgs }, + { NULL, NULL } + }; + +FlyNodePath::FlyNodePath() + { + turn_speed = 10.0f; + anim = "fly"; + speed = 200.0f; + CurrentNode = -1; + NeedNextNode = true; + NumberOfNodes = 0; + goal = NULL; + } + +void FlyNodePath::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + { + speed = ev->GetFloat( 2 ); + } + + if ( ev->NumArgs() > 2 ) + { + turn_speed = ev->GetFloat( 3 ); + } + + if ( ev->NumArgs() > 3 ) + { + NodeType = ev->GetString( 4 ); + } + + if ( ev->NumArgs() > 4 ) + { + NumberOfNodes = ev->GetInteger( 5 ); + } + } + +void FlyNodePath::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyNodePath::Begin + ( + Actor &self + ) + + { + + if ( anim.length() ) + self.SetAnim( anim ); + + fly.Begin( self ); + fly.SetTurnSpeed( turn_speed ); + fly.SetSpeed( speed ); + } + +BehaviorReturnCode_t FlyNodePath::Evaluate + ( + Actor &self + ) + + { + str pathnode_name; + Vector dir; + + if(NeedNextNode) + { + CurrentNode++; + + if(CurrentNode > NumberOfNodes) + CurrentNode = 0; + + pathnode_name = NodeType + CurrentNode; + + goal = thePathManager.FindNode( pathnode_name ); + + NeedNextNode = false; + } + + if ( !goal ) + { + gi.WDPrintf( "Can't find position %s\n", pathnode_name.c_str() ); + return BEHAVIOR_SUCCESS; + } + + fly.SetGoalPoint( goal->origin ); + fly.Evaluate( self ); + + dir = self.origin - goal->origin; + + if ( dir.length() < 100.0f ) + { + NeedNextNode = true; + } + + if ( (CurrentNode == NumberOfNodes) && NeedNextNode ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void FlyNodePath::End + ( + Actor &self + ) + + { + fly.End( self ); + } + +/**************************************************************************** + + FlyCircle Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyCircle, NULL ) + { + { &EV_Behavior_Args, &FlyCircle::SetArgs }, + { NULL, NULL } + }; + +FlyCircle::FlyCircle() + { + anim = "fly"; + fly_clockwise = true; + circle_player = false; + original_z = 0.0f; + } + +void FlyCircle::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + fly_clockwise = ev->GetBoolean( 2 ); + + if (ev->NumArgs() > 2 ) + fly.setAdjustYawAndRoll( ev->GetBoolean( 3 ) ); + + if (ev->NumArgs() > 3 ) + circle_player = ev->GetBoolean ( 4 ); + else + circle_player = false; + + if (ev->NumArgs() > 4 ) + fly.SetSpeed( ev->GetFloat( 5 ) ); + } + +void FlyCircle::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyCircle::Begin + ( + Actor &self + ) + + { + original_z = self.origin.z; + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + fly.Begin( self ); + fly.SetTurnSpeed( 5.0f ); + } + +BehaviorReturnCode_t FlyCircle::Evaluate + ( + Actor &self + ) + + { + Vector goal; + trace_t trace; + Vector dir; + Vector angle; + Vector left; + qboolean too_far = false; + Vector new_dir; + Vector fly_dir; + + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( !self.IsEntityAlive( currentEnemy ) && !circle_player ) + { + return BEHAVIOR_SUCCESS; + } + + + if ( self.movementSubsystem->getLastMove() == STEPMOVE_OK ) + { + fly.SetTurnSpeed( 5.0f ); + + if (circle_player) + { + Player *player = NULL; + Player *temp_player = NULL; + + // Make sure the player is alive and well + for(int i = 0; i < game.maxclients; i++) + { + temp_player = GetPlayer(i); + + // don't target while player is not in the game or he's in notarget + if ( temp_player && !( temp_player->flags & FL_NOTARGET ) ) + { + player = temp_player; + break; + } + } + + if ( !player ) + return BEHAVIOR_SUCCESS; + + dir = player->centroid - self.origin; + dir.z = 0.0f; + + if ( dir.length() > ( ( self.origin.z - player->centroid.z ) / .5f ) ) + { + too_far = true; + } + + } + else + { + dir = currentEnemy->centroid - self.origin; + dir.z = 0.0f; + + if ( dir.length() > ( ( self.origin.z - currentEnemy->centroid.z ) / 2.0f ) ) + { + too_far = true; + } + + } + + angle = dir.toAngles(); + + angle.AngleVectors( NULL, &left, NULL ); + + if ( fly_clockwise ) + fly_dir = left; + else + fly_dir = left * -1.0f; + + dir.normalize(); + + if ( too_far ) + { + new_dir = ( fly_dir * 0.5f ) + ( dir * 0.5f ); + new_dir.normalize(); + } + else + { + new_dir = fly_dir; + } + + //goal = self.origin + new_dir * 200; + goal = self.origin + ( new_dir * 700.0f ); + + trace = G_Trace( self.origin, self.mins, self.maxs, goal, &self, self.edict->clipmask, false, "FlyCircle" ); + + if ( trace.fraction < 1.0f ) + { + if ( too_far ) + trace.fraction /= 2.0f; + + new_dir = ( fly_dir * trace.fraction ) + ( dir * ( 1.0f - trace.fraction ) ); + new_dir.normalize(); + + //goal = self.origin + new_dir * 200; + goal = self.origin + ( new_dir * 700.0f ); + } + else + { + goal = trace.endpos; + } + + fly.SetGoalPoint( goal ); + } + else + { + fly.SetTurnSpeed( 20.0f ); + } + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +void FlyCircle::End + ( + Actor &self + ) + + { + fly.End( self ); + } + +/**************************************************************************** + + FlyStrafe Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyStrafe, NULL ) + { + { &EV_Behavior_Args, &FlyStrafe::SetArgs }, + { NULL, NULL } + }; + +FlyStrafe::FlyStrafe() + { + anim = "fly"; + } + +void FlyStrafe::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + speed = ev->GetFloat( 2 ); + right = ev->GetBoolean( 3 ); + roll = ev->GetFloat( 4 ); + } + +void FlyStrafe::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyStrafe::Begin + ( + Actor &self + ) + + { + self.SetAnim( anim ); + self.movementSubsystem->setForwardSpeed( speed ); + + /* + if ( right ) + self.FlightDirection = FLY_RIGHT; + else + self.FlightDirection = FLY_LEFT; + */ + } + +BehaviorReturnCode_t FlyStrafe::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector delta; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + dir = currentEnemy->centroid - self.origin; + dir = dir.toAngles(); + + if ( right ) + dir[ROLL] -= roll; + else + dir[ROLL] += roll; + + self.setAngles( dir ); + + + + + return BEHAVIOR_EVALUATING; + } + + +void FlyStrafe::End + ( + Actor &self + ) + + { + //self.FlightDirection = FLY_FORWARD; + } + +/**************************************************************************** + + FlyCircleRandomPoint Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FlyCircleRandomPoint, NULL ) + { + { &EV_Behavior_Args, &FlyCircleRandomPoint::SetArgs }, + { NULL, NULL } + }; + +FlyCircleRandomPoint::FlyCircleRandomPoint() + { + anim = "fly"; + //original_z = 0.0f; + fly_clockwise = true; + change_course_time = 5; + try_to_go_up = false; + next_change_course_time = 0; + } + +void FlyCircleRandomPoint::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + fly_clockwise = ev->GetBoolean( 2 ); + + if ( ev->NumArgs() > 2 ) + change_course_time = ev->GetFloat( 3 ); + + if ( ev->NumArgs() > 3 ) + try_to_go_up = ev->GetBoolean( 4 ); + + if (ev->NumArgs() > 4 ) + fly.setAdjustYawAndRoll( ev->GetBoolean( 5 ) ); + + if (ev->NumArgs() > 5 ) + fly.SetSpeed( ev->GetFloat( 6 ) ); + + } + +void FlyCircleRandomPoint::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyCircleRandomPoint::Begin + ( + Actor &self + ) + + { + //original_z = self.origin.z; + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + fly.Begin( self ); + fly.SetTurnSpeed( 5.0f ); + fly.ForceGoal(); + } + +BehaviorReturnCode_t FlyCircleRandomPoint::Evaluate + ( + Actor &self + ) + + { + trace_t trace; + Vector dir; + Vector angle; + Vector left; + qboolean too_far = false; + Vector new_dir; + Vector fly_dir; + float length; + int goal_try; + Vector temp_goal; + float max_dist = 0; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( !self.IsEntityAlive( currentEnemy ) ) + { + return BEHAVIOR_SUCCESS; + } + + + if ( self.movementSubsystem->getLastMove() == STEPMOVE_OK ) + { + fly.SetTurnSpeed( 5.0f ); + + if ( next_change_course_time <= level.time ) //self.lastmove != STEPMOVE_OK ) + { + + dir = goal - self.origin; + dir.z = 0; + length = dir.length(); + + fly_clockwise = !fly_clockwise; + + for( goal_try = 0 ; goal_try < 5 ; goal_try++ ) + { + + temp_goal = self.origin; + + //temp_goal[0] += G_Random( 10000 ) - 2500.0; + //temp_goal[1] += G_Random( 10000 ) - 2500.0; + + temp_goal[0] += G_Random( 10000.0f ) + 10000.0f; + temp_goal[1] += G_Random( 10000.0f ) + 10000.0f; + if ( try_to_go_up ) + temp_goal[2] += G_Random( 1000.0f ) - 250.0f; + else + temp_goal[2] += G_Random( 100.0f ) - 50.0f; + + trace = G_Trace( self.origin, self.mins, self.maxs, temp_goal, &self, self.edict->clipmask, false, "FlyCircleRandomPoint" ); + + temp_goal = trace.endpos; + + dir = temp_goal - self.origin; + length = dir.length(); + + if ( length > max_dist ) + { + max_dist = length; + goal = temp_goal; + + if ( length > 1000.0f ) + break; + } + + } + + next_change_course_time = (level.time + change_course_time); + } + + if ( dir.length() > ( (self.origin.z - goal.z ) / 2.0f ) ) + { + too_far = true; + } + + angle = dir.toAngles(); + + angle.AngleVectors( NULL, &left, NULL ); + + if ( fly_clockwise ) + fly_dir = left; + else + fly_dir = left * -1.0f; + + dir.normalize(); + + if ( too_far ) + { + new_dir = ( fly_dir * 0.5f ) + ( dir * 0.5f ); + new_dir.normalize(); + } + else + { + new_dir = fly_dir; + } + + goal = self.origin + ( new_dir * 30.0f ); + //goal = self.origin + new_dir * 100; + + + trace = G_Trace( self.origin, self.mins, self.maxs, goal, &self, self.edict->clipmask, false, "FlyCircleRandomPoint" ); + + if ( trace.fraction < 1.0f ) + { + if ( too_far ) + trace.fraction /= 2.0f; + + new_dir = ( fly_dir * trace.fraction ) + ( dir * ( 1.0f - trace.fraction ) ); + new_dir.normalize(); + + //goal = self.origin + new_dir * 200; + goal = self.origin + ( new_dir * 100.0f ); + } + else + { + goal = trace.endpos; + } + + fly.SetGoalPoint( goal ); + } + else + { + fly.SetTurnSpeed( 30.0f ); + } + + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + + } + +void FlyCircleRandomPoint::End + ( + Actor &self + ) + + { + fly.End( self ); + } + +/**************************************************************************** + + FlyDive Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyDive, NULL ) + { + { &EV_Behavior_Args, &FlyDive::SetArgs }, + { NULL, NULL } + }; + +FlyDive::FlyDive() + { + anim = "fly"; + speed = 2000; + damage = 0; + } + +void FlyDive::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + { + speed = ev->GetFloat( 2 ); + } + + if ( ev->NumArgs() > 2 ) + { + damage = ev->GetFloat( 3 ); + } + } + +void FlyDive::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyDive::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + self.SetAnim( anim ); + + fly.Begin( self ); + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + if ( !self.IsEntityAlive( currentEnemy ) ) + { + return; + } + + goal = currentEnemy->centroid - self.origin; + goal.normalize(); + goal *= 10000.0f; + goal += self.origin; + + self.movementSubsystem->setDiveDir( self.origin ); + + fly.SetGoalPoint( goal ); + + fly.SetTurnSpeed( 100.0f ); + fly.SetSpeed( speed ); + fly.SetRandomAllowed( false ); + fly.ForceGoal(); + } + +BehaviorReturnCode_t FlyDive::Evaluate + ( + Actor &self + ) + + { + trace_t trace; + Vector dir; + Entity *hit_entity; + qboolean stuck; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( !self.IsEntityAlive( currentEnemy ) ) + return BEHAVIOR_SUCCESS; + + if ( self.origin.z < ( currentEnemy->origin.z + 50.0f ) ) + return BEHAVIOR_SUCCESS; + + if ( (self.movementSubsystem->getLastMove() == STEPMOVE_STUCK ) || ( self.movementSubsystem->getLastMove() == STEPMOVE_BLOCKED_BY_WATER ) ) + { + stuck = true; + + dir = self.movementSubsystem->getMoveDir() * 100.0f; + + trace = G_Trace( self.origin, self.mins, self.maxs, self.origin + dir, &self, self.edict->clipmask, false, "FlyDive" ); + + if ( trace.entityNum != ENTITYNUM_NONE ) + { + hit_entity = G_GetEntity( trace.entityNum ); + + // Damage entity hit + //if ( hit_entity->isSubclassOf( Sentient ) ) + if ( hit_entity->takedamage ) + { + //hit_entity->Damage( &self, &self, damage, Vector (0, 0, 0), Vector (0, 0, 0), Vector (0, 0, 0), 0, 0, MOD_CRUSH ); + dir.normalize(); + hit_entity->Damage( &self, &self, damage, self.centroid, dir, vec_zero, 0, 0, MOD_CRUSH ); + self.AddStateFlag( STATE_FLAG_MELEE_HIT ); + stuck = false; + } + } + + // Make sure we really are still stuck + + if ( trace.fraction > 0.05f ) + stuck = false; + + self.angles[PITCH] = 0.0f; + self.setAngles( self.angles ); + + if ( stuck ) + self.AddStateFlag( STATE_FLAG_STUCK ); + + return BEHAVIOR_SUCCESS; + } + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +void FlyDive::End + ( + Actor &self + ) + + { + fly.End( self ); + } + + +/**************************************************************************** + + FlyCharge Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyCharge, NULL ) + { + { &EV_Behavior_Args, &FlyCharge::SetArgs }, + { NULL, NULL } + }; + +FlyCharge::FlyCharge() + { + anim = "fly"; + speed = 2000; + damage = 0; + } + +void FlyCharge::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + { + speed = ev->GetFloat( 2 ); + } + + if ( ev->NumArgs() > 2 ) + { + damage = ev->GetFloat( 3 ); + } + } + +void FlyCharge::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyCharge::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + self.SetAnim( anim ); + + fly.Begin( self ); + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + if ( !self.IsEntityAlive( currentEnemy ) ) + { + return; + } + + goal = currentEnemy->centroid - self.origin; + goal.normalize(); + goal *= 10000.0f; + goal += self.origin; + goal.z = self.origin.z; + + self.movementSubsystem->setDiveDir( self.origin ); + + fly.SetGoalPoint( goal ); + + fly.SetTurnSpeed( 100.0f ); + fly.SetSpeed( speed ); + fly.SetRandomAllowed( false ); + fly.ForceGoal(); + } + +BehaviorReturnCode_t FlyCharge::Evaluate + ( + Actor &self + ) + + { + trace_t trace; + Vector dir; + Entity *hit_entity; + qboolean stuck; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( !self.IsEntityAlive( currentEnemy ) ) + return BEHAVIOR_SUCCESS; + + if ( ( self.movementSubsystem->getLastMove() == STEPMOVE_STUCK ) || ( self.movementSubsystem->getLastMove() == STEPMOVE_BLOCKED_BY_WATER ) ) + { + stuck = true; + + dir = self.movementSubsystem->getMoveDir() * 100.0f; + + trace = G_Trace( self.origin, self.mins, self.maxs, self.origin + dir, &self, self.edict->clipmask, false, "FlyDive" ); + + if ( trace.entityNum != ENTITYNUM_NONE ) + { + hit_entity = G_GetEntity( trace.entityNum ); + + // Damage entity hit + //if ( hit_entity->isSubclassOf( Sentient ) ) + if ( hit_entity->takedamage ) + { + //hit_entity->Damage( &self, &self, damage, Vector (0, 0, 0), Vector (0, 0, 0), Vector (0, 0, 0), 0, 0, MOD_CRUSH ); + dir.normalize(); + hit_entity->Damage( &self, &self, damage, self.centroid, dir, vec_zero, 0, 0, MOD_CRUSH ); + self.AddStateFlag( STATE_FLAG_MELEE_HIT ); + stuck = false; + } + } + + // Make sure we really are still stuck + + if ( trace.fraction > 0.05f ) + stuck = false; + + self.angles[PITCH] = 0.0f; + self.setAngles( self.angles ); + + if ( stuck ) + self.AddStateFlag( STATE_FLAG_STUCK ); + + return BEHAVIOR_SUCCESS; + } + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +void FlyCharge::End + ( + Actor &self + ) + + { + fly.End( self ); + } + + +/**************************************************************************** + + FlyClimb Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyClimb, NULL ) + { + { &EV_Behavior_Args, &FlyClimb::SetArgs }, + { NULL, NULL } + }; + +FlyClimb::FlyClimb() + { + anim = "fly"; + height = 500; + speed = 0; + collision_buffer = 0; + next_height_check = level.time + 2.0; + last_check_height = 0.0f; + } + +void FlyClimb::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + height = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + speed = ev->GetFloat( 3 ); + + if ( ev->NumArgs() > 3 ) + collision_buffer = ev->GetFloat( 1 ); + } + +void FlyClimb::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyClimb::Begin + ( + Actor &self + ) + + { + + Vector forward; + trace_t trace; + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + fly.Begin( self ); + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + + if ( currentEnemy ) + height = currentEnemy->origin.z + height; + else + height = self.origin.z + height; + + self.angles.AngleVectors( &forward ); + + //Okay, here's what we are attempting. We are first going to get our initial point, + //this will be our forward vector * height, then add the the height to the the Z. + //Next we will trace out, to see how far along that vector we got. We will use our + //trace.endpos as a goal position + + //Now, we don't want to use this goal position as a final vector, because if we ran into + //something, then we will smack it when we get there... this would be bad, so, we run a + //a vector from our origin to our goal position and get the length, we then shave some off of + //this length and multiply it by another vector and add it to the origin, this will get us + //our final position + + goal = forward * height; + goal.z = height; + goal += self.origin; + + trace = G_Trace( self.centroid, self.mins, self.maxs, goal, NULL, MASK_SOLID, false, "flyclimb" ); + goal = trace.endpos; + + Vector dir; + dir = self.origin - goal; + + float length; + + length = dir.length(); + + if ( collision_buffer ) + length -= collision_buffer; + else + length -= (length / 10.0f); + + if ( length > 0.0f ) + { + goal = forward * length; + goal.z = length; + goal += self.origin; + } + + + //if ( !self.divedir.x && !self.divedir.y && !self.divedir.z ) + // { + // goal = forward * height; + // goal.z = height; + // goal += self.origin; + // trace = G_Trace( self.centroid, self.mins, self.maxs, goal, NULL, MASK_SOLID, false, "flyclimb" ); + + + + + // } + //else + // { + // goal = self.divedir; + // goal.z = height; + // } + + + + + fly.SetTurnSpeed( 10.0f ); + + fly.SetGoalPoint( goal ); + + last_check_height = self.origin.z; + + if ( speed ) + fly.SetSpeed( speed ); + } + +BehaviorReturnCode_t FlyClimb::Evaluate + ( + Actor &self + ) + + { + + if ( self.origin.z >= height ) + { + return BEHAVIOR_SUCCESS; + } + + if ( next_height_check < level.time ) + { + if ( self.origin.z < ( last_check_height + 25.0f ) ) + return BEHAVIOR_SUCCESS; + + next_height_check = level.time + 2.0f; + last_check_height = self.origin.z; + } + + if ( self.movementSubsystem->getLastMove() == STEPMOVE_OK ) + fly.SetGoalPoint( goal ); + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +void FlyClimb::End + ( + Actor &self + ) + + { + fly.End( self ); + } + +/**************************************************************************** + + FlySplinePath Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, FlySplinePath, NULL ) + { + { &EV_Behavior_Args, &FlySplinePath::SetArgs }, + { NULL, NULL } + }; + +FlySplinePath::FlySplinePath() + { + ent = NULL; + clamp = true; + ignoreAngles = false; + splineAngles = true; + havePath = false; + startTime = 0.0f; + } + +void FlySplinePath::SetArgs + ( + Event *ev + ) + + { + ent = ev->GetEntity( 1 ); + ignoreAngles = ev->GetBoolean( 2 ); + splineAngles = ev->GetBoolean( 3 ); + clamp = ev->GetBoolean( 4 ); + } + +void FlySplinePath::CreatePath + ( + SplinePath *path, + splinetype_t type + ) + + { + + SplinePath *node; + + splinePath.Clear(); + splinePath.SetType( type ); + + node = path; + while( node != NULL ) + { + splinePath.AppendControlPoint( node->origin, node->angles, node->speed ); + node = node->GetNext(); + + if ( node == path ) + { + break; + } + } + + } +void FlySplinePath::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlySplinePath::Begin + ( + Actor &self + ) + + { + + if ( ent ) + havePath = true; + else + return; + + if ( ent->isSubclassOf( SplinePath ) ) + { + + SplinePath* path; + + path = (SplinePath*)(Entity*)ent; + + if ( clamp ) + CreatePath( path, SPLINE_CLAMP ); + else + CreatePath( path, SPLINE_LOOP ); + + currentNode = path; + + if (currentNode->thread != "" ) + { + if ( !ExecuteThread( currentNode->thread ) ) + { + gi.Error( ERR_DROP, "FlySplinePath could not run thread '%s' from info_splinepath '%s'\n", + currentNode->thread.c_str(), currentNode->targetname.c_str() ); + } + } + + if ( currentNode->triggertarget != "" ) + { + Entity *ent; + Event *event; + + ent = NULL; + do + { + ent = G_FindTarget( ent, currentNode->triggertarget.c_str() ); + if ( !ent ) + { + break; + } + event = new Event( EV_Activate ); + + Actor* actorPtr = &self; + Entity* entPtr = (Entity*)actorPtr; + event->AddEntity( entPtr ); + ent->PostEvent( event, 0.0f ); + } + while ( 1 ); + } + + } + + startTime = level.time + self.currentSplineTime; + + + } + +BehaviorReturnCode_t FlySplinePath::Evaluate + ( + Actor &self + ) + + { + + Vector goal; + Vector dir; + Vector angles; + + if(!havePath) + return BEHAVIOR_SUCCESS; + + + goal = splinePath.Eval( (level.time - startTime) ); + + if ( goal == oldGoal ) + { + self.currentSplineTime = 0; + return BEHAVIOR_SUCCESS; + } + + + oldGoal = goal; + + dir = goal - self.origin; + angles = dir.toAngles(); + + self.setOrigin(goal); + self.setAngles(angles); + + + return BEHAVIOR_EVALUATING; + + + } + +void FlySplinePath::End + ( + Actor &self + ) + + { + self.currentSplineTime = level.time - startTime; + } + +/**************************************************************************** + + Land Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, Land, NULL ) + { + { &EV_Behavior_Args, &Land::SetArgs }, + { NULL, NULL } + }; + +Land::Land() + { + anim = "fly"; + } + +void Land::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + } + +void Land::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void Land::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + self.velocity = Vector(0, 0, -40); + } + +BehaviorReturnCode_t Land::Evaluate + ( + Actor &self + ) + + { + self.angles[PITCH] = 0.0f; + self.angles[ROLL] = 0.0f; + + self.setAngles( self.angles ); + + self.velocity.z -= 20.0f; + + if ( self.velocity.z < -200.0f ) + { + self.velocity.z = -200.0f; + } + + if ( !self.groundentity ) + return BEHAVIOR_EVALUATING; + + return BEHAVIOR_SUCCESS; + } + +void Land::End + ( + Actor &self + ) + + { + } + + +/**************************************************************************** + + Vertical Takeoff Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, VerticalTakeOff, NULL ) + { + { &EV_Behavior_Args, &VerticalTakeOff::SetArgs }, + { NULL, NULL } + }; + +VerticalTakeOff::VerticalTakeOff() + { + anim = "fly"; + speed = 0; + height = 0; + } + +void VerticalTakeOff::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + speed = ev->GetFloat( 2 ); + height = ev->GetFloat( 3 ); + } + +void VerticalTakeOff::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void VerticalTakeOff::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + self.velocity.x = 0; + self.velocity.y = 0; + self.velocity.z = speed; + + } + +BehaviorReturnCode_t VerticalTakeOff::Evaluate + ( + Actor &self + ) + + { + self.angles[PITCH] = 0.0f; + self.angles[ROLL] = 0.0f; + + self.setAngles( self.angles ); + + self.velocity.z += 20.0f; + + if ( self.velocity.z > 200.0f ) + { + self.velocity.z = 200.0f; + } + + if( self.origin.z >= height ) + return BEHAVIOR_SUCCESS; + else + return BEHAVIOR_EVALUATING; + } + +void VerticalTakeOff::End + ( + Actor &self + ) + + { + } + + +/**************************************************************************** + + Hover Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Hover, NULL ) + { + { &EV_Behavior_Args, &Hover::SetArgs }, + { NULL, NULL } + }; + +Hover::Hover() + { + anim = "fly"; + } + +void Hover::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + } + +void Hover::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void Hover::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + fly.Begin( self ); + } + +BehaviorReturnCode_t Hover::Evaluate + ( + Actor &self + ) + + { + //self.angles[PITCH] = 0; + self.angles[ROLL] = 0; + + self.setAngles( self.angles ); + self.movementSubsystem->setForwardSpeed( 0 ); + + return BEHAVIOR_EVALUATING; + } + +void Hover::End + ( + Actor &self + ) + + { + fly.End(self); + } +/**************************************************************************** + + Wander Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, Wander, NULL ) + { + { &EV_Behavior_Args, &Wander::SetArgs }, + { NULL, NULL } + }; + +void Wander::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + } + +void Wander::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nseek:\n" ); + + } + +void Wander::Begin + ( + Actor &self + ) + + { + avoidtime = 0; + avoidvec = vec_zero; + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + } + + +BehaviorReturnCode_t Wander::Evaluate + ( + Actor &self + ) + + { + if ( ( self.movementSubsystem->getLastMove() != STEPMOVE_OK ) || ( avoidtime <= level.time ) ) + { + Vector dir; + Vector ang; + float time; + + time = 5.0f; + //self.Chatter( "snd_idle", 4 ); + avoidvec = ChooseRandomDirection( self, avoidvec, &time, 0, false ); + self.movementSubsystem->setMovingBackwards( false ); + avoidtime = level.time + time; + } + + float maxSpeed = 100.0f; + if ( self.movementSubsystem->getMoveSpeed() != 1.0f ) + { + maxSpeed = self.movementSubsystem->getMoveSpeed(); + } + + self.movementSubsystem->Accelerate( + self.movementSubsystem->SteerTowardsPoint( avoidvec, vec_zero, self.movementSubsystem->getMoveDir(), maxSpeed) + ); + + return BEHAVIOR_EVALUATING; + } + +void Wander::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + GetCloseToEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, GetCloseToEnemy, NULL ) + { + { &EV_Behavior_Args, &GetCloseToEnemy::SetArgs }, + { NULL, NULL } + }; + +void GetCloseToEnemy::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + forever = ev->GetBoolean( 2 ); + else + forever = true; + } + +void GetCloseToEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase.ShowInfo( self ); + } + +void GetCloseToEnemy::Begin + ( + Actor &self + ) + + { + Vector dir; + dir = self.movementSubsystem->getAnimDir(); + dir = dir.toAngles(); + self.setAngles( dir ); + + if ( !anim.length() ) + { + anim = "run"; + } + + if ( anim != self.animname || self.newanim.length() ) + { + self.SetAnim( anim, EV_Actor_NotifyBehavior ); + } + + chase.Begin( self ); + wander.Begin( self ); + + next_think_time = 0; + + } + +BehaviorReturnCode_t GetCloseToEnemy::Evaluate + ( + Actor &self + ) + + { + qboolean result; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( next_think_time <= level.time ) + { + if ( self.groundentity && ( self.groundentity->s.number == currentEnemy->entnum ) ) + { + wander.Evaluate( self ); + result = true; + } + else + { + float radius=96.0f; + chase.SetGoal( currentEnemy, radius, self ); + + result = chase.Evaluate( self ); + } + + if ( self.GetActorFlag( ACTOR_FLAG_SIMPLE_PATHFINDING ) ) + next_think_time = level.time + ( 2.0f * FRAMETIME ); + else + next_think_time = 0.0f; + } + else + result = true; + + if ( !forever && !result ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void GetCloseToEnemy::End + ( + Actor &self + ) + + { + chase.End( self ); + wander.End( self ); + } + +/**************************************************************************** + + GetCloseToPlayer Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, GetCloseToPlayer, NULL ) + { + { &EV_Behavior_Args, &GetCloseToPlayer::SetArgs }, + { NULL, NULL } + }; + +void GetCloseToPlayer::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + forever = ev->GetBoolean( 2 ); + else + forever = true; + + if ( ev->NumArgs() > 2 ) + speed = ev->GetFloat( 3 ); + else + speed = 100; + } + +void GetCloseToPlayer::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase.ShowInfo( self ); + } + +void GetCloseToPlayer::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + if ( anim != self.animname || self.newanim.length() ) + { + self.SetAnim( anim, EV_Actor_NotifyBehavior ); + } + + chase.Begin( self ); + wander.Begin( self ); + + self.movementSubsystem->setForwardSpeed( speed ); + + next_think_time = 0; + } + +BehaviorReturnCode_t GetCloseToPlayer::Evaluate + ( + Actor &self + ) + + { + qboolean result; + + Player *player = NULL; + Player *temp_player = NULL; + // Make sure the player is alive and well + for(int i = 0; i < game.maxclients; i++) + { + temp_player = GetPlayer(i); + + // don't target while player is not in the game or he's in notarget + if( temp_player && !( temp_player->flags & FL_NOTARGET ) ) + { + player = temp_player; + break; + } + } + + if ( !player ) + return BEHAVIOR_SUCCESS; + + if ( next_think_time <= level.time ) + { + if ( self.groundentity && ( self.groundentity->s.number == player->entnum ) ) + { + wander.Evaluate( self ); + result = true; + } + else + { + float radius=96.0f; + chase.SetGoal( player, radius, self ); + + result = chase.Evaluate( self ); + } + + if ( self.GetActorFlag( ACTOR_FLAG_SIMPLE_PATHFINDING ) ) + next_think_time = level.time + ( 2.0f * FRAMETIME ); + else + next_think_time = 0.0f; + } + else + result = true; + + if ( !forever && !result ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void GetCloseToPlayer::End + ( + Actor &self + ) + + { + chase.End( self ); + wander.End( self ); + self.movementSubsystem->setForwardSpeed( 0 ); + } + +/**************************************************************************** + + RetreatFromEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, RetreatFromEnemy, NULL ) + { + { &EV_Behavior_Args, &RetreatFromEnemy::SetArgs }, + { NULL, NULL } + }; + +void RetreatFromEnemy::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + forever = ev->GetBoolean( 2 ); + else + forever = true; + } + +void RetreatFromEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase.ShowInfo( self ); + } + +void RetreatFromEnemy::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + if ( anim != self.animname || self.newanim.length() ) + { + self.SetAnim( anim, EV_Actor_NotifyBehavior ); + } + + chase.Begin( self ); + wander.Begin( self ); + + next_think_time = 0; + } + +BehaviorReturnCode_t RetreatFromEnemy::Evaluate + ( + Actor &self + ) + + { + qboolean result; + Entity *currentEnemy; + + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( next_think_time <= level.time ) + { + if ( self.groundentity && ( self.groundentity->s.number == currentEnemy->entnum ) ) + { + wander.Evaluate( self ); + result = true; + } + else + { + + float currentZ = self.centroid.z; + Vector FleeVec = (self.centroid - currentEnemy->centroid) + self.centroid; + FleeVec.z = currentZ; + + trace_t trace; + trace = G_Trace( self.centroid, self.mins, self.maxs, FleeVec, NULL, MASK_SOLID, false, "RetreatFromEnemy" ); + + float radius=96.0f; + chase.SetGoal( trace.endpos, radius, self ); + + result = chase.Evaluate( self ); + } + + if ( self.GetActorFlag( ACTOR_FLAG_SIMPLE_PATHFINDING ) ) + next_think_time = level.time + ( 2.0f * FRAMETIME ); + else + next_think_time = 0.0f; + } + else + result = true; + + if ( !forever && !result ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void RetreatFromEnemy::End + ( + Actor &self + ) + + { + chase.End( self ); + wander.End( self ); + } + +/**************************************************************************** + + GotoDeadEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, GotoDeadEnemy, NULL ) + { + { &EV_Behavior_Args, &GotoDeadEnemy::SetArgs }, + { NULL, NULL } + }; + +void GotoDeadEnemy::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + } + +void GotoDeadEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase.ShowInfo( self ); + } + +void GotoDeadEnemy::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + if ( anim != self.animname || self.newanim.length() ) + { + self.SetAnim( anim, EV_Actor_NotifyBehavior ); + } + + chase.Begin( self ); + } + +BehaviorReturnCode_t GotoDeadEnemy::Evaluate + ( + Actor &self + ) + + { + qboolean result; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( !currentEnemy->deadflag ) + return BEHAVIOR_SUCCESS; + + float radius=96.0f; + chase.SetGoal( currentEnemy->origin, radius, self ); + + result = chase.Evaluate( self ); + + if ( !result ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void GotoDeadEnemy::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +/**************************************************************************** + + Investigate Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Investigate, NULL ) + { + { &EV_Behavior_Args, &Investigate::SetArgs }, + { NULL, NULL } + }; + +#define INVESTIGATE_MODE_INVESTIGATE 0 +#define INVESTIGATE_MODE_LOOKAROUND 1 +#define INVESTIGATE_MODE_RETURN 2 +#define INVESTIGATE_MODE_TURN2 3 + +Investigate::Investigate() + { + investigate_time = 10.0f; + moveanim = "run"; + lookaroundanim = "idle"; + curioustime = 0.0f; + lookaroundtime = 2.0f; + mode = INVESTIGATE_MODE_INVESTIGATE; + return_to_original_location = true; + start_yaw = 0.0f; + } + +void Investigate::SetArgs + ( + Event *ev + ) + + { + moveanim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + investigate_time = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + return_to_original_location = ev->GetBoolean( 3 ); + else + return_to_original_location = true; + + if( ev->NumArgs() > 3 ) + lookaroundanim = ev->GetString( 4 ); + + if( ev->NumArgs() > 4 ) + lookaroundtime = ev->GetFloat( 5 ); + } + +void Investigate::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase.ShowInfo( self ); + gi.Printf( "\nanim: %s\n", moveanim.c_str() ); + gi.Printf( "curioustime: %f\n", curioustime ); + gi.Printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z ); + } + +void Investigate::Begin( + Actor &self + ) + +{ +// Note: the state machine for this character needs to use "forgetenemies" or it will get +// stuck in a loop. + + Vector trace_start_pos; + Vector trace_end_pos; + PathNode *goal_node; + + // Find the goal position + if( self.sensoryPerception->GetLastSoundType() == SOUNDTYPE_NONE ) + { + EntityPtr enemy = self.enemyManager->GetCurrentEnemy(); + if( enemy != NULL ) + { + // Didn't hear a sound... must've seen someone + goal = enemy->origin; + } + else + { + // Didn't hear or see anything? wtf?! Just hang out where you are, ai. + goal = self.origin; + } + } + else + { + // Heard a sound + goal = self.sensoryPerception->GetNoisePosition(); + } + + // Try to find a pathnode close to the goal position to ensure good pathfinding + + goal_node = self.NearestNodeInPVS( goal ); + if ( goal_node ) + { + goal = goal_node->origin; + } + + // Let anyone else who cares know that this guy is investigating + + self.SetActorFlag( ACTOR_FLAG_INVESTIGATING, true ); + + // Only search for a set amount of time before returning to non-investigative state + + curioustime = level.time + investigate_time; + + // Make some sounds? + + self.Chatter( "snd_investigate" ); + + // Setup movement code to move toward the goal position + + chase.Begin( self ); + chase.SetGoal( goal, 96.0f, self ); + + // Turn toward ??? + + turnto.Begin( self ); + + // Don't start movement anim if it's so close there will be no actual movement + + if ( moveanim.length() && ( Vector::DistanceXY( goal, self.origin ) >= 100.0f ) ) + { + self.SetAnim( moveanim ); + } + + start_pos = self.origin; + start_yaw = self.angles[YAW]; +} + +BehaviorReturnCode_t Investigate::Evaluate( + Actor &self + ) +{ + Vector dir; + Vector angles; + Steering::ReturnValue steeringResult; + + switch ( mode ) + { + // Go to spot where the noise was heard + case INVESTIGATE_MODE_INVESTIGATE: + + // Go to next mode if time elapsed or pathing failed + + steeringResult = chase.Evaluate( self ); + if ( (steeringResult != Steering::EVALUATING) || curioustime < level.time ) + { + mode = INVESTIGATE_MODE_LOOKAROUND; + self.SetAnim( lookaroundanim ); + lookaroundtime_end = level.time + lookaroundtime; + } + else + { + // Go to next mode if close enough to goal position + + float dist = Vector::DistanceXY( goal, self.origin ); + if( dist < 100.0f ) + { + mode = INVESTIGATE_MODE_LOOKAROUND; + self.SetAnim( lookaroundanim ); + lookaroundtime_end = level.time + lookaroundtime; + } + } + + break; + + // look around for stuff for a second + case INVESTIGATE_MODE_LOOKAROUND: + + if( level.time > lookaroundtime_end ) + { + curioustime = level.time + (investigate_time * 2.0f ); + mode = INVESTIGATE_MODE_RETURN; + self.SetAnim( moveanim ); + } + + break; + + // go back from whence you came, foul beast + case INVESTIGATE_MODE_RETURN : + + if ( !return_to_original_location ) + return BEHAVIOR_SUCCESS; + + // Return back to our original position + chase.SetGoal( start_pos, 96.0f, self ); + + if ( chase.Evaluate( self ) != Steering::EVALUATING ) + mode = INVESTIGATE_MODE_TURN2; + + if ( curioustime < level.time ) + return BEHAVIOR_SUCCESS; + + break; + + // got stuck going back to start position, just face original direction? + case INVESTIGATE_MODE_TURN2 : + + self.SetAnim( "idle" ); + + // Turn back to our original direction + + turnto.SetDirection( start_yaw ); + if ( !turnto.Evaluate( self ) ) + return BEHAVIOR_SUCCESS; + + break; + } + + return BEHAVIOR_EVALUATING; +} + +void Investigate::End +( + Actor &self + ) + +{ + self.SetActorFlag( ACTOR_FLAG_INVESTIGATING, false ); + self.SetActorFlag( ACTOR_FLAG_NOISE_HEARD, false ); + chase.End( self ); + turnto.End( self ); +} + +/**************************************************************************** + + TurnInvestigate Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, TurnInvestigate, NULL ) + { + { &EV_Behavior_Args, &TurnInvestigate::SetArgs }, + { NULL, NULL } + }; + +void TurnInvestigate::SetArgs + ( + Event *ev + ) + + { + left_anim = ev->GetString( 1 ); + right_anim = ev->GetString( 2 ); + turn_speed = ev->GetFloat( 3 ); + } + +void TurnInvestigate::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "goal: ( %f, %f, %f )\n", goal.x, goal.y, goal.z ); + } + +void TurnInvestigate::Begin + ( + Actor &self + ) + + { + goal = self.sensoryPerception->GetNoisePosition(); + self.SetActorFlag( ACTOR_FLAG_INVESTIGATING, true ); + self.SetAnim( "idle" ); + } + +BehaviorReturnCode_t TurnInvestigate::Evaluate + ( + Actor &self + ) + + { + str turn_anim_name; + + Vector dir; + self.angles.AngleVectors( &dir ); + + Vector newSteeringForce = self.movementSubsystem->SteerTowardsPoint( goal, vec_zero, dir, 1.0f); + + // See if we have turned all of the way to the noise position + + if ( ( newSteeringForce[YAW] < .5f ) && ( newSteeringForce[YAW] > -.5f ) ) + return BEHAVIOR_SUCCESS; + + // Make sure we are not turning faster than out turn speed + if ( newSteeringForce[YAW] > turn_speed ) + { + newSteeringForce[YAW] = turn_speed; + } + + if ( newSteeringForce[YAW] < -turn_speed ) + { + newSteeringForce[YAW] = -turn_speed; + } + + newSteeringForce[PITCH] = 0.0f; + newSteeringForce[ROLL] = 0.0f; + + // Set the correct animation (left or right) + + if ( newSteeringForce[YAW] > 0.0f ) + turn_anim_name = left_anim; + else + turn_anim_name = right_anim; + + if ( turn_anim_name != self.animname ) + self.SetAnim( turn_anim_name ); + + // Actually turn here + + self.movementSubsystem->Accelerate( newSteeringForce ); + + return BEHAVIOR_EVALUATING; + } + +void TurnInvestigate::End + ( + Actor &self + ) + + { + self.SetActorFlag( ACTOR_FLAG_INVESTIGATING, false ); + self.SetActorFlag( ACTOR_FLAG_NOISE_HEARD, false ); + } + +/**************************************************************************** + + TurnToEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, TurnToEnemy, NULL ) + { + { &EV_Behavior_Args, &TurnToEnemy::SetArgs }, + { &EV_Behavior_AnimDone, &TurnToEnemy::AnimDone }, + { NULL, NULL } + }; + +void TurnToEnemy::SetArgs + ( + Event *ev + ) + + { + left_anim = ev->GetString( 1 ); + right_anim = ev->GetString( 2 ); + turn_speed = ev->GetFloat( 3 ); + forever = ev->GetBoolean( 4 ); + + if ( ev->NumArgs() > 4 ) + use_last_known_position = ev->GetBoolean( 5 ); + else + use_last_known_position = false; + } + +void TurnToEnemy::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void TurnToEnemy::Begin + ( + Actor &self + ) + + { + + self.SetAnim( "idle" ); + } + +void TurnToEnemy::AnimDone + ( + Event *ev + ) + + { + anim_done = true; + } + + +BehaviorReturnCode_t TurnToEnemy::Evaluate + ( + Actor &self + ) + + { + + Entity *currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + Vector dir; + self.angles.AngleVectors( &dir ); + + Vector targetPosition=currentEnemy->origin; + if ( use_last_known_position ) + { + targetPosition = self.last_known_enemy_pos; + } + + Vector newSteeringForce = self.movementSubsystem->SteerTowardsPoint( targetPosition, vec_zero, dir, 1.0f ); + + // See if we have turned all of the way to the enemy position + if ( ( newSteeringForce[YAW] < .5f ) && ( newSteeringForce[YAW] > -.5f ) ) + newSteeringForce[YAW] = 0.0f; + + // Make sure we are not turning faster than out turn speed + + if ( newSteeringForce[YAW] > turn_speed ) + { + newSteeringForce[YAW] = turn_speed; + } + + if ( newSteeringForce[YAW] < -turn_speed ) + { + newSteeringForce[YAW] = -turn_speed; + } + + newSteeringForce[PITCH] = 0.0f; + newSteeringForce[ROLL] = 0.0f; + + // Set the correct animation (left or right) + str turn_anim_name; + if ( newSteeringForce[YAW] > 0.0f ) + turn_anim_name = left_anim; + else if ( newSteeringForce[YAW] < 0.0f ) + turn_anim_name = right_anim; + else if ( anim_done ) + turn_anim_name = "idle"; + else + turn_anim_name = self.animname.c_str(); + + if ( turn_anim_name != self.animname ) + self.SetAnim( turn_anim_name, EV_Actor_NotifyBehavior ); + + // Actually turn here + + self.movementSubsystem->Accelerate( newSteeringForce ); + + // See if we have turned all of the way to the enemy position + + if ( ( newSteeringForce[YAW] < turn_speed ) && ( newSteeringForce[YAW] > -turn_speed ) && !forever ) + return BEHAVIOR_SUCCESS; + + anim_done = false; + + return BEHAVIOR_EVALUATING; + } + +void TurnToEnemy::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + PickupEntity Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, PickupEntity, NULL ) + { + { &EV_Behavior_Args, &PickupEntity::SetArgs }, + { &EV_Behavior_AnimDone, &PickupEntity::AnimDone }, + { NULL, NULL } + }; + +void PickupEntity::SetArgs + ( + Event *ev + ) + + { + ent_to_pickup = ev->GetEntity( 1 ); + pickup_anim_name = ev->GetString( 2 ); + } + +void PickupEntity::Begin + ( + Actor &self + ) + + { + anim_done = false; + self.pickup_ent = ent_to_pickup; + + self.SetAnim( pickup_anim_name.c_str(), EV_Actor_NotifyBehavior ); + } + +void PickupEntity::AnimDone + ( + Event *ev + ) + + { + anim_done = true; + } + + +BehaviorReturnCode_t PickupEntity::Evaluate + ( + Actor &self + ) + + { + if ( !ent_to_pickup ) + return BEHAVIOR_SUCCESS; + + if ( anim_done ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void PickupEntity::End + ( + Actor &self + ) + + { + self.SetAnim( "idle" ); + self.pickup_ent = NULL; + } + +/**************************************************************************** + + ThrowEntity Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, ThrowEntity, NULL ) + { + { &EV_Behavior_Args, &ThrowEntity::SetArgs }, + { &EV_Behavior_AnimDone, &ThrowEntity::AnimDone }, + { NULL, NULL } + }; + +void ThrowEntity::SetArgs + ( + Event *ev + ) + + { + throw_anim_name = ev->GetString( 1 ); + } + +void ThrowEntity::Begin + ( + Actor &self + ) + + { + anim_done = false; + self.SetAnim( throw_anim_name, EV_Actor_NotifyBehavior ); + } + +void ThrowEntity::AnimDone + ( + Event *ev + ) + + { + anim_done = true; + } + + +BehaviorReturnCode_t ThrowEntity::Evaluate + ( + Actor &self + ) + + { + if ( anim_done ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + +void ThrowEntity::End + ( + Actor &self + ) + + { + self.SetAnim( "idle" ); + } + + + +/**************************************************************************** +***************************************************************************** + + Behaviors for specific creatures + +***************************************************************************** +****************************************************************************/ + + + +/**************************************************************************** + + BurrowAttack Class Definition + +****************************************************************************/ + +#define BURROW_MODE_MOVING 0 +#define BURROW_MODE_ATTACK 1 + +CLASS_DECLARATION( Behavior, BurrowAttack, NULL ) + { + { NULL, NULL } + }; + +void BurrowAttack::SetArgs + ( + Event *ev + ) + + { + use_last_known_position = ev->GetBoolean( 1 ); + } + +void BurrowAttack::Begin + ( + Actor &self + ) + + { + Vector attack_dir; + Vector start_pos; + Vector end_pos; + trace_t trace; + + if ( self.animname != "idle_down" ) + self.SetAnim( "idle_down" ); + + // Setup our goal point + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( currentEnemy ) + { + if ( use_last_known_position ) + goal = self.last_known_enemy_pos; + else + goal = currentEnemy->origin; + + start_pos = goal + Vector( "0 0 10" ); + end_pos = start_pos + Vector( "0 0 -250" ); + + trace = G_Trace( start_pos, vec_zero, vec_zero, end_pos, NULL, MASK_DEADSOLID, false, "BurrowAttack::Begin" ); + + goal = trace.endpos; + } + + // Setup our starting point, a little ways in front of our origin + + attack_dir = goal - self.origin; + + too_close = false; + + if ( attack_dir.length() < 300.0f ) + too_close = true; + + attack_dir.normalize(); + + attack_origin = self.origin + ( attack_dir * 100.0f ); + + burrow_mode = BURROW_MODE_MOVING; + + stage = self.stage; + + burrow_speed = 80.0f; + + if ( stage == 3 ) + { + attacks_left = 2; + } + else if ( stage == 4 ) + { + attacks_left = 3 + (int)G_Random( 3.0f ); + burrow_speed = 120.0f; + } + } + +BehaviorReturnCode_t BurrowAttack::Evaluate + ( + Actor &self + ) + + { + Vector attack_dir; + Vector new_origin; + float total_dist; + Vector start_pos; + Vector end_pos; + Entity *dirt; + trace_t trace; + Vector temp_angles; + int cont; + Vector temp_endpos; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( too_close ) + return BEHAVIOR_SUCCESS; + + switch ( burrow_mode ) + { + case BURROW_MODE_MOVING : + + attack_dir = goal - attack_origin; + total_dist = attack_dir.length(); + attack_dir.normalize(); + + if ( total_dist < burrow_speed ) + { + new_origin = goal; + } + else + { + new_origin = attack_origin + ( attack_dir * burrow_speed ); + total_dist = burrow_speed; + } + + // Spawn in dirt or water + + start_pos = attack_origin + attack_dir + Vector( "0 0 10" ); + end_pos = start_pos + Vector( "0 0 -250" ); + + trace = G_Trace( start_pos, vec_zero, vec_zero, end_pos, NULL, MASK_DEADSOLID | MASK_WATER, false, "BurrowAttack" ); + + temp_endpos = trace.endpos; + temp_endpos -= Vector( "0 0 5" ); + cont = gi.pointcontents( temp_endpos, 0 ); + + dirt = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + dirt->setOrigin( trace.endpos ); + + temp_angles = vec_zero; + + if (cont & MASK_WATER) + dirt->setModel( "fx_splashsmall.tik" ); + else + dirt->setModel( "fx_dirtcloud.tik" ); + + dirt->setAngles( temp_angles ); + + dirt->animate->RandomAnimate( "idle" ); + + dirt->PostEvent( EV_Remove, 5.0f ); + + attack_origin = new_origin; + + if ( attack_origin == goal ) + { + // Got to our goal position, do attack + + if ( stage == 1 ) + { + SpawnArm( self, leg1, attack_origin + Vector( " 25 25 0" ), "attack1", 0.0f ); + SpawnArm( self, leg2, attack_origin + Vector( " 25 -25 0" ), "attack1", 0.0f ); + SpawnArm( self, leg3, attack_origin + Vector( "-25 25 0" ), "attack1", 0.0f ); + SpawnArm( self, leg4, attack_origin + Vector( "-25 -25 0" ), "attack1", 0.0f ); + } + else if ( stage == 2 ) + { + SpawnArm( self, leg1, attack_origin + Vector( " 25 0 0" ), "attack2", 0.0f ); + SpawnArm( self, leg2, attack_origin + Vector( "-25 25 0" ), "attack2", 120.0f ); + SpawnArm( self, leg3, attack_origin + Vector( "-25 -25 0" ), "attack2", 240.0f ); + } + else + { + SpawnArm( self, leg1, attack_origin, "attack1", 0.0f ); + } + + burrow_mode = BURROW_MODE_ATTACK; + } + + break; + case BURROW_MODE_ATTACK : + + // Wait until all of the legs are done + + if ( !leg1 && !leg2 && !leg3 && !leg4 ) + { + if ( self.animname != "idle_down" ) + self.SetAnim( "idle_down" ); + + if ( ( stage == 1 ) || ( stage == 2 ) ) + { + return BEHAVIOR_SUCCESS; + } + else + { + attacks_left--; + + if ( attacks_left > 0 ) + { + if ( use_last_known_position ) + if ( self.sensoryPerception->CanSeeEntity( &self , currentEnemy , true , true ) ) + goal = currentEnemy->origin; + else + return BEHAVIOR_SUCCESS; + else + goal = currentEnemy->origin; + + burrow_mode = BURROW_MODE_MOVING; + } + else + { + return BEHAVIOR_SUCCESS; + } + } + } + + break; + } + + return BEHAVIOR_EVALUATING; + } + +void BurrowAttack::SpawnArm + ( + Actor &self, + EntityPtr &leg, + const Vector &original_arm_origin, + const char *anim_name, + float angle + ) + + { + Vector angles; + trace_t trace; + Vector start_pos; + Vector end_pos; + str anim_to_play; + Entity *leg_animate_ptr; + Vector dir; + Entity *dirt; + Vector arm_origin; + + + // Find correct spot to spawn + + arm_origin = original_arm_origin; + + arm_origin[2] = -575.0f; + + start_pos = arm_origin + Vector( "0 0 64" ); + end_pos = arm_origin - Vector( "0 0 100" ); + + //trace = G_Trace( start_pos, Vector(-5, -5, 0), Vector(5, 5, 0), end_pos, NULL, MASK_DEADSOLID, false, "BurrowAttack" ); + trace = G_Trace( start_pos, Vector(-10.0f, -10.0f, 0.0f), Vector(10.0f, 10.0f, 0.0f), end_pos, NULL, MASK_DEADSOLID, false, "BurrowAttack" ); + + arm_origin = trace.endpos; + + // Make sure can spawn here + + end_pos = arm_origin + Vector( "0 0 50" ); + + trace = G_Trace( arm_origin, Vector(-10.0f, -10.0f, 0.0f), Vector(10.0f, 10.0f, 0.0f), end_pos, NULL, MASK_DEADSOLID, false, "BurrowAttack" ); + + if ( ( trace.fraction < 1.0f ) || trace.startsolid || trace.allsolid ) + { + if ( trace.entityNum == ENTITYNUM_WORLD || !( trace.ent && trace.ent->entity && trace.ent->entity->takedamage ) ) + return; + } + + // Spawn some dirt + + dirt = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + dirt->setOrigin( arm_origin ); + dirt->setModel( "fx_dirtcloud.tik" ); + dirt->setAngles( vec_zero ); + dirt->animate->RandomAnimate( "idle" ); + dirt->PostEvent( EV_Remove, 5.0f ); + + // Spawn leg + + leg = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + leg->setModel( "vmama_arm.tik" ); + leg->setOrigin( arm_origin ); + + leg->ProcessPendingEvents(); + + //leg->edict->clipmask = MASK_MONSTERSOLID; + leg->setContents( 0 ); + leg->setSolidType( SOLID_NOT ); + + leg->PostEvent( EV_BecomeNonSolid, 0.0f ); + + angles = vec_zero; + angles[YAW] = angle; + + leg->setAngles( angles ); + + anim_to_play = anim_name; + + // See if we should get stuck or not + + if ( strcmp( anim_name, "attack1" ) == 0 ) + { + end_pos = arm_origin + Vector( "0 0 250" ); + + leg_animate_ptr = leg; + + trace = G_Trace( arm_origin, Vector(-5.0f, -5.0f, 0.0f), Vector(5.0f, 5.0f, 0.0f), end_pos, leg_animate_ptr, MASK_DEADSOLID, false, "BurrowAttack" ); + + if ( trace.fraction != 1.0f ) + { + if ( self.animname != "struggle" ) + self.SetAnim( "struggle" ); + + anim_to_play = "getstuck"; + } + } + + // Damage entities in way + + if ( strcmp( anim_name, "attack1" ) == 0 ) + { + start_pos = arm_origin; + end_pos = arm_origin + Vector( "0 0 250" ); + + dir = Vector ( G_CRandom( 5.0f ), G_CRandom( 5.0f ), 10.0f ); + } + else + { + start_pos = arm_origin + Vector( "0 0 10" ); + + angles.AngleVectors( &dir ); + + end_pos = start_pos + ( dir * 250.0f ); + } + + leg_animate_ptr = leg; + + MeleeAttack( start_pos, end_pos, 50.0f, &self, MOD_IMPALE, 10.0f, 0.0f, 0.0f, 100.0f ); + + leg->animate->RandomAnimate( anim_to_play.c_str(), EV_Remove ); + } + +void BurrowAttack::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + CircleEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, CircleEnemy, NULL ) + { + { &EV_Behavior_Args, &CircleEnemy::SetArgs }, + { NULL, NULL } + }; + +void CircleEnemy::SetArgs + ( + Event *ev + ) + + { + center_part_name = ev->GetString( 1 ); + } + +void CircleEnemy::Begin + ( + Actor &self + ) + + { + ent_to_circle = self.enemyManager->GetCurrentEnemy(); + last_angle_change = 0; + } + +float CircleEnemy::GetAngleDiff + ( + const Actor &self, + const Actor *center_actor, + const Vector &origin + ) + + { + Vector dir; + Vector enemy_angles; + Vector actor_angles; + float angle_diff; + + if ( !ent_to_circle ) + return 0.0f; + + dir = ent_to_circle->origin - center_actor->origin; + enemy_angles = dir.toAngles(); + + dir = origin - center_actor->origin; + actor_angles = dir.toAngles(); + + angle_diff = AngleDelta( actor_angles[YAW], enemy_angles[YAW] ); + + return angle_diff; + } + +#define MAX_CIRCLE_ACCELERATION .125 +#define MAX_CIRCLE_VELOCITY 10 + +BehaviorReturnCode_t CircleEnemy::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Actor *center_actor; + Vector actor_angles; + float angle_diff; + float other_angle_diff; + float abs_angle_diff; + float other_abs_angle_diff = 180.0f; + float angle_change = MAX_CIRCLE_VELOCITY; + float length; + float real_angle_change; + Actor *other; + + + if ( !ent_to_circle ) + return BEHAVIOR_SUCCESS; + + center_actor = self.FindPartActor( center_part_name.c_str() ); + + if ( !center_actor ) + return BEHAVIOR_SUCCESS; + + angle_diff = GetAngleDiff( self, center_actor, self.origin ); + + if ( angle_diff < 0.0f ) + abs_angle_diff = -angle_diff; + else + abs_angle_diff = angle_diff; + + other = self.FindPartActor( self.part_name ); + + if ( other ) + { + other_angle_diff = GetAngleDiff( self, center_actor, other->origin ); + + if ( other_angle_diff < 0.0f ) + other_abs_angle_diff = -other_angle_diff; + else + other_abs_angle_diff = other_angle_diff; + } + + if ( abs_angle_diff < other_abs_angle_diff ) + { + // Turn towards enemy + + if ( abs_angle_diff < angle_change ) + angle_change = abs_angle_diff; + + if ( angle_diff < 0.0f ) + real_angle_change = angle_change; + else + real_angle_change = -angle_change; + } + else + { + // Turn away from enemy + + if ( 180.0f - abs_angle_diff < angle_change ) + angle_change = 180.0f - abs_angle_diff; + + if ( angle_diff < 0.0f ) + real_angle_change = -angle_change; + else + real_angle_change = angle_change; + } + + if ( ( real_angle_change < 1.0f ) && ( real_angle_change > -1.0f ) ) + real_angle_change = 0.0f; + + if ( real_angle_change > 0.0f ) + { + if ( real_angle_change > ( last_angle_change + MAX_CIRCLE_ACCELERATION ) ) + real_angle_change = last_angle_change + MAX_CIRCLE_ACCELERATION; + } + else if ( real_angle_change < 0.0f ) + { + if ( real_angle_change < ( last_angle_change - MAX_CIRCLE_ACCELERATION ) ) + real_angle_change = last_angle_change - MAX_CIRCLE_ACCELERATION; + } + + last_angle_change = real_angle_change; + + dir = self.origin - center_actor->origin; + length = dir.length(); + + actor_angles = dir.toAngles(); + actor_angles[YAW] += real_angle_change; + + // Find new position + + actor_angles.AngleVectors( &dir, NULL, NULL ); + + dir *= length; + dir.z = 0.0f; + + self.setOrigin( center_actor->origin + dir ); + + // Set the actors angle to look at the center + + dir[0] = -dir[0]; + dir[1] = -dir[1]; + dir[2] = -dir[2]; + + self.angles[YAW] = dir.toYaw(); + self.setAngles( self.angles ); + + return BEHAVIOR_EVALUATING; + } + +void CircleEnemy::End + ( + Actor &self + ) + + { + } +/**************************************************************************** + + CircleCurrentEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, CircleCurrentEnemy, NULL ) + { + { &EV_Behavior_Args, &CircleCurrentEnemy::SetArgs }, + { NULL, NULL } + }; + +void CircleCurrentEnemy::SetArgs + ( + Event *ev + ) + + { + anim = "walk"; + + radius = 0; + maintainDistance = true; + clockwise = true; + + if (ev->NumArgs() > 0 ) + anim = ev->GetString( 1 ); + + if (ev->NumArgs() > 1 ) + radius = ev->GetFloat( 2 ); + + if (ev->NumArgs() > 2 ) + maintainDistance = ev->GetBoolean( 3 ); + + if (ev->NumArgs() > 3 ) + clockwise = ev->GetBoolean( 4 ); + + } + +void CircleCurrentEnemy::Begin + ( + Actor &self + ) + + { + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + self.SetAnim(anim); + dirToEnemy = self.origin - currentEnemy->origin; + + if(!radius) + radius = dirToEnemy.length(); + + stuck = 0; + stuckCheck = 0; + + if (clockwise) + { + turnAngle = 90; + } + else + turnAngle = -90; + + oldAngle = 0; + angleAdjusted = false; + } + + +BehaviorReturnCode_t CircleCurrentEnemy::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector dirAngles; + Vector Angles; + + float dirYaw; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + dirToEnemy = currentEnemy->origin - self.origin; + dirYaw = dirToEnemy.toYaw(); + + if(!stuck) + { + float len = dirToEnemy.length(); + if(len > radius && maintainDistance && !angleAdjusted) + { + oldAngle = turnAngle; + turnAngle*=.5f; + angleAdjusted = true; + } + + if(len <= radius && angleAdjusted) + { + turnAngle = oldAngle; + angleAdjusted = false; + } + } + + dirYaw+=turnAngle; + + self.angles[YAW] = dirYaw; + + if ( self.movementSubsystem->getLastMove() != STEPMOVE_OK ) + { + if(stuck >= 2) + { + if(stuck < 3 ) + { + turnAngle = -turnAngle; + } + else + { + self.angles[YAW] = dirYaw + angleStep; + angleStep += 45.0f; + } + } + + stuck++; + } + + stuckCheck++; + if(stuckCheck > stuck) + { + stuckCheck = 0; + stuck = 0; + angleStep = 45.0f; + } + + self.setAngles(self.angles); + + return BEHAVIOR_EVALUATING; + } + +void CircleCurrentEnemy::End + ( + Actor &self + ) + + { + + } + +/**************************************************************************** + + ChaoticDodge Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, ChaoticDodge, NULL ) + { + { &EV_Behavior_Args, &ChaoticDodge::SetArgs }, + { NULL, NULL } + }; + +void ChaoticDodge::SetArgs + ( + Event *ev + ) + + { + anim = "walk"; + stuck = 0; + time = .5; + changeTime = 0; + turnspeed = 45; + adjusting = false; + turnAngle = 0; + + if (ev->NumArgs() > 0 ) + anim = ev->GetString( 1 ); + + if (ev->NumArgs() > 1 ) + time = ev->GetFloat( 2 ); + + if (ev->NumArgs() > 2 ) + turnspeed = ev->GetFloat( 3 ); + + } + +void ChaoticDodge::Begin + ( + Actor &self + ) + + { + self.SetAnim(anim); + + } + + +BehaviorReturnCode_t ChaoticDodge::Evaluate + ( + Actor &self + ) + + { + + float dirYaw = GetNewYaw(); + + float CurrentYaw = self.origin.toYaw(); + + if ( self.movementSubsystem->getLastMove() != STEPMOVE_OK ) + { + stuck++; + turnAngle += 30.0f; + if (stuck > 3 ) + { + self.angles[YAW]+=turnAngle; + self.setAngles(self.angles); + time=1; + changeTime = level.time + time; + stuck = 0; + turnAngle = 0.0f; + } + } + + if ( level.time <= changeTime ) + return BEHAVIOR_EVALUATING; + + dirYaw+=CurrentYaw; + + self.angles[YAW] = dirYaw; + self.setAngles(self.angles); + time = G_Random ( .15f ) +.15f; + changeTime = level.time + time; + + return BEHAVIOR_EVALUATING; + } + +float ChaoticDodge::GetNewYaw + ( + void + ) + + { + float dirYaw = G_Random( 20.0f ) + turnspeed; + float randomSide = G_Random(); + if (randomSide < .5f ) + randomSide = 1.0f; + else + randomSide = -1.0f; + + dirYaw*=randomSide; + + return dirYaw; + } + +void ChaoticDodge::End + ( + Actor &self + ) + + { + + } +/**************************************************************************** + + ShockWater Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, ShockWater, NULL ) + { + { NULL, NULL } + }; + +ShockWater::ShockWater() + { + left_beam = NULL; + right_beam = NULL; + center_beam = NULL; + already_started = false; + } + +void ShockWater::Begin + ( + Actor &self + ) + + { + } + +BehaviorReturnCode_t ShockWater::Evaluate + ( + Actor &self + ) + + { + Vector left_tag_orig; + Vector right_tag_orig; + Vector end_pos; + Vector center_point; + Actor *center_actor; + trace_t trace; + Entity *hit_entity; + Vector diff_vect; + float diff; + Vector dir; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + center_actor = self.FindPartActor( "body" ); + + if ( !center_actor ) + return BEHAVIOR_SUCCESS; + + if ( ( self.newanimnum == -1 ) && !already_started ) + { + // Get tag positions + self.GetTag( "tag_left", &left_tag_orig ); + self.GetTag( "tag_right", &right_tag_orig ); + + // Get end position + end_pos = left_tag_orig + right_tag_orig; + end_pos *= .5f; + end_pos[2] -= 120.0f; + + dir = end_pos - self.origin; + dir.z = 0.0f; + dir *= 0.5f; + + end_pos += dir; + + // Add the left and right beams + left_beam = CreateBeam( NULL, "emap1", left_tag_orig, end_pos, 10, 1.5f, 0.2f ); + right_beam = CreateBeam( NULL, "emap1", right_tag_orig, end_pos, 10, 1.5f, 0.2f ); + + center_point = center_actor->origin; + + trace = G_Trace( center_point, vec_zero, vec_zero, end_pos, &self, MASK_SHOT, false, "ShockAttack" ); + + if ( ( trace.fraction < 1.0f ) && ( trace.entityNum != center_actor->entnum ) ) + { + hit_entity = G_GetEntity( trace.entityNum ); + if ( hit_entity ) + { + center_point = hit_entity->origin; + } + } + else + { + // Shock head + center_actor->AddStateFlag( STATE_FLAG_IN_PAIN ); + + center_actor->SpawnEffect( "fx_elecstrike.tik", center_actor->origin, vec_zero, 2.0f ); + center_actor->Sound( "sound/weapons/sword/electric/hitmix2.wav", 0, 1.0f, 500.0f ); + } + + // create the center beam + center_beam = CreateBeam( NULL, "emap1", end_pos, center_point, 20, 3.0f, 0.2f ); + + // Damage player if in water + + diff_vect = currentEnemy->origin - center_actor->origin; + diff_vect[2] = 0.0f; + + diff = diff_vect.length(); + //if ( diff < 240 && self.currentEnemy->groundentity ) + if ( ( diff < 350.0f ) && currentEnemy->groundentity ) + { + currentEnemy->Damage( &self, &self, 10, Vector (0.0f, 0.0f, 0.0f), Vector (0.0f, 0.0f, 0.0f), Vector (0.0f, 0.0f, 0.0f), 0, 0, MOD_ELECTRICWATER ); + } + + already_started = true; + } + + return BEHAVIOR_EVALUATING; + } + +void ShockWater::End + ( + Actor &self + ) + + { + delete left_beam; + delete right_beam; + delete center_beam; + } + +/**************************************************************************** + + Shock Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Shock, NULL ) + { + { &EV_Behavior_Args, &Shock::SetArgs }, + { NULL, NULL } + }; + +Shock::Shock() + { + beam = NULL; + damage = 10; + already_started = false; + random_angle = 0; + beamShader = "emap1"; + z_offset = 100; + } + +void Shock::SetArgs + ( + Event *ev + ) + + { + tag_name = ev->GetString( 1 ); + beamShader = ev->GetString( 2 ); + + if ( ev->NumArgs() > 3 ) + { + damage = ev->GetInteger( 3 ); + } + + if ( ev->NumArgs() > 3 ) + { + random_angle = ev->GetFloat( 4 ); + } + + if ( ev->NumArgs() > 4 ) + z_offset = ev->GetFloat( 5 ); + } + +void Shock::Begin + ( + Actor &self + ) + + { + } + +BehaviorReturnCode_t Shock::Evaluate + ( + Actor &self + ) + + { + Vector tag_orig; + Vector angles; + Vector end_pos; + trace_t trace; + Vector dir; + float yaw_diff; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( ( self.newanimnum == -1 ) && !already_started ) + { + // Get tag position + if ( tag_name.length() == 0 ) + { + return BEHAVIOR_SUCCESS; + } + + self.GetTag( tag_name.c_str(), &tag_orig ); + + // See if the enemy is in front of us + + dir = currentEnemy->centroid - self.centroid; + angles = dir.toAngles(); + + yaw_diff = AngleNormalize180( angles[YAW] - self.angles[YAW] ); + + //if ( ( yaw_diff < 60.0f ) && ( yaw_diff > -60.0f ) ) + // { + // The enemy is in front of us + + angles[YAW] += G_CRandom( random_angle ); + + angles.AngleVectors( &end_pos, NULL, NULL ); + end_pos *= 500.0f; + end_pos += tag_orig; + end_pos.z -= z_offset; + // } + /* + else + { + // Get end position + angles = self.angles; + + angles[YAW] += G_Random( random_angle ) - ( random_angle / 2.0f ); + angles[PITCH] = 0.0f; + angles[ROLL] = 0.0f; + + angles.AngleVectors( &end_pos, NULL, NULL ); + end_pos *= 500.0f; + end_pos += tag_orig; + end_pos.z -= z_offset; + } + */ + trace = G_Trace( tag_orig, Vector (-15.0f, -15.0f, -15.0f), Vector (15.0f, 15.0f, 15.0f), end_pos, &self, MASK_SHOT, false, "ShockAttack" ); + + if ( ( trace.fraction < 1.0f ) && ( trace.entityNum == currentEnemy->entnum ) ) + { + end_pos = currentEnemy->centroid; + dir = end_pos - tag_orig; + dir.normalize(); + currentEnemy->Damage( &self, &self, damage, vec_zero, dir, vec_zero, 0, 0, MOD_ELECTRIC ); + } + + // Add the beam + beam = CreateBeam( NULL, beamShader.c_str(), tag_orig, end_pos, 2, 1.5f, 0.25f ); + + already_started = true; + } + else if ( already_started ) + { + self.GetTag( tag_name.c_str(), &tag_orig ); + if ( beam ) + beam->setOrigin( tag_orig ); + } + + return BEHAVIOR_EVALUATING; + } + +void Shock::End + ( + Actor &self + ) + + { + if ( beam ) + { + beam->ProcessEvent( EV_Remove ); + beam = NULL; + } + } + +/**************************************************************************** + + MultiShock Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, MultiShock, NULL ) + { + { &EV_Behavior_Args, &MultiShock::SetArgs }, + { NULL, NULL } + }; + +MultiShock::MultiShock() + { + beam1 = NULL; + beam2 = NULL; + damage = 10; + already_started = false; + random_angle = 0; + beamShader = "emap1"; + z_offset = 100; + } + +void MultiShock::SetArgs + ( + Event *ev + ) + + { + tag_name1 = ev->GetString( 1 ); + tag_name2 = ev->GetString( 2 ); + beamShader = ev->GetString( 3 ); + damage = ev->GetFloat( 4 ); + random_angle = ev->GetFloat( 5 ); + z_offset = ev->GetFloat( 6 ); + } + + +void MultiShock::Begin + ( + Actor &self + ) + + { + } + +BehaviorReturnCode_t MultiShock::Evaluate + ( + Actor &self + ) + + { + Vector tag1_orig; + Vector tag2_orig; + + Vector angles; + Vector end_pos1; + Vector end_pos2; + + trace_t trace; + Vector dir; + float yaw_diff; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + //if ( self.newanimnum == -1 && !already_started ) + if ( !already_started ) + { + // Get tag position + if ( tag_name1.length() == 0 ) + return BEHAVIOR_SUCCESS; + + if ( tag_name2.length() == 0 ) + return BEHAVIOR_SUCCESS; + + self.GetTag( tag_name1.c_str(), &tag1_orig ); + self.GetTag( tag_name2.c_str(), &tag2_orig ); + + // See if the enemy is in front of us + + dir = currentEnemy->origin - self.origin; + angles = dir.toAngles(); + + yaw_diff = AngleNormalize180( angles[YAW] - self.angles[YAW] ); + + if ( ( yaw_diff < 60.0f ) && ( yaw_diff > -60.0f ) ) + { + // The enemy is in front of us + + angles[YAW] += G_CRandom( random_angle ); + + angles.AngleVectors( &end_pos1, NULL, NULL ); + end_pos1 *= 500.0f; + end_pos1 += tag1_orig; + end_pos1.z -= z_offset; + + angles.AngleVectors( &end_pos2, NULL, NULL ); + end_pos2 *= 500.0f; + end_pos2 += tag1_orig; + end_pos2.z -= z_offset; + } + else + { + // Get end position + angles = self.angles; + + angles[YAW] += G_Random( random_angle ) - ( random_angle / 2.0f ); + angles[PITCH] = 0.0f; + angles[ROLL] = 0.0f; + + angles.AngleVectors( &end_pos1, NULL, NULL ); + end_pos1 *= 500.0f; + end_pos1 += tag1_orig; + end_pos1.z -= z_offset; + + angles.AngleVectors( &end_pos2, NULL, NULL ); + end_pos2 *= 500.0f; + end_pos2 += tag1_orig; + end_pos2.z -= z_offset; + } + + trace = G_Trace( tag1_orig, Vector (-15.0f, -15.0f, -15.0f), Vector (15.0f, 15.0f, 15.0f), end_pos1, &self, MASK_SHOT, false, "ShockAttack" ); + + if ( ( trace.fraction < 1.0f ) && ( trace.entityNum == currentEnemy->entnum ) ) + { + end_pos1 = currentEnemy->centroid; + dir = end_pos1 - tag1_orig; + dir.normalize(); + currentEnemy->Damage( &self, &self, damage, vec_zero, dir, vec_zero, 0, 0, MOD_ELECTRIC ); + } + + // Add the beam + beam1 = CreateBeam( NULL, beamShader.c_str(), tag1_orig, end_pos1, 20, 1.5f, 0.2f ); + + trace = G_Trace( tag2_orig, Vector (-15.0f, -15.0f, -15.0f), Vector (15.0f, 15.0f, 15.0f), end_pos2, &self, MASK_SHOT, false, "ShockAttack" ); + + if ( ( trace.fraction < 1.0f ) && ( trace.entityNum == currentEnemy->entnum ) ) + { + end_pos2 = currentEnemy->centroid; + dir = end_pos2 - tag2_orig; + dir.normalize(); + currentEnemy->Damage( &self, &self, damage, vec_zero, dir, vec_zero, 0, 0, MOD_ELECTRIC ); + } + + // Add the beam + beam2 = CreateBeam( NULL, beamShader.c_str(), tag2_orig, end_pos2, 20, 1.5f, 0.2f ); + + already_started = true; + } + else + { + self.GetTag( tag_name1.c_str(), &tag1_orig ); + self.GetTag( tag_name2.c_str(), &tag2_orig ); + + if ( beam1 ) + beam1->setOrigin( tag1_orig ); + + if ( beam2 ) + beam2->setOrigin( tag2_orig ); + } + + return BEHAVIOR_EVALUATING; + } + +void MultiShock::End + ( + Actor &self + ) + + { + if ( beam1 ) + { + beam1->ProcessEvent( EV_Remove ); + beam1 = NULL; + } + + if ( beam2 ) + { + beam2->ProcessEvent( EV_Remove ); + beam2 = NULL; + } + } + +/**************************************************************************** + + ShockDown Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, ShockDown, NULL ) + { + { &EV_Behavior_Args, &ShockDown::SetArgs }, + { NULL, NULL } + }; + +ShockDown::ShockDown() + { + beam = NULL; + damage = 10; + already_started = false; + beamShader = "emap1"; + } + +void ShockDown::SetArgs + ( + Event *ev + ) + + { + tag_name = ev->GetString( 1 ); + beamShader = ev->GetString( 2 ); + + if ( ev->NumArgs() > 3 ) + { + damage = ev->GetInteger( 3 ); + } + + } + +void ShockDown::Begin + ( + Actor &self + ) + + { + } + +BehaviorReturnCode_t ShockDown::Evaluate + ( + Actor &self + ) + + { + Vector tag_orig; + Vector angles; + Vector end_pos; + trace_t trace; + Vector dir; + + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( ( self.newanimnum == -1 ) && !already_started ) + { + // Get tag position + if ( tag_name.length() == 0 ) + { + return BEHAVIOR_SUCCESS; + } + + self.GetTag( tag_name.c_str(), &tag_orig ); + + end_pos = self.origin; + end_pos.z -= 10000.0f; + + trace = G_Trace( tag_orig, Vector (-15.0f, -15.0f, -15.0f), Vector (15.0f, 15.0f, 15.0f), end_pos, &self, MASK_SHOT, false, "ShockAttack" ); + end_pos = trace.endpos; + + dir = end_pos - tag_orig; + angles = dir.toAngles(); + + if ( ( trace.fraction < 1.0f ) && ( trace.entityNum == currentEnemy->entnum ) ) + { + end_pos = currentEnemy->centroid; + dir = end_pos - tag_orig; + dir.normalize(); + currentEnemy->Damage( &self, &self, damage, vec_zero, dir, vec_zero, 0, 0, MOD_ELECTRIC ); + } + + // Add the beam + beam = CreateBeam( NULL, beamShader.c_str(), tag_orig, end_pos, 20, 1.5f, 0.2f ); + + already_started = true; + } + else if ( already_started ) + { + self.GetTag( tag_name.c_str(), &tag_orig ); + + if ( beam ) + beam->setOrigin( tag_orig ); + } + + return BEHAVIOR_EVALUATING; + } + +void ShockDown::End + ( + Actor &self + ) + + { + if ( beam ) + { + beam->ProcessEvent( EV_Remove ); + beam = NULL; + } + } +/**************************************************************************** + + CircleAttack Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, CircleAttack, NULL ) + { + { &EV_Behavior_Args, &CircleAttack::SetArgs }, + { NULL, NULL } + }; + +CircleAttack::CircleAttack() + { + next_time = 0.0f; + current_direction = 1; + number_of_attacks = 1; + } + +void CircleAttack::SetArgs + ( + Event *ev + ) + + { + command = ev->GetString( 1 ); + direction = ev->GetString( 2 ); + } + +Actor *CircleAttack::FindClosestPart + ( + const Actor &self, + float angle + ) + { + float closest_diff = 360.0f; + int i; + part_t *part; + Entity *partent; + Actor *partact; + Vector dir; + Vector angles; + float angle_diff; + Actor *closest_part = NULL; + + for( i = 1 ; i <= self.parts.NumObjects(); i++ ) + { + part = &self.parts.ObjectAt( i ); + + partent = part->ent; + partact = ( Actor * )partent; + + if ( partact && ( partact->part_name == "smallarm" ) ) + { + dir = partact->origin - self.origin; + angles = dir.toAngles(); + + angle_diff = AngleDelta( angles[ YAW ], angle ); + + if ( angle_diff < 0.0f ) + { + angle_diff = -angle_diff; + } + + if ( angle_diff < closest_diff ) + { + closest_part = partact; + closest_diff = angle_diff; + } + } + } + + return closest_part; + } + +void CircleAttack::Begin + ( + Actor &self + ) + + { + float random_direction; + Actor *closest_part; + + // Pick which arm to start with + random_direction = G_Random( 360.0f ); + + closest_part = FindClosestPart( self, random_direction ); + if ( closest_part != NULL ) + { + first_part = closest_part; + current_part = closest_part; + + closest_part->command = command; + next_time = level.time + 0.2f; + } + + current_direction = 1; + + if ( direction == "counterclockwise" ) + { + current_direction = 0; + } + } + +BehaviorReturnCode_t CircleAttack::Evaluate + ( + Actor &self + ) + + { + Entity *current_part_entity; + Actor *current_part_actor; + Vector dir; + Vector angles; + Actor *closest_part; + + if ( level.time >= next_time ) + { + current_part_entity = current_part; + current_part_actor = ( Actor * )current_part_entity; + + // Find the next part + + dir = current_part_actor->origin - self.origin; + angles = dir.toAngles(); + + if ( direction == "changing" ) + { + if ( number_of_attacks >= 20 ) + { + return BEHAVIOR_SUCCESS; + } + + if ( G_Random( 1.0f ) < .3f ) + { + current_direction = !current_direction; + } + } + + if ( current_direction == 1 ) + { + angles[YAW] -= 360.0f / 8.0f; + } + else + { + angles[YAW] += 360.0f / 8.0f; + } + + closest_part = FindClosestPart( self, angles[YAW] ); + + if ( ( closest_part == first_part ) && ( direction != "changing" ) ) + { + return BEHAVIOR_SUCCESS; + } + + current_part = closest_part; + + closest_part->command = command; + next_time = level.time + 0.2f; + + number_of_attacks++; + } + + return BEHAVIOR_EVALUATING; + } + +void CircleAttack::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + DragEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, DragEnemy, NULL ) + { + { &EV_Behavior_Args, &DragEnemy::SetArgs }, + { NULL, NULL } + }; + +void DragEnemy::SetArgs + ( + Event *ev + ) + + { + tag_name = ev->GetString( 1 ); + damage = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + drop = ev->GetBoolean( 3 ); + else + drop = false; + } + +void DragEnemy::Begin + ( + Actor &self + ) + + { + Actor *body; + Vector dir; + Vector angles; + + ent_to_drag = self.enemyManager->GetCurrentEnemy(); + + if ( !ent_to_drag ) + return; + + attached = false; + + if ( damage > 0.0f && !drop ) + { + ent_to_drag->Damage( &self, &self, damage, vec_zero, vec_zero, vec_zero, 0, 0, MOD_EAT ); + } + + body = self.FindPartActor( "body" ); + + if ( body ) + { + dir = body->origin - self.origin; + angles = dir.toAngles(); + + target_yaw = angles[ YAW ]; + last_turn_time = level.time; + } + else + { + target_yaw = self.angles[ YAW ]; + } + } + +BehaviorReturnCode_t DragEnemy::Evaluate + ( + Actor &self + ) + + { + Vector orig; + str anim_name; + + if ( !ent_to_drag ) + return BEHAVIOR_SUCCESS; + + if ( drop && ( damage > 0.0f ) ) + { + if ( self.GetTag( tag_name.c_str(), &orig ) ) + { + if ( ent_to_drag->isClient() ) + { + orig[ 2 ] -= 85.0f; + } + else + { + offset[ 2 ] = ( ent_to_drag->absmin[ 2 ] - ent_to_drag->absmax[ 2 ] ) * 0.5f; + + if ( offset[ 2 ] < -40.0f ) + { + offset[ 2 ] -= 25.0f; + } + orig += offset; + } + + ent_to_drag->setOrigin( orig ); + } + + ent_to_drag->Damage( &self, &self, damage, vec_zero, vec_zero, vec_zero, 0, 0, MOD_EAT ); + } + + if ( ent_to_drag->deadflag ) + return BEHAVIOR_SUCCESS; + + anim_name = self.animate->AnimName(); + + if ( ( anim_name == "raise" ) && ( self.newanim == "" ) ) + { + if ( !attached ) + { + Event *ev; + + if ( damage > 0.0f ) + { + if ( ent_to_drag->isClient() ) + { + offset[ 2 ] -= 85.0f; + } + else + { + offset[ 2 ] = ( ent_to_drag->absmin[ 2 ] - ent_to_drag->absmax[ 2 ] ) * 0.5f; + + if ( offset[ 2 ] < -40.0f ) + { + offset[ 2 ] -= 25.0f; + } + } + + ev = new Event( EV_Attach ); + ev->AddEntity( &self ); + ev->AddString( tag_name.c_str() ); + ev->AddInteger( false ); + ev->AddVector( offset ); + ent_to_drag->ProcessEvent( ev ); + + ent_to_drag->flags |= FL_PARTIAL_IMMOBILE; + } + + attached = true; + } + + if ( target_yaw != self.angles[ YAW ] ) + { + float yaw_diff = target_yaw - self.angles[ YAW ]; + + if ( yaw_diff > 180.0f ) + { + target_yaw -= 360.0f; + yaw_diff -= 360.0f; + } + + if ( yaw_diff < -180.0f ) + { + target_yaw += 360.0f; + yaw_diff += 360.0f; + } + + if ( yaw_diff < 0.0f ) + { + self.angles[ YAW ] -= 90.0f * (level.time - last_turn_time); + + if ( self.angles[ YAW ] < 0.0f ) + { + self.angles[ YAW ] += 360.0f; + } + + if ( self.angles[ YAW ] < target_yaw ) + { + self.angles[ YAW ] = target_yaw; + } + + self.setAngles( self.angles ); + } + else if ( yaw_diff > 0.0f ) + { + self.angles[ YAW ] += 90.0f * (level.time - last_turn_time); + + if ( self.angles[ YAW ] > 360.0f ) + { + self.angles[ YAW ] -= 360.0f; + } + + if ( self.angles[ YAW ] > target_yaw ) + { + self.angles[ YAW ] = target_yaw; + } + + self.setAngles( self.angles ); + } + + last_turn_time = level.time; + } + } + + return BEHAVIOR_EVALUATING; + } + +void DragEnemy::End + ( + Actor &self + ) + + { + Vector orig; + Event *ev; + trace_t trace; + + + if ( !ent_to_drag ) + return; + + if ( drop || ( strcmp( self.currentState->getName(), "SUICIDE" ) == 0 ) ) + { + ent_to_drag->flags &= ~FL_PARTIAL_IMMOBILE; + + ev = new Event( EV_Detach ); + ent_to_drag->ProcessEvent( ev ); + + ent_to_drag->setSolidType( SOLID_BBOX ); + + if ( self.GetTag( tag_name.c_str(), &orig ) ) + { + trace = G_Trace( orig - Vector( "0 0 100" ), ent_to_drag->mins, ent_to_drag->maxs, orig, ent_to_drag, ent_to_drag->edict->clipmask, false, "DragEnemy" ); + + if ( trace.allsolid ) + gi.WDPrintf( "Dropping entity into a solid!\n" ); + + ent_to_drag->setOrigin( trace.endpos ); + + /* if ( ent_to_drag->isClient() ) + { + offset[2] = -85; + } + else + { + offset[2] = ( ent_to_drag->absmin[2] - ent_to_drag->absmax[2] ) * 0.5; + + if ( offset[2] < -40 ) + { + offset[2] -= 25; + } + } + + ent_to_drag->setOrigin( orig + offset ); */ + } + } + + ent_to_drag->velocity = Vector(0, 0, -20); + } + + +/**************************************************************************** + + PickupEnemy Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, PickupEnemy, NULL ) + { + { &EV_Behavior_Args, &PickupEnemy::SetArgs }, + { NULL, NULL } + }; + +void PickupEnemy::SetArgs + ( + Event *ev + ) + + { + tag_name = ev->GetString( 1 ); + damage = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + drop = ev->GetBoolean( 3 ); + else + drop = false; + } + +void PickupEnemy::Begin + ( + Actor &self + ) + + { + Actor *body; + Vector dir; + Vector angles; + + ent_to_drag = self.enemyManager->GetCurrentEnemy(); + + if ( !ent_to_drag ) + return; + + attached = false; + + if ( ( damage > 0.0f ) && !drop ) + { + ent_to_drag->Damage( &self, &self, damage, vec_zero, vec_zero, vec_zero, 0, 0, MOD_EAT ); + } + + body = self.FindPartActor( "body" ); + + if ( body ) + { + dir = body->origin - self.origin; + angles = dir.toAngles(); + + target_yaw = angles[ YAW ]; + last_turn_time = level.time; + } + else + { + target_yaw = self.angles[ YAW ]; + } + } + +BehaviorReturnCode_t PickupEnemy::Evaluate + ( + Actor &self + ) + + { + Vector orig; + str anim_name; + + if ( !ent_to_drag ) + return BEHAVIOR_SUCCESS; + + if ( drop && ( damage > 0.0f ) ) + { + if ( self.GetTag( tag_name.c_str(), &orig ) ) + { + if ( ent_to_drag->isClient() ) + { + orig[ 2 ] -= 85.0f; + } + else + { + offset[ 2 ] = ( ent_to_drag->absmin[ 2 ] - ent_to_drag->absmax[ 2 ] ) * 0.5f; + + if ( offset[ 2 ] < -40.0f ) + { + offset[ 2 ] -= 25.0f; + } + orig += offset; + } + + ent_to_drag->setOrigin( orig ); + } + + ent_to_drag->Damage( &self, &self, damage, vec_zero, vec_zero, vec_zero, 0, 0, MOD_EAT ); + } + + if ( ent_to_drag->deadflag ) + return BEHAVIOR_SUCCESS; + + anim_name = self.animate->AnimName(); + + if ( !attached ) + { + Event *ev; + + if ( damage > 0.0f ) + { + if ( ent_to_drag->isClient() ) + { + offset[ 2 ] -= 85.0f; + } + else + { + offset[ 2 ] = ( ent_to_drag->absmin[ 2 ] - ent_to_drag->absmax[ 2 ] ) * 0.5f; + + if ( offset[ 2 ] < -40.0f ) + { + offset[ 2 ] -= 25.0f; + } + } + + ev = new Event( EV_Attach ); + ev->AddEntity( &self ); + ev->AddString( tag_name.c_str() ); + ev->AddInteger( false ); + ev->AddVector( offset ); + ent_to_drag->ProcessEvent( ev ); + + ent_to_drag->flags |= FL_PARTIAL_IMMOBILE; + } + + attached = true; + } + + if ( target_yaw != self.angles[ YAW ] ) + { + float yaw_diff = target_yaw - self.angles[ YAW ]; + + if ( yaw_diff > 180.0f ) + { + target_yaw -= 360.0f; + yaw_diff -= 360.0f; + } + + if ( yaw_diff < -180.0f ) + { + target_yaw += 360.0f; + yaw_diff += 360.0f; + } + + if ( yaw_diff < 0.0f ) + { + self.angles[ YAW ] -= 90.0f * (level.time - last_turn_time); + + if ( self.angles[ YAW ] < 0.0f ) + { + self.angles[ YAW ] += 360.0f; + } + + if ( self.angles[ YAW ] < target_yaw ) + { + self.angles[ YAW ] = target_yaw; + } + + self.setAngles( self.angles ); + } + else if ( yaw_diff > 0.0f ) + { + self.angles[ YAW ] += 90.0f * (level.time - last_turn_time); + + if ( self.angles[ YAW ] > 360.0f ) + { + self.angles[ YAW ] -= 360.0f; + } + + if ( self.angles[ YAW ] > target_yaw ) + { + self.angles[ YAW ] = target_yaw; + } + + self.setAngles( self.angles ); + } + + last_turn_time = level.time; + } + + return BEHAVIOR_EVALUATING; + } + +void PickupEnemy::End + ( + Actor &self + ) + + { + Vector orig; + Event *ev; + trace_t trace; + + + if ( !ent_to_drag ) + return; + + if ( drop || ( strcmp( self.currentState->getName(), "SUICIDE" ) == 0 ) ) + { + ent_to_drag->flags &= ~FL_PARTIAL_IMMOBILE; + + ev = new Event( EV_Detach ); + ent_to_drag->ProcessEvent( ev ); + + ent_to_drag->setSolidType( SOLID_BBOX ); + + if ( self.GetTag( tag_name.c_str(), &orig ) ) + { + trace = G_Trace( orig - Vector( "0 0 100" ), ent_to_drag->mins, ent_to_drag->maxs, orig, ent_to_drag, ent_to_drag->edict->clipmask, false, "DragEnemy" ); + + if ( trace.allsolid ) + gi.WDPrintf( "Dropping entity into a solid!\n" ); + + ent_to_drag->setOrigin( trace.endpos ); + + /* if ( ent_to_drag->isClient() ) + { + offset[2] = -85; + } + else + { + offset[2] = ( ent_to_drag->absmin[2] - ent_to_drag->absmax[2] ) * 0.5; + + if ( offset[2] < -40 ) + { + offset[2] -= 25; + } + } + + ent_to_drag->setOrigin( orig + offset ); */ + } + } + + ent_to_drag->velocity = Vector(0, 0, -20); + } + +/**************************************************************************** + + Teleport Class Definition + +****************************************************************************/ + +#define TELEPORT_BEHIND 0 +#define TELEPORT_TOLEFT 1 +#define TELEPORT_TORIGHT 2 +#define TELEPORT_INFRONT 3 + +#define TELEPORT_NUMBER_OF_POSITIONS 4 + +CLASS_DECLARATION( Behavior, Teleport, NULL ) + { + { NULL, NULL } + }; + + +void Teleport::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void Teleport::Begin + ( + Actor &self + ) + + { + + } + +qboolean Teleport::TestPosition + ( + Actor &self, + int test_pos, + Vector &good_position, + qboolean use_enemy_dir + ) + { + Vector test_position; + Vector enemy_angles; + Vector enemy_forward; + Vector enemy_left; + trace_t trace; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return false; + + // Get the position to test + + if ( use_enemy_dir ) + { + // Get the enemy direction info + + enemy_angles = currentEnemy->angles; + enemy_angles.AngleVectors( &enemy_forward, &enemy_left ); + + test_position = currentEnemy->origin; + + if ( test_pos == TELEPORT_BEHIND ) + test_position -= enemy_forward * 135.0f; + else if ( test_pos == TELEPORT_INFRONT ) + test_position += enemy_forward * 135.0f; + else if ( test_pos == TELEPORT_TOLEFT ) + test_position += enemy_left * 135.0f; + else + test_position -= enemy_left * 135.0f; + } + else + { + test_position = currentEnemy->origin; + + if ( test_pos == TELEPORT_BEHIND ) + test_position += Vector(-110, 0, 0); + else if ( test_pos == TELEPORT_INFRONT ) + test_position += Vector(110, 0, 0); + else if ( test_pos == TELEPORT_TOLEFT ) + test_position += Vector(0, -110, 0); + else + test_position += Vector(0, 110, 0); + } + + test_position += Vector(0, 0, 64); + + // Test to see if we can fit at the new position + + trace = G_Trace( test_position, self.mins, self.maxs, test_position - Vector( "0 0 1000" ), &self, self.edict->clipmask, false, "Teleport::TestPosition" ); + + if ( trace.allsolid || trace.startsolid ) + //if ( trace.allsolid ) + return false; + + // Make sure we can see the enemy from this position + + if ( !self.IsEntityAlive( currentEnemy ) || !self.sensoryPerception->CanSeeEntity( Vector( trace.endpos ), currentEnemy , true , true ) ) + return false; + + + // This is a good position + + good_position = trace.endpos; + return true; + } + +BehaviorReturnCode_t Teleport::Evaluate + ( + Actor &self + ) + + { + int current_position; + float random_number; + Vector teleport_position; + qboolean teleport_position_found; + Vector new_position; + int i; + Vector dir; + Vector angles; + + + // Make sure we stll have an enemy to teleport near + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + // Default the teleport position to where we are now + + teleport_position = self.origin; + teleport_position_found = false; + + // Determine which position to try first + + random_number = G_Random(); + + if ( random_number < .5f ) + current_position = TELEPORT_BEHIND; + else if ( random_number < .7f ) + current_position = TELEPORT_TOLEFT; + else if ( random_number < .9f ) + current_position = TELEPORT_TORIGHT; + else + current_position = TELEPORT_INFRONT; + + // Try positions + + for( i = 0 ; i < TELEPORT_NUMBER_OF_POSITIONS ; i++ ) + { + // Test this position + + if ( TestPosition( self, current_position, new_position, true ) ) + { + teleport_position = new_position; + teleport_position_found = true; + break; + } + + // Try the next position + + current_position++; + + if ( current_position >= TELEPORT_NUMBER_OF_POSITIONS ) + current_position = 0; + } + + if ( !teleport_position_found ) + { + // Try again with not using the enemies angles + + if ( current_position >= TELEPORT_NUMBER_OF_POSITIONS ) + current_position = 0; + + for( i = 0 ; i < TELEPORT_NUMBER_OF_POSITIONS ; i++ ) + { + // Test this position + + if ( TestPosition( self, current_position, new_position, false ) ) + { + teleport_position = new_position; + teleport_position_found = true; + break; + } + + // Try the next position + + current_position++; + + if ( current_position >= TELEPORT_NUMBER_OF_POSITIONS ) + current_position = 0; + } + } + + // Do teleport stuff + + if ( teleport_position_found ) + { + self.setOrigin( teleport_position ); + self.NoLerpThisFrame(); + + dir = currentEnemy->origin - teleport_position; + angles = dir.toAngles(); + + angles[ROLL] = 0; + angles[PITCH] = 0; + + self.setAngles( angles ); + } + + return BEHAVIOR_SUCCESS; + } + +void Teleport::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + TeleportToPlayer Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, TeleportToPlayer, NULL ) + { + { NULL, NULL } + }; + + +void TeleportToPlayer::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void TeleportToPlayer::Begin + ( + Actor &self + ) + + { + + } + +qboolean TeleportToPlayer::TestPosition + ( + Actor &self, + int test_pos, + Vector &good_position, + Entity* player, + qboolean use_player_dir + ) + { + Vector test_position; + Vector player_angles; + Vector player_forward; + Vector player_left; + trace_t trace; + + + // Get the position to test + test_position = player->origin; + + if ( use_player_dir ) + { + // Get the player direction info + + player_angles = player->angles; + player_angles.AngleVectors( &player_forward, &player_left ); + + // Check Behind the Player + test_position -= player_forward * 75.0f; + } + else + { + + // Check Behind the Player + test_position += Vector(-60, 0, 0); + } + + // Final Tweaking + test_position += Vector(0, 0, 64); + + // Test to see if we can fit at the new position + + trace = G_Trace( test_position, self.mins, self.maxs, test_position - Vector( "0 0 250" ), &self, self.edict->clipmask, false, "Teleport::TestPosition" ); + + if ( trace.allsolid || trace.startsolid ) + return false; + + if ( trace.fraction == 1.0 ) + return false; + + // Make sure we can see the Player from this position + + if ( !self.IsEntityAlive( player ) || !self.sensoryPerception->CanSeeEntity( Vector( trace.endpos ), player , true , true ) ) + return false; + + + // This is a good position + + good_position = trace.endpos; + return true; + } + +BehaviorReturnCode_t TeleportToPlayer::Evaluate + ( + Actor &self + ) + + { + int current_position; + Vector teleport_position; + qboolean teleport_position_found; + Vector new_position; + int i; + Vector dir; + Vector angles; + + Player *player = NULL; + Player *temp_player = NULL; + // Make sure the player is alive and well + for(i = 0; i < game.maxclients; i++) + { + player = GetPlayer(i); + + // don't target while player is not in the game or he's in notarget + if ( temp_player && !( temp_player->flags & FL_NOTARGET ) ) + { + player = temp_player; + break; + } + } + + if ( !player ) + return BEHAVIOR_SUCCESS; + + // Default the teleport position to where we are now + + teleport_position = self.origin; + teleport_position_found = false; + + // Always teleport BEHIND the player - - we don't want him to see us pop in. + current_position = TELEPORT_BEHIND; + + // Test this position + if ( TestPosition( self, current_position, new_position, player, true ) ) + { + teleport_position = new_position; + teleport_position_found = true; + } + else + { + if ( TestPosition( self, current_position, new_position, player, false ) ) + { + teleport_position = new_position; + teleport_position_found = true; + } + } + + // Do teleport stuff + if ( teleport_position_found ) + { + self.setOrigin( teleport_position ); + self.NoLerpThisFrame(); + + dir = player->origin - teleport_position; + angles = dir.toAngles(); + + angles[ROLL] = 0.0f; + angles[PITCH] = 0.0f; + + self.setAngles( angles ); + } + + return BEHAVIOR_SUCCESS; + } + +void TeleportToPlayer::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + TeleportToPosition Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, TeleportToPosition, NULL ) + { + { &EV_Behavior_Args, &TeleportToPosition::SetArgs }, + { NULL, NULL } + }; + +void TeleportToPosition::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void TeleportToPosition::SetArgs + ( + Event *ev + ) + + { + teleport_position_name = ev->GetString( 1 ); + number_of_teleport_positions = ev->GetInteger( 2 ); + } + +void TeleportToPosition::Begin + ( + Actor &self + ) + + { + } + +BehaviorReturnCode_t TeleportToPosition::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector angles; + trace_t trace; + str pathnode_name; + PathNode *goal; + Vector teleport_position; + Vector attack_position; + float half_height; + //Entity *effect; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + + // Get the pathnode name to teleport to + + pathnode_name = teleport_position_name; + pathnode_name += (int)G_Random( (float)number_of_teleport_positions ) + 1 ; + + // Find the path node + + goal = thePathManager.FindNode( pathnode_name ); + + if ( !goal ) + { + gi.WDPrintf( "Can't find position %s\n", pathnode_name.c_str() ); + return BEHAVIOR_SUCCESS; + } + + // Set the teleport position + + teleport_position = goal->origin; + + // Kill anything at this position + + half_height = self.maxs.z / 2.0f; + attack_position = teleport_position; + attack_position.z += half_height; + + MeleeAttack( attack_position, attack_position, 10000.0f, &self, MOD_TELEFRAG, self.maxs.x, -half_height, half_height, 0 ); + + // Test to see if we can fit at the new position + + trace = G_Trace( teleport_position + Vector( "0 0 64" ), self.mins, self.maxs, teleport_position - Vector( "0 0 128" ), &self, MASK_PATHSOLID, false, "TeleportToPosition" ); + //trace = G_Trace( teleport_position, self.mins, self.maxs, teleport_position, &self, MASK_PATHSOLID, false, "TeleportToPosition" ); + + if ( trace.allsolid ) + { + gi.WDPrintf( "Failed to teleport to %s\n", goal->targetname.c_str() ); + return BEHAVIOR_SUCCESS; + } + + teleport_position = trace.endpos; + + // Do teleport stuff + + /* + // Spawn in teleport effect at old positiond + + effect = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + effect->setModel( "fx_teleport3.tik" ); + effect->setOrigin( self.origin ); + effect->setScale( 2.0f ); + effect->setSolidType( SOLID_NOT ); + effect->animate->RandomAnimate( "idle", EV_Remove ); + //effect->Sound( "snd_teleport" ); + */ + + // Set new position + + self.setOrigin( teleport_position ); + + // Spawn in teleport effect at new position + /* + effect = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + effect->setModel( "fx_teleport3.tik" ); + effect->setOrigin( self.origin ); + effect->setScale( 2.0f ); + effect->setSolidType( SOLID_NOT ); + effect->animate->RandomAnimate( "idle", EV_Remove ); + //effect->Sound( "snd_teleport" ); + */ + + self.NoLerpThisFrame(); + + if ( currentEnemy ) + { + dir = currentEnemy->origin - teleport_position; + angles = dir.toAngles(); + + angles[ROLL] = 0.0f; + angles[PITCH] = 0.0f; + + self.setAngles( angles ); + } + + return BEHAVIOR_SUCCESS; + } + +void TeleportToPosition::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** + + GhostAttack Class Definition + +****************************************************************************/ + +#define GHOST_ATTACK_START 0 +#define GHOST_ATTACK_END 1 + +#define GHOST_ATTACK_SPEED 350.0f + +CLASS_DECLARATION( Behavior, GhostAttack, NULL ) + { + { &EV_Behavior_Args, &GhostAttack::SetArgs }, + { NULL, NULL } + }; + +void GhostAttack::SetArgs + ( + Event *ev + ) + + { + real_attack = ev->GetBoolean( 1 ); + } + + +void GhostAttack::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void GhostAttack::Begin + ( + Actor &self + ) + + { + mode = GHOST_ATTACK_START; + + fly.Begin( self ); + fly.SetTurnSpeed( 25.0f ); + fly.SetRandomAllowed( false ); + fly.SetSpeed( GHOST_ATTACK_SPEED ); + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( currentEnemy ) + attack_position = currentEnemy->centroid; + else + attack_position = self.origin; + + if ( !real_attack ) + { + attack_position[0] += G_CRandom( 300.0f ); + attack_position[1] += G_CRandom( 300.0f ); + attack_position[2] += G_Random( 100.0f ); + } + + fly.SetGoalPoint( attack_position ); + + attack_dir = attack_position - self.origin; + + if ( attack_dir == vec_zero ) + attack_dir = Vector(1, 1, 0); + + attack_dir.normalize(); + + self.Sound( "snd_fadein", CHAN_VOICE ); + } + +BehaviorReturnCode_t GhostAttack::Evaluate + ( + Actor &self + ) + + { + Vector dir; + float dist; + float zdist; + Vector new_pos; + float new_alpha; + float light_radius; + float r, g, b; + qboolean success; + Vector start; + Vector end; + Event *event; + + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( mode == GHOST_ATTACK_START ) + { + // Move closer to the enemy + + fly.Evaluate( self ); + + // Get the new distance info + + dir = attack_position - self.origin; + + dist = dir.length(); + + zdist = dir.z; + + if ( zdist < 0.0f ) + zdist = -zdist; + + dir.z = 0.0f; + + // If we are close enough change to shootable_only + + if ( real_attack && ( dist < 200.0f ) ) + { + // Attackable now + + self.setSolidType( SOLID_BBOX ); + self.setContents( CONTENTS_SHOOTABLE_ONLY ); + + event = new Event( EV_Actor_SetTargetable ); + event->AddInteger( 1 ); + self.ProcessEvent( event ); + } + + // If we are close enough damage enemy and goto end mode + + start = self.origin; + end = self.origin + ( attack_dir * 1.0f ); + + if ( real_attack ) + { + success = MeleeAttack( start, end, 7.5f, &self, MOD_LIFEDRAIN, 32.0f, 0.0f, 64.0f, 0.0f ); + self.AddStateFlag( STATE_FLAG_MELEE_HIT ); + } + else + success = false; + + if ( success || ( dist <= GHOST_ATTACK_SPEED / 40.0f ) ) + { + // Attack mode is done go to retreat mode + + if ( self.attack_blocked && ( self.attack_blocked_time == level.time ) ) + { + Vector retreat_angles; + + dir = attack_dir * -1.0f; + + retreat_angles = dir.toAngles(); + retreat_angles[YAW] += G_CRandom( 45.0f ); + retreat_angles[PITCH] += G_CRandom( 30.0f ); + retreat_angles.AngleVectors( &dir ); + + dir *= 500.0f; + + self.setAngles( retreat_angles ); + } + else + { + dir = attack_dir; + + dir.z = 0.0f; + dir.normalize(); + dir *= 1000.0f; + dir.z = 300.0f; + } + + retreat_position = self.origin + dir; + + fly.SetGoalPoint( retreat_position ); + + mode = GHOST_ATTACK_END; + } + + // Fade in depending on how close we are to attack position + + if ( dist > 400.0f ) + new_alpha = 0.0f; + else + new_alpha = ( 400.0f - dist ) / 400.0f; + + if ( new_alpha > 0.4f ) + new_alpha = 0.4f; + + r = new_alpha / 0.4f; + g = new_alpha / 0.4f; + b = new_alpha / 0.4f; + + light_radius = 0.0f; + + G_SetConstantLight( &self.edict->s.constantLight, &r, &g, &b, &light_radius ); + + self.setAlpha( new_alpha ); + } + else + { + // Move away from enemy + + fly.Evaluate( self ); + + // Get the new distance info + + dir = attack_position - self.origin; + dist = dir.length(); + + if ( real_attack && ( dist > 200.0f ) ) + { + // Not attackable again + + self.setSolidType( SOLID_NOT ); + self.setContents( 0 ); + + event = new Event( EV_Actor_SetTargetable ); + event->AddInteger( 0 ); + self.ProcessEvent( event ); + } + + // Fade out depending on how far we are from the attack position + + if ( dist > 400.0f ) + new_alpha = 0.0f; + else + new_alpha = ( 400.0f - dist ) / 400.0f; + + if ( new_alpha > 0.4f ) + new_alpha = 0.4f; + + r = new_alpha / 0.4f; + g = new_alpha / 0.4f; + b = new_alpha / 0.4f; + + light_radius = 0.0f; + + G_SetConstantLight( &self.edict->s.constantLight, &r, &g, &b, &light_radius ); + + self.setAlpha( new_alpha ); + + // See if we are far enough to be done + + if ( new_alpha == 0.0f ) + return BEHAVIOR_SUCCESS; + } + return BEHAVIOR_EVALUATING; + } + +void GhostAttack::End + ( + Actor &self + ) + + { + // Make sure we can't be shot any more + + self.setSolidType( SOLID_NOT ); + self.setContents( 0 ); + + fly.End( self ); + } + +/**************************************************************************** + + Levitate Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, Levitate, NULL ) + { + { &EV_Behavior_Args, &Levitate::SetArgs }, + { NULL, NULL } + }; + +void Levitate::SetArgs + ( + Event *ev + ) + + { + distance = ev->GetFloat( 1 ); + speed = ev->GetFloat( 2 ); + } + + +void Levitate::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void Levitate::Begin + ( + Actor &self + ) + + { + final_z = self.origin.z + distance; + } + +BehaviorReturnCode_t Levitate::Evaluate + ( + Actor &self + ) + + { + trace_t trace; + Vector start; + Vector end; + + + start = self.origin; + end = self.origin; + + if ( final_z < self.origin.z ) + { + end.z -= speed; + + if ( end.z < final_z ) + end.z = final_z; + } + else + { + end.z += speed; + + if ( end.z > final_z ) + end.z = final_z; + } + + trace = G_Trace( start, self.mins, self.maxs, end, &self, self.edict->clipmask, false, "Levitate" ); + + if ( trace.fraction != 1.0 ) + return BEHAVIOR_SUCCESS; + + if ( end.z == final_z ) + return BEHAVIOR_SUCCESS; + + self.setOrigin( trace.endpos ); + + return BEHAVIOR_EVALUATING; + } + +void Levitate::End + ( + Actor &self + ) + + { + } + +/**************************************************************************** +***************************************************************************** + + Utility functions + +***************************************************************************** +****************************************************************************/ + + + +Vector ChooseRandomDirection + ( + Actor &self, + const Vector &previousdir, + float *time, + int disallowcontentsmask, + qboolean usepitch + ) + { + //static float x[ 9 ] = { 0, 22, -22, 45, -45, 0, 22, -22, 45 }; + static float x[ 5 ] = { 0, 22, -22, 0, 22 }; + Vector dir; + Vector ang; + Vector bestdir; + Vector newdir; + Vector step; + Vector endpos; + Vector groundend; + float bestfraction; + trace_t trace; + trace_t groundtrace; + int i; + int k; + int t; + int u; + int contents; + Vector centroid; + float random_yaw; + float movespeed; + + //if ( self.movespeed != 1 ) + if ( self.movementSubsystem->getMoveSpeed() != 1.0f ) + movespeed = self.movementSubsystem->getMoveSpeed(); + else + movespeed = 100.0f; + + centroid = self.centroid - self.origin; + + step = Vector( 0.0f, 0.0f, STEPSIZE ); + bestfraction = -1.0f; + bestdir = self.origin; + + random_yaw = G_Random( 360.0f ); + + for( i = 0; i <= 8; i++ ) + { + t = (int)random_yaw + ( i * 45 ); + + ang.y = self.angles.y + (float)t; + + if ( usepitch ) + { + u = ( int )G_Random( 5.0f ); + + //for( k = 0; k < 5; k++ ) + for( k = 0; k < 3; k++ ) + { + ang.x = x[ k + u ]; + ang.AngleVectors( &dir, NULL, NULL ); + + dir *= movespeed * (*time); + dir += self.origin; + trace = G_Trace( self.origin, self.mins, self.maxs, dir, &self, self.edict->clipmask, false, "ChooseRandomDirection 1" ); + + if ( !trace.startsolid && !trace.allsolid ) + { + newdir = Vector( trace.endpos ); + + if ( disallowcontentsmask ) + { + contents = gi.pointcontents( ( newdir + centroid ), 0 ); + + if ( contents & disallowcontentsmask ) + continue; + } + + if ( + ( trace.fraction > bestfraction ) && + ( newdir != bestdir ) && + ( newdir != previousdir ) + ) + { + bestdir = newdir; + bestfraction = trace.fraction; + + if ( bestfraction > .9f ) + { + *time *= bestfraction; + + return bestdir; + } + } + } + } + } + else + { + ang.x = 0.0f; + ang.AngleVectors( &dir, NULL, NULL ); + + endpos = self.origin + ( dir * movespeed * (*time) ) + step; + + trace = G_Trace( self.origin + step, self.mins, self.maxs, endpos, &self, self.edict->clipmask, false, "ChooseRandomDirection 2" ); + + if ( !trace.startsolid && !trace.allsolid ) + { + newdir = Vector( trace.endpos ); + + if ( disallowcontentsmask ) + { + contents = gi.pointcontents( ( newdir + centroid ), 0 ); + + if ( contents & disallowcontentsmask ) + continue; + } + + if ( + ( trace.fraction > bestfraction ) && + ( newdir != bestdir ) && + ( newdir != previousdir ) + ) + { + groundend = endpos - ( step * 2.0f ); + + groundtrace = G_Trace( endpos, self.mins, self.maxs, groundend, &self, self.edict->clipmask, false, "Chase::ChooseRandomDirection 3" ); + + if ( groundtrace.fraction == 1.0f ) + trace.fraction /= 2.0f; + + if ( trace.fraction > bestfraction ) + { + bestdir = newdir; + bestfraction = trace.fraction; + + if ( bestfraction > .9f ) + { + *time *= bestfraction; + + return bestdir; + } + } + } + } + } + } + + if ( bestfraction > 0.0f ) + { + *time *= bestfraction; + } + else + { + *time = 0.0f; + } + + return bestdir; + } + + +// +// WayPoint Stuff +// +/**************************************************************************** + + GotoWayPoint Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, GotoWayPoint, NULL ) + { + { &EV_Behavior_Args, &GotoWayPoint::SetArgs }, + { NULL, NULL } + }; + +void GotoWayPoint::SetArgs + ( + Event *ev + ) + + { + path_name = ev->GetString( 1 ); + anim = ev->GetString( 2 ); + current_waypoint_name = path_name; + current_waypoint = NULL; + } + +void GotoWayPoint::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase.ShowInfo( self ); + } + +void GotoWayPoint::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + if ( anim != self.animname || self.newanim.length() ) + { + self.SetAnim( anim ); + } + + chase.Begin( self ); + + next_think_time = 0.0f; + } + +BehaviorReturnCode_t GotoWayPoint::Evaluate + ( + Actor &self + ) + + { + //First Check if we are at a way point; + Vector dest; + Vector check; + + if (!current_waypoint) + { + current_waypoint = GetWayPoint( current_waypoint_name ); + if (!current_waypoint) + return BEHAVIOR_SUCCESS; + } + + dest = current_waypoint->origin; + check = dest - self.origin; + + // Check if we're close enough + if ( check.length() < 10.0f ) + { + // Run Our Thread + str waypointThread; + waypointThread = current_waypoint->GetThread(); + + if (waypointThread.length() ) + self.RunThread( waypointThread ); + + // See if we have another point to go to + if ( current_waypoint->target.length() == 0 ) + { + return BEHAVIOR_SUCCESS; + } + + // Check for new anim + anim = ""; + anim = current_waypoint->GetActorAnim(); + + if ( anim.length() ) + self.SetAnim( anim ); + + // Go To the Next Point + current_waypoint_name = current_waypoint->target; + current_waypoint = GetWayPoint( current_waypoint_name ); + + if (!current_waypoint) + return BEHAVIOR_SUCCESS; + + dest = current_waypoint->origin; + + //Clear out anim strings + anim = ""; + + + } + + float radius=96.0f; + chase.SetGoal( current_waypoint->origin, radius, self ); + chase.Evaluate(self); + return BEHAVIOR_EVALUATING; + } + +void GotoWayPoint::End + ( + Actor &self + ) + + { + chase.End( self ); + } + +WayPointNode* GotoWayPoint::GetWayPoint + ( + const str &waypoint_name + ) + + { + Entity* ent_in_range; + gentity_t *ed; + + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + + ent_in_range = g_entities[i].entity; + + if( ent_in_range->isSubclassOf( WayPointNode ) ) + { + if (!Q_stricmp(ent_in_range->targetname.c_str() , waypoint_name.c_str() )) + { + return (WayPointNode*)ent_in_range; + } + } + } + + return NULL; + } + +/**************************************************************************** + + FlyCircleAroundWaypoint Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, FlyCircleAroundWaypoint, NULL ) + { + { &EV_Behavior_Args, &FlyCircleAroundWaypoint::SetArgs }, + { NULL, NULL } + }; + +FlyCircleAroundWaypoint::FlyCircleAroundWaypoint() + { + anim = "fly"; + nearestPlayer = false; + fly_clockwise = true; + } + +void FlyCircleAroundWaypoint::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + fly_clockwise = ev->GetBoolean( 2 ); + waypointname = ev->GetString( 3 ); + + if (ev->NumArgs() > 3 ) + fly.SetSpeed( ev->GetFloat( 4 ) ); + + if (ev->NumArgs() > 4 ) + fly.setAdjustYawAndRoll( ev->GetBoolean( 5 ) ); + + if ( !Q_stricmp(waypointname.c_str() , "player" ) ) + nearestPlayer = true; + + } + +void FlyCircleAroundWaypoint::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void FlyCircleAroundWaypoint::Begin + ( + Actor &self + ) + + { + //original_z = self.origin.z; + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + fly.Begin( self ); + fly.SetTurnSpeed( 5.0f ); + } + +BehaviorReturnCode_t FlyCircleAroundWaypoint::Evaluate + ( + Actor &self + ) + + { + Vector goal; + trace_t trace; + Vector dir; + Vector angle; + Vector left; + qboolean too_far = false; + Vector new_dir; + Vector fly_dir; + WayPointNode *goalNode; + + + goalNode = NULL; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( !self.IsEntityAlive( currentEnemy ) ) + { + return BEHAVIOR_SUCCESS; + } + + //if ( self.lastmove == STEPMOVE_OK ) + if ( self.movementSubsystem->getLastMove() == STEPMOVE_OK ) + { + fly.SetTurnSpeed( 5.0f ); + + if ( nearestPlayer ) + goalNode = GetWayPointNearestPlayer( self ); + else + goalNode = GetWayPoint( self ); + + if ( !goalNode ) + return BEHAVIOR_SUCCESS; + + dir = goalNode->origin - self.origin; + dir.z = 0; + + if ( dir.length() > 200.0f ) //radius + { + too_far = true; + } + + + angle = dir.toAngles(); + angle.AngleVectors( NULL, &left, NULL ); + + if ( fly_clockwise ) + fly_dir = left; + else + fly_dir = left * -1.0f; + + dir.normalize(); + + if ( too_far ) + { + new_dir = ( fly_dir * 0.5f ) + ( dir * 0.5f ); + new_dir.normalize(); + } + else + { + new_dir = fly_dir; + } + + goal = self.origin + ( new_dir * 200.0f ); + + trace = G_Trace( self.origin, self.mins, self.maxs, goal, &self, self.edict->clipmask, false, "FlyCircle" ); + + if ( trace.fraction < 1.0f ) + { + if ( too_far ) + trace.fraction /= 2.0f; + + new_dir = ( fly_dir * trace.fraction ) + ( dir * ( 1.0f - trace.fraction ) ); + new_dir.normalize(); + goal = self.origin + ( new_dir * 200.0f ); + } + else + { + goal = trace.endpos; + } + + fly.SetGoalPoint( goal ); + } + else + { + fly.SetTurnSpeed( 20.0f ); + } + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +WayPointNode *FlyCircleAroundWaypoint::GetWayPoint( Actor &self ) + { + Entity* ent_in_range; + gentity_t *ed; + + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + + ent_in_range = g_entities[i].entity; + + if( ent_in_range->isSubclassOf( WayPointNode ) ) + { + if (!Q_stricmp(ent_in_range->targetname.c_str() , waypointname.c_str() )) + { + return (WayPointNode*)ent_in_range; + } + } + } + + return NULL; + } + +WayPointNode *FlyCircleAroundWaypoint::GetWayPointNearestPlayer( Actor &self) + { + Vector DistanceFromPlayer; + float distance; + Entity* ent_in_range; + Entity* best_ent; + + gentity_t *ed; + + ent_in_range = NULL; + best_ent = NULL; + distance = 100000000; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return NULL; + + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + + ent_in_range = g_entities[i].entity; + + if( ent_in_range->isSubclassOf( WayPointNode ) ) + { + DistanceFromPlayer = ent_in_range->origin - currentEnemy->origin; + if (DistanceFromPlayer.length() < distance ) + { + best_ent = ent_in_range; + distance = DistanceFromPlayer.length(); + } + } + } + + return (WayPointNode*)best_ent; + } + +void FlyCircleAroundWaypoint::End + ( + Actor &self + ) + + { + fly.End( self ); + } + +/**************************************************************************** + + HelicopterFlyToPoint Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, HelicopterFlyToPoint, NULL ) + { + { NULL, NULL } + }; + +HelicopterFlyToPoint::HelicopterFlyToPoint() + { + turn_speed = 10.0; + old_turn_speed = turn_speed; + speed = 480.0; + random_allowed = true; + force_goal = false; + adjustYawAndRoll = true; + offsetOrigin = false; + avoidtime = 0; + old_forward_speed = 0; + stuck = 0; + use_temp_goal = false; + } + +void HelicopterFlyToPoint::SetTurnSpeed( float new_turn_speed ) + { + turn_speed = new_turn_speed; + } + +void HelicopterFlyToPoint::SetGoalPoint( const Vector &goal_point ) + { + if ( goal_point != goal ) + avoidtime = 0; + + goal = goal_point; + } + +void HelicopterFlyToPoint::SetRandomAllowed( qboolean allowed ) + { + random_allowed = allowed; + } + +void HelicopterFlyToPoint::SetSpeed( float speed_value ) + { + speed = speed_value; + } + +void HelicopterFlyToPoint::ForceGoal( void ) + { + force_goal = true; + } + +void HelicopterFlyToPoint::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void HelicopterFlyToPoint::Begin + ( + Actor &self + ) + + { + avoidtime = 0; + //old_forward_speed = self.forwardspeed; + old_forward_speed = self.movementSubsystem->getForwardSpeed(); + stuck = 0; + use_temp_goal = false; + } + +BehaviorReturnCode_t HelicopterFlyToPoint::Evaluate + ( + Actor &self + ) + + { + trace_t trace; + Vector dir; + Vector ang; + float time; + float length; + float old_yaw; + float old_pitch; + + float x_offset = 0.0f; + float y_offset = 0.0f; + //float z_offset = 0.0f; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + + if (offsetOrigin) + { + x_offset = self.centroid.x - self.origin.x; + y_offset = self.centroid.y - self.origin.y; + //z_offset = self.centroid.z - self.origin.z; + + self.origin.x+=x_offset; + self.origin.y+=y_offset; + //self.origin.z+=z_offset; + } + + //if ( self.lastmove != STEPMOVE_OK ) + if ( self.movementSubsystem->getLastMove() != STEPMOVE_OK ) + stuck++; + else + stuck = 0; + + if ( ( stuck > 1 ) || ( avoidtime <= level.time ) ) + { + time = G_Random( .3f ) + .3f; + + use_temp_goal = false; + + if ( !force_goal ) + { + trace = G_Trace( self.origin, self.mins, self.maxs, goal, &self, self.edict->clipmask, false, "FlyToPoint" ); + + if ( ( trace.fraction < 0.5f ) || ( stuck > 2 ) ) + { + old_turn_speed = self.movementSubsystem->getTurnSpeed(); + self.movementSubsystem->setTurnSpeed( 60.0f ); + //self.turnspeed = 60; + temp_goal = ChooseRandomDirection( self, goal, &time, MASK_WATER, false ); + self.movementSubsystem->setTurnSpeed( old_turn_speed ); + //self.turnspeed = old_turn_speed; + use_temp_goal = true; + avoidtime = level.time + time; + + stuck = 0; + } + else + { + goal = trace.endpos; + avoidtime = level.time + time; + } + } + else + { + avoidtime = level.time + time; + } + + if ( use_temp_goal ) + dir = temp_goal - self.origin; + else + dir = goal - self.origin; + + length = dir.length(); + dir.normalize(); + + //new stuff + //self.FlightDir = dir; + + + ang = dir.toAngles(); + + if ( ( length > 150.0f ) && random_allowed && !use_temp_goal ) + { + //ang[YAW] += G_Random( 20 ) - 5.0; + //ang[PITCH] += G_Random( 20 ) - 5.0; + } + + target_angle = ang; + + target_angle[YAW] = AngleNormalize180( target_angle[YAW] ); + target_angle[PITCH] = AngleNormalize180( target_angle[PITCH] ); + + if ( currentEnemy ) + { + Vector enemyDir; + enemyDir = currentEnemy->origin - self.origin; + enemyDir.normalize(); + target_angle[PITCH] = AngleNormalize180(-enemyDir.toPitch()); + if ( target_angle[PITCH] < -40.0f ) + target_angle[PITCH] = -40.0f; + + if ( target_angle[PITCH] > 40.0f ) + target_angle[PITCH] = 40.0f; + + target_angle[YAW] = enemyDir.toYaw(); + target_angle[ROLL] = 0.0f; + } + } + + target_angle[YAW] = AngleNormalize360( target_angle[YAW] ); + target_angle[PITCH] = AngleNormalize360( target_angle[PITCH] ); + + if ( (self.angles[YAW] != target_angle[YAW]) || (self.angles[PITCH] != target_angle[PITCH]) ) + { + self.movementSubsystem->setForwardSpeed( speed * 0.8f ); + //self.forwardspeed = speed * 0.8f; + } + else + { + self.movementSubsystem->setForwardSpeed( speed ); + //self.forwardspeed = speed; + } + + old_yaw = self.angles[YAW]; + old_pitch = self.angles[PITCH]; + + ang[YAW] = LerpAngle( self.angles[YAW], target_angle[YAW], turn_speed ); + ang[PITCH] = LerpAngle( self.angles[PITCH], target_angle[PITCH], turn_speed ); + ang[ROLL] = self.angles[ROLL]; + + /* + if( adjustYawAndRoll ) + { + + if ( (AngleDelta( ang[YAW], old_yaw ) > 0) && (ang[ROLL] > 315 || ang[ROLL] <= 45) ) + { + ang[ROLL] += 5; + } + else if ( (AngleDelta( ang[YAW], old_yaw ) < 0) && (ang[ROLL] < 45 || ang[ROLL] >= 315) ) + { + ang[ROLL] -= 5; + } + else if ( (AngleDelta( ang[YAW], old_yaw ) == 0 ) ) + { + if ( ang[ROLL] != 0 ) + { + + if ( ang[ROLL] < 5 || ang[ROLL] > 355 ) + { + ang[ROLL] = 0; + } + else + { + if ( ang[ROLL] < 180 ) + ang[ROLL] += 5; + else + ang[ROLL] -= 5; + } + + } + } + } + */ + + if ( adjustYawAndRoll ) + { + //Vector travelAngleDelta = dir.toAngles(); + float yawDelta = dir[YAW] - self.angles[YAW]; + + yawDelta = AngleNormalize360(yawDelta); + + if ( ( yawDelta > 0.0f ) && ( yawDelta <= 180.0f ) ) + { + ang[ROLL] = self.angles[ROLL] + 2.0f; + if ( AngleNormalize180(ang[ROLL]) > 30.0f ) + ang[ROLL] = 30.0f; + } + + if ( ( yawDelta >= 180.0f ) && ( yawDelta <= 359.0f ) ) + { + ang[ROLL] = self.angles[ROLL] - 2.0f; + if ( AngleNormalize180(ang[ROLL]) < -30.0f ) + ang[ROLL] = -30.0f; + } + + } + + ang[YAW] = AngleNormalize360( ang[YAW] ); + ang[PITCH] = AngleNormalize360( ang[PITCH] ); + ang[ROLL] = AngleNormalize360( ang[ROLL] ); + + // Don't get stuck if still turning + + if ( ( AngleDelta( ang[YAW], old_yaw ) > .5f ) || ( AngleDelta( ang[YAW], old_yaw ) < -.5f ) || + ( AngleDelta( ang[PITCH], old_pitch ) > .5f ) || ( AngleDelta( ang[PITCH], old_pitch ) < -.5f ) ) + { + stuck = 0; + } + + self.setAngles( ang ); + + if (offsetOrigin) + { + self.origin.x-=x_offset; + self.origin.y-=y_offset; + //self.origin.z-=z_offset; + } + + return BEHAVIOR_EVALUATING; + } + +float HelicopterFlyToPoint::LerpAngle( float old_angle, float new_angle, float lerp_amount ) + { + float diff; + float abs_diff; + float lerp_angle; + + new_angle = AngleNormalize360( new_angle ); + old_angle = AngleNormalize360( old_angle ); + + diff = new_angle - old_angle; + + if ( diff > 180.0f ) + { + diff -= 360.0f; + } + + if ( diff < -180.0f ) + { + diff += 360.0f; + } + + lerp_angle = old_angle; + + abs_diff = diff; + + if ( abs_diff < 0.0f ) + { + abs_diff = -abs_diff; + } + + if ( abs_diff < lerp_amount ) + { + lerp_amount = abs_diff; + } + + if ( diff < 0.0f ) + { + lerp_angle -= lerp_amount; + } + else if ( diff > 0.0f ) + { + lerp_angle += lerp_amount; + } + + lerp_angle = AngleNormalize360( lerp_angle ); + + return lerp_angle; + } + +void HelicopterFlyToPoint::End + ( + Actor &self + ) + + { + self.movementSubsystem->setForwardSpeed( old_forward_speed ); + //self.FlightDir = vec_zero; + } + +/**************************************************************************** + + FlyCircle Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, HelicopterFlyCircle, NULL ) + { + { &EV_Behavior_Args, &HelicopterFlyCircle::SetArgs }, + { NULL, NULL } + }; + +HelicopterFlyCircle::HelicopterFlyCircle() + { + anim = "fly"; + fly_clockwise = true; + circle_player = false; + } + +void HelicopterFlyCircle::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + fly_clockwise = ev->GetBoolean( 2 ); + + if (ev->NumArgs() > 2 ) + fly.setAdjustYawAndRoll( false ); + //fly.setAdjustYawAndRoll( ev->GetBoolean( 3 ) ); + + if (ev->NumArgs() > 3 ) + circle_player = ev->GetBoolean ( 4 ); + else + circle_player = false; + + if (ev->NumArgs() > 4 ) + fly.SetSpeed( ev->GetFloat( 5 ) ); + } + +void HelicopterFlyCircle::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void HelicopterFlyCircle::Begin + ( + Actor &self + ) + + { + //original_z = self.origin.z; + + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + fly.Begin( self ); + fly.SetTurnSpeed( 5.0f ); + } + +BehaviorReturnCode_t HelicopterFlyCircle::Evaluate + ( + Actor &self + ) + + { + Vector goal; + trace_t trace; + Vector dir; + Vector angle; + Vector left; + qboolean too_far = false; + Vector new_dir; + Vector fly_dir; + + //Vector or; + + //or = self.currentEnemy->centroid - self.origin; + //or = or.toAngles(); + + //self.angles = or; + //self.setAngles( or ); + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( !self.IsEntityAlive( currentEnemy ) && !circle_player ) + { + return BEHAVIOR_SUCCESS; + } + + //if ( self.lastmove == STEPMOVE_OK ) + if ( self.movementSubsystem->getLastMove() == STEPMOVE_OK ) + { + fly.SetTurnSpeed( 5.0f ); + + if (circle_player) + { + Player *player = NULL; + Player *temp_player = NULL; + // Make sure the player is alive and well + for(int i = 0; i < game.maxclients; i++) + { + temp_player = GetPlayer(i); + + // don't target while player is not in the game or he's in notarget + if ( temp_player && !( temp_player->flags & FL_NOTARGET ) ) + { + player = temp_player; + break; + } + } + + if ( !player ) + return BEHAVIOR_SUCCESS; + + dir = player->centroid - self.origin; + dir.z = 0; + + //if ( dir.length() > (self.origin.z - player->centroid.z) / .5 ) + if ( dir.length() > 600.0f ) + { + too_far = true; + } + + } + else + { + dir = currentEnemy->centroid - self.origin; + dir.z = 0; + + if ( dir.length() > ( ( self.origin.z - currentEnemy->centroid.z) / 2.0f ) ) + { + too_far = true; + } + + } + + angle = dir.toAngles(); + + angle.AngleVectors( NULL, &left, NULL ); + + Vector newAngles = self.angles; + if ( fly_clockwise ) + { + fly_dir = left; + + newAngles[ROLL] -= 5.0f; + if ( AngleNormalize180(newAngles[ROLL]) < -30.0f ) + newAngles[ROLL] = -30.0f; + + } + else + { + fly_dir = left * -1.0f; + newAngles[ROLL] += 5.0f; + if ( AngleNormalize180(newAngles[ROLL]) > 30.0f ) + newAngles[ROLL] = 30.0f; + + } + + self.setAngles(newAngles); + + dir.normalize(); + + if ( too_far ) + { + new_dir = ( fly_dir * 0.5f ) + ( dir * 0.5f ); + new_dir.normalize(); + } + else + { + new_dir = fly_dir; + } + + goal = self.origin + ( new_dir * 200.0f ); + + trace = G_Trace( self.origin, self.mins, self.maxs, goal, &self, self.edict->clipmask, false, "FlyCircle" ); + + if ( trace.fraction < 1.0f ) + { + if ( too_far ) + trace.fraction /= 2.0f; + + new_dir = ( fly_dir * trace.fraction ) + ( dir * ( 1.0f - trace.fraction ) ); + new_dir.normalize(); + + goal = self.origin + ( new_dir * 200.0f ); + } + else + { + goal = trace.endpos; + } + + fly.SetGoalPoint( goal ); + } + else + { + fly.SetTurnSpeed( 20.0f ); + } + + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +void HelicopterFlyCircle::End + ( + Actor &self + ) + + { + fly.End( self ); + } + + +/**************************************************************************** + + HelicopterStrafeAttack Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, HelicopterStrafeAttack, NULL ) + { + { &EV_Behavior_Args, &HelicopterStrafeAttack::SetArgs }, + { NULL, NULL } + }; + +HelicopterStrafeAttack::HelicopterStrafeAttack() + { + anim = "fly"; + turnTime = 5; + setUpLerp = false; + completedLerp = false; + + lerpStart = 0.0f; + lerpEnd = 0.0f; + + startYaw = 0.0f; + endYaw = 0.0f; + } + +void HelicopterStrafeAttack::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + } + +void HelicopterStrafeAttack::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void HelicopterStrafeAttack::Begin + ( + Actor &self + ) + + { + self.SetAnim(anim); + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return; + + //First we find the waypoint closest to the player + //These waypoints should be pointed along the axis + //that we want the helicopter to strafe + WayPointNode *wp = GetWayPointNearestPlayer(self); + + str tName = wp->Target(); + WayPointNode *target = GetWayPoint(self , tName ); + + //Set up goal location + //goal = target->origin; + goal = wp->origin; + goal.z = self.origin.z; + + //We need the cross product + Vector wpForward; + Vector wpUp; + Vector cross; + + wpForward = target->origin - wp->origin; + wpForward.normalize(); + + + wpUp.x = 0; + wpUp.y = 0; + wpUp.z = 1; + + CrossProduct(wpForward, wpUp, cross ); + + + + //wpOrigin.AngleVectors(NULL,&left,NULL); + + //Get the vector from the player to the actor + Vector playerToActor; + playerToActor = self.origin - currentEnemy->origin; + + + //Normalize the vectors + playerToActor.normalize(); + cross.normalize(); + + //Now we get the Dot product to see if towardPlayer + float dot; + dot = DotProduct(cross,playerToActor); + + + //Now we get our actual direction Vector; + if ( dot > 0.0f ) + dir = cross * -1.0f; + else + dir = cross; + + + //Now set our angles appropriately + targetAngles = self.angles; + targetAngles[YAW] = dir.toYaw(); + + startYaw = AngleNormalize360(self.angles[YAW]); + endYaw = AngleNormalize360(targetAngles[YAW]); + + if ( ( endYaw == 0.0f ) && ( startYaw > 180.0f ) ) + endYaw = 360.0f; + + } + +BehaviorReturnCode_t HelicopterStrafeAttack::Evaluate + ( + Actor &self + ) + + { + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( !setUpLerp ) + { + lerpStart = level.time; + lerpEnd = lerpStart + turnTime; + setUpLerp = true; + } + + if ( !completedLerp ) + LerpToNewAngle( self ); + + + //Get our travel destination + Vector travelDir; + travelDir = goal - self.origin; + + //Check if we are at our destination + float length = travelDir.length(); + + if ( length < 25.0f ) //We're close enough + return BEHAVIOR_SUCCESS; + + travelDir.normalize(); + //self.FlightDir = travelDir; + self.movementSubsystem->setForwardSpeed( 300.0f ); + + + return BEHAVIOR_EVALUATING; + } + +WayPointNode *HelicopterStrafeAttack::GetWayPointNearestPlayer( Actor &self) + { + Vector DistanceFromPlayer; + float distance; + Entity* ent_in_range; + Entity* best_ent; + + gentity_t *ed; + + ent_in_range = NULL; + best_ent = NULL; + distance = 100000000; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return NULL; + + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + + ent_in_range = g_entities[i].entity; + + if( ent_in_range->isSubclassOf( WayPointNode ) ) + { + DistanceFromPlayer = ent_in_range->origin - currentEnemy->origin; + if (DistanceFromPlayer.length() < distance ) + { + best_ent = ent_in_range; + distance = DistanceFromPlayer.length(); + } + } + } + + return (WayPointNode*)best_ent; + } + +WayPointNode *HelicopterStrafeAttack::GetWayPoint( Actor &self , const str &name ) + { + Entity* ent_in_range; + gentity_t *ed; + + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + + ent_in_range = g_entities[i].entity; + + if( ent_in_range->isSubclassOf( WayPointNode ) ) + { + if (!Q_stricmp(ent_in_range->targetname.c_str() , name.c_str() )) + { + return (WayPointNode*)ent_in_range; + } + } + } + + return NULL; + } + +void HelicopterStrafeAttack::LerpToNewAngle( Actor &self ) + { + Vector ang; + float newYaw; + + ang = self.angles; + + newYaw = ( endYaw - startYaw ) / ( lerpEnd - lerpStart ); + newYaw = newYaw * (level.time - lerpStart ); + newYaw = newYaw + startYaw; + + ang[YAW] = newYaw; + // Clamp it down in the end + if ( level.time >= lerpEnd ) + { + ang[YAW] = endYaw; + completedLerp = true; + } + + self.setAngles(ang); + + } + +void HelicopterStrafeAttack::End + ( + Actor &self + ) + + { + //self.FlightDir = vec_zero; + } + + +/**************************************************************************** + + HelicopterFlyToWaypoint Class Definition + +****************************************************************************/ + + +CLASS_DECLARATION( Behavior, HelicopterFlyToWaypoint, NULL ) + { + { &EV_Behavior_Args, &HelicopterFlyToWaypoint::SetArgs }, + { NULL, NULL } + }; + +HelicopterFlyToWaypoint::HelicopterFlyToWaypoint() + { + anim = "fly"; + nearestPlayer = false; + nearestPlayerTarget = false; + + } + +void HelicopterFlyToWaypoint::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + waypointname = ev->GetString( 2 ); + + if (ev->NumArgs() > 2 ) + fly.SetSpeed( ev->GetFloat( 3 ) ); + + if (ev->NumArgs() > 3 ) + fly.setAdjustYawAndRoll( ev->GetBoolean( 4 ) ); + + if ( !Q_stricmp(waypointname.c_str() , "player" ) ) + nearestPlayer = true; + + if ( !Q_stricmp(waypointname.c_str() , "target" ) ) + nearestPlayerTarget = true; + + } + +void HelicopterFlyToWaypoint::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + } + +void HelicopterFlyToWaypoint::Begin + ( + Actor &self + ) + + { + if ( anim.length() ) + { + self.SetAnim( anim ); + } + + fly.Begin( self ); + fly.SetTurnSpeed( 5.0f ); + } + +BehaviorReturnCode_t HelicopterFlyToWaypoint::Evaluate + ( + Actor &self + ) + + { + Vector goal; + WayPointNode *goalNode; + goalNode = NULL; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return BEHAVIOR_SUCCESS; + + if ( !self.IsEntityAlive( currentEnemy ) ) + { + return BEHAVIOR_SUCCESS; + } + + //if ( self.lastmove == STEPMOVE_OK ) + if ( self.movementSubsystem->getLastMove() == STEPMOVE_OK ) + { + fly.SetTurnSpeed( 5.0f ); + + if ( nearestPlayer ) + goalNode = GetWayPointNearestPlayer( self ); + else if ( nearestPlayerTarget ) + { + goalNode = GetWayPointNearestPlayer( self ); + waypointname = goalNode->TargetName(); + } + + if ( !nearestPlayer ) + goalNode = GetWayPoint( self ); + + } + + + if ( !goalNode ) + return BEHAVIOR_SUCCESS; + + goal = goalNode->origin; + goal.z = self.origin.z; + + //Check if we have arrived + Vector dir; + dir = self.origin - goal; + + if ( dir.length() <= 25.0f ) + return BEHAVIOR_SUCCESS; + + fly.SetGoalPoint( goal ); + fly.Evaluate( self ); + + return BEHAVIOR_EVALUATING; + } + +WayPointNode *HelicopterFlyToWaypoint::GetWayPoint( Actor &self ) + { + Entity* ent_in_range; + gentity_t *ed; + + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + + ent_in_range = g_entities[i].entity; + + if( ent_in_range->isSubclassOf( WayPointNode ) ) + { + if (!Q_stricmp(ent_in_range->targetname.c_str() , waypointname.c_str() )) + { + return (WayPointNode*)ent_in_range; + } + } + } + + return NULL; + } + +WayPointNode *HelicopterFlyToWaypoint::GetWayPointNearestPlayer( Actor &self) + { + Vector DistanceFromPlayer; + float distance; + Entity* ent_in_range; + Entity* best_ent; + + gentity_t *ed; + + ent_in_range = NULL; + best_ent = NULL; + distance = 100000000; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return NULL; + + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + + ent_in_range = g_entities[i].entity; + + if( ent_in_range->isSubclassOf( WayPointNode ) ) + { + DistanceFromPlayer = ent_in_range->origin - currentEnemy->origin; + if (DistanceFromPlayer.length() < distance ) + { + best_ent = ent_in_range; + distance = DistanceFromPlayer.length(); + } + } + } + + return (WayPointNode*)best_ent; + } + +void HelicopterFlyToWaypoint::End + ( + Actor &self + ) + + { + fly.End( self ); + } + + +/**************************************************************************** + + GetWithinRangeOfPlayer Class Definition + +****************************************************************************/ + +CLASS_DECLARATION( Behavior, GetWithinRangeOfPlayer, NULL ) + { + { &EV_Behavior_Args, &GetWithinRangeOfPlayer::SetArgs }, + { NULL, NULL } + }; + +void GetWithinRangeOfPlayer::SetArgs + ( + Event *ev + ) + + { + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + forever = ev->GetBoolean( 2 ); + else + forever = true; + + if ( ev->NumArgs() > 2 ) + speed = ev->GetFloat( 3 ); + else + speed = 100; + + if ( ev->NumArgs() > 3 ) + chase.SetRadius( ev->GetFloat( 4 ) ); + + } + +void GetWithinRangeOfPlayer::ShowInfo + ( + Actor &self + ) + + { + Behavior::ShowInfo( self ); + + gi.Printf( "\nchase:\n" ); + chase.ShowInfo( self ); + } + +void GetWithinRangeOfPlayer::Begin + ( + Actor &self + ) + + { + if ( !anim.length() ) + { + anim = "run"; + } + + if ( anim != self.animname || self.newanim.length() ) + { + self.SetAnim( anim, EV_Actor_NotifyBehavior ); + } + + chase.Begin( self ); + wander.Begin( self ); + + self.movementSubsystem->setForwardSpeed( speed ); + + next_think_time = 0; + + startRangeMin = self.preferredMax; + startRangeMax = startRangeMin * 1.25; + } + +BehaviorReturnCode_t GetWithinRangeOfPlayer::Evaluate + ( + Actor &self + ) + + { + Steering::ReturnValue result=Steering::EVALUATING; + + Player *player = NULL; + Player *temp_player = NULL; + // Make sure the player is alive and well + for(int i = 0; i < game.maxclients; i++) + { + temp_player = GetPlayer(i); + + // don't target while player is not in the game or he's in notarget + if( temp_player && !( temp_player->flags & FL_NOTARGET ) ) + { + player = temp_player; + break; + } + } + + if ( !player ) + return BEHAVIOR_SUCCESS; + + if ( next_think_time <= level.time ) + { + if ( self.groundentity && ( self.groundentity->s.number == player->entnum ) ) + { + wander.Evaluate( self ); + result = Steering::SUCCESS; + } + else + { + float radius=96.0f; + chase.SetGoal( player, radius, self ); + + result = chase.Evaluate( self ); + } + + if ( self.GetActorFlag( ACTOR_FLAG_SIMPLE_PATHFINDING ) ) + next_think_time = level.time + ( 2.0f * FRAMETIME ); + else + next_think_time = 0.0f; + } + else + result = Steering::SUCCESS; + + if ( !forever && ( result == Steering::SUCCESS) ) + return BEHAVIOR_SUCCESS; + + if ( !forever) + { + switch (result) + { + case Steering::EVALUATING: + return BEHAVIOR_EVALUATING; + break; + + case Steering::SUCCESS: + return BEHAVIOR_SUCCESS; + break; + + case Steering::FAILED_BLOCKED_BY_ENEMY: + // lint -fallthrough + case Steering::FAILED_BLOCKED_BY_CIVILIAN: + // lint -fallthrough + case Steering::FAILED_BLOCKED_BY_FRIEND: + // lint -fallthrough + case Steering::FAILED_BLOCKED_BY_TEAMMATE: + // lint -fallthrough + case Steering::FAILED_BLOCKED_BY_WORLD: + // lint -fallthrough + case Steering::FAILED_BLOCKED_BY_DOOR: + // lint -fallthrough + case Steering::FAILED_NO_PATH: + // lint -fallthrough + case Steering::FAILED: + // lint -fallthrough + return BEHAVIOR_FAILED; + break; + } + } + + return BEHAVIOR_EVALUATING; + } + +void GetWithinRangeOfPlayer::End + ( + Actor &self + ) + + { + chase.End( self ); + wander.End( self ); + self.movementSubsystem->setForwardSpeed( 0 ); + } diff --git a/dlls/game/behavior.h b/dlls/game/behavior.h new file mode 100644 index 0000000..e597616 --- /dev/null +++ b/dlls/game/behavior.h @@ -0,0 +1,3036 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/behavior.h $ +// $Revision:: 72 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Standard class for creating AI behaviors +// + +#ifndef __BEHAVIOR_H__ +#define __BEHAVIOR_H__ + +#include "g_local.h" +#include "entity.h" +#include "path.h" +#include "steering.h" +#include "beam.h" +#include "sentient.h" +#include "bspline.h" +#include "waypoints.h" +#include "FollowPath.h" +#include "FollowPathToPoint.h" +#include "FollowPathToEntity.h" + +extern Event EV_Behavior_Args; +extern Event EV_Behavior_AnimDone; + +class Actor; + +typedef enum +{ + BEHAVIOR_SUCCESS, + BEHAVIOR_EVALUATING, + BEHAVIOR_FAILED, + BEHAVIOR_FAILED_STEERING_BLOCKED_BY_ENEMY, + BEHAVIOR_FAILED_STEERING_BLOCKED_BY_CIVILIAN, + BEHAVIOR_FAILED_STEERING_BLOCKED_BY_FRIEND, + BEHAVIOR_FAILED_STEERING_BLOCKED_BY_TEAMMATE, + BEHAVIOR_FAILED_STEERING_BLOCKED_BY_WORLD, + BEHAVIOR_FAILED_STEERING_BLOCKED_BY_DOOR, + BEHAVIOR_FAILED_STEERING_CANNOT_GET_TO_PATH, + BEHAVIOR_FAILED_STEERING_NO_PATH, + BEHAVIOR_INVALID, + BEHAVIOR_NUMBER_OF_RETURN_VALUES +} BehaviorReturnCode_t; + +class Behavior : public Listener + { + private: + str _failureReason; + str _internalStateName; + Listener *_controller ; + Actor* _self; + + protected: + PathNodePtr movegoal; + + public: + CLASS_PROTOTYPE( Behavior ); + + Behavior(); + virtual void ShowInfo( Actor &self ); + virtual void Begin( Actor &self ); + virtual BehaviorReturnCode_t Evaluate( Actor &self ); + virtual void End( Actor &self ); + virtual void Archive( Archiver &arc ); + + void SetFailureReason( const str &reason ) { _failureReason = reason; } + str GetFailureReason() { return _failureReason; } + + void SetInternalStateName( const str &reason ) { _internalStateName = reason; } + str GetInternalStateName() { return _internalStateName; } + + Listener* GetController( ) { return _controller ; } + void SetController( Listener *controller ) { _controller = controller ; } + + void SetSelf( Actor* self ) { _self = self; } + Actor* GetSelf() { return _self; } + }; + +inline void Behavior::Archive( Archiver &arc ) +{ + Listener::Archive( arc ); + + arc.ArchiveString ( &_failureReason ); + arc.ArchiveString ( &_internalStateName ); + arc.ArchiveObjectPointer( ( Class ** )&_controller ); + arc.ArchiveObjectPointer( ( Class ** )&_self ); + arc.ArchiveSafePointer ( &movegoal ); +} + +typedef SafePtr BehaviorPtr; + +class Idle : public Behavior + { + private: + float nexttwitch; + + public: + CLASS_PROTOTYPE( Idle ); + + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Idle::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveFloat( &nexttwitch ); + } + +class Pain : public Behavior + { + private: + int current_pain_type; + int pain_anim_number; + qboolean anim_done; + int number_of_pains; + int max_pains; + + public: + CLASS_PROTOTYPE( Pain ); + + void AnimDone( Event *ev ); + + void SetPainAnim( Actor &self, int new_pain_type, int new_anim_number ); + int GetNumberOfPainAnims( Actor &self, int new_pain_type ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Pain::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveInteger( ¤t_pain_type ); + arc.ArchiveInteger( &pain_anim_number ); + arc.ArchiveBoolean( &anim_done ); + arc.ArchiveInteger( &number_of_pains ); + arc.ArchiveInteger( &max_pains ); + } + +class Watch : public Behavior + { + private: + EntityPtr ent_to_watch; + float turn_speed; + float old_turn_speed; + + public: + CLASS_PROTOTYPE( Watch ); + + Watch(); + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Watch::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &ent_to_watch ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveFloat( &old_turn_speed ); + } + +class Turn : public Behavior + { + private: + float turn_speed; + + public: + CLASS_PROTOTYPE( Turn ); + + Turn(); + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Turn::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveFloat( &turn_speed ); + } + +class CircleEnemy : public Behavior + { + private: + EntityPtr ent_to_circle; + str center_part_name; + float last_angle_change; + + public: + CLASS_PROTOTYPE( CircleEnemy ); + + void SetArgs( Event *ev ); + float GetAngleDiff( const Actor &self, const Actor *center_actor, const Vector &origin ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void CircleEnemy::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &ent_to_circle ); + arc.ArchiveString( ¢er_part_name ); + arc.ArchiveFloat( &last_angle_change ); + } + + + +class BurrowAttack : public Behavior + { + private: + Vector goal; + Vector attack_origin; + int burrow_mode; + EntityPtr leg1; + EntityPtr leg2; + EntityPtr leg3; + EntityPtr leg4; + int stage; + int attacks_left; + float burrow_speed; + qboolean too_close; + qboolean use_last_known_position; + public: + CLASS_PROTOTYPE( BurrowAttack ); + + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + void SpawnArm( Actor &self, EntityPtr &leg, const Vector &original_arm_origin, const char *anim_name, float angle ); + virtual void Archive( Archiver &arc ); + }; + +inline void BurrowAttack::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveVector( &goal ); + arc.ArchiveVector( &attack_origin ); + arc.ArchiveInteger( &burrow_mode ); + arc.ArchiveSafePointer( &leg1 ); + arc.ArchiveSafePointer( &leg2 ); + arc.ArchiveSafePointer( &leg3 ); + arc.ArchiveSafePointer( &leg4 ); + arc.ArchiveInteger( &stage ); + arc.ArchiveInteger( &attacks_left ); + arc.ArchiveFloat( &burrow_speed ); + arc.ArchiveBoolean( &too_close ); + arc.ArchiveBoolean( &use_last_known_position ); + } + +class ShockWater : public Behavior + { + private: + EntityPtr left_beam; + EntityPtr right_beam; + EntityPtr center_beam; + qboolean already_started; + + public: + CLASS_PROTOTYPE( ShockWater ); + + ShockWater(); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void ShockWater::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &left_beam ); + arc.ArchiveSafePointer( &right_beam ); + arc.ArchiveSafePointer( ¢er_beam ); + arc.ArchiveBoolean( &already_started ); + } + +class Shock : public Behavior + { + private: + EntityPtr beam; + str tag_name; + float damage; + qboolean already_started; + float random_angle; + str beamShader; + float z_offset; + + public: + CLASS_PROTOTYPE( Shock ); + + Shock(); + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Shock::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &beam ); + arc.ArchiveString( &tag_name ); + arc.ArchiveFloat( &damage ); + arc.ArchiveBoolean( &already_started ); + arc.ArchiveFloat( &random_angle ); + arc.ArchiveString( &beamShader ); + arc.ArchiveFloat( &z_offset ); + } + +class MultiShock : public Behavior + { + private: + EntityPtr beam1; + EntityPtr beam2; + str tag_name1; + str tag_name2; + float damage; + qboolean already_started; + float random_angle; + str beamShader; + float z_offset; + + public: + CLASS_PROTOTYPE( MultiShock ); + + MultiShock(); + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void MultiShock::Archive + ( + Archiver &arc + ) + + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &beam1 ); + arc.ArchiveSafePointer( &beam2 ); + arc.ArchiveString( &tag_name1 ); + arc.ArchiveString( &tag_name2 ); + arc.ArchiveFloat( &damage ); + arc.ArchiveBoolean( &already_started ); + arc.ArchiveFloat( &random_angle ); + arc.ArchiveString( &beamShader ); + arc.ArchiveFloat( &z_offset ); + } + +class ShockDown : public Behavior + { + private: + EntityPtr beam; + str tag_name; + float damage; + qboolean already_started; + str beamShader; + + + public: + CLASS_PROTOTYPE( ShockDown ); + + ShockDown(); + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void ShockDown::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &beam ); + arc.ArchiveString( &tag_name ); + arc.ArchiveFloat( &damage ); + arc.ArchiveBoolean( &already_started ); + arc.ArchiveString( &beamShader ); + } + +class CircleAttack : public Behavior + { + private: + EntityPtr first_part; + EntityPtr current_part; + str command; + str direction; + float next_time; + int current_direction; + int number_of_attacks; + public: + CLASS_PROTOTYPE( CircleAttack ); + + CircleAttack(); + Actor *FindClosestPart( const Actor &self, float angle ); + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void CircleAttack::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &first_part ); + arc.ArchiveSafePointer( ¤t_part ); + arc.ArchiveString( &command ); + arc.ArchiveString( &direction ); + arc.ArchiveFloat( &next_time ); + arc.ArchiveInteger( ¤t_direction ); + arc.ArchiveInteger( &number_of_attacks ); + } + +class DragEnemy : public Behavior + { + private: + EntityPtr ent_to_drag; + str tag_name; + float damage; + float target_yaw; + float last_turn_time; + qboolean attached; + Vector offset; + qboolean drop; + public: + CLASS_PROTOTYPE( DragEnemy ); + + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void DragEnemy::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &ent_to_drag ); + arc.ArchiveString( &tag_name ); + arc.ArchiveFloat( &damage ); + arc.ArchiveFloat( &target_yaw ); + arc.ArchiveFloat( &last_turn_time ); + arc.ArchiveBoolean( &attached ); + arc.ArchiveVector( &offset ); + arc.ArchiveBoolean( &drop ); + } + +class PickupEnemy : public Behavior + { + private: + EntityPtr ent_to_drag; + str tag_name; + float damage; + float target_yaw; + float last_turn_time; + qboolean attached; + Vector offset; + qboolean drop; + public: + CLASS_PROTOTYPE( PickupEnemy ); + + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void PickupEnemy::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &ent_to_drag ); + arc.ArchiveString( &tag_name ); + arc.ArchiveFloat( &damage ); + arc.ArchiveFloat( &target_yaw ); + arc.ArchiveFloat( &last_turn_time ); + arc.ArchiveBoolean( &attached ); + arc.ArchiveVector( &offset ); + arc.ArchiveBoolean( &drop ); + } + +class Aim : public Behavior + { + private: + EntityPtr target; + + public: + CLASS_PROTOTYPE( Aim ); + + void SetTarget( Entity *ent ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Aim::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &target ); + } + +class TurnTo : public Behavior + { + private: + EntityPtr ent; + Vector dir; + float yaw; + int mode; + qboolean anim_done; + bool useTurnAnim; + int extraFrames; + bool _useAnims; + + public: + CLASS_PROTOTYPE( TurnTo ); + + TurnTo(); + void SetDirection( float yaw ); + void SetTarget( Entity *ent ); + void AnimDone( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + void SetUseTurnAnim( bool useAnim ); + void useAnims( bool useAnims ); + virtual void Archive( Archiver &arc ); + }; + +inline void TurnTo::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &ent ); + arc.ArchiveVector( &dir ); + arc.ArchiveFloat( &yaw ); + arc.ArchiveInteger( &mode ); + arc.ArchiveBoolean( &anim_done ); + arc.ArchiveBool( &useTurnAnim ); + arc.ArchiveInteger ( &extraFrames ); + arc.ArchiveBool( &_useAnims ); + } + +class RotateToEnemy : public Behavior + { + private: + float turnSpeed; + str anim; + + public: + CLASS_PROTOTYPE( RotateToEnemy ); + + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void RotateToEnemy::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveFloat( &turnSpeed ); + arc.ArchiveString( &anim ); +} + +class PickupEntity : public Behavior + { + private: + str pickup_anim_name; + qboolean anim_done; + EntityPtr ent_to_pickup; + + public: + CLASS_PROTOTYPE( PickupEntity ); + + void SetArgs( Event *ev ); + void AnimDone( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void PickupEntity::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &pickup_anim_name ); + arc.ArchiveBoolean( &anim_done ); + arc.ArchiveSafePointer( &ent_to_pickup ); + } + +class ThrowEntity : public Behavior + { + private: + str throw_anim_name; + qboolean anim_done; + + public: + CLASS_PROTOTYPE( ThrowEntity ); + + void SetArgs( Event *ev ); + void AnimDone( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void ThrowEntity::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &throw_anim_name ); + arc.ArchiveBoolean( &anim_done ); + } + +class HeadWatch : public Behavior + { + private: + EntityPtr ent_to_watch; + Vector current_head_angles; + float max_speed; + qboolean forever; + qboolean usingEyes; + + + public: + CLASS_PROTOTYPE( HeadWatch ); + + HeadWatch(); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + void useEyes( qboolean moveEyes ); + }; + +inline void HeadWatch::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &ent_to_watch ); + arc.ArchiveVector( ¤t_head_angles ); + arc.ArchiveFloat( &max_speed ); + arc.ArchiveBoolean( &forever ); + arc.ArchiveBoolean( &usingEyes ); + } + +class HeadWatchEnemy : public Behavior + { + private: + EntityPtr ent_to_watch; + Vector current_head_angles; + Vector current_torso_angles; + float max_speed; + qboolean forever; + float threshold; + qboolean usingEyes; + public: + CLASS_PROTOTYPE( HeadWatchEnemy ); + + HeadWatchEnemy(); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + void useEyes( qboolean moveEyes ); + }; + +inline void HeadWatchEnemy::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &ent_to_watch ); + arc.ArchiveVector( ¤t_head_angles ); + arc.ArchiveVector( ¤t_torso_angles ); + arc.ArchiveFloat( &max_speed ); + arc.ArchiveBoolean( &forever ); + arc.ArchiveFloat( &threshold ); + arc.ArchiveBoolean( &usingEyes ); + } + +class EyeWatch : public Behavior + { + private: + EntityPtr ent_to_watch; + Vector current_left_eye_angles; + Vector current_right_eye_angles; + float max_speed; + qboolean forever; + float threshold; + + public: + CLASS_PROTOTYPE( EyeWatch ); + + EyeWatch(); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void EyeWatch::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &ent_to_watch ); + arc.ArchiveVector( ¤t_left_eye_angles ); + arc.ArchiveVector( ¤t_right_eye_angles ); + arc.ArchiveFloat( &max_speed ); + arc.ArchiveBoolean( &forever ); + arc.ArchiveFloat( &threshold ); + } + +class EyeWatchEnemy : public Behavior + { + private: + EntityPtr ent_to_watch; + Vector current_left_eye_angles; + Vector current_right_eye_angles; + float max_speed; + qboolean forever; + float threshold; + + public: + CLASS_PROTOTYPE( EyeWatchEnemy ); + + EyeWatchEnemy(); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void EyeWatchEnemy::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &ent_to_watch ); + arc.ArchiveVector( ¤t_left_eye_angles ); + arc.ArchiveVector( ¤t_right_eye_angles ); + arc.ArchiveFloat( &max_speed ); + arc.ArchiveBoolean( &forever ); + arc.ArchiveFloat( &threshold ); + } + +class HeadAndEyeWatch : public Behavior + { + private: + HeadWatch headWatch; + EyeWatch eyeWatch; + + public: + CLASS_PROTOTYPE( HeadAndEyeWatch ); + + HeadAndEyeWatch(); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void HeadAndEyeWatch::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveObject(&headWatch); + arc.ArchiveObject(&eyeWatch); + } + +class TorsoTurn : public Behavior + { + private: + int turn_towards_enemy; + float speed; + int forever; + qboolean use_pitch; + float current_yaw; + float current_pitch; + str tag_name; + float tolerance; + float offset; + + public: + CLASS_PROTOTYPE( TorsoTurn ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + + void SetRequiredParameters( int TurnTowardsEnemy, int Speed, int Forever ); + }; + +inline void TorsoTurn::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveInteger( &turn_towards_enemy ); + arc.ArchiveFloat( &speed ); + arc.ArchiveInteger( &forever ); + arc.ArchiveBoolean( &use_pitch); + arc.ArchiveFloat( ¤t_yaw ); + arc.ArchiveFloat( ¤t_pitch ); + arc.ArchiveString( &tag_name ); + arc.ArchiveFloat( &tolerance ); + arc.ArchiveFloat( &offset ); + } + + +class TorsoWatchEnemy : public Behavior + { + private: + float speed; + int forever; + qboolean use_pitch; + float current_yaw; + float current_pitch; + str tag_name; + float threshold; + float offset; + qboolean invert; + qboolean reset; + qboolean invertLegs; + float nextFlipTime; + + + public: + CLASS_PROTOTYPE( TorsoWatchEnemy ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void TorsoWatchEnemy::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveFloat( &speed ); + arc.ArchiveInteger( &forever ); + arc.ArchiveBoolean( &use_pitch); + arc.ArchiveFloat( ¤t_yaw ); + arc.ArchiveFloat( ¤t_pitch ); + arc.ArchiveString( &tag_name ); + arc.ArchiveFloat( &threshold ); + arc.ArchiveFloat( &offset ); + arc.ArchiveBoolean( &invert ); + arc.ArchiveBoolean( &reset ); + arc.ArchiveBoolean( &invertLegs ); + arc.ArchiveFloat( &nextFlipTime ); + } + +class FallToDeath : public Behavior + { + private: + float forwardmove; + float sidemove; + float distance; + float time; + float speed; + str startAnim; + str fallAnim; + str deathAnim; + Vector yaw_forward; + Vector yaw_left; + qboolean did_impulse; + float impulse_time; + + qboolean animdone; + int state; + + public: + CLASS_PROTOTYPE( FallToDeath ); + + FallToDeath(); + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + + }; + +inline void FallToDeath::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveFloat( &forwardmove ); + arc.ArchiveFloat( &sidemove ); + arc.ArchiveFloat( &distance ); + arc.ArchiveFloat( &time ); + arc.ArchiveFloat( &speed ); + arc.ArchiveString( &startAnim ); + arc.ArchiveString( &fallAnim ); + arc.ArchiveString( &deathAnim ); + arc.ArchiveVector( &yaw_forward ); + arc.ArchiveVector( &yaw_left ); + arc.ArchiveBoolean( &did_impulse ); + arc.ArchiveFloat( &impulse_time ); + arc.ArchiveBoolean( &animdone ); + arc.ArchiveInteger( &state ); + } + +class GotoPathNode : public Behavior + { + private: + TurnTo turnto; + FollowPath *chase; + int state; + qboolean usevec; + //float time; + str anim; + EntityPtr goalent; + Vector goal; + EntityPtr entity_to_watch; + HeadWatch head_watch; + bool _followingEntity; + + public: + CLASS_PROTOTYPE( GotoPathNode ); + + GotoPathNode(); + void SetArgs( Event *ev ); + void SetGoal( PathNode *node ); + void AnimDone( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void GotoPathNode::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveObject( &turnto ); + + // Archive chase last + + arc.ArchiveInteger( &state ); + arc.ArchiveBoolean( &usevec ); + //arc.ArchiveFloat( &time ); + arc.ArchiveString( &anim ); + arc.ArchiveSafePointer( &goalent ); + arc.ArchiveVector( &goal ); + arc.ArchiveSafePointer( &entity_to_watch ); + arc.ArchiveObject( &head_watch ); + arc.ArchiveBool( &_followingEntity ); + + if ( arc.Saving() ) + { + arc.ArchiveObject( chase ); + } + else + { + if ( _followingEntity ) + chase = new FollowPathToEntity; + else + chase = new FollowPathToPoint; + + arc.ArchiveObject( chase ); + } +} + + +class Flee : public Behavior + { + private: + FollowPathToPoint chase; + str anim; + PathNodePtr flee_node; + + public: + CLASS_PROTOTYPE( Flee ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + void FindFleeNode( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Flee::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveObject( &chase ); + arc.ArchiveString( &anim ); + arc.ArchiveSafePointer( &flee_node ); + } + +/* +class PlayAnim : public Behavior + { + private: + str anim; + + public: + CLASS_PROTOTYPE( PlayAnim ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void PlayAnim::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + } + + */ + +class FindCover : public Behavior + { + private: + str anim; + str crouch_anim; + FollowPathToPoint chase; + int state; + float nextsearch; + + public: + CLASS_PROTOTYPE( FindCover ); + + void SetArgs( Event *ev ); + PathNode *FindCoverNode( Actor &self ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FindCover::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveString( &crouch_anim ); + arc.ArchiveObject( &chase ); + arc.ArchiveInteger( &state ); + arc.ArchiveFloat( &nextsearch ); + } + +class FindFlee : public Behavior + { + private: + str anim; + FollowPathToPoint chase; + int state; + float nextsearch; + + public: + CLASS_PROTOTYPE( FindFlee ); + + void SetArgs( Event *ev ); + PathNode *FindFleeNode( Actor &self ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FindFlee::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &chase ); + arc.ArchiveInteger( &state ); + arc.ArchiveFloat( &nextsearch ); + } + +class FindEnemy : public Behavior + { + private: + str anim; + FollowPathToPoint chase; + int state; + float nextsearch; + PathNodePtr lastSearchNode; + Vector lastSearchPos; + + public: + CLASS_PROTOTYPE( FindEnemy ); + + PathNode *FindClosestSightNode( Actor &self ); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FindEnemy::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &chase ); + arc.ArchiveInteger( &state ); + arc.ArchiveFloat( &nextsearch ); + arc.ArchiveSafePointer( &lastSearchNode ); + arc.ArchiveVector( &lastSearchPos ); + } + +class AimAndShoot : public Behavior + { + private: + Aim aim; + TorsoTurn torsoTurn; + int mode; + int maxshots; + int numshots; + qboolean animdone; + float enemy_health; + float aim_time; + str animprefix; + str aimanim; + str fireanim; + + public: + CLASS_PROTOTYPE( AimAndShoot ); + + AimAndShoot(); + void SetMaxShots( int num ); + void SetArgs( Event *ev ); + void AnimDone( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void AimAndShoot::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveObject( &aim ); + arc.ArchiveObject( &torsoTurn ); + arc.ArchiveInteger( &mode ); + arc.ArchiveInteger( &maxshots ); + arc.ArchiveInteger( &numshots ); + arc.ArchiveBoolean( &animdone ); + arc.ArchiveFloat( &enemy_health ); + arc.ArchiveFloat( &aim_time ); + arc.ArchiveString( &animprefix ); + arc.ArchiveString( &aimanim ); + arc.ArchiveString( &fireanim ); + } + +class AimAndMelee : public Behavior + { + private: + Aim aim; + int mode; + int maxshots; + int numshots; + qboolean animdone; + str anim_name; + + public: + CLASS_PROTOTYPE( AimAndMelee ); + + AimAndMelee(); + void SetArgs( Event *ev ); + void AnimDone( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void AimAndMelee::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveObject( &aim ); + arc.ArchiveInteger( &mode ); + arc.ArchiveInteger( &maxshots ); + arc.ArchiveInteger( &numshots ); + arc.ArchiveBoolean( &animdone ); + arc.ArchiveString( &anim_name ); + } + +class JumpToPathNode : public Behavior + { + private: + Jump jump; + public: + CLASS_PROTOTYPE( JumpToPathNode ); + + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void JumpToPathNode::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveObject( &jump ); + } + +class LeapToEnemy : public Behavior + { + private: + Jump jump; + + public: + CLASS_PROTOTYPE( LeapToEnemy ); + + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void LeapToEnemy::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + arc.ArchiveObject(&jump ); + + } + +class FlyToPoint : public Behavior + { + private: + float avoidtime; + Vector target_angle; + float turn_speed; + float old_turn_speed; + float speed; + float old_forward_speed; + Vector goal; + qboolean random_allowed; + qboolean force_goal; + int stuck; + Vector temp_goal; + qboolean use_temp_goal; + qboolean adjustYawAndRoll; + qboolean offsetOrigin; + + public: + CLASS_PROTOTYPE( FlyToPoint ); + + FlyToPoint(); + + void SetTurnSpeed( float new_turn_speed ); + void SetGoalPoint( const Vector &goal_point ); + void SetRandomAllowed( qboolean allowed ); + void SetSpeed( float speed_value ); + void ForceGoal( void ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + float LerpAngle( float old_angle, float new_angle, float lerp_amount ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + void setAdjustYawAndRoll( bool adjust ) { adjustYawAndRoll = adjust; } + void setOffsetOrigin( bool setOffset ) { offsetOrigin = setOffset; } + }; + +inline void FlyToPoint::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveFloat( &avoidtime ); + arc.ArchiveVector( &target_angle ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveFloat( &old_turn_speed ); + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &old_forward_speed ); + arc.ArchiveVector( &goal ); + arc.ArchiveBoolean( &random_allowed ); + arc.ArchiveBoolean( &force_goal ); + arc.ArchiveInteger( &stuck ); + arc.ArchiveVector( &temp_goal ); + arc.ArchiveBoolean( &use_temp_goal ); + arc.ArchiveBoolean( &adjustYawAndRoll ); + arc.ArchiveBoolean( &offsetOrigin ); + } + +class FlyCloseToEnemy : public Behavior + { + private: + str anim; + float turn_speed; + float speed; + FlyToPoint fly; + float next_goal_time; + qboolean adjustPitch; + + public: + CLASS_PROTOTYPE( FlyCloseToEnemy ); + + FlyCloseToEnemy(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyCloseToEnemy::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveFloat( &speed ); + arc.ArchiveObject( &fly ); + arc.ArchiveFloat( &next_goal_time ); + arc.ArchiveBoolean( &adjustPitch ); +} + +class FlyCloseToPlayer : public Behavior + { + private: + str anim; + float turn_speed; + float speed; + FlyToPoint fly; + float next_goal_time; + + public: + CLASS_PROTOTYPE( FlyCloseToPlayer ); + + FlyCloseToPlayer(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyCloseToPlayer::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveFloat( &speed ); + arc.ArchiveObject( &fly ); + arc.ArchiveFloat( &next_goal_time ); + } + +class FlyCloseToParent : public Behavior + { + private: + str anim; + float turn_speed; + float speed; + FlyToPoint fly; + float next_goal_time; + + public: + CLASS_PROTOTYPE( FlyCloseToParent ); + + FlyCloseToParent(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyCloseToParent::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveFloat( &speed ); + arc.ArchiveObject( &fly ); + arc.ArchiveFloat( &next_goal_time ); + } + + +class FlyDescend: public Behavior + { + private: + str anim; + FlyToPoint fly; + Vector goal; + float height; + float speed; + float next_height_check; + float last_check_height; + + public: + CLASS_PROTOTYPE( FlyDescend ); + + FlyDescend(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyDescend::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &fly ); + arc.ArchiveVector( &goal ); + arc.ArchiveFloat( &height ); + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &next_height_check ); + arc.ArchiveFloat( &last_check_height ); + } + + +class FlyWander : public Behavior + { + private: + str anim; + float turn_speed; + float speed; + FlyToPoint fly; + float change_course_time; + float next_change_course_time; + float original_z; + Vector goal; + qboolean try_to_go_up; + + public: + CLASS_PROTOTYPE( FlyWander ); + + FlyWander(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyWander::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveFloat( &speed ); + arc.ArchiveObject( &fly ); + arc.ArchiveFloat( &change_course_time ); + arc.ArchiveFloat( &next_change_course_time ); + arc.ArchiveFloat( &original_z ); + arc.ArchiveVector( &goal ); + arc.ArchiveBoolean( &try_to_go_up ); + } + +class FlyToNode : public Behavior + { + private: + str anim; + float turn_speed; + float speed; + FlyToPoint fly; + //float original_z; + //Vector goal; + //qboolean try_to_go_up; + str NodeType; + //int NodeIdx; + int NumberOfNodes; + + + public: + CLASS_PROTOTYPE( FlyToNode ); + + FlyToNode(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyToNode::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveFloat( &speed ); + arc.ArchiveObject( &fly ); + //arc.ArchiveFloat( &original_z ); + arc.ArchiveString( &NodeType ); + arc.ArchiveInteger( &NumberOfNodes ); + } + +class FlyToRandomNode : public Behavior + { + private: + str anim; + float turn_speed; + float speed; + FlyToPoint fly; + //float original_z; + str NodeType; + int NumberOfNodes; + int CurrentNode; + PathNodePtr goal; + qboolean NeedNextNode; + + public: + CLASS_PROTOTYPE( FlyToRandomNode ); + + FlyToRandomNode(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyToRandomNode::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveFloat( &speed ); + arc.ArchiveObject( &fly ); + //arc.ArchiveFloat( &original_z ); + arc.ArchiveString( &NodeType ); + arc.ArchiveInteger( &NumberOfNodes ); + arc.ArchiveInteger( &CurrentNode ); + arc.ArchiveSafePointer( &goal ); + arc.ArchiveBoolean( &NeedNextNode ); + } + +class FlyToNodeNearestPlayer : public Behavior + { + private: + str anim; + float turn_speed; + float speed; + FlyToPoint fly; + //float original_z; + str NodeType; + int NumberOfNodes; + int CurrentNode; + PathNodePtr goal; + qboolean NeedNextNode; + + public: + CLASS_PROTOTYPE( FlyToNodeNearestPlayer ); + + FlyToNodeNearestPlayer(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyToNodeNearestPlayer::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveFloat( &speed ); + arc.ArchiveObject( &fly ); + //arc.ArchiveFloat( &original_z ); + arc.ArchiveString( &NodeType ); + arc.ArchiveInteger( &NumberOfNodes ); + arc.ArchiveInteger( &CurrentNode ); + arc.ArchiveSafePointer( &goal ); + arc.ArchiveBoolean( &NeedNextNode ); +} + +class FlyNodePath : public Behavior + { + private: + str anim; + float turn_speed; + float speed; + FlyToPoint fly; + //float original_z; + str NodeType; + int NumberOfNodes; + int CurrentNode; + PathNodePtr goal; + qboolean NeedNextNode; + + public: + CLASS_PROTOTYPE( FlyNodePath ); + + FlyNodePath(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyNodePath::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveFloat( &speed ); + arc.ArchiveObject( &fly ); + //arc.ArchiveFloat( &original_z ); + arc.ArchiveString( &NodeType ); + arc.ArchiveInteger( &NumberOfNodes ); + arc.ArchiveInteger( &CurrentNode ); + arc.ArchiveSafePointer( &goal ); + arc.ArchiveBoolean( &NeedNextNode ); +} + +class FlyCircle : public Behavior + { + private: + str anim; + FlyToPoint fly; + float original_z; + qboolean fly_clockwise; + qboolean circle_player; + + public: + CLASS_PROTOTYPE( FlyCircle ); + + FlyCircle(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyCircle::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &fly ); + arc.ArchiveFloat( &original_z ); + arc.ArchiveBoolean( &fly_clockwise ); + arc.ArchiveBoolean( &circle_player ); + } + +class FlyCircleRandomPoint : public Behavior + { + private: + str anim; + FlyToPoint fly; + //float original_z; + qboolean fly_clockwise; + float change_course_time; + float next_change_course_time; + Vector goal; + qboolean try_to_go_up; + //float speed; + + public: + CLASS_PROTOTYPE( FlyCircleRandomPoint ); + + FlyCircleRandomPoint(); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyCircleRandomPoint::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &fly ); + //arc.ArchiveFloat( &original_z ); + arc.ArchiveBoolean( &fly_clockwise ); + + arc.ArchiveFloat( &change_course_time ); + arc.ArchiveFloat( &next_change_course_time ); + arc.ArchiveVector( &goal ); + arc.ArchiveBoolean( &try_to_go_up ); +} + +class FlyDive : public Behavior + { + private: + str anim; + FlyToPoint fly; + Vector goal; + float speed; + //float old_speed; + float damage; + + public: + CLASS_PROTOTYPE( FlyDive ); + + FlyDive(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyDive::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &fly ); + arc.ArchiveVector( &goal ); + arc.ArchiveFloat( &speed ); + //arc.ArchiveFloat( &old_speed ); + arc.ArchiveFloat( &damage ); + } + +class FlyCharge : public Behavior + { + private: + str anim; + FlyToPoint fly; + Vector goal; + float speed; + //float old_speed; + float damage; + + public: + CLASS_PROTOTYPE( FlyCharge ); + + FlyCharge(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyCharge::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &fly ); + arc.ArchiveVector( &goal ); + arc.ArchiveFloat( &speed ); + //arc.ArchiveFloat( &old_speed ); + arc.ArchiveFloat( &damage ); + } + +class FlyStrafe : public Behavior + { + private: + str anim; + float speed; + qboolean right; + float roll; + + + public: + CLASS_PROTOTYPE( FlyStrafe ); + + FlyStrafe(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyStrafe::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &speed ); + arc.ArchiveBoolean( &right ); + arc.ArchiveFloat( &roll ); + } + +class FlyClimb : public Behavior + { + private: + str anim; + FlyToPoint fly; + Vector goal; + float height; + float speed; + float next_height_check; + float last_check_height; + float collision_buffer; + + public: + CLASS_PROTOTYPE( FlyClimb ); + + FlyClimb(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyClimb::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &fly ); + arc.ArchiveVector( &goal ); + arc.ArchiveFloat( &height ); + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &next_height_check ); + arc.ArchiveFloat( &last_check_height ); + arc.ArchiveFloat( &collision_buffer ); + } + +class FlySplinePath : public Behavior + { + private: + EntityPtr ent; + BSpline splinePath; + SplinePathPtr currentNode; + qboolean clamp; + qboolean ignoreAngles; + qboolean splineAngles; + float startTime; + Vector oldGoal; + qboolean havePath; + + + + public: + CLASS_PROTOTYPE( FlySplinePath ); + + FlySplinePath(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + + void CreatePath( SplinePath *path, splinetype_t type ); + }; + +inline void FlySplinePath::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveSafePointer( &ent ); + arc.ArchiveObject( &splinePath ); + arc.ArchiveSafePointer( ¤tNode ); + arc.ArchiveBoolean( &clamp ); + arc.ArchiveBoolean( &ignoreAngles ); + arc.ArchiveBoolean( &splineAngles ); + arc.ArchiveFloat( &startTime ); + arc.ArchiveVector( &oldGoal ); + arc.ArchiveBoolean( &havePath ); + } + +class Land : public Behavior + { + private: + str anim; + + public: + CLASS_PROTOTYPE( Land ); + + Land(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Land::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + } + +class VerticalTakeOff : public Behavior + { + private: + str anim; + float speed; + float height; + + public: + CLASS_PROTOTYPE( VerticalTakeOff ); + + VerticalTakeOff(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void VerticalTakeOff::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &height ); + } + +class Hover : public Behavior + { + private: + str anim; + FlyToPoint fly; + Vector goal; + + public: + CLASS_PROTOTYPE( Hover ); + + Hover(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Hover::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &fly ); + arc.ArchiveVector( &goal ); +} + + +// Fixme / don't think this works right now + +class Wander : public Behavior + { + private: + //ObstacleAvoidance avoid; + str anim; + float avoidtime; + Vector avoidvec; + + public: + CLASS_PROTOTYPE( Wander ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Wander::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + //arc.ArchiveObject( &avoid ); + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &avoidtime ); + arc.ArchiveVector( &avoidvec ); + } + +class CircleCurrentEnemy : public Behavior + { + private: + str anim; + float radius; + qboolean maintainDistance; + qboolean clockwise; + Vector dirToEnemy; + + float turnAngle; + float oldAngle; + float angleStep; + + int stuck; + int stuckCheck; + + qboolean angleAdjusted; + + + public: + CLASS_PROTOTYPE( CircleCurrentEnemy ); + + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void CircleCurrentEnemy::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &radius ); + arc.ArchiveBoolean( &maintainDistance ); + arc.ArchiveBoolean( &clockwise ); + arc.ArchiveVector( &dirToEnemy ); + arc.ArchiveFloat( &turnAngle ); + arc.ArchiveFloat( &oldAngle ); + arc.ArchiveFloat( &angleStep ); + arc.ArchiveInteger( &stuck ); + arc.ArchiveInteger( &stuckCheck ); + arc.ArchiveBoolean( &angleAdjusted ); + + } + +class ChaoticDodge : public Behavior + { + private: + str anim; + + float turnAngle; + float oldAngle; + float angleStep; + float time; + float changeTime; + + int stuck; + int stuckCheck; + qboolean adjusting; + + qboolean angleAdjusted; + float turnspeed; + float turnTime; + + + public: + CLASS_PROTOTYPE( ChaoticDodge ); + + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + float GetNewYaw(); + virtual void Archive( Archiver &arc ); + + }; + +inline void ChaoticDodge::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveFloat( &turnAngle ); + arc.ArchiveFloat( &oldAngle ); + arc.ArchiveFloat( &angleStep ); + arc.ArchiveFloat( &time ); + arc.ArchiveFloat( &changeTime ); + + arc.ArchiveInteger( &stuck ); + arc.ArchiveInteger( &stuckCheck ); + arc.ArchiveBoolean( &adjusting ); + + arc.ArchiveBoolean( &angleAdjusted ); + arc.ArchiveFloat( &turnspeed ); + arc.ArchiveFloat( &turnTime ); +} + +class GetCloseToEnemy : public Behavior + { + private: + str anim; + FollowPathToEntity chase; + Wander wander; + qboolean forever; + float next_think_time; + + public: + CLASS_PROTOTYPE( GetCloseToEnemy ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void GetCloseToEnemy::Archive + ( + Archiver &arc + ) + + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &chase ); + arc.ArchiveObject( &wander ); + arc.ArchiveBoolean( &forever ); + arc.ArchiveFloat( &next_think_time ); + } + +class GetCloseToPlayer : public Behavior + { + private: + str anim; + FollowPathToEntity chase; + Wander wander; + qboolean forever; + float next_think_time; + float speed; + + public: + CLASS_PROTOTYPE( GetCloseToPlayer ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void GetCloseToPlayer::Archive + ( + Archiver &arc + ) + + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &chase ); + arc.ArchiveObject( &wander ); + arc.ArchiveBoolean( &forever ); + arc.ArchiveFloat( &next_think_time ); + arc.ArchiveFloat( &speed ); + } + +class GetWithinRangeOfPlayer : public Behavior + { + private: + str anim; + FollowPathToEntity chase; + Wander wander; + qboolean forever; + float next_think_time; + float speed; + float startRangeMax; + float startRangeMin; + + public: + CLASS_PROTOTYPE( GetWithinRangeOfPlayer ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void GetWithinRangeOfPlayer::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &chase ); + arc.ArchiveObject( &wander ); + arc.ArchiveBoolean( &forever ); + arc.ArchiveFloat( &next_think_time ); + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &startRangeMax ); + arc.ArchiveFloat( &startRangeMin ); +} + +class RetreatFromEnemy : public Behavior + { + private: + str anim; + FollowPathToPoint chase; + Wander wander; + qboolean forever; + float next_think_time; + + public: + CLASS_PROTOTYPE( RetreatFromEnemy ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void RetreatFromEnemy::Archive + ( + Archiver &arc + ) + + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &chase ); + arc.ArchiveObject( &wander ); + arc.ArchiveBoolean( &forever ); + arc.ArchiveFloat( &next_think_time ); + } + +class GotoDeadEnemy : public Behavior + { + private: + str anim; + FollowPathToPoint chase; + + public: + CLASS_PROTOTYPE( GotoDeadEnemy ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void GotoDeadEnemy::Archive + ( + Archiver &arc + ) + + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &chase ); + } + +class Investigate : public Behavior +{ +private: + FollowPathToPoint chase; + str moveanim; + str lookaroundanim; + Vector goal; + float curioustime; + float lookaroundtime; + float lookaroundtime_end; + TurnTo turnto; + float investigate_time; + int mode; + Vector start_pos; + float start_yaw; + qboolean return_to_original_location; + +public: + CLASS_PROTOTYPE( Investigate ); + Investigate(); + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); +}; + +inline void Investigate::Archive +( + Archiver &arc + ) +{ + Behavior::Archive( arc ); + + arc.ArchiveObject( &chase ); + arc.ArchiveString( &moveanim ); + arc.ArchiveString( &lookaroundanim ); + arc.ArchiveVector( &goal ); + arc.ArchiveFloat( &curioustime ); + arc.ArchiveFloat( &lookaroundtime ); + arc.ArchiveFloat( &lookaroundtime_end ); + arc.ArchiveObject( &turnto ); + arc.ArchiveFloat( &investigate_time ); + arc.ArchiveInteger( &mode ); + arc.ArchiveVector( &start_pos ); + arc.ArchiveFloat( &start_yaw ); + arc.ArchiveBoolean( &return_to_original_location ); +} + + +class TurnInvestigate : public Behavior + { + private: + str left_anim; + str right_anim; + float turn_speed; + Vector goal; + public: + CLASS_PROTOTYPE( TurnInvestigate ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void TurnInvestigate::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &left_anim ); + arc.ArchiveString( &right_anim ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveVector( &goal ); + } + +class TurnToEnemy : public Behavior + { + private: + str left_anim; + str right_anim; + float turn_speed; + qboolean forever; + qboolean anim_done; + qboolean use_last_known_position; + public: + CLASS_PROTOTYPE( TurnToEnemy ); + + void SetArgs( Event *ev ); + void AnimDone( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void TurnToEnemy::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &left_anim ); + arc.ArchiveString( &right_anim ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveBoolean( &forever ); + arc.ArchiveBoolean( &anim_done ); + arc.ArchiveBoolean( &use_last_known_position ); + } + +class Teleport : public Behavior + { + private: + public: + CLASS_PROTOTYPE( Teleport ); + + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean TestPosition( Actor &self, int test_pos, Vector &good_position, qboolean use_enemy_dir ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + }; + +class TeleportToPlayer : public Behavior + { + private: + public: + CLASS_PROTOTYPE( TeleportToPlayer ); + + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + qboolean TestPosition( Actor &self, int test_pos, Vector &good_position, Entity* player, qboolean use_player_dir ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + }; + +class TeleportToPosition : public Behavior + { + private: + str teleport_position_name; + int number_of_teleport_positions; + public: + CLASS_PROTOTYPE( TeleportToPosition ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + //qboolean TestPosition( Actor &self, int test_pos, const Vector &good_position ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void TeleportToPosition::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &teleport_position_name ); + arc.ArchiveInteger( &number_of_teleport_positions ); + } + +class GhostAttack : public Behavior + { + private: + int mode; + Vector attack_dir; + Vector attack_position; + Vector retreat_position; + FlyToPoint fly; + qboolean real_attack; + public: + CLASS_PROTOTYPE( GhostAttack ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void GhostAttack::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveInteger( &mode ); + arc.ArchiveVector( &attack_dir ); + arc.ArchiveVector( &attack_position ); + arc.ArchiveVector( &retreat_position ); + arc.ArchiveObject( &fly ); + arc.ArchiveBoolean( &real_attack ); + } + +class Levitate : public Behavior + { + private: + float distance; + float speed; + float final_z; + public: + CLASS_PROTOTYPE( Levitate ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Levitate::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveFloat( &distance ); + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &final_z ); + } + +// +// WayPoint Behaviors +// +class GotoWayPoint : public Behavior + { + private: + str anim; + str path_name; + str start_point; + FollowPathToPoint chase; + Wander wander; + str current_waypoint_name; + WayPointNodePtr current_waypoint; + float next_think_time; + + + public: + CLASS_PROTOTYPE( GotoWayPoint ); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + WayPointNode* GetWayPoint( const str& waypoint_name ); + + virtual void Archive( Archiver &arc ); + }; + +inline void GotoWayPoint::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveString( &path_name ); + arc.ArchiveString( &start_point ); + arc.ArchiveObject( &chase ); + arc.ArchiveObject( &wander ); + arc.ArchiveString( ¤t_waypoint_name ); + arc.ArchiveSafePointer( ¤t_waypoint ); + arc.ArchiveFloat( &next_think_time ); +} + +//============================================= +// FlyCircleAroundWaypoint +// Allows a flying creature to circle around a given +// Waypoint node. +//============================================= +class FlyCircleAroundWaypoint : public Behavior + { + private: + str anim; + FlyToPoint fly; + //float original_z; + qboolean fly_clockwise; + str waypointname; + qboolean nearestPlayer; + + public: + CLASS_PROTOTYPE( FlyCircleAroundWaypoint ); + + FlyCircleAroundWaypoint(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + WayPointNode *GetWayPoint( Actor &self ); + WayPointNode *GetWayPointNearestPlayer( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void FlyCircleAroundWaypoint::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &fly ); + //arc.ArchiveFloat( &original_z ); + arc.ArchiveBoolean( &fly_clockwise ); + arc.ArchiveString( &waypointname ); + arc.ArchiveBoolean( &nearestPlayer ); + } + +//================================================================ +// Sort of a hack for now. This is basically a specialized version +// of Fly to point. But it's not really cleaned up. +//=============================================================== +class HelicopterFlyToPoint : public Behavior + { + private: + float avoidtime; + Vector target_angle; + float turn_speed; + float old_turn_speed; + float speed; + float old_forward_speed; + Vector goal; + qboolean random_allowed; + qboolean force_goal; + int stuck; + Vector temp_goal; + qboolean use_temp_goal; + qboolean adjustYawAndRoll; + qboolean offsetOrigin; + + public: + CLASS_PROTOTYPE( HelicopterFlyToPoint ); + + HelicopterFlyToPoint(); + + void SetTurnSpeed( float new_turn_speed ); + void SetGoalPoint( const Vector &goal_point ); + void SetRandomAllowed( qboolean allowed ); + void SetSpeed( float speed_value ); + void ForceGoal( void ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + float LerpAngle( float old_angle, float new_angle, float lerp_amount ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + void setAdjustYawAndRoll( bool adjust ) { adjustYawAndRoll = adjust; } + void setOffsetOrigin( bool setOffset ) { offsetOrigin = setOffset; } + }; + +inline void HelicopterFlyToPoint::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveFloat( &avoidtime ); + arc.ArchiveVector( &target_angle ); + arc.ArchiveFloat( &turn_speed ); + arc.ArchiveFloat( &old_turn_speed ); + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &old_forward_speed ); + arc.ArchiveVector( &goal ); + arc.ArchiveBoolean( &random_allowed ); + arc.ArchiveBoolean( &force_goal ); + arc.ArchiveInteger( &stuck ); + arc.ArchiveVector( &temp_goal ); + arc.ArchiveBoolean( &use_temp_goal ); + arc.ArchiveBoolean( &adjustYawAndRoll ); + arc.ArchiveBoolean( &offsetOrigin ); +} + +//================================================================ +// Sort of a hack for now. This is basically a specialized version +// of Fly to point. But it's not really cleaned up. +//=============================================================== +class HelicopterFlyCircle : public Behavior + { + private: + str anim; + HelicopterFlyToPoint fly; + //float original_z; + qboolean fly_clockwise; + qboolean circle_player; + + public: + CLASS_PROTOTYPE( HelicopterFlyCircle ); + + HelicopterFlyCircle(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void HelicopterFlyCircle::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &fly ); + //arc.ArchiveFloat( &original_z ); + arc.ArchiveBoolean( &fly_clockwise ); + arc.ArchiveBoolean( &circle_player ); + } + +//=================================================== +// Helicopter Strafe Attack +//================================================== +class HelicopterStrafeAttack : public Behavior + { + private: + str anim; + Vector dir; + Vector targetAngles; + //float angleDelta; + float turnTime; + + float lerpStart; + float lerpEnd; + float startYaw; + float endYaw; + qboolean setUpLerp; + qboolean completedLerp; + + Vector goal; + + + public: + CLASS_PROTOTYPE( HelicopterStrafeAttack ); + + HelicopterStrafeAttack(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + WayPointNode *GetWayPointNearestPlayer( Actor &self ); + WayPointNode *GetWayPoint( Actor &self , const str &name ); + void LerpToNewAngle( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void HelicopterStrafeAttack::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveVector( &dir ); + arc.ArchiveVector( &targetAngles ); + arc.ArchiveFloat( &turnTime ); + + arc.ArchiveFloat( &lerpStart ); + arc.ArchiveFloat( &lerpEnd ); + arc.ArchiveFloat( &startYaw ); + arc.ArchiveFloat( &endYaw ); + arc.ArchiveBoolean( &setUpLerp ); + arc.ArchiveBoolean( &completedLerp ); + + arc.ArchiveVector( &goal ); +} + +//============================================= +// HelicopterFlyToWaypoint +// Allows a flying creature to circle around a given +// Waypoint node. +//============================================= +class HelicopterFlyToWaypoint : public Behavior + { + private: + str anim; + HelicopterFlyToPoint fly; + str waypointname; + qboolean nearestPlayer; + qboolean nearestPlayerTarget; + + public: + CLASS_PROTOTYPE( HelicopterFlyToWaypoint ); + + HelicopterFlyToWaypoint(); + + void SetArgs( Event *ev ); + void ShowInfo( Actor &self ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + WayPointNode *GetWayPoint( Actor &self ); + WayPointNode *GetWayPointNearestPlayer( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void HelicopterFlyToWaypoint::Archive + ( + Archiver &arc + ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &anim ); + arc.ArchiveObject( &fly ); + arc.ArchiveString( &waypointname ); + arc.ArchiveBoolean( &nearestPlayer ); + arc.ArchiveBoolean( &nearestPlayerTarget ); + } + +// +//I know, I know... It hurts me too, but until I get all this behavior transition stuff squared away +//there's not much that can be done about it. +// +#include "PlayAnim.hpp" + +#endif /* behavior.h */ diff --git a/dlls/game/behaviors.h b/dlls/game/behaviors.h new file mode 100644 index 0000000..4fb6127 --- /dev/null +++ b/dlls/game/behaviors.h @@ -0,0 +1,7 @@ +#ifndef __BEHAVIORS_H__ +#define __BEHAVIORS_H__ + +#include "behavior.h" +//#include "rotateToEntity.hpp" + +#endif /* __BEHAVIORS_H__ */ diff --git a/dlls/game/behaviors_general.cpp b/dlls/game/behaviors_general.cpp new file mode 100644 index 0000000..823f68a --- /dev/null +++ b/dlls/game/behaviors_general.cpp @@ -0,0 +1,7581 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/behaviors_general.cpp $ +// $Revision:: 190 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +#include "_pch_cpp.h" +//#include "g_local.h" +#include "behavior.h" +#include "actor.h" +#include "doors.h" +#include "object.h" +#include "explosion.h" +#include "weaputils.h" +#include "player.h" +#include "behaviors_general.h" + +extern Container HelperNodes; +extern Event EV_Contents; +extern Event EV_HelperNodeCommand; + + + + +//============================================================================== +// Define +//============================================================================== +#define YAW_MAXIMUM 100.0f +#define TURN_THRESHOLD_MAJOR 12.0f +#define TURN_THRESHOLD_MINOR 3.0f +#define CONE_OF_FIRE 14.0f +#define MAJOR_YAW_CHANGE 10.0f +#define MAJOR_PITCH_CHANGE 8.0f + + + +#define WORK_NODE_OCCUPIED_TIME 16.5f + +#define DUCK_DISTANCE 350.0f +#define RUN_DISTANCE 90.0f + +#define POINT_BLANK 50.0f + +#define MIN_POSITION_NODE_DIST 50.0f +#define MAX_POSITION_NODE_DIST 256.0f + + +//============================================================================== +// Helper Node Specific Defines +//============================================================================== +#define WORK_HELPER_NODE_STATE_TURN_TO_WORK 1 +#define WORK_HELPER_NODE_STATE_START_ANIM 2 +#define WORK_HELPER_NODE_STATE_PLAY_ANIM 3 +#define WORK_HELPER_NODE_STATE_WAITING_FOR_ANIM 4 +#define WORK_HELPER_NODE_STATE_FINISH 5 + +#define PATROL_HELPER_NODE_STATE_GET_NEXT_NODE 1 +#define PATROL_HELPER_NODE_STATE_MOVING 2 +#define PATROL_HELPER_NODE_STATE_AT_NODE 3 +#define PATROL_HELPER_NODE_STATE_WAITING 4 +#define PATROL_HELPER_NODE_STATE_WAITING_FOR_ANIM 5 +#define PATROL_HELPER_NODE_STATE_FINISHED 6 + + + +//=================================================================================== +// +// Known Working Behaviors -- May need some clean up, but they do work +// +//=================================================================================== + + +//============================================================================== +// WarpToPosition +//============================================================================== + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, WarpToPosition, NULL ) + { + { &EV_Behavior_Args, &WarpToPosition::SetArgs }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: WarpToPosition +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void WarpToPosition::SetArgs ( Event *ev) + { + _position = ev->GetVector( 1 ); + } + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: WarpToPosition +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void WarpToPosition::Begin( Actor &self ) + { + _state = WARP_TO_POSITION_CHECK_POSITION; + } + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: WarpToPosition +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t WarpToPosition::Evaluate ( Actor &self ) + { + switch ( _state ) + { + case WARP_TO_POSITION_CHECK_POSITION: + checkPosition( self ); + break; + + case WARP_TO_POSITION_WARP: + warpToPosition( self ); + break; + + case WARP_TO_POSITION_FAILED: + return BEHAVIOR_FAILED; + break; + + case WARP_TO_POSITION_SUCCESS: + return BEHAVIOR_SUCCESS; + break; + } + + return BEHAVIOR_EVALUATING; + } + + +//-------------------------------------------------------------- +// Name: End() +// Class: WarpToPosition +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void WarpToPosition::End ( Actor &self ) + { + } + +//-------------------------------------------------------------- +// Name: checkPosition() +// Class: WarpToPosition +// +// Description: Checks if the actor will fit in the position requested +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void WarpToPosition::checkPosition( Actor &self ) +{ + trace_t trace; + trace = G_Trace( _position + Vector( "0 0 64" ), self.mins, self.maxs, _position - Vector( "0 0 128" ), &self, self.edict->clipmask, false, "WarpToPosition" ); + _position = trace.endpos; + + if ( trace.allsolid ) + checkPositionFailed( self ); + else + _state = WARP_TO_POSITION_WARP; +} + +//-------------------------------------------------------------- +// Name: checkPositionFailed +// Class: WarpToPosition +// +// Description: Failure Handler for CheckPosition -- Sets our animation +// to idle and sets our state to FAILED +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void WarpToPosition::checkPositionFailed( Actor &self ) +{ + self.SetAnim( "idle" ); + _state = WARP_TO_POSITION_FAILED; +} + +//-------------------------------------------------------------- +// Name: warpToPosition() +// Class: WarpToPosition +// +// Description: Sets our Origin to the specified location +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void WarpToPosition::warpToPosition( Actor &self ) +{ + self.setOrigin( _position ); + self.NoLerpThisFrame(); + _state = WARP_TO_POSITION_SUCCESS; +} + + + + + +//============================================================================== +// WarpToEntity +//============================================================================== + +//-------------------------------------------------------------- +// +// Init Static Vars +// +//-------------------------------------------------------------- +const float WarpToEntity::DIST_TO_ENTITY = 64.0f; + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, WarpToEntity, NULL ) + { + { &EV_Behavior_Args, &WarpToEntity::SetArgs }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: WarpToEntity +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void WarpToEntity::SetArgs ( Event *ev) +{ + _entity = ev->GetEntity( 1 ); +} + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: WarpToEntity +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void WarpToEntity::Begin( Actor &self ) +{ + _state = WARP_TO_ENTITY_SELECT_POSITION; + _position = POSITION_REAR; +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: WarpToEntity +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t WarpToEntity::Evaluate ( Actor &self ) +{ + switch ( _state ) + { + case WARP_TO_ENTITY_SELECT_POSITION: + selectPosition( self ); + break; + + case WARP_TO_ENTITY_WARP: + warpToPosition( self ); + break; + + case WARP_TO_ENTITY_FAILED: + return BEHAVIOR_FAILED; + break; + + case WARP_TO_ENTITY_SUCCESS: + return BEHAVIOR_SUCCESS; + break; + } + + return BEHAVIOR_EVALUATING; +} + + +//-------------------------------------------------------------- +// Name: End() +// Class: WarpToEntity +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void WarpToEntity::End ( Actor &self ) +{ +} + +//-------------------------------------------------------------- +// Name: selectPosition() +// Class: WarpToEntity +// +// Description: Checks what position we should try and gets a vector +// to pass to the WarpToPosition Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void WarpToEntity::selectPosition( Actor &self ) +{ + Vector entAngles; + Vector f,l,u; + + entAngles = _entity->angles; + entAngles.AngleVectors( &f, &l, &u ); + + switch ( _position ) + { + case POSITION_REAR: + _destination = ( f * -1 ) * 64.0f; + break; + + case POSITION_LEFT: + _destination = l * 64.0f; + break; + + case POSITION_RIGHT: + _destination = ( l * -1 ) * 64.0f; + break; + + case POSITION_FRONT: + _destination = f * 64.0f; + break; + } + + _destination += _entity->origin; + + setupWarp( self ); + _state = WARP_TO_ENTITY_WARP; + +} + +//-------------------------------------------------------------- +// Name: setupWarp() +// Class: WarpToEntity +// +// Description: Sets up our Warp Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void WarpToEntity::setupWarp( Actor &self ) +{ + _warp.SetPosition( _destination ); + _warp.Begin( self ); +} + +//-------------------------------------------------------------- +// Name: warpToPosition() +// Class: WarpToEntity +// +// Description: Sets our Origin to the specified location +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void WarpToEntity::warpToPosition( Actor &self ) +{ + BehaviorReturnCode_t result; + result = _warp.Evaluate( self ); + + if ( result == BEHAVIOR_SUCCESS ) + { + _state = WARP_TO_ENTITY_SUCCESS; + return; + } + + if ( result != BEHAVIOR_EVALUATING ) + { + warpToPositionFailed( self ); + return; + } +} + + +//-------------------------------------------------------------- +// Name: warpToPositionFailed +// Class: WarpToEntity +// +// Description: Failure Handler for warpToPosition -- Increments +// us to our next position to check, or fails +// us out right if we've exhausted all our possibilities +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void WarpToEntity::warpToPositionFailed( Actor &self ) +{ + _position++; + + //See if we checked all of our options + if ( _position >= POSITION_NUMBER_OF_POSITIONS ) + { + _state = WARP_TO_ENTITY_FAILED; + return; + } + + _state = WARP_TO_ENTITY_SELECT_POSITION; +} + +// +//============================================================================== +// GotoEntity +//============================================================================== +// + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, GotoEntity, NULL ) + { + { &EV_Behavior_Args, &GotoEntity::SetArgs }, + { NULL, NULL } + }; + + + +GotoEntity::GotoEntity() +{ + _dist = 96.0f; +} + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: GotoEntity +// +// Description: +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void GotoEntity::SetArgs( Event *ev ) + { + // Set some default values here + _dist = 96.0f; + + // We have to have an anim + _anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + _dist = ev->GetFloat( 2 ); + + } + + + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: GotoEntity +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void GotoEntity::Begin( Actor &self ) + { + if ( _entity ) + { + _chase.SetGoal( _entity, _dist, self ); + _chase.Begin( self ); + } + + self.SetAnim( _anim ); + } + + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: GotoEntity +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoEntity::Evaluate( Actor &self ) + { + unsigned int chaseResult; + + if ( !_entity ) + return BEHAVIOR_FAILED; + + //self.SetAnim( _anim ); + + chaseResult = _chase.Evaluate( self ); + + + // Return the appropriate code + switch ( chaseResult ) + { + case Steering::SUCCESS: + return BEHAVIOR_SUCCESS; + break; + + case Steering::EVALUATING: + return BEHAVIOR_EVALUATING; + break; + + case Steering::FAILED: + self.SetAnim( "idle" ); + SetFailureReason( "Steering returned FAILED" ); + self.AddStateFlag( STATE_FLAG_STEERING_FAILED ); + return BEHAVIOR_FAILED; + break; + + case Steering::FAILED_BLOCKED_BY_ENEMY: + self.SetAnim( "idle" ); + SetFailureReason( "Steering returned BLOCKED_BY_ENEMY" ); + self.AddStateFlag( STATE_FLAG_STEERING_FAILED ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_ENEMY; + break; + + case Steering::FAILED_BLOCKED_BY_CIVILIAN: + self.SetAnim( "idle" ); + SetFailureReason( "Steering returned BLOCKED_BY_CIVILIAN" ); + self.AddStateFlag( STATE_FLAG_STEERING_FAILED ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_CIVILIAN; + break; + + case Steering::FAILED_BLOCKED_BY_FRIEND: + self.SetAnim( "idle" ); + SetFailureReason( "Steering returned BLOCKED_BY_FRIEND" ); + self.AddStateFlag( STATE_FLAG_STEERING_FAILED ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_FRIEND; + break; + + case Steering::FAILED_BLOCKED_BY_TEAMMATE: + self.SetAnim( "idle" ); + SetFailureReason( "Steering returned BLOCKED_BY_TEAMMATE" ); + self.AddStateFlag( STATE_FLAG_STEERING_FAILED ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_TEAMMATE; + break; + + case Steering::FAILED_BLOCKED_BY_WORLD: + self.SetAnim( "idle" ); + SetFailureReason( "Steering returned BLOCKED_BY_WORLD" ); + self.AddStateFlag( STATE_FLAG_STEERING_FAILED ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_WORLD; + break; + + case Steering::FAILED_BLOCKED_BY_DOOR: + self.SetAnim( "idle" ); + SetFailureReason( "Steering returned BLOCKED_BY_DOOR" ); + self.AddStateFlag( STATE_FLAG_STEERING_FAILED ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_DOOR; + break; + + case Steering::FAILED_CANNOT_GET_TO_PATH: + self.AddStateFlag( STATE_FLAG_NO_PATH ); + self.SetAnim( "idle" ); + SetFailureReason( "Steering returned CANNOT_GET_TO_PATH" ); + self.AddStateFlag( STATE_FLAG_STEERING_FAILED ); + return BEHAVIOR_FAILED_STEERING_CANNOT_GET_TO_PATH; + break; + + case Steering::FAILED_NO_PATH: + self.AddStateFlag( STATE_FLAG_NO_PATH ); + /* + if ( !self.GetActorFlag(ACTOR_FLAG_DISPLAYING_FAILURE_FX) ) + { + Event* event; + event = new Event( EV_DisplayEffect ); + event->AddString( "electric" ); + self.ProcessEvent( event ); + self.SetActorFlag( ACTOR_FLAG_DISPLAYING_FAILURE_FX , true ); + } + */ + + self.SetAnim( "idle" ); + SetFailureReason( "Steering returned NO_PATH" ); + self.AddStateFlag( STATE_FLAG_STEERING_FAILED ); + return BEHAVIOR_FAILED_STEERING_NO_PATH; + break; + + case Steering::ERROR: + self.SetAnim( "idle" ); + gi.Error( ERR_DROP, "Steering Error"); + break; + + } + + return BEHAVIOR_EVALUATING; + } + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: GotoEntity +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void GotoEntity::End(Actor &self) + { + _chase.End( self ); + } + + + + + + + +// +//============================================================================== +// GotoPoint +//============================================================================== +// + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, GotoPoint, NULL ) + { + { &EV_Behavior_Args, &GotoPoint::SetArgs }, + { NULL, NULL } + }; + + + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: GotoPoint +// +// Description: +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void GotoPoint::SetArgs( Event *ev ) + { + // Set some default values here + dist = 96.0f; + + // We have to have an anim + anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + dist = ev->GetFloat( 2 ); + + } + + + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: GotoPoint +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void GotoPoint::Begin( Actor &self ) + { + _chaseFailed = false; + _chase.SetGoal( point, dist, self ); + + unsigned int testResult; + testResult = _chase.Evaluate( self ); + + if ( testResult == Steering::EVALUATING ) + self.SetAnim( anim ); + else + { + self.SetAnim ( "idle" ); + _chaseFailed = true; + } + } + + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: GotoPoint +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoPoint::Evaluate( Actor &self ) + { + unsigned int chaseResult; + + //E3 2002 HACK LOVIN' + if ( self.state_flags & STATE_FLAG_STUCK ) + { + self.SetAnim( "idle" ); + SetFailureReason( "I'm stuck!!!!!!" ); + return BEHAVIOR_FAILED; + } + + chaseResult = _chase.Evaluate( self ); + + + // Return the appropriate code + switch ( chaseResult ) + { + case Steering::SUCCESS: + return BEHAVIOR_SUCCESS; + break; + + case Steering::EVALUATING: + if ( _chaseFailed ) + { + self.SetAnim( anim ); + _chaseFailed = false; + } + + return BEHAVIOR_EVALUATING; + break; + + case Steering::FAILED: + self.SetAnim( "idle" ); + return BEHAVIOR_FAILED; + break; + + case Steering::FAILED_BLOCKED_BY_ENEMY: + self.SetAnim( "idle" ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_ENEMY; + break; + + case Steering::FAILED_BLOCKED_BY_CIVILIAN: + self.SetAnim( "idle" ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_CIVILIAN; + break; + + case Steering::FAILED_BLOCKED_BY_FRIEND: + self.SetAnim( "idle" ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_FRIEND; + break; + + case Steering::FAILED_BLOCKED_BY_TEAMMATE: + self.SetAnim( "idle" ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_TEAMMATE; + break; + + case Steering::FAILED_BLOCKED_BY_WORLD: + self.SetAnim( "idle" ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_WORLD; + break; + + case Steering::FAILED_BLOCKED_BY_DOOR: + self.SetAnim( "idle" ); + return BEHAVIOR_FAILED_STEERING_BLOCKED_BY_DOOR; + break; + + case Steering::FAILED_CANNOT_GET_TO_PATH: + self.AddStateFlag( STATE_FLAG_NO_PATH ); + self.SetAnim( "idle" ); + return BEHAVIOR_FAILED_STEERING_CANNOT_GET_TO_PATH; + break; + + case Steering::FAILED_NO_PATH: + self.AddStateFlag( STATE_FLAG_NO_PATH ); + self.SetAnim( "idle" ); + return BEHAVIOR_FAILED_STEERING_NO_PATH; + break; + + case Steering::ERROR: + self.SetAnim( "idle" ); + gi.Error( ERR_DROP, "Steering Error"); + break; + + } + + + return BEHAVIOR_EVALUATING; + } + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: GotoPoint +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void GotoPoint::End(Actor &self) + { + _chase.End( self ); + } + + + +//-------------------------------------------------------------- +// Name: SetEntity() +// Class: GotoPoint +// +// Description: Mutator +// +// Parameters: Entity *ent +// +// Returns: None +//-------------------------------------------------------------- +void GotoPoint::SetPoint( const Vector &position ) + { + point = position; + } + + + +//-------------------------------------------------------------- +// Name: SetAnim() +// Class: GotoPoint +// +// Description: Mutator +// +// Parameters: const str &animName +// +// Returns: None +//-------------------------------------------------------------- +void GotoPoint::SetAnim( const str &animName ) + { + anim = animName; + } + + + +//-------------------------------------------------------------- +// Name: SetDistance() +// Class: GotoPoint +// +// Description: Mutator +// +// Parameters: float distance +// +// Returns: None +//-------------------------------------------------------------- +void GotoPoint::SetDistance( float distance ) + { + dist = distance; + } + + +// +//============================================================================== +// MoveDirectlyToPoint +//============================================================================== +// + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, MoveDirectlyToPoint, NULL ) +{ + { &EV_Behavior_Args, &MoveDirectlyToPoint::SetArgs }, + { NULL, NULL } +}; + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: MoveDirectlyToPoint +// +// Description: +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void MoveDirectlyToPoint::SetArgs( Event *ev ) +{ + // Set some default values here + _dist = 96.0f; + + // We have to have an anim + _anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + _dist = ev->GetFloat( 2 ); +} + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: MoveDirectlyToPoint +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void MoveDirectlyToPoint::Begin( Actor &self ) +{ + _dist = 16.0f; + _motion.SetRadius( _dist ); +} + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: MoveDirectlyToPoint +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t MoveDirectlyToPoint::Evaluate( Actor &self ) +{ + self.SetAnim( _anim ); + + unsigned int motionResult = _motion.Evaluate( self ); + + + // Return the appropriate code + switch ( motionResult ) + { + case Steering::SUCCESS: + return BEHAVIOR_SUCCESS; + break; + + case Steering::EVALUATING: + return BEHAVIOR_EVALUATING; + break; + } + + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// +// Name: End() +// Class: MoveDirectlyToPoint +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void MoveDirectlyToPoint::End( Actor &self ) +{ + _motion.End( self ); +} + +//-------------------------------------------------------------- +// Name: SetAnim() +// Class: MoveDirectlyToPoint +// +// Description: Mutator +// +// Parameters: const str &animName +// +// Returns: None +//-------------------------------------------------------------- +void MoveDirectlyToPoint::SetAnim( const str &animName ) +{ + _anim = animName; +} + +//-------------------------------------------------------------- +// Name: SetEntity() +// Class: MoveDirectlyToPoint +// +// Description: Mutator +// +// Parameters: Entity *ent +// +// Returns: None +//-------------------------------------------------------------- +void MoveDirectlyToPoint::SetPoint( const Vector &position ) +{ + _motion.SetDestination( position ); +} + +//-------------------------------------------------------------- +// Name: SetDistance() +// Class: MoveDirectlyToPoint +// +// Description: Mutator +// +// Parameters: float distance +// +// Returns: None +//-------------------------------------------------------------- +void MoveDirectlyToPoint::SetDistance( const float distance ) +{ + _dist = distance; +} + +//============================================================================== +// GotoSpecified +//============================================================================== + + +//-------------------------------------------------------------- +// +// Init Static Vars +// +//-------------------------------------------------------------- +const float GotoSpecified::DIST_TO_TARGET_POSITION = 16.0f; +const float GotoSpecified::DIST_TO_TARGET_ENTITY = 64.0f; + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, GotoSpecified, NULL ) + { + { &EV_Behavior_Args, &GotoSpecified::SetArgs }, + { &EV_Behavior_AnimDone, &GotoSpecified::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: GotoSpecified +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::SetArgs ( Event *ev) +{ + TargetList *tlist; + PathNode *node; + str parm; + + _state = GOTO_SPEC_FAILED; + _turnAtEnd = false ; + _maxFailures = 5; + + // Get our Animation + _anim = ev->GetString( 1 ); + + + // Check if we were given a specific position to go to + if ( ev->IsVectorAt( 2 ) ) + { + _targetPosition = ev->GetVector( 2 ); + _state = GOTO_SPEC_CHASE_TARGET; + _mode = GOTO_SPEC_CHASE_POSITION; + } + // Check if we were given an entity to go to + else if ( ev->IsEntityAt( 2 ) ) + { + _targetEntity = ev->GetEntity( 2 ); + _state = GOTO_SPEC_CHASE_TARGET; + _mode = GOTO_SPEC_CHASE_ENTITY; + } + else + { + // First see if we were given the target name of a pathnode + parm = ev->GetString( 2 ); + node = thePathManager.FindNode( parm.c_str() ); + + if ( node ) + { + _targetPosition = node->origin; + _endAngles = node->angles; + _state = GOTO_SPEC_CHASE_TARGET; + _mode = GOTO_SPEC_CHASE_POSITION; + _turnAtEnd = true ; + } + // Now see if we were given the target name of another entity + else + { + tlist = world->GetTargetList( parm ); + if (tlist->list.NumObjects() > 0 ) + { + _targetEntity = tlist->list.ObjectAt( 1 ); + _state = GOTO_SPEC_CHASE_TARGET; + _mode = GOTO_SPEC_CHASE_ENTITY; + } + else + { + HelperNode *helperNode; + + // Try helper nodes + + helperNode = HelperNode::GetTargetedHelperNode( parm ); + + if ( helperNode ) + { + _targetPosition = helperNode->origin; + _endAngles = helperNode->angles; + _state = GOTO_SPEC_CHASE_TARGET; + _mode = GOTO_SPEC_CHASE_POSITION; + _turnAtEnd = true; + } + } + } + } + + // See if we have any kind of headwatch target + if ( ev->NumArgs() > 2 ) + { + if ( ev->IsEntityAt( 3 ) ) + _headwatchTarget = ev->GetEntity( 3 ); + else + { + parm = ev->GetString( 3 ); + // We have a headwatch target, but we need to make sure its not set to "none" + // that might occur if we're going to do a forceToTarget, but we don't want to + // headwatch anything + if ( stricmp( parm.c_str() , "none" ) ) + { + tlist = world->GetTargetList( parm ); + if (tlist->list.NumObjects() > 0 ) + { + _headwatchTarget = tlist->list.ObjectAt( 1 ); + } + } + } + + } + + // See if we're going to try and force ourselves to the target + if ( ev->NumArgs() > 3 ) + _forceToTarget = ev->GetBoolean( 4 ); + else + _forceToTarget = false; + + if ( ev->NumArgs() > 4 ) + _maxFailures = ev->GetInteger( 5 ); + + // Now, let's assert if our _state is still FAILED. That basically means we didn't find + // anything to try and goto so we need to throw up a flag + assert ( _state != GOTO_SPEC_FAILED ); + +} + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: GotoSpecified +// +// Description: Catches the Anim Done event +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::AnimDone ( Event *ev ) +{ +} + +//-------------------------------------------------------------- +// Name: Begin() +// Class: GotoSpecified +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::Begin( Actor &self ) +{ + if ( _mode == GOTO_SPEC_CHASE_POSITION ) + setupChasePosition( self ); + else if ( _mode == GOTO_SPEC_CHASE_ENTITY ) + setupChaseEntity( self ); + + _moveFailures = 0; + _holdTime = 0.0f; + + if ( _headwatchTarget ) + self.SetHeadWatchTarget( _headwatchTarget ); + + _attemptedPathWarp = false; + + // Setup our TorsoAnim if we need to + str torsoAnim; + self.ClearTorsoAnim(); + if ( self.combatSubsystem->HaveWeapon() ) + { + if ( self.enemyManager->HasEnemy() ) + torsoAnim = self.combatSubsystem->GetAnimForMyWeapon( "CombatGunIdle" ); + else + torsoAnim = self.combatSubsystem->GetAnimForMyWeapon( "IdleGunIdle" ); + } + + if ( torsoAnim.length() ) + { + self.ClearTorsoAnim(); + self.SetAnim( torsoAnim, NULL , torso ); + } + + +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: GotoSpecified +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoSpecified::Evaluate ( Actor &self ) +{ + switch ( _state ) + { + case GOTO_SPEC_CHASE_TARGET: + if ( _mode == GOTO_SPEC_CHASE_POSITION ) + chasePosition( self ); + else + chaseEntity( self ); + break; + + case GOTO_SPEC_HOLD: + hold( self ); + break; + + case GOTO_SPEC_WARP_TO_PATH: + warpToNearestPathNode( self ); + break; + + case GOTO_SPEC_WARP_TO_DESTINATION: + warpToDestination( self ); + break; + + case GOTO_SPEC_SUCCESS: + if ( _turnAtEnd ) + { + setAngles( self ); + } + self.SetAnim( "idle" ); + return BEHAVIOR_SUCCESS; + break; + + case GOTO_SPEC_FAILED: + self.SetAnim( "idle" ); + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; +} + + +//-------------------------------------------------------------- +// Name: End() +// Class: GotoSpecified +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::End ( Actor &self ) +{ +} + +//-------------------------------------------------------------- +// Name: setupChaseEntity() +// Class: GotoSpecified +// +// Description: Sets up the ChaseEntity Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::setupChaseEntity( Actor &self ) +{ + _chaseEntity.SetAnim( _anim ); + _chaseEntity.SetDistance( DIST_TO_TARGET_ENTITY ); + _chaseEntity.SetEntity( self, _targetEntity ); + _chaseEntity.Begin( self ); +} + +//-------------------------------------------------------------- +// Name: setupChasePosition() +// Class: GotoSpecified +// +// Description: Sets up the ChasePosition Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::setupChasePosition( Actor &self ) +{ + _chasePosition.SetAnim( _anim ); + _chasePosition.SetDistance( DIST_TO_TARGET_POSITION ); + _chasePosition.SetPoint( _targetPosition ); + _chasePosition.Begin( self ); +} + +//-------------------------------------------------------------- +// Name: setupWarpToPathNode() +// Class: GotoSpecified +// +// Description: Sets up our WarpToDestination Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::setupWarpToPathNode( Actor &self ) +{ + // Find the path node nearest to us + PathNode *goalNode = thePathManager.NearestNode( _targetPosition ); + if ( !goalNode ) + { + _state = GOTO_SPEC_WARP_TO_DESTINATION; + setupWarpToDestination( self ); + } + + _warpToPosition.SetPosition( goalNode->origin ); + _warpToPosition.Begin( self ); + +} + +//-------------------------------------------------------------- +// Name: setupWarpToDestination() +// Class: GotoSpecified +// +// Description: Based on our _mode, will set up our WarpToEntity +// or WarpToPosition Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::setupWarpToDestination( Actor &self ) +{ + if ( _mode == GOTO_SPEC_CHASE_POSITION ) + { + _warpToPosition.SetPosition( _targetPosition ); + _warpToPosition.Begin( self ); + return; + } + else + { + _warpToEntity.SetEntity( _targetEntity ); + _warpToPosition.Begin( self ); + return; + } + +} + +//-------------------------------------------------------------- +// Name: setupHold() +// Class: GotoSpecified +// +// Description: Sets actor to the idle animation and sets up the hold time +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::setupHold( Actor &self ) +{ + self.SetAnim( "idle" ); + _holdTime = level.time + G_Random() + .5f; +} + +//-------------------------------------------------------------- +// Name: chaseEntity() +// Class: GotoSpecified +// +// Description: Evaluates the ChaseEntity component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::chaseEntity( Actor &self ) +{ + BehaviorReturnCode_t result; + result = _chaseEntity.Evaluate( self ); + + switch ( result ) + { + case BEHAVIOR_SUCCESS: + _state = GOTO_SPEC_SUCCESS; + return; + break; + + case BEHAVIOR_FAILED: + chaseFailed( self ); + SetFailureReason("_chaseEntity returned BEHAVIOR_FAILED" ); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_ENEMY: + chaseFailed( self ); + SetFailureReason("_chaseEntity returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_ENEMY"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_CIVILIAN: + chaseFailed( self ); + SetFailureReason("_chaseEntity returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_CIVILIAN"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_FRIEND: + chaseFailed( self ); + SetFailureReason("_chaseEntity returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_FRIEND"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_TEAMMATE: + chaseFailed( self ); + SetFailureReason( "_chaseEntity returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_TEAMMATE"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_WORLD: + chaseFailed( self ); + SetFailureReason("_chaseEntity returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_WORLD"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_DOOR: + chaseFailed( self ); + SetFailureReason("_chaseEntity returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_DOOR"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_CANNOT_GET_TO_PATH: + chaseFailed( self ); + SetFailureReason("_chaseEntity returned BEHAVIOR_FAILED_STEERING_CANNOT_GET_TO_PATH"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_NO_PATH: + chaseFailed( self ); + SetFailureReason("_chaseEntity returned BEHAVIOR_FAILED_STEERING_NO_PATH"); + return; + break; + + case BEHAVIOR_INVALID: + chaseFailed( self ); + SetFailureReason("_chaseEntity returned BEHAVIOR_INVALID"); + return; + break; + } + + // We are still evaluating, which means we can clear out our failure counter + _moveFailures = 0; +} + +//-------------------------------------------------------------- +// Name: chasePosition() +// Class: GotoSpecified +// +// Description: Evaluates our ChasePosition component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::chasePosition( Actor &self ) +{ + BehaviorReturnCode_t result; + result = _chasePosition.Evaluate( self ); + + switch ( result ) + { + case BEHAVIOR_SUCCESS: + _state = GOTO_SPEC_SUCCESS; + return; + break; + + case BEHAVIOR_FAILED: + chaseFailed( self ); + SetFailureReason("_chasePosition returned BEHAVIOR_FAILED" ); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_ENEMY: + chaseFailed( self ); + SetFailureReason("_chasePosition returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_ENEMY"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_CIVILIAN: + chaseFailed( self ); + SetFailureReason("_chasePosition returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_CIVILIAN"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_FRIEND: + chaseFailed( self ); + SetFailureReason("_chasePosition returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_FRIEND"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_TEAMMATE: + chaseFailed( self ); + SetFailureReason( "_chasePosition returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_TEAMMATE"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_WORLD: + chaseFailed( self ); + SetFailureReason("_chasePosition returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_WORLD"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_BLOCKED_BY_DOOR: + chaseFailed( self ); + SetFailureReason("_chasePosition returned BEHAVIOR_FAILED_STEERING_BLOCKED_BY_DOOR"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_CANNOT_GET_TO_PATH: + chaseFailed( self ); + SetFailureReason("_chasePosition returned BEHAVIOR_FAILED_STEERING_CANNOT_GET_TO_PATH"); + return; + break; + + case BEHAVIOR_FAILED_STEERING_NO_PATH: + chaseFailed( self ); + SetFailureReason("_chasePosition returned BEHAVIOR_FAILED_STEERING_NO_PATH"); + return; + break; + + case BEHAVIOR_INVALID: + chaseFailed( self ); + SetFailureReason("_chasePosition returned BEHAVIOR_INVALID"); + return; + break; + } + + // We are still evaluating, which means we can clear out our failure counter + _moveFailures = 0; +} + + + +//-------------------------------------------------------------- +// Name: warpToNearestPathNode() +// Class: GotoSpecified +// +// Description: Evaluates the WarpToPosition Component in an attempt +// to warp us to a nearby pathnode +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::warpToNearestPathNode( Actor &self ) +{ + BehaviorReturnCode_t result; + result = _warpToPosition.Evaluate( self ); + + _attemptedPathWarp = true; + _moveFailures = 0; + + if ( result != BEHAVIOR_EVALUATING ) + { + str tname; + tname = self.TargetName(); + + gi.WDPrintf( "=============================================================\n" ); + gi.WDPrintf( "Actor %s is failing to reach destination\n" , tname.c_str() ); + gi.WDPrintf( "=============================================================\n" ); + gi.WDPrintf( "Reported Reason\n" ); + gi.WDPrintf( "%s\n" , GetFailureReason().c_str() ); + gi.WDPrintf( "=============================================================\n" ); + + _state = GOTO_SPEC_CHASE_TARGET; + if ( _mode == GOTO_SPEC_CHASE_POSITION ) + { + setupChasePosition( self ); + } + else + { + setupChaseEntity( self ); + } + } + + //_state = GOTO_SPEC_CHASE_TARGET; +} + + +//-------------------------------------------------------------- +// Name: warpToDestination() +// Class: GotoSpecified +// +// Description: Depending on our _mode, will evaluate either +// the warpToEntity or warpToPosition component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::warpToDestination( Actor &self ) +{ + BehaviorReturnCode_t result; + result = BEHAVIOR_EVALUATING; + + if ( _mode == GOTO_SPEC_CHASE_POSITION ) + result = _warpToPosition.Evaluate( self ); + + if ( _mode == GOTO_SPEC_CHASE_ENTITY ) + result = _warpToEntity.Evaluate( self ); + + + if ( result == BEHAVIOR_SUCCESS ) + { + _state = GOTO_SPEC_SUCCESS; + return; + } + + if ( result != BEHAVIOR_EVALUATING ) + { + _state = GOTO_SPEC_FAILED; + return; + } + +} + +//-------------------------------------------------------------- +// Name: hold() +// Class: GotoSpecified +// +// Description: Holds actor in position until the hold time has expired +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::hold( Actor &self ) +{ + if ( level.time >= _holdTime ) + { + if ( _mode == GOTO_SPEC_CHASE_POSITION ) + { + setupChasePosition( self ); + _state = GOTO_SPEC_CHASE_TARGET ; + } + else + { + setupChaseEntity( self ); + _state = GOTO_SPEC_CHASE_TARGET ; + } + } +} + + +//-------------------------------------------------------------- +// Name: setAngles() +// Class: GotoSpecified +// +// Description: Sets the angles and animdir of the actor to _endAngles +// _endAngles will be the angles of a pathnode -- if +// that is the destionation -- otherwise it will be 0 , 0 , 0 +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::setAngles( Actor &self ) +{ + Vector angles; + Vector animDir; + + _endAngles.AngleVectors( &animDir ); + + if ( _mode == GOTO_SPEC_CHASE_POSITION ) + { + self.setAngles( _endAngles ); + self.movementSubsystem->setAnimDir( animDir ); + } +} + +//-------------------------------------------------------------- +// Name: chaseFailed() +// Class: GotoSpecified +// +// Description: Failure Handler for the chase type components +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoSpecified::chaseFailed( Actor &self ) +{ + str tname; + tname = self.TargetName(); + + if ( _mode == GOTO_SPEC_CHASE_POSITION ) + _chasePosition.End( self ); + else + _chaseEntity.End( self ); + + // + // Give everybody 5 chances before bombing out + // + if ( _maxFailures > 0 && _moveFailures > _maxFailures ) + { + if ( !_forceToTarget ) + { + gi.WDPrintf( "=============================================================\n" ); + gi.WDPrintf( "Actor %s is failing to reach destination\n" , tname.c_str() ); + gi.WDPrintf( "=============================================================\n" ); + gi.WDPrintf( "Reported Reason\n" ); + gi.WDPrintf( "%s\n" , GetFailureReason().c_str() ); + gi.WDPrintf( "=============================================================\n" ); + _state = GOTO_SPEC_FAILED; + return; + } + + setupWarpToPathNode( self ); + _state = GOTO_SPEC_WARP_TO_PATH; + return; + } + + _moveFailures++; + setupHold( self ); + _state = GOTO_SPEC_HOLD; + return; + +} + + + + +//============================================================================== +// MoveFromConeOfFire +//============================================================================== + + +//-------------------------------------------------------------- +// +// Init Static Vars +// +//-------------------------------------------------------------- +const float MoveFromConeOfFire::CONE_OF_FIRE_RADIUS = 500.0f; + + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, MoveFromConeOfFire, NULL ) + { + { &EV_Behavior_Args, &MoveFromConeOfFire::SetArgs }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: MoveFromConeOfFire +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void MoveFromConeOfFire::SetArgs ( Event *ev) + { + if ( ev->NumArgs() > 0 ) + _anim = ev->GetString( 1 ); + } + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: MoveFromConeOfFire +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void MoveFromConeOfFire::Begin( Actor &self ) + { + Vector dir; + + movegoal = NULL; + + dir = self.movementSubsystem->getAnimDir(); + dir = dir.toAngles(); + self.setAngles( dir ); + + _nextsearch = 0.0f; + _torsoAnim = ""; + + _state = MOVE_FCOF_SEARCHING_FOR_SPOT; + _stuckOnPlayer = false; + _nextToObstacle = false; + + _chase.SetAnim( _anim ); + _chase.SetDistance( 16.0f ); + _oldTurnSpeed = self.movementSubsystem->getTurnSpeed(); + self.movementSubsystem->setTurnSpeed( 360.0f ); + } + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: MoveFromConeOfFire +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t MoveFromConeOfFire::Evaluate ( Actor &self ) + { + if ( _stuckOnPlayer ) + return BEHAVIOR_FAILED; +/* + if ( g_showactortrace ) + { + G_DebugLine( self.origin , _left, 1.0f, 0.0f, 1.0f, 1.0f ); + G_DebugLine( self.origin , _right, 1.0f, 0.0f, 1.0f, 1.0f ); + G_DebugLine( self.origin , _destination , 1.0f ,1.0f , 1.0f, 1.0f ); + } +*/ + + switch ( _state ) + { + case MOVE_FCOF_SEARCHING_FOR_SPOT: + _setDirectionVectors(self); + break; + + case MOVE_FCOF_STATE_FOUND_SPOT: + _foundDestination( self ); + break; + + case MOVE_FCOF_STATE_NO_SPOT: + _noDestination( self ); + break; + + case MOVE_FCOF_STATE_SEARCHING_FOR_NODE: + _searchForNode( self ); + break; + + case MOVE_FCOF_STATE_FOUND_NODE : + _foundDestination( self ); + break; + + case MOVE_FCOF_SUCCESS: + return BEHAVIOR_SUCCESS; + break; + + case MOVE_FCOF_FAILED: + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; + + } + + +//-------------------------------------------------------------- +// Name: End() +// Class: MoveFromConeOfFire +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void MoveFromConeOfFire::End ( Actor &self ) + { + _chase.End( self ); + self.movementSubsystem->setTurnSpeed( _oldTurnSpeed ); + self.movementSubsystem->setMovingBackwards( false ); + self.movementSubsystem->setAdjustAnimDir( true ); + } + + +//-------------------------------------------------------------- +// Name: _setDirectionVectors() +// Class: MoveFromConeOfFire +// +// Description: Determines where we are going to go +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void MoveFromConeOfFire::_setDirectionVectors(Actor &self) + { + float dot; + Vector selfToPlayer; + Vector playerAngles; + Player *player; + + player = GetPlayer( 0 ); + assert( player ); + + // Get our Direction Vectors set up + selfToPlayer = self.origin - player->origin; + playerAngles = player->GetVAngles(); + + playerAngles[PITCH] = 0.0f; + playerAngles[ROLL] = 0.0f; + playerAngles.AngleVectors( NULL , &_left , NULL ); + assert( fSmallEnough( _left.z, fEpsilon()) ); + + const Vector startPos = self.origin; + //startPos.z += 32; + + // Check if we're left or right of the player + dot = DotProduct( selfToPlayer, _left ); + + _right = _left * -1; + + _right *= 75.0f; + _right += startPos; + + _left *= 75.0f; + _left += startPos; + + _right.z = startPos.z; + _left.z = startPos.z; + + + if ( dot <= 0 ) + { + // Go Right First + if ( !_checkDesiredMovement( self , startPos , _right ) ) + { + gi.WDPrintf( "=============================\n" ); + gi.WDPrintf( "Right Failed, trying Left\n" ); + gi.WDPrintf( "Anim = %s\n" , _anim.c_str() ); + gi.WDPrintf( "=============================\n" ); + + if ( _nextToObstacle ) + { + _state = MOVE_FCOF_FAILED; + return; + } + + //Well, no luck there, let's try left + //if ( !_checkDesiredMovement( self , startPos , _left ) ) + // { + // _state = MOVE_FCOF_FAILED; + // return; + // } + + _state = MOVE_FCOF_FAILED; + return; + + } + } + else + { + // Go Left First + if ( !_checkDesiredMovement( self , startPos , _left ) ) + { + gi.WDPrintf( "=============================\n" ); + gi.WDPrintf( "Left Failed, trying Right\n" ); + gi.WDPrintf( "Anim = %s\n" , _anim.c_str() ); + gi.WDPrintf( "=============================\n" ); + + if ( _nextToObstacle ) + { + _state = MOVE_FCOF_FAILED; + return; + } + + + //Well, no luck there, let's try left + //if ( !_checkDesiredMovement( self , startPos , _right ) ) + // { + // _state = MOVE_FCOF_FAILED; + // return; + // } + _state = MOVE_FCOF_FAILED; + return; + } + } + + _chase.Begin( self ); + } + + +//-------------------------------------------------------------- +// Name: _checkDesiredMovement() +// Class: MoveFromConeOfFire +// +// Description: Checks if the desired movement is possibe +// +// Parameters: Actor &self +// const Vector &startPos, +// const Vector &endPos +// +// Returns: true or false +//-------------------------------------------------------------- +bool MoveFromConeOfFire::_checkDesiredMovement( Actor &self , const Vector &startPos , const Vector &endPos ) + { + trace_t trace; + + trace = G_Trace( startPos, self.mins, self.maxs, endPos, &self, self.edict->clipmask, false, "MoveFromConeOfFire: Right Direction Test" ); + //G_DebugLine( startPos, endPos, 1.0f, 0.0f, 1.0f, 1.0f ); + + // See if we're stuck on the player + if ( trace.entityNum == 0 ) + _stuckOnPlayer = true; + + // See if we got far enough + if ( trace.fraction < .35 ) + return false; + + if ( trace.fraction < .20 ) + { + _nextToObstacle = true; + return false; + } + + // Update our Search Time + _nextsearch = level.time + 0.25f; + + _destination = trace.endpos; + + //New stuff to fix bumping the wall + Vector selfToDestination; + selfToDestination = trace.endpos - startPos; + selfToDestination *= .80f; + _destination = selfToDestination + startPos; + + + Vector selfToDestinationAngles = _destination - self.origin; + Vector animAngles = self.movementSubsystem->getAnimDir(); + float yawDiff; + + selfToDestinationAngles = selfToDestinationAngles.toAngles(); + animAngles = animAngles.toAngles(); + yawDiff = AngleNormalize180(selfToDestinationAngles[YAW] - animAngles[YAW] ); + + + if ( yawDiff >= -45.0 && yawDiff <= 45.0 ) + _anim = "walk"; + + if ( yawDiff >= -135.0 && yawDiff <= -45.0 ) + { + _anim = "strafe_left_clear"; + //_anim = "strafe_left"; + //_anim = "strafe_right"; + self.movementSubsystem->setAdjustAnimDir( false ); + } + + if ( yawDiff >= 45.0 && yawDiff <= 135.0f ) + { + _anim = "strafe_right_clear"; + //_anim = "strafe_right"; + //_anim = "strafe_left"; + self.movementSubsystem->setAdjustAnimDir( false ); + } + + if ( yawDiff >= 135.0 && yawDiff <= 180.0f ) + { + _anim = "backpedal"; + self.movementSubsystem->setMovingBackwards( true ); + } + + if ( yawDiff <= -135.0 && yawDiff >= -180.0 ) + { + _anim = "backpedal"; + self.movementSubsystem->setMovingBackwards( true ); + } + + //Setup Torso Anim if appropriate + if ( !self.torsoBehavior ) + { + _torsoAnim = self.combatSubsystem->GetAnimForMyWeapon( "Idle" ); + if ( _torsoAnim.length() ) + { + self.SetAnim( _torsoAnim, NULL , torso ); + } + } + + // Setup our Component + _chase.SetDistance( 32.0f ); + _chase.SetPoint( _destination ); + _chase.SetAnim( _anim ); + //_chase.Begin( self ); + + _state = MOVE_FCOF_STATE_FOUND_SPOT; + + return true; + } + + +//-------------------------------------------------------------- +// Name: _foundDestination() +// Class: MoveFromConeOfFire +// +// Description: Handles the evaluation of our GotoPoint component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void MoveFromConeOfFire::_foundDestination( Actor &self ) + { + BehaviorReturnCode_t result; + + + //self.SetAnim( _anim ); + result = _chase.Evaluate( self ); + + if ( result == BEHAVIOR_EVALUATING ) + return; + + // Check for any failure + if ( result != BEHAVIOR_SUCCESS ) + { + _state = MOVE_FCOF_FAILED; + return; + } + + // Reached spot + _chase.End( self ); + self.SetAnim( "idle" ); + _state = MOVE_FCOF_SUCCESS; + } + + +//-------------------------------------------------------------- +// Name: _noDestination() +// Class: MoveFromConeOfFire +// +// Description: Handles failure to find a destination +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void MoveFromConeOfFire::_noDestination( Actor &self ) + { + _state = MOVE_FCOF_STATE_SEARCHING_FOR_NODE; + _nextsearch = 0.0f; + } + + +//-------------------------------------------------------------- +// Name: _searchForNode() +// Class: MoveFromConeOfFire +// +// Description: Handles searching for a viable node +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void MoveFromConeOfFire::_searchForNode( Actor &self ) + { + if ( level.time > _nextsearch ) + movegoal = _FindNode( self ); + + if ( !movegoal ) + { + self.SetAnim( "idle" ); + return; + } + + _state = MOVE_FCOF_STATE_FOUND_NODE; + + // Found node, going to it + _nextsearch = level.time + 1.0f; + } + + +//-------------------------------------------------------------- +// Name: _FindNode() +// Class: MoveFromConeOfFire +// +// Description: Finds the a viable node, if one is available +// +// Parameters: Actor &self +// +// Returns: PathNode* +//-------------------------------------------------------------- +PathNode *MoveFromConeOfFire::_FindNode( Actor &self ) + { + int i; + float dot; + //float bestdist; + float bestdot; + float testdot; + float newDot; + Vector delta; + Vector distanceToNode; + Vector playerToSelf; + Vector nodeToSelf; + Vector l; + PathNode *bestnode; + PathNode *node; + Player *player; + + + //bestdist = 0.0f; + bestdot = 0.0f; + testdot = 0.0f; + bestnode = NULL; + + player = GetPlayer( 0 ); + playerToSelf = self.origin - player->origin; + + for ( i = 0 ; i < thePathManager.NumNodes() ; i++ ) + { + node = thePathManager.GetNode( i ); + + if ( !node ) + return NULL; + + distanceToNode = node->origin - self.origin; + if ( distanceToNode.length() > CONE_OF_FIRE_RADIUS ) + continue; + + nodeToSelf = self.origin - node->origin; + + dot = DotProduct( playerToSelf, nodeToSelf ) ; + if ( dot < 0 ) + { + playerToSelf = playerToSelf.toAngles(); + playerToSelf.AngleVectors(NULL, &l, NULL ); + + newDot = DotProduct ( l , nodeToSelf ); + testdot = abs( (int)newDot ); + + if ( testdot > bestdot ) + { + bestnode = node; + bestdot = testdot; + } + } + + } + + + if ( bestnode ) + { + bestnode->occupiedTime = level.time + 1.5f; + bestnode->entnum = self.entnum; + + _chase.SetPoint( bestnode->origin ); + _chase.Begin( self ); + + return bestnode; + + } + + return NULL; + } + +//============================================================================== +// Strafe +//============================================================================== + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, Strafe, NULL ) + { + { &EV_Behavior_Args, &Strafe::SetArgs }, + { &EV_Behavior_AnimDone, &Strafe::AnimDone }, + { NULL, NULL } + }; + + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: Strafe +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Strafe::SetArgs ( Event *ev) + { + SetMode( ev->GetInteger( 1 ) ); + } + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: Strafe +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void Strafe::AnimDone( Event *ev ) + { + _strafeComplete = true; + } + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: Strafe +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Strafe::Begin( Actor &self ) + { + _canStrafe = true; + _strafeComplete = false; + + _init( self ); + + } + + +//-------------------------------------------------------------- +// Name: _init() +// Class: Strafe +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void Strafe::_init( Actor &self ) +{ + float chance; + chance = G_Random(); + + // First see if our mode is explicit + if ( mode != STRAFE_RANDOM ) + { + _setAnim( self ); + return; + } + + //We'll go random then + if ( chance < .5 ) + { + mode = STRAFE_LEFT; + _setAnim( self ); + + if ( !_canStrafe ) + { + mode = STRAFE_RIGHT; + _setAnim( self ); + } + } + else + { + mode = STRAFE_RIGHT; + _setAnim( self ); + + if ( !_canStrafe ) + { + mode = STRAFE_LEFT; + _setAnim( self ); + } + } + +} + +//-------------------------------------------------------------- +// Name: _setAnim() +// Class: Strafe +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void Strafe::_setAnim( Actor &self ) + { + str currentPostureState = self.postureController->getCurrentPostureName(); + + switch ( mode ) + { + case STRAFE_LEFT: + if ( !self.checkLeftDirectionClear( 64.0f ) ) + { + _canStrafe = false; + return; + } + + if ( currentPostureState.length() ) + { + if ( currentPostureState == "STAND" ) + _anim = "strafe_left"; + else if ( currentPostureState == "DUCK" ) + _anim = "roll_left"; + } + else + { + _anim = "strafe_left"; + } + + + break; + + case STRAFE_RIGHT: + if ( !self.checkRightDirectionClear( 64.0f ) ) + { + _canStrafe = false; + return; + } + + if ( currentPostureState.length() ) + { + if ( currentPostureState == "STAND" ) + _anim = "strafe_right"; + else if ( currentPostureState == "DUCK" ) + _anim = "roll_right"; + } + else + { + _anim = "strafe_right"; + } + + break; + } + + if ( _canStrafe ) + { + self.SetAnim( _anim , EV_Actor_NotifyBehavior , legs ); + } + + } + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: Strafe +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t Strafe::Evaluate ( Actor &self ) + { + //self.SetAnim( _anim , EV_Actor_NotifyBehavior , legs ); + + if ( !_canStrafe ) + return BEHAVIOR_FAILED; + + if ( _strafeComplete ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + } + + + +//-------------------------------------------------------------- +// Name: End() +// Class: Strafe +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Strafe::End ( Actor &self ) + { + + } + + +void Strafe::SetMode( unsigned int strafeMode ) + { + if ( strafeMode >= STRAFE_NUMBER_OF_MODES ) + gi.Error( ERR_DROP, "Strafe -- Unknown Strafe Mode"); + + mode = strafeMode; + } + + + + +//============================================================================== +// CircleStrafeEntity +//============================================================================== + +CLASS_DECLARATION( Behavior, CircleStrafeEntity, NULL ) + { + { &EV_Behavior_Args, &CircleStrafeEntity::SetArgs }, + { NULL, NULL } + }; + + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: CircleStrafeEntity +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void CircleStrafeEntity::SetArgs ( Event *ev) +{ + _checkParameters(ev); + + _type = ev->GetString ( 1 ); + _legAnim = ev->GetString ( 2 ); + _radius = ev->GetFloat ( 3 ); + _clockwise = ev->GetBoolean( 4 ); + + if ( ev->NumArgs() > 4 ) + _testDistance = ev->GetFloat ( 5 ); + else + _testDistance = 80.0f; +} + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: CircleStrafeEntity +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void CircleStrafeEntity::Begin( Actor &self ) +{ + // Set our Strafe Target + _strafeTarget = _getStrafeTarget( self , _type ); + + // Set our Anim + self.SetAnim( _legAnim, EV_Actor_NotifyBehavior ); + + _failed = false; + + // Initialize + _lastPosition = self.origin; + _moveAttempts = 0; + _startWanderTime = 0; + _recheckTime = -1; + _init( self ); +} + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: CircleStrafeEntity +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t CircleStrafeEntity::Evaluate ( Actor &self ) +{ + Vector Destination; + Vector SelfToTarget; + + if ( !_strafeTarget ) + return BEHAVIOR_FAILED; + + if ( _failed ) + return BEHAVIOR_FAILED; + + SelfToTarget = _strafeTarget->origin - self.origin; + SelfToTarget = SelfToTarget.toAngles(); + + SelfToTarget[PITCH] = 0; + SelfToTarget[ROLL] = 0; + _init( self ); + + _holdAngles.AngleVectors( &Destination ); + + if ( _checkIfStuck(self) ) + return BEHAVIOR_FAILED; + else + { + self.movementSubsystem->setMoveDir( Destination ); + self.setAngles(SelfToTarget); + } + + return BEHAVIOR_EVALUATING; +} + + +void CircleStrafeEntity::_init( Actor &self ) +{ + Vector SelfToTarget; + //trace_t traceccw, tracecw; + trace_t tracecw; + float StrafeAngle; + float FallbackAngle; + float EmergencyAngle; + + if ( !_strafeTarget ) + { + _failed = true; + return; + } + + //_recheckTime = level.time + G_Random() + .75; + SelfToTarget = _strafeTarget->origin - self.origin; + _holdAngles = SelfToTarget.toAngles(); + _holdAngles.z = 0; + _holdAngles.EulerNormalize(); + + if ( self.WithinDistance( _strafeTarget , _radius ) ) + StrafeAngle = 90; + else + StrafeAngle = 45 + G_Random ( 10 ); + + FallbackAngle = 80 + G_Random ( 10 ); + EmergencyAngle = 120 + G_Random ( 10 ); + if ( !_clockwise ) + { + StrafeAngle *= -1; + FallbackAngle *= -1; + EmergencyAngle *= -1; + } + + + tracecw = self.Trace(StrafeAngle, _testDistance, "CircleStrafe Avoid Trace"); + + if ( tracecw.fraction < 1.0 ) + { + tracecw = self.Trace(FallbackAngle, _testDistance, "CircleStrafe Avoid Trace"); + + if ( tracecw.fraction < 1.0 ) + { + tracecw = self.Trace(EmergencyAngle, _testDistance, "CircleStrafe Avoid Trace"); + + if ( tracecw.fraction < 1.0 ) + { + _failed = true; + return; + } + else + { + _holdAngles[YAW] += FallbackAngle; + return; + } + } + else + { + _holdAngles[YAW] += FallbackAngle; + return; + } + } + else + { + _holdAngles[YAW] += StrafeAngle; + } + + +} + +//-------------------------------------------------------------- +// Name: End() +// Class: CircleStrafeEntity +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void CircleStrafeEntity::End ( Actor &self ) + { + _wander.End( self ); + } + + + +//-------------------------------------------------------------- +// Name: _getStrafeTarget() +// Class: CircleStrafeEntity +// +// Description: Sets the Strafe Target +// +// Parameters: Actor &self +// const str &target +// +// Returns: Entity *ent +//-------------------------------------------------------------- +Entity* CircleStrafeEntity::_getStrafeTarget( Actor &self, const str &target ) + { + Entity *ent = NULL; + + if ( target == "player" ) + ent = GetPlayer( 0 ); + else if ( target == "enemy" ) + ent = self.enemyManager->GetCurrentEnemy(); + + return ent; + } + + +//-------------------------------------------------------------- +// Name: _checkParameters() +// Class: CircleStrafeEntity +// +// Description: Checks the Parameters for proper number and type +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void CircleStrafeEntity::_checkParameters( Event *ev ) + { + if ( ev->NumArgs() < 4 ) + gi.Error( ERR_DROP, "CircleStrafeEntity::_checkParameters -- Wrong Parameter Count"); + + } + + + +//-------------------------------------------------------------- +// Name: _checkIfStuck() +// Class: CircleStrafeEntity +// +// Description: Checks if the actor is stuck in position +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +qboolean CircleStrafeEntity::_checkIfStuck(Actor &self) + { + // See if we're stuck + Vector checkDistance = _lastPosition - self.origin; + if ( checkDistance.length() < 1.5f ) + _moveAttempts++; + else + { + _moveAttempts = 0; + _lastPosition = self.origin; + } + + if ( _moveAttempts >= 3 ) + return true; + else + return false; + } + + +//============================================================================== +// FollowInFormation +//============================================================================== + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, FollowInFormation, NULL ) + { + { &EV_Behavior_Args, &FollowInFormation::SetArgs }, + { NULL, NULL } + }; + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: FollowInFormation +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::SetArgs ( Event *ev) +{ + _anim = ev->GetString ( 1 ); + + if ( ev->NumArgs() > 1 ) + _emergencyDistance = ev->GetFloat( 2 ); + else + _emergencyDistance = 0; + + if ( ev->NumArgs() > 2 ) + _catchupSpeed = ev->GetFloat( 3 ); + else + _catchupSpeed = 10.0f; +} + +//-------------------------------------------------------------- +// Name: Begin() +// Class: FollowInFormation +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::Begin( Actor &self ) +{ + + // Set appropriate flags + self.SetActorFlag( ACTOR_FLAG_FOLLOWING_IN_FORMATION , true ); + + _selectedFollowTarget = false; + + _followDist = self.followTarget.maxRangeIdle; + _followDistMin = _followDist * 0.75f; + + if ( _followDistMin < 96.0f ) + _followDistMin = 96.0f; + + // If we don't have a follow target, then we are going to assume + // we're following the player + SetDefaultFollowTarget( self ); + + + _state = FOLLOW_TARGET_STATE_SELECT_STATE; + + + // Setup failure handlers + _nextFollowAttemptTime = 0.0f; + _nextTargetCheckTime = 0.0f; + _endHold = 0.0f; + _followFailureTime = 0.0f; + _setFollowFailureTime = false; + _attemptedWarpToPath = false; + _codeDriven = false; + _oldForwardSpeed = self.movementSubsystem->getForwardSpeed(); + _oldTurnSpeed = self.movementSubsystem->getTurnSpeed(); + +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: FollowInFormation +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t FollowInFormation::Evaluate ( Actor &self ) +{ + + switch ( _state ) + { + case FOLLOW_TARGET_STATE_SELECT_STATE: + selectState( self ); + break; + + case FOLLOW_TARGET_STATE_FOLLOW_TARGET: + follow( self ); + break; + + case FOLLOW_TARGET_STATE_FOLLOW_HOLD: + hold ( self ); + break; + + case FOLLOW_TARGET_STATE_FOLLOW_WARP_TO_NEAREST_PATHNODE: + warpToPathNode( self ); + break; + + case FOLLOW_TARGET_STATE_FOLLOW_WARP_TO_FOLLOW_TARGET: + warpToTarget( self ); + break; + + case FOLLOW_TARGET_STATE_FIND_FOLLOW_TARGET: + findFollowTarget( self ); + break; + + case FOLLOW_TARGET_STATE_FOLLOW_FAILED: + return BEHAVIOR_FAILED; + break; + + case FOLLOW_TARGET_STATE_FOLLOW_NO_TARGET: + return BEHAVIOR_FAILED; + + case FOLLOW_TARGET_STATE_FOLLOW_SUCCESS: + return BEHAVIOR_SUCCESS; + break; + } + + return BEHAVIOR_EVALUATING; +} + + +//-------------------------------------------------------------- +// Name: End() +// Class: FollowInFormation +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::End ( Actor &self ) +{ + self.SetActorFlag( ACTOR_FLAG_FOLLOWING_IN_FORMATION , false ); + self.movementSubsystem->setForwardSpeed( _oldForwardSpeed ); + self.movementSubsystem->setTurnSpeed( _oldTurnSpeed ); +} + + +//-------------------------------------------------------------- +// Name: SetDefaultFollowTarget() +// Class: FollowInFormation +// +// Description: Checks if we have a specified follow target, if not +// we default it to the player +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::SetDefaultFollowTarget( Actor &self ) +{ + if ( !self.followTarget.specifiedFollowTarget ) + { + Player *player; + player = GetPlayer( 0 ); + + if ( !player ) + { + _state = FOLLOW_TARGET_STATE_FOLLOW_NO_TARGET; + return; + } + + self.followTarget.specifiedFollowTarget = player; + } +} + + +//-------------------------------------------------------------- +// Name: findFollowTarget() +// Class: FollowInFormation +// +// Description: Iterates through everyone in the actor's group +// and, based on distance, determines who it's +// current FollowTarget is. +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::findFollowTarget( Actor &self ) +{ + Vector selfToFollowTarget; + Vector actToFollowTarget; + Vector selfToAct; + + float actDistToFollowTarget; + float distToFollowTarget; + float distToAct; + float bestDist; + + Actor *act; + + + if ( level.time < _nextTargetCheckTime || !self.followTarget.specifiedFollowTarget ) + return; + + + // We default to our specifed target + self.followTarget.currentFollowTarget = self.followTarget.specifiedFollowTarget; + + selfToFollowTarget = self.followTarget.currentFollowTarget->origin - self.origin; + + //Get our distance; selfToFollowTarget = self.followTarget.specifiedFollowTarget->origin - self.origin; + distToFollowTarget = selfToFollowTarget.length(); + bestDist = 9999999.9f; + + + // Here we iterate through the active actors looking for groupmembers who have the same + // follow target AND are currently following in formation. We find the group member who + // is closer to the specified target AND closest to this actor and we follow it. This + // will allow a nice single-file formation + for( int i = 1; i <= ActiveList.NumObjects(); i++ ) + { + act = ActiveList.ObjectAt( i ); + if ( act && act->entnum != self.entnum && act->GetGroupID() == self.GetGroupID() ) + { + if ( !act->GetActorFlag(ACTOR_FLAG_FOLLOWING_IN_FORMATION) || + act->followTarget.specifiedFollowTarget != self.followTarget.specifiedFollowTarget ) + continue; + + actToFollowTarget = self.followTarget.currentFollowTarget->origin - act->origin; + actDistToFollowTarget = actToFollowTarget.length(); + + if ( actDistToFollowTarget < distToFollowTarget && actDistToFollowTarget < bestDist ) + { + selfToAct = act->origin - self.origin; + distToAct = selfToAct.length(); + if ( distToAct < bestDist ) + { + // We need to maintain who is closest to us, here. + self.followTarget.currentFollowTarget = act; + bestDist = distToAct; + } + } + + } + + } + + _selectedFollowTarget = true; + + // Set up our component behavior + _followEntity.SetAnim( _anim ); + _followEntity.SetEntity( self, self.followTarget.currentFollowTarget ); + + if ( self.followTarget.currentFollowTarget != self.followTarget.specifiedFollowTarget ) + _followEntity.SetDistance( _followDistMin ); + else + _followEntity.SetDistance( _followDist ); + + _followEntity.Begin( self ); + _state = FOLLOW_TARGET_STATE_SELECT_STATE; + + _nextTargetCheckTime = level.time + G_Random(); +} + + +//-------------------------------------------------------------- +// Name: selectState() +// Class: FollowInFormation +// +// Description: Sets the internal state of this behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::selectState( Actor &self ) +{ + if ( !_selectedFollowTarget ) + { + _state = FOLLOW_TARGET_STATE_FIND_FOLLOW_TARGET; + return; + } + + if ( !self.WithinDistanceXY( self.followTarget.currentFollowTarget , _followDist ) ) + { + setupFollow( self ); + _state = FOLLOW_TARGET_STATE_FOLLOW_TARGET; + return; + } + + _state = FOLLOW_TARGET_STATE_FOLLOW_HOLD; + setupHold( self ); +} + + +//-------------------------------------------------------------- +// Name: follow() +// Class: FollowInFormation +// +// Description: Evaluates our GotoEntity Component Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::follow( Actor &self ) +{ + findFollowTarget( self ); + BehaviorReturnCode_t gotoEntityResult; + + //checkSpeed( self ); + + if ( !self.followTarget.currentFollowTarget ) + return; + + if ( self.WithinDistanceXY(self.followTarget.currentFollowTarget , _followDist + 96.0f ) ) + { + _followEntity.SetAnim( "walk" ); + } + + + gotoEntityResult = _followEntity.Evaluate( self ); + + + if ( gotoEntityResult == BEHAVIOR_SUCCESS ) + { + _followEntity.End( self ); + _nextFollowAttemptTime = 0.0f; + _state = FOLLOW_TARGET_STATE_SELECT_STATE; + return; + } + + if ( gotoEntityResult != BEHAVIOR_EVALUATING ) + { + // Comment this line out if you need to disable + // fail safes for debugging purposes + //followFailed( self ); + _state = FOLLOW_TARGET_STATE_FOLLOW_FAILED; + return; + } + +} + + +//-------------------------------------------------------------- +// Name: followFailed() +// Class: FollowInFormation +// +// Description: Failure Handler for _follow +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::followFailed( Actor &self ) +{ + if ( !_setFollowFailureTime ) + { + _followFailureTime = level.time + 1.5; + _setFollowFailureTime = true; + return; + } + + if ( level.time >= _followFailureTime ) + { + _followFailureTime = 0.0f; + _setFollowFailureTime = false; + + if ( !_attemptedWarpToPath ) + { + _state = FOLLOW_TARGET_STATE_FOLLOW_WARP_TO_NEAREST_PATHNODE; + setupWarpToPathNode( self ); + return; + } + else + { + _state = FOLLOW_TARGET_STATE_FOLLOW_WARP_TO_FOLLOW_TARGET; + setupWarpToTarget( self ); + return; + } + + } + else + _state = FOLLOW_TARGET_STATE_SELECT_STATE; + + _nextFollowAttemptTime = level.time + G_Random( 1.0f ); +} + + +//-------------------------------------------------------------- +// Name: setupWarpToPathNode() +// Class: FollowInFormation +// +// Description: Sets up our WarpToPosition Component to warp us +// to the nearest pathnode +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::setupWarpToPathNode( Actor &self ) +{ + // Find the path node nearest to us + PathNode *goalNode = thePathManager.NearestNode( self.origin , NULL , true , false ); + if ( !goalNode ) + { + _state = FOLLOW_TARGET_STATE_FOLLOW_WARP_TO_FOLLOW_TARGET; + setupWarpToTarget( self ); + return; + } + + _warpToPosition.SetPosition( goalNode->origin ); + _warpToPosition.Begin( self ); +} + + +//-------------------------------------------------------------- +// Name: warpToPathNode() +// Class: FollowInFormation +// +// Description: Evaluates our WarpToPosition Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::warpToPathNode( Actor &self ) +{ + BehaviorReturnCode_t result; + + _attemptedWarpToPath = true; + + result = _warpToPosition.Evaluate( self ); + if ( result != BEHAVIOR_EVALUATING ) + { + setupFollow( self ); + _state = FOLLOW_TARGET_STATE_FOLLOW_TARGET; + } + +} + + +//-------------------------------------------------------------- +// Name: setupWarpToTarget() +// Class: FollowInFormation +// +// Description: Sets up our WarpToEntity Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::setupWarpToTarget( Actor &self ) +{ + _warpToEntity.SetEntity( self.followTarget.currentFollowTarget ); + _warpToEntity.Begin( self ); +} + + +//-------------------------------------------------------------- +// Name: warpToTarget() +// Class: FollowInFormation +// +// Description: Evaluates our WarpToEntity Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::warpToTarget( Actor &self ) +{ + BehaviorReturnCode_t result; + + result = _warpToEntity.Evaluate( self ); + if ( result != BEHAVIOR_EVALUATING ) + { + setupFollow( self ); + _state = FOLLOW_TARGET_STATE_FOLLOW_TARGET; + } + +} + + +//-------------------------------------------------------------- +// Name: setupFollow() +// Class: FollowInFormation +// +// Description: Calls Begin() on our GotoEntity Component Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::setupFollow( Actor &self ) +{ + _followEntity.Begin( self ); +} + + +//-------------------------------------------------------------- +// Name: setupHold() +// Class: FollowInFormation +// +// Description: Sets up back to the "idle" animation so that we +// don't go running into walls +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::setupHold( Actor &self ) +{ + _endHold = level.time + .75; + self.SetAnim( "idle" ); +} + + +//-------------------------------------------------------------- +// Name: hold() +// Class: FollowInFormation +// +// Description: Keeps us stationary until our current follow +// target moves out of range +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowInFormation::hold( Actor &self ) +{ + if ( !self.followTarget.currentFollowTarget ) + { + _state = FOLLOW_TARGET_STATE_FOLLOW_NO_TARGET; + return; + } + + findFollowTarget( self ); + + if ( level.time > _endHold ) + { + _state = FOLLOW_TARGET_STATE_SELECT_STATE; + return; + } + +} + + +void FollowInFormation::checkSpeed( Actor &self ) +{ + if ( _emergencyDistance < 1 ) + return; + + if ( !self.WithinDistanceXY( self.followTarget.currentFollowTarget , _emergencyDistance ) ) + { + _followEntity.SetAnim( "run_codedriven" ); + self.movementSubsystem->setForwardSpeed( _catchupSpeed ); + self.movementSubsystem->setTurnSpeed( 360.0f ); + _codeDriven = true; + } + else if ( _codeDriven ) + { + self.movementSubsystem->setForwardSpeed( 0.0 ); //E3 2002 Hack -- Needs to be_oldForwardSpeed, but it's bugged right now + self.movementSubsystem->setTurnSpeed( 25.0 ); //E3 2002 Hack -- Needs to be _oldTurnspeed, but it's bugged right now + _codeDriven = false; + } +} + + + +//============================================================================== +// GroupFollow +//============================================================================== +const float GroupFollow::_minRangeMultiplier = 0.9f; +const float GroupFollow::_maxRangeMultiplier = 1.1f; + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, GroupFollow, NULL ) +{ + { &EV_Behavior_Args, &GroupFollow::SetArgs }, + { &EV_Behavior_AnimDone, &GroupFollow::AnimDone }, + { NULL, NULL } +}; + +//-------------------------------------------------------------- +float GetAnimationRate( Entity &entity, const int animation ) +{ + const float time = gi.Anim_Time( entity.edict->s.modelindex, animation ); + assert( time != 0.0f ); + + if ( time == 0.0f ) + { + gi.DPrintf( "Invalid animation for %d\n", entity.entnum ); + return 0.0f; + } + + Vector myNewAnimationTotalDelta; + gi.Anim_Delta( entity.edict->s.modelindex, animation, myNewAnimationTotalDelta ); + + return myNewAnimationTotalDelta.length() / time; +} + +//-------------------------------------------------------------- +float GetAnimationRate( Entity &entity, const str &animationName ) +{ + const int animation = gi.Anim_Random( entity.edict->s.modelindex, animationName ); + + return GetAnimationRate( entity, animation ); +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: GroupFollow +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void GroupFollow::SetArgs ( Event *ev) +{ + _stopDistance = ev->GetFloat( 1 ); + _paceDistance = ev->GetFloat( 2 ); + _idleAnimation = "idle"; + _paceAnimation = "walk"; + _closeAnimation = "run"; + if (ev->NumArgs() > 2 ) + { + _idleAnimation = ev->GetString( 3 ); + if (ev->NumArgs() > 3 ) + { + _paceAnimation = ev->GetString( 4 ); + if (ev->NumArgs() > 4 ) + { + _closeAnimation = ev->GetString( 5 ); + } + } + } +} + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: GroupFollow +// +// Description: Resets the animation rate each time the previous +// animation cycle finishes +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void GroupFollow::AnimDone( Event *ev ) +{ + _animationRateNeedsUpdate = true; +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: GroupFollow +// +// Description: Sets the arguments of the behavior +// +// Parameters: const str &anim, EntityPtr specifiedTarget, +// const float stopDistance, const float paceDistance +// +// Returns: None +//-------------------------------------------------------------- +void GroupFollow::SetArgs ( const float stopDistance, const float paceDistance ) +{ + _stopDistance = stopDistance; + _paceDistance = paceDistance; + +} + +//-------------------------------------------------------------- +// Name: Begin() +// Class: GroupFollow +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GroupFollow::Begin( Actor &self ) +{ + _nextFindFollowTime = 0.0f; + _nextPathLenCheckTime = 0.0f; + _nextPathLenCheckTime2 = 0.0f; + + if ( !self.followTarget.specifiedFollowTarget ) + { + self.followTarget.specifiedFollowTarget = GetPlayer( 0 ); + assert( self.followTarget.specifiedFollowTarget ); + } + + self.followTarget.currentFollowTarget = self.followTarget.specifiedFollowTarget; + + // Set appropriate flags + self.SetActorFlag( ACTOR_FLAG_FOLLOWING_IN_FORMATION , true ); + _animationRateNeedsUpdate = false; + _follow.Begin( self ); + GotoHoldState( self ); + + //Setup Torso Anim if appropriate + _torsoAnimation = self.combatSubsystem->GetAnimForMyWeapon( "Idle" ); + if ( _torsoAnimation.length() ) + { + self.SetAnim( _torsoAnimation, NULL , torso ); + } + + // Setup failure handlers + _endHold = 0.0f; + _oldForwardSpeed = self.movementSubsystem->getForwardSpeed(); + +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: GroupFollow +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t GroupFollow::Evaluate ( Actor &self ) +{ + BehaviorReturnCode_t returnCode = BEHAVIOR_INVALID; + + FindFollowTarget( self ); + + switch ( _state ) + { + case CLOSE_WITH_TARGET: + returnCode = CloseWithTarget( self ); + break; + + case PACE_TARGET: + returnCode = PaceTarget( self ); + break; + + case HOLD: + returnCode = Hold( self ); + break; + + case WANDER: + returnCode = Wander(self); + break; + + default: + assert( false ); // the default case is not valid + break; + } + + //_follow.SetEntity( self, self.followTarget.currentFollowTarget ); + + return returnCode; +} + + +//-------------------------------------------------------------- +// Name: End() +// Class: GroupFollow +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GroupFollow::End ( Actor &self ) +{ + self.SetActorFlag( ACTOR_FLAG_FOLLOWING_IN_FORMATION , false ); + self.movementSubsystem->setForwardSpeed( _oldForwardSpeed ); +} + + +//-------------------------------------------------------------- +// Name: findFollowTarget() +// Class: GroupFollow +// +// Description: Iterates through everyone in the actor's group +// and, based on distance, determines who it's +// current FollowTarget is. +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GroupFollow::FindFollowTarget( Actor &self ) +{ + + // FindFollowTarget is expensive, we're putting a timer on it to keep it throttled + if ( level.time <= _nextFindFollowTime ) return BEHAVIOR_EVALUATING; + + _nextFindFollowTime = level.time + (G_Random() + 0.25 ); + + // Here we iterate through the active actors looking for groupmembers who have the same + // follow target AND are currently following in formation. We find the group member who + // is closer to the specified target AND closest to this actor and we follow it. This + // will allow a nice single-file formation + + FindMovementPath find; + Path *path; + float distanceFromCurrentActorToTarget; + float distanceFromGroupMemberToTarget; + + // Set up our pathing heuristics + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + // First Check how far away we are from our specified followtarget, if we're pretty far away, chances are + // something is all kinds of screwed up, so we're just going to follow our specified target for a while + path = find.FindPath( self.followTarget.specifiedFollowTarget->origin, self.origin ); + + if ( path ) + { + distanceFromCurrentActorToTarget = path->Length(); + delete path; + path = NULL; + } + else + { + distanceFromCurrentActorToTarget = Vector::Distance( self.followTarget.specifiedFollowTarget->origin, self.origin ); + } + + Entity* currentEnemy = NULL; + float maxDistance; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( currentEnemy ) + { + maxDistance = self.followTarget.maxRangeCombat; + } + else + { + maxDistance = self.followTarget.maxRangeIdle; + } + + //If our specified target is more than 3 times farther away than our maximum follow range + //(based on our combat situation ) then we forget about trying to find other targets and run + //for our "master" -- This should help prevent wacky circular screwups.where: + //A is Following B who is Following C who is Following A + if ( distanceFromCurrentActorToTarget > ( maxDistance * 3 ) ) + { + _follow.SetEntity( self, self.followTarget.specifiedFollowTarget ); + return BEHAVIOR_EVALUATING; + } + + + // Create a list of group members sorted by distance to target + Container< Actor * > groupList; + for( int i = 1; i <= ActiveList.NumObjects(); i++ ) + { + Actor ¤tActor = *ActiveList.ObjectAt( i ); + if ( currentActor.GetGroupID() == self.GetGroupID() ) + { + if ( + ( currentActor.GetActorFlag(ACTOR_FLAG_FOLLOWING_IN_FORMATION ) ) && + ( currentActor.followTarget.specifiedFollowTarget == self.followTarget.specifiedFollowTarget ) + ) + { + path = NULL; + if ( level.time >= _nextPathLenCheckTime ) + { + if ( sv_traceinfo->integer ) + gi.WDPrintf( "Pathing To FollowTarget1" ); + + path = find.FindPath( self.followTarget.specifiedFollowTarget->origin, currentActor.origin ); + _nextPathLenCheckTime = level.time + (G_Random() + 1.0); + } + + if ( path ) + { + distanceFromCurrentActorToTarget = path->Length(); + delete path; + path = NULL; + } + else + //Was DistanceXY + distanceFromCurrentActorToTarget = Vector::Distance( self.followTarget.specifiedFollowTarget->origin, currentActor.origin ); + + int j; + for ( j = 1; j <= groupList.NumObjects(); j++ ) + { + path = NULL; + const Actor ¤tGroupMember = *groupList.ObjectAt( j ); + if ( level.time >= _nextPathLenCheckTime2 ) + { + if ( sv_traceinfo->integer ) + gi.WDPrintf( "Pathing To FollowTarget2" ); + + path = find.FindPath( self.followTarget.specifiedFollowTarget->origin, currentGroupMember.origin ); + _nextPathLenCheckTime2 = level.time + (G_Random() + 1.0); + } + + if ( path ) + { + distanceFromGroupMemberToTarget = path->Length(); + delete path; + path = NULL; + } + else + //Was DistanceXY + distanceFromGroupMemberToTarget = Vector::Distance( self.followTarget.specifiedFollowTarget->origin, currentGroupMember.origin ); + + if ( distanceFromCurrentActorToTarget < distanceFromGroupMemberToTarget ) + { + break; + } + } + if ( j > groupList.NumObjects() ) + { + groupList.AddObject( ¤tActor ); + } + else + { + groupList.InsertObjectAt( j, ¤tActor ); + } + } + } + } + + // Make the next closest group member our current target, + // if we are closest then set our current target to our + // specified target + int j; + for ( j = 1; j <= groupList.NumObjects(); j++ ) + { + if ( groupList.ObjectAt( j )->entnum == self.entnum ) + { + break; + } + } + + Entity *currentFollowEntity; + currentFollowEntity = _follow.GetEntity(); + if ( !currentFollowEntity ) + { + _follow.SetEntity( self, self.followTarget.currentFollowTarget ); + } + + if ( j == 1 ) + { + if ( self.followTarget.currentFollowTarget != self.followTarget.specifiedFollowTarget ) + { + self.followTarget.currentFollowTarget = self.followTarget.specifiedFollowTarget; + + currentFollowEntity = _follow.GetEntity(); + + if ( !currentFollowEntity ) + { + _follow.SetEntity( self, self.followTarget.currentFollowTarget ); + } + else if ( currentFollowEntity != self.followTarget.currentFollowTarget ) + { + _follow.SetEntity( self, self.followTarget.currentFollowTarget ); + } + + } + } + else + { + if ( self.followTarget.currentFollowTarget != groupList.ObjectAt( j - 1 ) ) + { + self.followTarget.currentFollowTarget = groupList.ObjectAt( j - 1 ); + + currentFollowEntity = _follow.GetEntity(); + + if ( !currentFollowEntity ) + { + _follow.SetEntity( self, self.followTarget.currentFollowTarget ); + } + else if ( currentFollowEntity != self.followTarget.currentFollowTarget ) + { + _follow.SetEntity( self, self.followTarget.currentFollowTarget ); + } + } + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: hold() +// Class: GroupFollow +// +// Description: Keeps us stationary until our current follow +// target moves out of range +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GroupFollow::Hold( Actor &self ) +{ + assert( _stopDistance * _maxRangeMultiplier < _paceDistance * _minRangeMultiplier ); + + //float distanceToTarget = Vector::DistanceXY( self.origin, self.followTarget.currentFollowTarget->origin ); + float distanceToTarget = Vector::Distance( self.origin, self.followTarget.currentFollowTarget->origin ); + + if ( level.time > _endHold ) + { + if ( distanceToTarget > _stopDistance * _maxRangeMultiplier) + { + GotoPaceTargetState( self ); + } + else + { + return BEHAVIOR_SUCCESS; + } + } + return BEHAVIOR_EVALUATING; +} + + +//-------------------------------------------------------------- +// Name: PaceTarget() +// Class: GroupFollow +// +// Description: Handles the state where the behavior wants to +// maintain distance with a moving target or slowly +// close with a stationary target +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GroupFollow::PaceTarget( Actor &self ) +{ + + if ( _animationRateNeedsUpdate ) + { + const float animationRate = ComputeAnimationRate( self, _paceAnimation, ComputePaceAnimationRateMultiplier( self ) ); + if ( !fSmallEnough( animationRate, fEpsilon() ) ) + { + if ( !fCloseEnough( animationRate, self.edict->s.animationRate, 0.5f) ) + { + self.SetAnim( _paceAnimation, EV_Actor_NotifyBehavior, legs, animationRate ); + _follow.Begin( self ); + } + } + else + { + GotoHoldState( self ); + return BEHAVIOR_EVALUATING; + } + _animationRateNeedsUpdate = false; + } + + + BehaviorReturnCode_t gotoEntityResult = _follow.Evaluate( self ); + + if ( gotoEntityResult != BEHAVIOR_EVALUATING ) + { + _follow.End( self ); + + if ( gotoEntityResult == BEHAVIOR_FAILED ) + GotoWanderState(self); + else + GotoHoldState( self ); + } + else + { + assert( _stopDistance * _maxRangeMultiplier < _paceDistance * _minRangeMultiplier ); + + //float distanceToTarget = Vector::DistanceXY( self.origin, self.followTarget.currentFollowTarget->origin ); + float distanceToTarget = Vector::Distance( self.origin, self.followTarget.currentFollowTarget->origin ); + + if ( distanceToTarget > _paceDistance * _maxRangeMultiplier ) + { + GotoCloseWithTargetState( self ); + } + else if ( distanceToTarget < _stopDistance * _minRangeMultiplier ) + { + GotoHoldState( self ); + } + } + return BEHAVIOR_EVALUATING; +} + + +//-------------------------------------------------------------- +// Name: CloseWithTarget() +// Class: GroupFollow +// +// Description: Handles the state where the behavior wants to +// rapidly close with its target +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GroupFollow::CloseWithTarget( Actor &self ) +{ + if ( _animationRateNeedsUpdate ) + { + const float animationRate = ComputeAnimationRate( self, _closeAnimation, 1.75f ); + if ( !fCloseEnough( animationRate, self.edict->s.animationRate, 0.5f) ) + { + self.SetAnim( _closeAnimation, EV_Actor_NotifyBehavior, legs, animationRate ); + _follow.Begin( self ); + _animationRateNeedsUpdate = false; + } + } + + BehaviorReturnCode_t gotoEntityResult = _follow.Evaluate( self ); + + /* + if ( gotoEntityResult != BEHAVIOR_SUCCESS && gotoEntityResult != BEHAVIOR_EVALUATING ) + { + _follow.End( self ); + //GotoHoldState( self ); + GotoWanderState(self); + } + + if ( gotoEntityResult == BEHAVIOR_SUCCESS ) + { + _follow.End( self ); + GotoHoldState( self ); + } + */ + + if ( gotoEntityResult != BEHAVIOR_EVALUATING ) + { + _follow.End( self ); + GotoHoldState( self ); + } + + + assert( _stopDistance * _maxRangeMultiplier < _paceDistance * _minRangeMultiplier ); + + float distanceToTarget = Vector::DistanceXY( self.origin, self.followTarget.currentFollowTarget->origin ); + + if ( distanceToTarget < _paceDistance * _minRangeMultiplier && self.sensoryPerception->CanSeeEntity( &self,self.followTarget.currentFollowTarget , false , false ) ) + { + GotoPaceTargetState( self ); + } + + return BEHAVIOR_EVALUATING; +} + + +//-------------------------------------------------------------- +// Name: GotoHoldState() +// Class: GroupFollow +// +// Description: Puts the behavior in the "Hold" state +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GroupFollow::GotoHoldState( Actor &self ) +{ + _follow.End( self ); + _state = HOLD; + _endHold = level.time + 0.75f; + self.SetAnim( _idleAnimation, EV_Actor_NotifyBehavior, legs, 1.0f ); +} + +float GroupFollow::ComputePaceAnimationRateMultiplier( Actor &self ) +{ + const float distanceToTarget = Vector::DistanceXY( self.origin, self.followTarget.currentFollowTarget->origin ); + + float animationRateMultiplier = 1.0f; + + if ( distanceToTarget < _stopDistance ) + { + animationRateMultiplier = 0.0f; + } + else if ( distanceToTarget < _paceDistance ) + { + animationRateMultiplier = 0.0f + ( ( distanceToTarget - _stopDistance) / ( _paceDistance - _stopDistance) ) * 1.0f; + } + + return animationRateMultiplier; +} + + +//-------------------------------------------------------------- +// Name: GotoPaceTargetState() +// Class: GroupFollow +// +// Description: Puts the behavior in the "PaceTarget" state +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GroupFollow::GotoPaceTargetState( Actor &self ) +{ + + float animationRateMultiplier = ComputePaceAnimationRateMultiplier( self ); + + _follow.End( self ); + _state = PACE_TARGET; + + const float animationRate = ComputeAnimationRate( self, _paceAnimation, animationRateMultiplier ); + + if ( !fSmallEnough( animationRate, fEpsilon() ) ) + { + self.SetAnim( _paceAnimation, EV_Actor_NotifyBehavior, legs, animationRate ); + _follow.Begin( self ); + } + else + { + GotoHoldState( self ); + } + +} + +float GroupFollow::ComputeAnimationRate( Actor &self, const str &animationName, const float scale ) +{ + const float myNewAnimationSpeed = GetAnimationRate( self, animationName); + + if ( !fSmallEnough( myNewAnimationSpeed, fEpsilon() ) ) + { + const float targetAnimationSpeed = GetAnimationRate( *self.followTarget.currentFollowTarget, self.followTarget.currentFollowTarget->CurrentAnim( legs ) ); + if ( !fSmallEnough( targetAnimationSpeed, fEpsilon() ) ) + { + const float animationRate = scale * targetAnimationSpeed / myNewAnimationSpeed; + if ( animationRate > 1.0f ) + { + return animationRate; + } + } + } + return 1.0f; +} +//-------------------------------------------------------------- +// Name: GotoCloseWithTargetState() +// Class: GroupFollow +// +// Description: Puts the behavior in the "CloseWithTarget" state +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GroupFollow::GotoCloseWithTargetState( Actor &self ) +{ + _follow.End( self ); + _state = CLOSE_WITH_TARGET; + + const float animationRate = ComputeAnimationRate( self, _closeAnimation, 1.1f ); + self.SetAnim( _closeAnimation, EV_Actor_NotifyBehavior, legs, animationRate ); + _follow.Begin( self ); +} + + +void GroupFollow::GotoWanderState( Actor &self ) +{ + _wander.SetAnim( "walk" ); + _wander.SetMinDistance( 32 ); + _wander.SetDistance( 48 ); + _wander.Begin(self); + _state = WANDER; +} + +BehaviorReturnCode_t GroupFollow::Wander( Actor &self ) +{ + BehaviorReturnCode_t result; + result = _wander.Evaluate( self ); + + if ( result == BEHAVIOR_FAILED ) + { + GotoHoldState(self); + } + + if ( result == BEHAVIOR_SUCCESS ) + { + GotoCloseWithTargetState(self); + } + + return BEHAVIOR_EVALUATING; +} +//============================================================================== +// MoveToDistanceFromEnemy +//============================================================================== + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, MoveToDistanceFromEnemy, NULL ) + { + { &EV_Behavior_Args, &MoveToDistanceFromEnemy::SetArgs }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: MoveToDistanceFromEnemy +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void MoveToDistanceFromEnemy::SetArgs ( Event *ev) + { + _checkParameters(ev); + + _anim = ev->GetString( 1 ); + _distance = ev->GetFloat( 2 ); + } + + + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: MoveToDistanceFromEnemy +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +// +//-------------------------------------------------------------- +void MoveToDistanceFromEnemy::Begin( Actor &self ) + { + movegoal = NULL; + _state = MOVE_TO_DISTANCE_STATE_SEARCHING_FOR_NODE; + + //self.SetAnim( _anim ); + + Vector dir; + dir = self.movementSubsystem->getAnimDir(); + dir = dir.toAngles(); + self.setAngles( dir ); + + self.testing = true; + self.SetActorFlag(ACTOR_FLAG_RETREATING , true ); + + _away = self.enemyManager->GetAwayFromEnemies(); + + _away = _away * _distance; + _away = _away + self.origin; + + } + + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: MoveToDistanceFromEnemy +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +// +//-------------------------------------------------------------- +BehaviorReturnCode_t MoveToDistanceFromEnemy::Evaluate ( Actor &self ) + { + + Entity *currentEnemy = NULL; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + return BEHAVIOR_FAILED; + + Vector startPos; + Vector endPos; + startPos = self.origin; + endPos = _away; + + startPos.z += 25; + endPos.z += 25; + + //G_DebugLine( startPos, endPos , 0.0f, 1.0f, 0.0f, 1.0 ); + + + if ( !movegoal ) + _state = MOVE_TO_DISTANCE_STATE_SEARCHING_FOR_NODE; + + switch( _state ) + { + case MOVE_TO_DISTANCE_STATE_SEARCHING_FOR_NODE : + // Checking for nodes + + /* + _away = self.enemyManager->GetAwayFromEnemies(); + _away = _away * _distance; + _away = _away + self.origin; + */ + + _chase.Begin( self ); + movegoal = _FindNode( self ); + + if ( !movegoal ) + return BEHAVIOR_SUCCESS; + + // Found node, going to it + self.SetAnim( _anim ); + _state = MOVE_TO_DISTANCE_STATE_FOUND_NODE; + _nextsearch = level.time + 1.25f; + + // lint -fallthrough + case MOVE_TO_DISTANCE_STATE_FOUND_NODE : + Vector distToGoal; + distToGoal = movegoal->origin - _away; + float dist; + dist = distToGoal.length(); + + + if ( dist >= 400 ) + { + return BEHAVIOR_FAILED; + } + + + + if ( _chase.Evaluate( self ) == Steering::EVALUATING ) + { + if ( _nextsearch < level.time ) + _state = MOVE_TO_DISTANCE_STATE_SEARCHING_FOR_NODE; + + return BEHAVIOR_EVALUATING; + } + + // Reached node + _chase.End( self ); + self.SetAnim( "idle" ); + return BEHAVIOR_SUCCESS; + + break; + } + + return BEHAVIOR_EVALUATING; + + } + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: MoveToDistanceFromEnemy +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +// +//-------------------------------------------------------------- +void MoveToDistanceFromEnemy::End ( Actor &self ) + { + self.testing = false; + self.SetActorFlag(ACTOR_FLAG_RETREATING , false ); + _chase.End( self ); + } + + + +//-------------------------------------------------------------- +// +// Name: _FindNode() +// Class: MoveToDistanceFromEnemy +// +// Description: Gets an appropriate pathnode, flagged as AI_ACTION +// +// Parameters: Actor &self +// +// Returns: PathNode* +// +//-------------------------------------------------------------- +PathNode *MoveToDistanceFromEnemy::_FindNode( Actor &self ) + { + int i; + PathNode *bestnode; + PathNode *node; + FindCoverPath find; + Vector delta; + Vector pos; + + pos = _away; + bestnode = NULL; + node = NULL; + + float bestdist; + float dist; + + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + return NULL; + + bestdist = 9999999999.9f; + for( i = 0; i <= thePathManager.NumNodes(); i++ ) + { + node = thePathManager.GetNode( i ); + + if ( node && (node->occupiedTime <= level.time) ) + { + // Get ourselves a good one + delta = node->origin - pos; + dist = delta.length(); + + if ( dist < bestdist ) + { + bestnode = node; + bestdist = dist; + } + + + } + } + + if ( bestnode ) + { + bestnode->occupiedTime = level.time + 1.5f; + bestnode->entnum = self.entnum; + + float radius=16.0f; + _chase.SetGoal( bestnode->origin, radius, self ); + return bestnode; + + } + + return NULL; + + } + + + +//-------------------------------------------------------------- +// +// Name: _checkParameters() +// Class: MoveToDistanceFromEnemy +// +// Description: Checks if the passed in parameters are what we are expecting +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void MoveToDistanceFromEnemy::_checkParameters( Event *ev ) + { + if ( ev->NumArgs() != 2 ) + gi.Error( ERR_DROP, "FindWork::FindWork -- Wrong Parameter Count"); + + if ( ev->IsNumericAt( 1 ) ) + gi.Error( ERR_DROP, "FindWork::_checkParameters -- Parameter Mismatch"); + + } + + + +//-------------------------------------------------------------- +// Name: SetAnim() +// Class: MoveToDistanceFromEnemy +// +// Description: Mutator +// +// Parameters: const str &anim +// +// Returns: None +//-------------------------------------------------------------- +void MoveToDistanceFromEnemy::SetAnim( const str &anim ) + { + _anim = anim; + } + + + +//-------------------------------------------------------------- +// Name: SetDistance() +// Class: MoveToDistanceFromEnemy +// +// Description: Mutator +// +// Parameters: float distance +// +// Returns: None +//-------------------------------------------------------------- +void MoveToDistanceFromEnemy::SetDistance( float distance ) + { + _distance = distance; + } + + + + + + + + + + +//============================================================================== +// Template +//============================================================================== + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, BackAwayFromEnemy, NULL ) + { + { &EV_Behavior_Args, &BackAwayFromEnemy::SetArgs }, + { &EV_Behavior_AnimDone, &BackAwayFromEnemy::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: BackAwayFromEnemy +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void BackAwayFromEnemy::SetArgs ( Event *ev) + { + _anim = ev->GetString( 1 ); + _dist = ev->GetFloat( 2 ); + _minDist = ev->GetFloat( 3 ); + } + +void BackAwayFromEnemy::AnimDone( Event *ev ) +{ +} + +//-------------------------------------------------------------- +// Name: Begin() +// Class: BackAwayFromEnemy +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void BackAwayFromEnemy::Begin( Actor &self ) + { + _state = BAFE_SELECT_STATE; + } + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: BackAwayFromEnemy +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t BackAwayFromEnemy::Evaluate ( Actor &self ) + { + switch ( _state ) + { + case BAFE_SELECT_STATE: + selectState( self ); + break; + + case BAFE_BACK_AWAY: + moveRandom( self ); + break; + + case BAFE_BACK_AWAY_FAILED: + return BEHAVIOR_FAILED; + break; + + case BAFE_BACK_AWAY_SUCCESS: + return BEHAVIOR_SUCCESS; + break; + } + + return BEHAVIOR_EVALUATING; + } + + +//-------------------------------------------------------------- +// Name: End() +// Class: BackAwayFromEnemy +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void BackAwayFromEnemy::End ( Actor &self ) +{ + self.movementSubsystem->setMovingBackwards( false ); +} + + +//-------------------------------------------------------------- +// Name: selectState() +// Class: BackAwayFromEnemy +// +// Description: Selects the state for our behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void BackAwayFromEnemy::selectState( Actor &self ) +{ + setupMoveRandom( self ); + _state = BAFE_BACK_AWAY; +} + + +//-------------------------------------------------------------- +// Name: setupMoveRandom() +// Class: BackAwayFromEnemy() +// +// Description: Sets up our _moveRandom component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void BackAwayFromEnemy::setupMoveRandom( Actor &self ) +{ + self.movementSubsystem->setMovingBackwards( true ); + _moveRandom.SetMode( MoveRandomDirection::RANDOM_MOVE_IN_BACK ); + _moveRandom.SetAnim( _anim ); + _moveRandom.SetDistance( _dist ); + _moveRandom.SetMinDistance( _minDist ); + _moveRandom.Begin( self ); +} + +//-------------------------------------------------------------- +// Name: moveRandom() +// Class: BackAwayFromEnemy() +// +// Description: Evaluates our _moveRandom component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void BackAwayFromEnemy::moveRandom( Actor &self ) +{ + BehaviorReturnCode_t result; + + result = _moveRandom.Evaluate( self ); + + if ( result == BEHAVIOR_SUCCESS ) + { + _state = BAFE_BACK_AWAY_SUCCESS; + self.movementSubsystem->setMovingBackwards( false ); + return; + } + + if ( result != BEHAVIOR_EVALUATING ) + moveRandomFailed( self ); +} + + +//-------------------------------------------------------------- +// Name: moveRandomFailed( Actor &self ) +// Class: BackAwayFromEnemy +// +// Description: Failure Handler for move Random +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void BackAwayFromEnemy::moveRandomFailed( Actor &self ) +{ + _state = BAFE_BACK_AWAY_FAILED; + self.movementSubsystem->setMovingBackwards( false ); +} + + + + + + + + + + + + + + + +//============================================================================== +// AlertIdle +//============================================================================== + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, AlertIdle, NULL ) + { + { &EV_Behavior_Args, &AlertIdle::SetArgs }, + { &EV_Behavior_AnimDone, &AlertIdle::AnimDone }, + { NULL, NULL } + }; + + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: AlertIdle +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::SetArgs ( Event *ev) + { + _followAnim = ev->GetString( 1 ); + _torsoAnim = ev->GetString( 2 ); + _baseIdleTime = ev->GetFloat( 3 ); + + if ( ev->NumArgs() > 3 ) + _emergencyDist = ev->GetFloat( 4 ); + else + _emergencyDist = 600.0f; + + if ( ev->NumArgs() > 4 ) + _followDist = ev->GetFloat( 5 ); + else + _followDist = 176.0f; + + if ( ev->NumArgs() > 5 ) + _wanderDist = ev->GetFloat( 6 ); + else + _wanderDist = 328.0f; + + if ( !_torsoAnim.length() ) + _useTorsoAnim = false; + else + _useTorsoAnim = true; + + + + } + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: AlertIdle +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::AnimDone( Event *ev ) + { + + } + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: AlertIdle +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::Begin( Actor &self ) + { + _init( self ); + } + + + +//-------------------------------------------------------------- +// Name: _init() +// Class: AlertIdle +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_init( Actor &self ) + { + // We need to make sure the actor has some form of follow target + // so we'll ask our follow component to set that up. + _setupFollow( self ); + + _nextFollowAttempt = 0.0f; + _nextWanderTime = -1.0f; + _self = &self; + _unableToFollow = false; + self.postureController->setPostureState( "STAND" , "STAND" ); + _state = ALERT_IDLE_SELECT_STATE; + } + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: AlertIdle +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t AlertIdle::Evaluate ( Actor &self ) + { + _setTorsoAnim( self ); + switch ( _state ) + { + case ALERT_IDLE_SELECT_STATE: + _selectState( self ); + break; + + case ALERT_IDLE_IN_THE_WAY: + _doGetOutOfTheWay( self ); + break; + + case ALERT_IDLE_FOLLOW: + _doFollow( self ); + break; + + case ALERT_IDLE_WANDER: + _doWander( self ); + break; + + case ALERT_IDLE_HOLD: + _hold ( self ); + break; + } + + return BEHAVIOR_EVALUATING; + } + + + +//-------------------------------------------------------------- +// Name: End() +// Class: AlertIdle +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::End ( Actor &self ) + { + self.SetActorFlag( ACTOR_FLAG_FOLLOWING_IN_FORMATION , false ); + } + + +//-------------------------------------------------------------- +// Name: _selectState() +// Class: AlertIdle +// +// Description: Selects the state for the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_selectState( Actor &self ) + { + //Entity *followTarget; + //followTarget = NULL; + + if ( level.time > _nextFollowAttempt ) + { + _setupFollow( self ); + _state = ALERT_IDLE_FOLLOW; + return; + } + + _setupHold( self ); + _state = ALERT_IDLE_HOLD; + + } + + +//-------------------------------------------------------------- +// Name: _setupGetOutOfTheWay() +// Class: AlertIdle +// +// Description: Sets up our MoveFromConeOfFire Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_setupGetOutOfTheWay( Actor &self ) + { + self.SetAnim( "idle" ); + if ( self.checkplayerranged() ) + _getOutOfTheWay.SetAnim( "run" ); + else + _getOutOfTheWay.SetAnim( "walk" ); + + _getOutOfTheWay.Begin( self ); + } + + +//-------------------------------------------------------------- +// Name: _doGetOutOfTheWay() +// Class: AlertIdle +// +// Description: Evaluates our MoveFromConeOfFire Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_doGetOutOfTheWay( Actor &self ) + { + BehaviorReturnCode_t result; + result = _getOutOfTheWay.Evaluate( self ); + + if ( result == BEHAVIOR_SUCCESS ) + { + self.ClearStateFlags(); + _getOutOfTheWay.End( self ); + _state = ALERT_IDLE_SELECT_STATE; + return; + } + + // If we got here, we caught a failure condition of some + // kind + if ( result != BEHAVIOR_EVALUATING ) + { + self.ClearStateFlags(); + _getOutOfTheWay.End( self ); + + self.SetAnim( "idle" ); + _setupGetOutOfTheWay( self ); + _state = ALERT_IDLE_SELECT_STATE; + } + + } + + +//-------------------------------------------------------------- +// Name: _setupHold() +// Class: AlertIdle +// +// Description: Set ourselves up to stand idle +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_setupHold( Actor &self ) + { + self.SetAnim( "idle" ); + _setNextWanderTime( self ); + } + + +//-------------------------------------------------------------- +// Name: _hold() +// Class: AlertIdle +// +// Description: Makes us hold in idle, unless we really need to +// move +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_hold( Actor &self ) + { + + + if ( self.checktouchedbyplayer() ) + { + _state = ALERT_IDLE_IN_THE_WAY; + _setupGetOutOfTheWay( self ); + return; + } + + if ( !self.WithinDistance( self.followTarget.specifiedFollowTarget , _followDist ) ) + { + _state = ALERT_IDLE_SELECT_STATE; + return; + } + + if ( level.time > _nextWanderTime ) + { + _setupWander( self ); + _state = ALERT_IDLE_WANDER; + return; + } + + } + + +//-------------------------------------------------------------- +// Name: _setupFollow() +// Class: AlertIdle +// +// Description: Sets up our Follow In Formation Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_setupFollow( Actor &self ) + { + _setTorsoAnim( self ); + + _nextWanderTime = -1.0f; + //_follow.SetArgs( 192.0f, 278.0f ); + _follow.SetAnim( _followAnim ); + _follow.SetEmergencyDistance( _emergencyDist ); + _follow.SetCatchupSpeed( 25.0f ); + + _follow.Begin( self ); + } + + +//-------------------------------------------------------------- +// Name: _doFollow() +// Class: AlertIdle +// +// Description: Evaluates our Follow In Formation Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_doFollow( Actor &self ) + { + BehaviorReturnCode_t result; + + if ( _checkInTheWay( self ) ) + return; + + + if ( _follow.GetState() == FollowInFormation::FOLLOW_TARGET_STATE_FOLLOW_HOLD ) + { + if ( _tryWander( self ) ) + return; + } + + result = _follow.Evaluate( self ); + + if ( result == BEHAVIOR_SUCCESS ) + { + self.SetAnim ( "idle" ); + _follow.End( self ); + _state = ALERT_IDLE_SELECT_STATE; + return; + } + + if ( result != BEHAVIOR_EVALUATING ) + { + //gi.WDPrintf( "%d: !!!!FAILURE!!!!!!\n" , self.entnum ); + _nextFollowAttempt = level.time + 0.25f; + _unableToFollow = true; + _setupWander( self ); + _state = ALERT_IDLE_WANDER; + + //_state = ALERT_IDLE_SELECT_STATE; + + //_setupHold( self ); + //_state = ALERT_IDLE_HOLD; + } + else + _unableToFollow = false; + + } + + +//-------------------------------------------------------------- +// Name: _tryWander() +// Class: AlertIdle +// +// Description: Attempts to put us in the Wander State +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool AlertIdle::_tryWander( Actor &self ) + { + if ( _nextWanderTime < 0 ) + _setNextWanderTime( self ); + + if ( !self.WithinDistance( self.followTarget.specifiedFollowTarget , _wanderDist ) ) + return false; + + if ( ( _nextWanderTime > 0 ) && ( level.time > _nextWanderTime ) ) + { + _setupWander( self ); + _state = ALERT_IDLE_WANDER; + return true; + } + + return false; + } + + +//-------------------------------------------------------------- +// Name: _setupWander() +// Class: AlertIdle +// +// Description: Sets up our MoveRandomDirection Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_setupWander( Actor &self ) + { + _follow.End( self ); + _wander.SetAnim( "walk" ); + _wander.SetDistance( 128.0f ); + _wander.SetMinDistance( 96.0f ); + _wander.SetMode( MoveRandomDirection::RANDOM_MOVE_ANYWHERE ); + _wander.Begin( self ); + } + + +//-------------------------------------------------------------- +// Name: _doWander() +// Class: AlertIdle +// +// Description: Evaluates our MoveRandomDirection Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_doWander( Actor &self ) + { + BehaviorReturnCode_t result; + + + if ( _checkInTheWay( self ) ) + return; + + result = _wander.Evaluate( self ); + + if ( !self.WithinDistance( self.followTarget.specifiedFollowTarget , _followDist ) && !_unableToFollow) + { + _state = ALERT_IDLE_SELECT_STATE; + return; + } + + + if ( result != BEHAVIOR_EVALUATING ) + { + self.SetAnim ( "idle" ); + _wander.End( self ); + _setNextWanderTime( self ); + _state = ALERT_IDLE_HOLD; + return; + } + + } + + +//-------------------------------------------------------------- +// Name: _setNextWanderTime() +// Class: AlertIdle +// +// Description: Sets up our Wander Time +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_setNextWanderTime( Actor &self ) + { + _nextWanderTime = level.time + G_Random( 3.0 ) + _baseIdleTime; + } + + +//-------------------------------------------------------------- +// Name: _checkInTheWay() +// Class: AlertIdle +// +// Description: Checks our "in the way" status and changes our +// state if appropriate +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +bool AlertIdle::_checkInTheWay( Actor &self ) + { + if ( self.checktouchedbyplayer() ) + { + _state = ALERT_IDLE_IN_THE_WAY; + _setupGetOutOfTheWay( self ); + return true; + } + + return false; + } + + +//-------------------------------------------------------------- +// Name: _setTorsoAnim() +// Class: AlertIdle +// +// Description: Sets our Torso Animation +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AlertIdle::_setTorsoAnim( Actor &self ) + { + if ( _useTorsoAnim ) + self.SetAnim( _torsoAnim , NULL , torso ); + } + + + +// +//============================================================================== +// DoAttack +//============================================================================== +// + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- + + + + + + + +// +//============================================================================== +// DoBeamAttack +//============================================================================== +// + +// Init Static Vars +const float DoBeamAttack::BEAMATTACK_SPREADFACTOR = 2.0f; + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, DoBeamAttack, NULL ) + { + { &EV_Behavior_Args, &DoBeamAttack::SetArgs }, + { &EV_Behavior_AnimDone, &DoBeamAttack::AnimDone }, + { NULL, NULL } + }; + + + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: DoBeamAttack +// +// Description: +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void DoBeamAttack::SetArgs( Event *ev ) + { + tagName = ev->GetString ( 1 ); + beamShader = ev->GetString ( 2 ); + impactModel = ev->GetString ( 3 ); + flashModel = ev->GetString ( 4 ); + anim = ev->GetString ( 5 ); + damage = ev->GetFloat ( 6 ); + time = ev->GetFloat ( 7 ); + turnspeed = ev->GetFloat ( 8 ); + trackEnemy = ev->GetBoolean ( 9 ); + if ( ev->NumArgs() > 9 ) + beamCount = ev->GetInteger( 10 ); + else + beamCount = 1; + + if ( ev->NumArgs() > 10 ) + useRotation = ev->GetBoolean( 11 ); + else + useRotation = false; + + } + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: DoBeamAttack +// +// Description: AnimDone Event Handler +// +// Parameters: Event *ev -- The AnimDone event +// +// Returns: None +//-------------------------------------------------------------- +void DoBeamAttack::AnimDone( Event *ev ) + { + if ( _state == BEAMATTACK_START_ANIM ) + _state = BEAMATTACK_START_ATTACK; + } + + + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: DoBeamAttack +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void DoBeamAttack::Begin( Actor &self ) + { + _initialRotationComplete = false; + _state = BEAMATTACK_SETUP; + } + + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: DoBeamAttack +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t DoBeamAttack::Evaluate( Actor &self ) + { + // If we've finished rotating towards the enemy, and + // we're supposed to track the enemy, then we need + // to continue rotating to keep on target + if ( _initialRotationComplete && trackEnemy ) + _rotate( self ); + + switch ( _state ) + { + case BEAMATTACK_SETUP: + _setupRotate( self ); + break; + + case BEAMATTACK_ROTATE: + _rotate( self ); + break; + + case BEAMATTACK_START_ANIM: + _playAttackAnim( self ); + break; + + case BEAMATTACK_START_ATTACK: + _createBeam( self ); + break; + + case BEAMATTACK_ATTACKING: + _updateBeam( self ); + break; + + case BEAMATTACK_COMPLETE: + return BEHAVIOR_SUCCESS; + break; + + case BEAMATTACK_FAILED: + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; + } + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: DoBeamAttack +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void DoBeamAttack::End(Actor &self) + { + + } + + + +//-------------------------------------------------------------- +// Name: _setupRotate() +// Class: DoBeamAttack +// +// Description: Sets up the Rotate Component Behavior +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void DoBeamAttack::_setupRotate( Actor &self ) + { + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( currentEnemy ) + { + _rotateBehavior.SetEntity( currentEnemy ); + _rotateBehavior.SetTurnSpeed( turnspeed ); + _rotateBehavior.Begin( self ); + } + + if ( useRotation ) + _state = BEAMATTACK_ROTATE; + else + _state = BEAMATTACK_START_ANIM; + + } + + + +//-------------------------------------------------------------- +// Name: _rotate() +// Class: DoBeamAttack +// +// Description: Evaluates the Rotate Component Behavior +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void DoBeamAttack::_rotate( Actor &self ) + { + BehaviorReturnCode_t result; + + result = _rotateBehavior.Evaluate( self ); + + if ( result == BEHAVIOR_SUCCESS ) + _state = BEAMATTACK_START_ANIM; + + } + + + +//-------------------------------------------------------------- +// Name: _playAttackAnim() +// Class: DoBeamAttack +// +// Description: Plays the specified attack animation +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void DoBeamAttack::_playAttackAnim( Actor &self ) + { + self.SetAnim( anim , EV_Actor_NotifyBehavior); + } + +void DoBeamAttack::_createBeam( Actor &self ) + { + Vector tagOrig; + Vector dir; + Vector angles; + Vector spread; + FuncBeam *beam; + + trace_t trace; + Entity *currentEnemy; + + // Snag our current enemy + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy || !_canAttack( self ) || tagName.length() == 0 ) + { + _attackFailed( self ); + return; + } + + self.setOrigin(); + + // Get our tag position + self.GetTag( tagName.c_str(), &tagOrig ); + + // Get the angles to our enemy + //dir = currentEnemy->centroid - self.centroid; + dir = currentEnemy->centroid - tagOrig; + angles = dir.toAngles(); + + for ( int i = 1 ; i <= beamCount ; i++ ) + { + // Calculate our spread + spread.x = G_CRandom(BEAMATTACK_SPREADFACTOR); + spread.y = G_CRandom(BEAMATTACK_SPREADFACTOR); + spread.z = G_CRandom(BEAMATTACK_SPREADFACTOR); + angles = angles + spread; + + // Get our beam's end position; + angles.AngleVectors( &_beamEndPos, NULL, NULL ); + _beamEndPos *= dir.length(); + _beamEndPos += tagOrig; + + // See if we hit our enemy + trace = _beamAttackTrace(self , tagOrig ); + + if ( ( trace.fraction < 1.0f ) ) + { + dir = _beamEndPos - tagOrig; + dir.normalize(); + + trace.ent->entity->Damage( &self, &self, damage, vec_zero, dir, vec_zero, 0, 0, MOD_ELECTRIC ); + } + + beam = CreateBeam( NULL, beamShader.c_str(), tagOrig, _beamEndPos, 1, 1.5f, 0.25f ); + _beamList.AddObject ( beam ); + } + + + // Add the beam + + _endTime = level.time + time; + + _state = BEAMATTACK_ATTACKING; + + + } + +trace_t DoBeamAttack::_beamAttackTrace( Actor &self , const Vector &startPos ) + { + //G_DebugLine( startPos , _beamEndPos, 1.0f, 0.0f, 1.0f, 1.0f ); + return G_Trace( startPos, Vector (-15.0f, -15.0f, -15.0f), Vector (15.0f, 15.0f, 15.0f), _beamEndPos, &self, MASK_SHOT, false, "doBeamAttack" ); + } + +void DoBeamAttack::_updateBeam( Actor &self ) + { + Vector tagOrig; + trace_t trace; + EntityPtr beam; + + if ( level.time >= _endTime ) + _state = BEAMATTACK_COMPLETE; + + self.GetTag( tagName.c_str(), &tagOrig ); + + for ( int i = 1 ; i <= beamCount ; i++ ) + { + beam = _beamList.ObjectAt( i ); + + if ( beam ) + { + beam->setOrigin( tagOrig ); + + self.Entity::SpawnEffect( flashModel , tagOrig , vec_zero , 0.25f ); + trace = _beamAttackTrace( self , tagOrig ); + + if ( trace.fraction < 1.0f ) + { + _beamEndPos = trace.endpos; + + FuncBeam* _theBeam; + _theBeam = (FuncBeam *)(Entity *)beam; + + _theBeam->SetEndPoint( _beamEndPos ); + + self.Entity::SpawnEffect(impactModel , _beamEndPos , vec_zero , 0.35f ); + } + } + + } + + } + +void DoBeamAttack::_attackFailed( Actor &self ) + { + _state = BEAMATTACK_FAILED; + } + +//-------------------------------------------------------------- +// Name: _canAttack() +// Class: DoBeamAttack +// +// Description: Checks if the actor can attack +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +bool DoBeamAttack::_canAttack( Actor &self ) + { + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy) + return false; + + if ( self.combatSubsystem->CanAttackTarget( currentEnemy ) ) + return true; + else + return false; + } + + + + + + +//============================================================================== +// FireWeapon +//============================================================================== + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, FireWeapon, NULL ) + { + { &EV_Behavior_Args, &FireWeapon::SetArgs }, + { NULL, NULL } + }; + + +FireWeapon::FireWeapon() +{ + _target = NULL; + _havePosition = false; +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: FireWeapon +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void FireWeapon::SetArgs ( Event *ev) + { + _anim = ev->GetString ( 1 ); + } + + + +//-------------------------------------------------------------- +// Name: SetAnim() +// Class: FireWeapon() +// +// Description: Sets the _anim +// +// Parameters: const str &anim -- The animation to set +// +// Returns: None +//-------------------------------------------------------------- +void FireWeapon::SetAnim(const str &anim ) + { + _anim = anim; + } + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: FireWeapon +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FireWeapon::Begin( Actor &self ) + { + if ( _havePosition ) + self.combatSubsystem->AimWeaponTag( _targetPosition ); + else if ( _target ) + self.combatSubsystem->AimWeaponTag(_target); + + self.SetAnim( _anim , EV_Actor_NotifyTorsoBehavior , torso ); + } + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: FireWeapon +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t FireWeapon::Evaluate ( Actor &self ) + { + + // no weapon? bad mojo, bail + if( !self.combatSubsystem->HaveWeapon() ) + return BEHAVIOR_FAILED; + + if ( !_target ) + _target = self.enemyManager->GetCurrentEnemy(); + + // still no target? bail + if( !_target ) + return BEHAVIOR_FAILED; + + if ( _havePosition ) + self.combatSubsystem->AimWeaponTag( _targetPosition ); + else if ( _target ) + self.combatSubsystem->AimWeaponTag(_target); + + self.combatSubsystem->AimWeaponTag(_target); + //self.SetAnim( _anim , EV_Torso_Anim_Done , torso ); + + return BEHAVIOR_EVALUATING; + } + + + +//-------------------------------------------------------------- +// Name: End() +// Class: FireWeapon +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FireWeapon::End ( Actor &self ) + { + _stopFire( self ); + } + + + +//-------------------------------------------------------------- +// Name: _stopFire() +// Class: FireWeapon +// +// Description: Creates a stop fire event, and tells the actor to process it +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FireWeapon::_stopFire( Actor &self ) + { + Event *StopFireEvent; + StopFireEvent = new Event( EV_Sentient_StopFire ); + StopFireEvent->AddString ( "dualhand" ); + + self.ProcessEvent( StopFireEvent ); + } + + +void FireWeapon::SetTargetPosition( const Vector &targetPos ) +{ + _targetPosition = targetPos; + _havePosition = true; +} + + + + +//-------------------------------------------------------------------------- +// +// MetaBehaviors +// +//-------------------------------------------------------------------------- + +//============================================================================== +// SimpleMelee +//============================================================================== + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, SimpleMelee, NULL ) + { + { &EV_Behavior_Args, &SimpleMelee::SetArgs }, + { &EV_Behavior_AnimDone, &SimpleMelee::AnimDone }, + { NULL, NULL } + }; + + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: SimpleMelee +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void SimpleMelee::SetArgs ( Event *ev) +{ + rushAnim = ev->GetString( 1 ); + attackAnim = ev->GetString( 2 ); + meleeDist = ev->GetFloat( 3 ); + turnSpeed = ev->GetFloat( 4 ); +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: SimpleMelee +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void SimpleMelee::AnimDone( Event *ev ) +{ + if ( _state == SIMPLE_MELEE_ATTACK ) + _attack.AnimDone( ev ); +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: SimpleMelee +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void SimpleMelee::Begin( Actor &self ) +{ + _init( self ); +} + + + +//-------------------------------------------------------------- +// Name: _init() +// Class: SimpleMelee +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void SimpleMelee::_init( Actor &self ) +{ + _self = &self; + _holdTime = 0.0f; + _strafeAttempts = 0; + _nextStrafeTime = 0.0f; + _nextEnemyCheckTime = 0.0f; + _holdCount = 0; + _state = SIMPLE_MELEE_SELECT_STATE; +} + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: SimpleMelee +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t SimpleMelee::Evaluate ( Actor &self ) +{ + if ( level.time > _nextEnemyCheckTime ) + { + self.enemyManager->FindHighestHateEnemy(); + _nextEnemyCheckTime = level.time + G_Random() + 2.0f; + } + + switch ( _state ) + { + case SIMPLE_MELEE_SELECT_STATE: + _selectState( self ); + break; + + case SIMPLE_MELEE_RUSH_ENEMY: + _rush( self ); + break; + + case SIMPLE_MELEE_CIRCLE_STRAFE: + _strafe( self ); + break; + + case SIMPLE_MELEE_ATTACK: + _meleeAttack( self ); + break; + + case SIMPLE_MELEE_HOLD: + _hold ( self ); + break; + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: SimpleMelee +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void SimpleMelee::End ( Actor &self ) +{ +} + + +void SimpleMelee::SetRushAnim( const str &anim ) +{ + rushAnim = anim; +} + +void SimpleMelee::SetAttackAnim( const str &anim ) +{ + attackAnim = anim; +} + +void SimpleMelee::SetMeleeDist( float dist ) +{ + meleeDist = dist; +} + +void SimpleMelee::SetTurnSpeed( float turnspeed ) +{ + turnSpeed = turnspeed; +} + +void SimpleMelee::_setupRush( Actor &self ) +{ + _nextStrafeTime = level.time + G_Random(.75); + _rushEnemy.setAnim( rushAnim ); + _rushEnemy.setDist( meleeDist ); + _rushEnemy.Begin( self ); +} + +void SimpleMelee::_rush( Actor &self ) +{ + BehaviorReturnCode_t result; + Entity *currentEnemy; + + + result = _rushEnemy.Evaluate( self ); + + if ( level.time >= _nextStrafeTime ) + { + _setupStrafe( self ); + _state = SIMPLE_MELEE_CIRCLE_STRAFE; + return; + } + + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + { + _state = SIMPLE_MELEE_SELECT_STATE; + return; + } + + if ( currentEnemy && self.WithinDistance( currentEnemy , meleeDist ) ) + { + _state = SIMPLE_MELEE_SELECT_STATE; + return; + } + + if ( result == BEHAVIOR_SUCCESS ) + { + _state = SIMPLE_MELEE_SELECT_STATE; + _holdCount = 0; + return; + } + + if ( result != BEHAVIOR_EVALUATING ) + { + float chance; + chance = G_Random(); + + if ( chance < .25 ) + { + _setupHold( self ); + _state = SIMPLE_MELEE_HOLD; + return; + } + } + + // If we get here, we're evaluating, which means we can clear + // out our hold count; + _holdCount = 0; + +} + + +void SimpleMelee::_setupStrafe( Actor &self ) +{ + _circleStrafe.SetAnim( rushAnim ); + _circleStrafe.SetRadius ( meleeDist ); + _circleStrafe.SetType( "enemy" ); + _circleStrafe.SetTestDistance( 128.0f ); + + float chance; + chance = G_Random(); + + if ( chance < .5 ) + _strafeClockwise = true; + else + _strafeClockwise = false; + + _circleStrafe.SetClockwise( _strafeClockwise ); + _circleStrafe.Begin( self ); +} + +void SimpleMelee::_strafe( Actor &self ) +{ + BehaviorReturnCode_t result; + + result = _circleStrafe.Evaluate( self ); + + if ( result == BEHAVIOR_FAILED || self.combatSubsystem->CanAttackTarget( self.enemyManager->GetCurrentEnemy() )) + { + _state = SIMPLE_MELEE_SELECT_STATE; + return; + } + +} + +void SimpleMelee::_setupMeleeAttack( Actor &self ) +{ + _attack.SetAnim( attackAnim ); + _attack.SetTurnSpeed ( turnSpeed ); + _attack.SetForceAttack( true ); + _attack.Begin( self ); +} + +void SimpleMelee::_meleeAttack( Actor &self ) +{ + BehaviorReturnCode_t result; + + result = _attack.Evaluate( self ); + + if ( result == BEHAVIOR_FAILED ) + { + _setupHold( self ); + _state = SIMPLE_MELEE_HOLD; + } + + if ( result == BEHAVIOR_SUCCESS ) + _state = SIMPLE_MELEE_SELECT_STATE; + + + if ( !self.combatSubsystem->CanAttackTarget( self.enemyManager->GetCurrentEnemy() ) ) + { + float chance; + chance = G_Random(); + + if ( chance <= .25 ) + { + self.enemyManager->FindNextEnemy(); + _nextEnemyCheckTime = level.time + G_Random() + 3.0f; + } + } + +} + +void SimpleMelee::_setupHold( Actor &self ) +{ + _holdCount++; + _holdTime = level.time + G_Random(0.25f) + 0.50f; +} + +void SimpleMelee::_hold( Actor &self ) +{ + self.SetAnim( "idle" ); + + if ( level.time >= _holdTime ) + _state = SIMPLE_MELEE_SELECT_STATE; +} + +//-------------------------------------------------------------- +// Name: _selectState() +// Class: SimpleMelee +// +// Description: Selects the state for the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void SimpleMelee::_selectState( Actor &self ) +{ + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + self.enemyManager->FindHighestHateEnemy(); + + if ( !self.WithinDistance( currentEnemy , meleeDist ) ) + { + _state = SIMPLE_MELEE_RUSH_ENEMY; + _setupRush( self ); + return; + } + + _setupMeleeAttack( self ); + _state = SIMPLE_MELEE_ATTACK; + return; + + +/* + if ( !self.combatSubsystem->CanShootTarget( currentEnemy ) && level.time > _nextStrafeTime ) + { + _setupStrafe( self ); + _state = SIMPLE_MELEE_CIRCLE_STRAFE; + return; + } +*/ + +/* + _setupHold( self ); + _state = SIMPLE_MELEE_HOLD; + return; +*/ +} + + + + +//============================================================================== +// Patrol +//============================================================================== + + + +//============================================================================== +// FollowPathBlindly +//============================================================================== + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, FollowPathBlindly, NULL ) +{ + { &EV_Behavior_Args, &FollowPathBlindly::SetArgs }, + { NULL, NULL } +}; + + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: FollowPathBlindly +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void FollowPathBlindly::SetArgs ( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + { + _animName = ev->GetString( 1 ); + } + if ( ev->NumArgs() > 1 ) + { + _offset = ev->GetFloat( 2 ); + } + else + { + _offset = 0.0f; + } + if ( ev->NumArgs() > 2 ) + { + _startNodeName = ev->GetString( 3 ); + } +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: FollowPathBlindly +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowPathBlindly::Begin( Actor &self ) +{ + HelperNodePtr startNode=NULL; + if ( _startNodeName.length() > 0 ) + { + startNode = HelperNode::GetTargetedHelperNode( _startNodeName ); + } + else + { + startNode = FindNearestNode( self ); + } + + if ( startNode != NULL ) + { + SetNode( self, startNode ); + } + + _gotoHelperNode.Begin( self ); + _gotoHelperNode.SetDistance( 32.0f ); + _gotoHelperNode.SetAnim( _animName ); +} + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: FollowPathBlindly +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t FollowPathBlindly::Evaluate ( Actor &self ) +{ + if ( _node == NULL ) + { + _node = FindNearestNode( self ); + if ( _node == NULL ) + { + return BEHAVIOR_FAILED; + } + } + + assert( _node != NULL ); + BehaviorReturnCode_t moveResult = _gotoHelperNode.Evaluate( self ); + + if ( moveResult == BEHAVIOR_SUCCESS ) + { + _node->RunExitThread(); + + // See if we hit the end of our path + HelperNodePtr lastNode = _node; + if ( !AdvanceNode( self ) ) + { + self.SetAnim( "idle" ); + return BEHAVIOR_SUCCESS; + } + _gotoHelperNode.SetPoint( ComputeTargetPoint( lastNode, _node, _nextNode ) ); + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: FollowPathBlindly +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void FollowPathBlindly::End ( Actor &self ) +{ +} + + +//-------------------------------------------------------------- +// Name: FindNextNode() +// Class: FollowPathBlindly +// +// Description: Attempts to find a nearby path node +// +// Parameters: Actor &self +// +// Returns: bool - whether node was found +//-------------------------------------------------------------- +HelperNodePtr FollowPathBlindly::FindNextNode( Actor &self ) +{ + if ( _node != NULL) + { + str nextNodeTargetName = _node->target; + if ( nextNodeTargetName.length() ) + { + return HelperNode::GetTargetedHelperNode( nextNodeTargetName ); + } + } + return NULL; +} + + +//-------------------------------------------------------------- +// Name: FindNearestNode() +// Class: FollowPathBlindly +// +// Description: Attempts to find a nearby path node +// +// Parameters: Actor &self +// +// Returns: bool - whether node was found +//-------------------------------------------------------------- +HelperNodePtr FollowPathBlindly::FindNearestNode( Actor &self ) +{ + return HelperNode::FindClosestHelperNodeWithoutPathing( self, 512.0f ); +} + +//-------------------------------------------------------------- +// Name: SetNode() +// Class: FollowPathBlindly +// +// Description: Sets node and nextnode +// +// Parameters: Actor &self +// +// Returns: none +//-------------------------------------------------------------- +void FollowPathBlindly::SetNode( Actor &self, HelperNodePtr node ) +{ + _node = node; + _nextNode = FindNextNode( self ); + _gotoHelperNode.SetPoint( ComputeTargetPoint( NULL, _node, _nextNode ) ); + +} + +//-------------------------------------------------------------- +// Name: AdvanceNode() +// Class: FollowPathBlindly +// +// Description: Advances _node down the waypoint path +// +// Parameters: Actor &self +// +// Returns: bool - whether node was found +//-------------------------------------------------------------- +const bool FollowPathBlindly::AdvanceNode( Actor &self ) +{ + _node = _nextNode; + if ( _node != NULL) + { + _nextNode = FindNextNode( self ); + return true; + } + return false; +} + + + +//-------------------------------------------------------------- +// Name: ComputeTargetPoint() +// Class: FollowPathBlindly +// +// Description: Adds offset into location that is move to +// +// Parameters: Actor &self +// +// Returns: bool - whether node was found +//-------------------------------------------------------------- +const Vector FollowPathBlindly::ComputeTargetPoint( const HelperNodePtr lastNode, HelperNodePtr const currentNode, const HelperNodePtr nextNode ) const +{ + if ( currentNode == NULL ) + { + return Vector::Identity(); + } + + if ( fSmallEnough(_offset, fEpsilon() ) ) + { + return ( currentNode->origin ); + } + + Vector averagePerpendicular; + Vector parallelDirection1; + if ( lastNode ) + { + parallelDirection1 = currentNode->origin - lastNode->origin; + parallelDirection1.normalize(); + averagePerpendicular.CrossProduct( parallelDirection1, Vector( 0, 0, 1 ) ); + } + + Vector parallelDirection2; + if ( nextNode ) + { + parallelDirection2 = nextNode->origin - currentNode->origin; + parallelDirection2.normalize(); + Vector secondPerpendicular; + secondPerpendicular.CrossProduct( parallelDirection2, Vector( 0, 0, 1 ) ); + if ( lastNode ) + { + averagePerpendicular += secondPerpendicular; + averagePerpendicular /= 2.0f; + } + else + { + averagePerpendicular = secondPerpendicular; + } + } + + if ( Vector::SmallEnough( averagePerpendicular ) ) + { + if ( lastNode ) + { + averagePerpendicular = parallelDirection1; + } + else + { + averagePerpendicular = -parallelDirection2; + } + } + + return currentNode->origin + ( averagePerpendicular * _offset ); +} + + + + +//============================================================================== +// Hibernate +//============================================================================== + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, Hibernate, NULL ) + { + { &EV_Behavior_Args, &Hibernate::SetArgs }, + { &EV_Behavior_AnimDone, &Hibernate::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: Hibernate +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::SetArgs ( Event *ev) + { + + } + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: Hibernate +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::AnimDone( Event *ev ) + { + switch ( _state ) + { + case HIBERNATE_START_HIBERNATE: + _state = HIBERNATE_HIBERNATE; + break; + + case HIBERNATE_END_HIBERNATE: + _state = HIBERNATE_SUCCESSFUL; + break; + + case HIBERNATE_WAIT: + _state = HIBERNATE_SUCCESSFUL; + break; + + } + } + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: Hibernate +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::Begin( Actor &self ) + { + _init( self ); + } + + + +//-------------------------------------------------------------- +// Name: _init() +// Class: Hibernate +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::_init( Actor &self ) + { + _nextMoveAttempt = 0.0f; + _moveFailures = 0; + + _state = HIBERNATE_FIND_CLOSEST_NODE; + if ( !_setupFindClosestHibernateNode( self ) ) + _setupFindClosestHibernateNodeFailed( self ); + } + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: Hibernate +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t Hibernate::Evaluate ( Actor &self ) + { + switch ( _state ) + { + case HIBERNATE_FIND_CLOSEST_NODE: + _findClosestHibernateNode( self ); + break; + + case HIBERNATE_MOVING_TO_NODE: + _moveToHibernateNode( self ); + break; + + case HIBERNATE_AT_NODE: + _atHibernateNode( self ); + break; + + case HIBERNATE_START_HIBERNATE: + //Intentionally Empty Here + break; + + case HIBERNATE_HIBERNATE: + _hibernate( self ); + break; + + case HIBERNATE_END_HIBERNATE: + _endHibernate( self ); + break; + + case HIBERNATE_HOLD: + _hold( self ); + break; + + case HIBERNATE_WAIT: + //_wait( self ); + break; + + case HIBERNATE_SUCCESSFUL: + self.SetActorFlag(ACTOR_FLAG_IN_ALCOVE , false ); + self.ignoreHelperNode.node = _node; + return BEHAVIOR_SUCCESS; + break; + + case HIBERNATE_FAILED: + _endHibernate( self ); + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; + } + + +void Hibernate::_wait( Actor &self ) +{ + self.SetAnim( "idle", EV_Actor_NotifyBehavior ); +} + +//-------------------------------------------------------------- +// Name: End() +// Class: Hibernate +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::End ( Actor &self ) + { + + } + + + +//-------------------------------------------------------------- +// Name: _setupFindClosestHibernateNode() +// Class: Hibernate +// +// Description: Sets up the behavior for the Find Node State +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool Hibernate::_setupFindClosestHibernateNode( Actor &self ) + { + _node = self.currentHelperNode.node; + return true; + } + + + +//-------------------------------------------------------------- +// Name: _setupFindClosestHibernateNodeFailed() +// Class: Hibernate +// +// Description: Failure Handler for _setupFindClosestWorkNode +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::_setupFindClosestHibernateNodeFailed( Actor &self ) + { + _state = HIBERNATE_FAILED; + } + + + +//-------------------------------------------------------------- +// Name: _findClosestHibernateNode() +// Class: Hibernate +// +// Description: Finds the closest work node +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::_findClosestHibernateNode( Actor &self ) + { + if ( !_node ) + _node = HelperNode::FindClosestHelperNode( self , "hibernate" , 512.0f ); + + if ( !_node ) + { + _findClosestHibernateNodeFailed(self); + return; + } + + _state = HIBERNATE_MOVING_TO_NODE; + if (!_setupMovingToHibernateNode( self ) ) + _setupMovingToHibernateNodeFailed( self ); + + } + + + +//-------------------------------------------------------------- +// Name: _findClosestHibernateNodeFailed() +// Class: Hibernate +// +// Description: Failure Handler for _findClosestWorkNode() +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::_findClosestHibernateNodeFailed( Actor &self ) + { + _state = HIBERNATE_FAILED; + } + + + +//-------------------------------------------------------------- +// Name: _setupMovingToHibernateNode() +// Class: Hibernate +// +// Description: Sets up Behavior to Move To Work Node +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool Hibernate::_setupMovingToHibernateNode( Actor &self ) + { + _gotoHelperNode.SetDistance( 16.0f ); + _gotoHelperNode.SetAnim( "walk" ); + _gotoHelperNode.SetPoint( _node->origin ); + _gotoHelperNode.Begin( self ); + + return true; + } + + + +//-------------------------------------------------------------- +// Name: _setupMovingToHibernateNodeFailed() +// Class: Hibernate +// +// Description: Failure Handler for _setupMovingToWorkNode +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::_setupMovingToHibernateNodeFailed( Actor &self ) + { + _state = HIBERNATE_FAILED; + } + + + +//-------------------------------------------------------------- +// Name: _moveToHibernateNode() +// Class: Hibernate +// +// Description: Moves to a work node +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::_moveToHibernateNode( Actor &self ) + { + BehaviorReturnCode_t moveResult; + + moveResult = _gotoHelperNode.Evaluate( self ); + + if ( moveResult == BEHAVIOR_SUCCESS ) + { + _nextMoveAttempt = 0.0f; + _moveFailures = 0; + _state = HIBERNATE_AT_NODE; + if ( !_setupAtHibernateNode( self ) ) + _setupAtHibernateNodeFailed( self ); + + } + else if ( moveResult == BEHAVIOR_FAILED ) + { + _moveToHibernateNodeFailed( self ); + } + } + + + +//-------------------------------------------------------------- +// Name: _moveToHibernateNodeFailed() +// Class: Hibernate +// +// Description: Failure Handler for _moveToWorkNode() +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::_moveToHibernateNodeFailed( Actor &self ) + { + _moveFailures++; + + if ( _moveFailures > 10 ) + { + _state = HIBERNATE_FAILED; + } + else + { + _state = HIBERNATE_HOLD; + if ( !_setupHold( self ) ) + _setupHoldFailed( self ); + } + + } + + + +//-------------------------------------------------------------- +// Name: _setupAtHibernateNode() +// Class: Hibernate +// +// Description: Sets up behavior for AtWorkNode +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool Hibernate::_setupAtHibernateNode( Actor &self ) + { + Vector nodeAngles; + + + nodeAngles = _node->angles; + + self.movementSubsystem->setAnimDir( nodeAngles ); + self.setAngles( nodeAngles ); + + return true; + } + + + +//-------------------------------------------------------------- +// Name: _setupAtHibernateNodeFailed() +// Class: Hibernate +// +// Description: Failure Handler for _setupAtWork() +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::_setupAtHibernateNodeFailed( Actor &self ) + { + _state = HIBERNATE_FAILED; + } + + +//-------------------------------------------------------------- +// Name: _atHibernateNode() +// Class: Hibernate +// +// Description: Determines how to work +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::_atHibernateNode( Actor &self ) + { + _node->RunEntryThread(); + _state = HIBERNATE_START_HIBERNATE; + + //Snap ourselves into place + self.setOrigin( _node->origin ); + _startHibernate( self ); + } + + + +//-------------------------------------------------------------- +// Name: _setupHold() +// Class: Hibernate +// +// Description: Sets up behavior to hold +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool Hibernate::_setupHold( Actor &self ) + { + self.SetAnim( "idle" ); + _nextMoveAttempt = level.time + 0.5f + G_Random(); + return true; + } + + + +//-------------------------------------------------------------- +// Name: _setupHoldFailed() +// Class: Hibernate +// +// Description: Failure Handler for _setupHold() +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::_setupHoldFailed( Actor &self ) + { + _state = HIBERNATE_FAILED; + } + + + +//-------------------------------------------------------------- +// Name: _hold() +// Class: Hibernate +// +// Description: Holds the Actor in place +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Hibernate::_hold( Actor &self ) + { + if ( level.time > _nextMoveAttempt ) + { + _state = HIBERNATE_MOVING_TO_NODE; + if ( !_setupMovingToHibernateNode( self ) ) + _setupMovingToHibernateNodeFailed( self ); + } + + } + +void Hibernate::_startHibernate( Actor &self ) + { + self.SetAnim( "alcove_deactivate", EV_Actor_NotifyBehavior ); + } + +void Hibernate::_hibernate( Actor &self ) + { + float tendencyToHibernate = self.personality->GetTendency( "hibernate" ); + + if ( G_Random() > tendencyToHibernate ) + { + _state = HIBERNATE_END_HIBERNATE; + } + + self.SetAnim( "alcove_idle", EV_Actor_NotifyBehavior ); + } + +void Hibernate::_endHibernate( Actor &self ) + { + self.SetAnim( "alcove_activate", EV_Actor_NotifyBehavior ); + _state = HIBERNATE_WAIT; + } + + + + + +//============================================================================== +// GotoLiftPosition +//============================================================================== + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, GotoLiftPosition, NULL ) + { + { &EV_Behavior_Args, &GotoLiftPosition::SetArgs }, + { &EV_Behavior_AnimDone, &GotoLiftPosition::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: GotoLiftPosition +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::SetArgs ( Event *ev) + { + } + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: GotoLiftPosition +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::AnimDone( Event *ev ) + { + } + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: GotoLiftPosition +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::Begin( Actor &self ) + { + _init( self ); + } + + + +//-------------------------------------------------------------- +// Name: _init() +// Class: GotoLiftPosition +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::_init( Actor &self ) + { + _nextMoveAttempt = 0.0f; + _moveFailures = 0; + _node = NULL; + + FindNodes( self ); + _state = GLP_FIND_NODE; + } + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: GotoLiftPosition +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: BehaviorReturnCode_t Return Code +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoLiftPosition::Evaluate ( Actor &self ) + { + switch ( _state ) + { + case GLP_FIND_NODE: + _findLiftNode( self ); + break; + + case GLP_MOVING_TO_NODE: + _moveToLiftNode( self ); + break; + + case GLP_MOVE_FAILED: + _moveToLiftNodeFailed( self ); + break; + + case GLP_AT_NODE: + _atLiftNode( self ); + break; + + case GLP_SUCCESSFUL: + return BEHAVIOR_SUCCESS; + break; + + case GLP_FAILED: + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; + } + + + +//-------------------------------------------------------------- +// Name: End() +// Class: GotoLiftPosition +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::End ( Actor &self ) + { + if ( _node ) + _node->UnreserveNode(); + + _availableNodes.ClearObjectList(); + _attemptedNodes.ClearObjectList(); + } + + +//-------------------------------------------------------------- +// Name: _findLiftNode() +// Class: GotoLiftPosition +// +// Description: Finds a good lift node position, based on priority +// and the currentCallVolume on the player +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::_findLiftNode( Actor &self ) + { + Player *player; + player = GetPlayer( 0 ); + + if ( !player ) + { + _state = GLP_FAILED; + return; + } + + //Search Through available nodes, and make sure that we haven't put it in our attempted list + HelperNode* theNode; + HelperNode* checkNode; + HelperNode* liftNode; + bool alreadyTriedNode; + int i, j; + + liftNode = NULL; + for( i = 1 ; i <= _availableNodes.NumObjects(); i++ ) + { + theNode = _availableNodes.ObjectAt(i); + + //First check if its in our attempted list + alreadyTriedNode = false; + for ( j = 1 ; j <= _attemptedNodes.NumObjects() ; j++ ) + { + checkNode = _attemptedNodes.ObjectAt(j); + if ( checkNode == theNode ) + { + alreadyTriedNode = true; + break; + } + } + + if ( alreadyTriedNode ) continue; + + liftNode = theNode; + } + + + if ( !liftNode ) + { + _state = GLP_FAILED; + return; + } + + _node = liftNode; + _node->ReserveNode(); + _setupMovingToLiftNode( self ); + _state = GLP_MOVING_TO_NODE; + } + +//-------------------------------------------------------------- +// Name: _setupMovingToLiftNode() +// Class: GotoLiftPosition +// +// Description: Sets up our GotoPoint Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::_setupMovingToLiftNode( Actor &self ) + { + _gotoHelperNode.SetAnim( "run" ); + _gotoHelperNode.SetDistance( 16.0f ); + _gotoHelperNode.SetPoint( _node->origin ); + _gotoHelperNode.Begin( self ); + } + +//-------------------------------------------------------------- +// Name: _moveToLiftNode() +// Class: GotoLiftPosition +// +// Description: Evaluates our MoveToPoint Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::_moveToLiftNode( Actor &self ) + { + BehaviorReturnCode_t result; + result = _gotoHelperNode.Evaluate( self ); + + if ( result == BEHAVIOR_FAILED ) + { + _setupMoveToLiftFailed( self ); + _state = GLP_MOVE_FAILED; + return; + } + + if ( result == BEHAVIOR_SUCCESS ) + { + _setupAtLiftNode( self ); + _state = GLP_AT_NODE; + return; + } + + if ( result == BEHAVIOR_FAILED_STEERING_NO_PATH ) + { + if ( !self.GetActorFlag(ACTOR_FLAG_IN_CALL_VOLUME) ) + { + _setupMoveToLiftFailed(self); + _state = GLP_MOVE_FAILED; + } + } + + } + +//-------------------------------------------------------------- +// Name: _setupMoveToLiftFailed() +// Class: GotoLiftPosition +// +// Description: Failure Handler +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::_setupMoveToLiftFailed( Actor &self ) + { + self.SetAnim( "idle" ); + _moveFailures++; + _nextMoveAttempt = level.time + G_Random() + 0.5f; + } + +//-------------------------------------------------------------- +// Name: _moveToLiftNodeFailed() +// Class: GotoLiftPosition +// +// Description: Depending on the failure count, will either try warping +// the actor to a convient pathnode, or warping it +// all the way to the position +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::_moveToLiftNodeFailed( Actor &self ) + { + if ( level.time > _nextMoveAttempt && !self.GetActorFlag(ACTOR_FLAG_IN_CALL_VOLUME) ) + { + + /*if ( _moveFailures > 5 ) + _warpToLiftNode( self ); + else if ( _moveFailures > 3 ) + _warpToPathNode( self ); + */ + _moveFailures++; + if ( _moveFailures > 5 ) + { + _attemptedNodes.AddObject(_node); + _findLiftNode(self); + + _setupMovingToLiftNode( self ); + _state = GLP_MOVING_TO_NODE; + } + } + } + +//-------------------------------------------------------------- +// Name: _setupAtLiftNode() +// Class: GotoLiftPosition +// +// Description: Sets us in the idle animation +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::_setupAtLiftNode( Actor &self ) + { + self.SetAnim( "idle" ); + _node->RunEntryThread(); + } + +//-------------------------------------------------------------- +// Name: _atLiftNode() +// Class: GotoLiftPosition +// +// Description: Holds us in position until the player +// is no longer in the call volume +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::_atLiftNode( Actor &self ) + { + //Check Player Range for Sanity as sometimes the trigger call volume fails to + //catch the player leaving -- It sucks, but it happens, this is bit of jazz here + //is for bullet proofing. + if ( self.checkSpecifiedFollowTargetOutOfRange() ) + self.SetActorFlag( ACTOR_FLAG_PLAYER_IN_CALL_VOLUME, false ); + + if ( !self.GetActorFlag( ACTOR_FLAG_PLAYER_IN_CALL_VOLUME ) ) + _state = GLP_SUCCESSFUL; + } + +//-------------------------------------------------------------- +// Name: _warpToLift +// Class: GotoLiftPosition +// +// Description: Sets our origin to the lift node's origin. +// We will likely need to modify this in the future +// to do a trace and verify that the position is +// clear +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::_warpToLiftNode( Actor &self ) + { + self.setOrigin( _node->origin ); + _setupAtLiftNode( self ); + _state = GLP_AT_NODE; + } + +//-------------------------------------------------------------- +// Name: _warpToPathNode() +// Class: GotoLiftPosition +// +// Description: Sets our origin to the pathnode nearest to us. +// Hopefully this will be enough to all pathfinding +// to find a good route +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoLiftPosition::_warpToPathNode( Actor &self ) + { + // Find the path node nearest to us + PathNode *goalNode = thePathManager.NearestNode( self.origin ); + if ( !goalNode ) + _warpToLiftNode( self ); + + } + +void GotoLiftPosition::FindNodes(Actor &self ) +{ + Player* player = GetPlayer(0); + str volumeName = player->GetCurrentCallVolume(); + HelperNode* node; + int nodeID; + + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + nodeID = node->GetID(); + + if ( nodeID != -1 && nodeID != self.currentHelperNode.nodeID ) + continue; + + if ( node->isReserved() ) + continue; + + if ( node->isOfType(NODETYPE_CUSTOM)) + { + str type; + type = node->GetCustomType(); + + if ( !stricmp(type.c_str() , "lift" ) && !stricmp(node->target, volumeName.c_str() ) ) + { + _availableNodes.AddObject(node); + } + + } + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/* + +//============================================================================== +// Template +//============================================================================== + + +//-------------------------------------------------------------- +// +// Init Static Vars +// +//-------------------------------------------------------------- +const float MoveFromConeOfFire::THE_CONSTANT = 500.0f; + + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, Template, NULL ) + { + { &EV_Behavior_Args, SetArgs }, + { &EV_Behavior_AnimDone, AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: Template +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Template::SetArgs ( Event *ev) + { + } + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: Template +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void MoveFromConeOfFire::Begin( Actor &self ) + { + } + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: Template +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t Template::Evaluate ( Actor &self ) + { + return BEHAVIOR_EVALUATING; + } + + +//-------------------------------------------------------------- +// Name: End() +// Class: Template +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void MoveFromConeOfFire::End ( Actor &self ) + { + } + + +*/ diff --git a/dlls/game/behaviors_general.h b/dlls/game/behaviors_general.h new file mode 100644 index 0000000..180b047 --- /dev/null +++ b/dlls/game/behaviors_general.h @@ -0,0 +1,2302 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/behaviors_general.h $ +// $Revision:: 115 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:11a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Generalized Behaviors will go in this file. Generalized, meaning, behaviors +// that are meant to be used easily by multiple actors +// + +//============================== +// Forward Declarations +//============================== +class CircleStrafeEntity; +class DoAttack; + +#ifndef __BEHAVIORS_GENERAL_H__ +#define __BEHAVIORS_GENERAL_H__ + +#include "behavior.h" +#include "behaviors.h" +#include "rotateToEntity.hpp" +#include "teleportToEntity.hpp" +#include "teleportToPosition.hpp" +#include "doAttack.hpp" +//#include "generalCombatWithMeleeWeapon.hpp" +#include "helper_node.h" +#include "actor.h" +#include "GoDirectlyToPoint.h" + +//============================= +// Enums for states +//============================= + + + + + + + + +//=================================================================================== +// +// Known Working Behaviors -- May need some clean up, but they do work +// +//=================================================================================== + + +//------------------------- CLASS ------------------------------ +// +// Name: WarpToPosition +// Base Class: Behavior +// +// Description: Attempts to Warp the Actor to the specified +// position +// +// Method of Use: State machine or another behavior +//-------------------------------------------------------------- + +class WarpToPosition : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + WARP_TO_POSITION_CHECK_POSITION, + WARP_TO_POSITION_WARP, + WARP_TO_POSITION_FAILED, + WARP_TO_POSITION_SUCCESS, + WARP_TO_POSITION_NUMBER_OF_STATES + } WarpToPosStates_t; + + + //------------------------------------ + // Parameters + //------------------------------------ + private: + Vector _position; + + public: + CLASS_PROTOTYPE( WarpToPosition ); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + // Mutators + void SetPosition ( const Vector &position ); + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void checkPosition ( Actor &self ); + void checkPositionFailed ( Actor &self ); + void warpToPosition ( Actor &self ); + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + // Member Vars + int _state; //-- Maintains our Behavior's current State + + }; + +inline void WarpToPosition::SetPosition( const Vector &position ) + { + _position = position; + } + +inline void WarpToPosition::Archive( Archiver &arc ) + { + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveVector ( &_position ); + + // Archive Member Variables + arc.ArchiveInteger ( &_state ); + + } + + +//------------------------- CLASS ------------------------------ +// +// Name: WarpToEntity +// Base Class: Behavior +// +// Description: Attempts to Warp the actor to the specified entity +// +// Method of Use: State machine or another behavior +//-------------------------------------------------------------- + +class WarpToEntity : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + WARP_TO_ENTITY_SELECT_POSITION, + WARP_TO_ENTITY_WARP, + WARP_TO_ENTITY_FAILED, + WARP_TO_ENTITY_SUCCESS, + WARP_TO_ENTITY_NUMBER_OF_STATES + } WarpToEntStates_t; + + + typedef enum + { + POSITION_REAR, + POSITION_LEFT, + POSITION_RIGHT, + POSITION_FRONT, + POSITION_NUMBER_OF_POSITIONS + } WarpToEntPositions_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + EntityPtr _entity; + + public: + CLASS_PROTOTYPE( WarpToEntity ); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + // Mutators + void SetEntity ( Entity *ent ); + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void selectPosition ( Actor &self ); + void setupWarp ( Actor &self ); + void warpToPosition ( Actor &self ); + void warpToPositionFailed ( Actor &self ); + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + // Components + WarpToPosition _warp; + + // Member Vars + unsigned int _state; + unsigned int _position; + Vector _destination; + + // Static Constants + static const float DIST_TO_ENTITY; + }; + +inline void WarpToEntity::SetEntity( Entity *ent ) + { + _entity = ent; + } + +inline void WarpToEntity::Archive( Archiver &arc ) + { + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveSafePointer ( &_entity ); + + // Archive Components + arc.ArchiveObject ( &_warp ); + + // Archive Member Variables + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveUnsigned ( &_position ); + arc.ArchiveVector ( &_destination ); + + } + + + +//------------------------- CLASS ------------------------------ +// +// Name: GotoEntity +// Base Class: Behavior +// +// Description: Makes the Actor move to the specified entity +// +// Method of Use: State machine or another behavior +//-------------------------------------------------------------- +class GotoEntity : public Behavior +{ + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _anim; + float _dist; + EntityPtr _entity; + + public: + CLASS_PROTOTYPE( GotoEntity ); + + GotoEntity(); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + // Accessors + void SetEntity ( Actor &self, Entity *ent ); + void SetAnim ( const str &animName ); + void SetDistance ( float distance ); + Entity* GetEntity (); + + private: + // Components + FollowPathToEntity _chase; +}; + +inline Entity* GotoEntity::GetEntity() +{ + return _entity; +} + +inline void GotoEntity::SetEntity( Actor &self, Entity *ent ) +{ + if ( ent ) + { + _entity = ent; + _chase.SetGoal( ent, _dist, self ); + _chase.Begin( self ); + } +} + +inline void GotoEntity::SetAnim( const str &animName ) +{ + _anim = animName; +} + +inline void GotoEntity::SetDistance( float distance ) +{ + _dist = distance; +} + +inline void GotoEntity::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Paramters + arc.ArchiveString ( &_anim ); + arc.ArchiveFloat ( &_dist ); + arc.ArchiveSafePointer ( &_entity ); + + // Archive Components + arc.ArchiveObject ( &_chase ); + + // Archive Member Variables +} + + +//------------------------- CLASS ------------------------------ +// +// Name: GotoPoint +// Base Class: Behavior +// +// Description: Makes the Actor move to the specified position +// +// Method of Use: State machine or another behavior +//-------------------------------------------------------------- +class GotoPoint : public Behavior +{ + private: // Parameters + str anim; + float dist; + Vector point; + + protected: + + public: + CLASS_PROTOTYPE( GotoPoint ); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + // Accessors + void SetPoint ( const Vector &position ); + void SetAnim ( const str &animName ); + void SetDistance ( float distance ); + + private: + // Components + FollowPathToPoint _chase; + bool _chaseFailed; + +}; + +inline void GotoPoint::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Paramters + arc.ArchiveString ( &anim ); + arc.ArchiveFloat ( &dist ); + arc.ArchiveVector ( &point ); + + // Archive Components + arc.ArchiveObject ( &_chase ); + arc.ArchiveBool ( &_chaseFailed ); + + + // Archive Member Variables +} + + +#include "MoveRandomDirection.hpp" + +//------------------------- CLASS ------------------------------ +// +// Name: MoveDirectlyToPoint +// Base Class: Behavior +// +// Description: Makes the Actor move to the specified position +// without regard for obstacle or worl avoidance +// +// Method of Use: State machine or another behavior +//-------------------------------------------------------------- +class MoveDirectlyToPoint : public Behavior +{ +public: + CLASS_PROTOTYPE( MoveDirectlyToPoint ); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + // Accessors + void SetAnim ( const str &animName ); + void SetPoint ( const Vector &position ); + void SetDistance ( const float distance ); + +private: + str _anim; + float _dist; + // Components + GoDirectlyToPoint _motion; +}; + +inline void MoveDirectlyToPoint::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Parameters + arc.ArchiveString ( &_anim ); + arc.ArchiveFloat ( &_dist ); + + // Archive Components + arc.ArchiveObject ( &_motion ); + +} + + +//------------------------- CLASS ------------------------------ +// +// Name: GotoSpecified +// Base Class: Behavior +// +// Description: Behavior that will the actor to a specified +// entity, or pathnode. This will mostly +// be called from script, and will be a replacement +// for the GotoPathNode Behavior +// +// Method of Use: State machine or another behavior +//-------------------------------------------------------------- +class GotoSpecified : public Behavior +{ + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + GOTO_SPEC_CHASE_TARGET, + GOTO_SPEC_HOLD, + GOTO_SPEC_WARP_TO_PATH, + GOTO_SPEC_WARP_TO_DESTINATION, + GOTO_SPEC_SUCCESS, + GOTO_SPEC_FAILED, + GOTO_SPEC_NUMBER_OF_STATES + } GotoSpecifiedStates_t; + + public: + typedef enum + { + GOTO_SPEC_CHASE_POSITION, + GOTO_SPEC_CHASE_ENTITY, + GOTO_SPEC_CHASE_NUMBER_OF_MODES, + } GotoSpecifiedModes_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _anim; + EntityPtr _targetEntity; + Vector _targetPosition; + EntityPtr _headwatchTarget; + bool _forceToTarget; + bool _turnAtEnd ; + int _maxFailures; + + public: + CLASS_PROTOTYPE( GotoSpecified ); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + // Accessors + void SetAnim ( const str &animName ); + void SetEntity ( Entity *ent ); + void SetPosition ( const Vector &pos ); + void SetHeadwatchTarget ( Entity *ent ); + void SetForce ( bool force ); + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void setupChasePosition ( Actor &self ); + void setupChaseEntity ( Actor &self ); + void setupWarpToPathNode ( Actor &self ); + void setupWarpToDestination ( Actor &self ); + void setupHold ( Actor &self ); + + void chasePosition ( Actor &self ); + void chaseEntity ( Actor &self ); + void warpToNearestPathNode ( Actor &self ); + void warpToDestination ( Actor &self ); + void hold ( Actor &self ); + void setAngles ( Actor &self ); + + void chaseFailed ( Actor &self ); + + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + // Components + GotoPoint _chasePosition; + GotoEntity _chaseEntity; + WarpToEntity _warpToEntity; + WarpToPosition _warpToPosition; + + // Member Variables + unsigned int _state; + unsigned int _mode; + int _moveFailures; + float _holdTime; + bool _attemptedPathWarp; + Vector _endAngles; + + // Constants + static const float DIST_TO_TARGET_POSITION; + static const float DIST_TO_TARGET_ENTITY; +}; + +inline void GotoSpecified::SetAnim ( const str &animName ) +{ + _anim = animName; +} + +inline void GotoSpecified::SetEntity ( Entity *ent ) +{ + _targetEntity = ent; + _mode = GOTO_SPEC_CHASE_ENTITY; +} + +inline void GotoSpecified::SetPosition ( const Vector &pos ) +{ + _targetPosition = pos; + _mode = GOTO_SPEC_CHASE_POSITION; +} + +inline void GotoSpecified::SetHeadwatchTarget( Entity *ent ) +{ + _headwatchTarget = ent; +} + +inline void GotoSpecified::SetForce ( bool force ) +{ + _forceToTarget = force; +} + +inline void GotoSpecified::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Paramters + arc.ArchiveString ( &_anim ); + arc.ArchiveSafePointer ( &_targetEntity ); + arc.ArchiveVector ( &_targetPosition ); + arc.ArchiveSafePointer ( &_headwatchTarget ); + arc.ArchiveBool ( &_forceToTarget ); + arc.ArchiveBool ( &_turnAtEnd ); + + // Archive Components + arc.ArchiveObject ( &_chasePosition ); + arc.ArchiveObject ( &_chaseEntity ); + arc.ArchiveObject ( &_warpToEntity ); + arc.ArchiveObject ( &_warpToPosition ); + + // Archive Member Variables + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveUnsigned ( &_mode ); + arc.ArchiveInteger ( &_moveFailures ); + arc.ArchiveFloat ( &_holdTime ); + arc.ArchiveBool ( &_attemptedPathWarp ); + arc.ArchiveVector ( &_endAngles ); + arc.ArchiveInteger ( &_maxFailures ); +} + +//------------------------- CLASS ------------------------------ +// +// Name: MoveFromConeOfFire +// Base Class: Behavior +// +// Description: The Actor does some traces to find a spot out +// of the way of the player's cone of fire. If +// the traces fail, it tries to find a node that +// satisfies that condition. If that fails, +// then the behavior fails. +// +// Method of Use: State machine or another behavior +//-------------------------------------------------------------- + +class MoveFromConeOfFire : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + MOVE_FCOF_SEARCHING_FOR_SPOT, + MOVE_FCOF_STATE_FOUND_SPOT, + MOVE_FCOF_STATE_SEARCHING_FOR_NODE, + MOVE_FCOF_STATE_FOUND_NODE, + MOVE_FCOF_STATE_NO_SPOT, + MOVE_FCOF_SUCCESS, + MOVE_FCOF_FAILED, + MOVE_FCOF_NUMBER_OF_STATES + } MoveFCOFStates_t; + + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _anim; // --The animation that will be used ( Walk, Run, SideStep, etc ) + + public: + CLASS_PROTOTYPE( MoveFromConeOfFire ); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + // Mutators + void SetAnim ( const str &anim ); + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + PathNode *_FindNode ( Actor &self ); + void _setDirectionVectors ( Actor &self ); + void _foundDestination ( Actor &self ); + void _noDestination ( Actor &self ); + void _searchForNode ( Actor &self ); + bool _checkDesiredMovement ( Actor &self , const Vector &startPos , const Vector &endPos ); + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + // Component Behaviors + GotoPoint _chase; //-- Behavior that will steer us to our desired position + + // Member Vars + int _state; //-- Maintains our Behavior's current State + float _nextsearch; //-- Next Time we are allowed to search for a position + Vector _newDestination; //-- Where we're going + Vector _left; //-- Holds Data for Left Direction Check + Vector _right; //-- Holds Data for Right Direction Check + bool _stuckOnPlayer; //-- Flag for being stuck on the player + float _oldTurnSpeed; + Vector _destination; + str _torsoAnim; + bool _nextToObstacle; + + // Constants + static const float CONE_OF_FIRE_RADIUS; + }; + + +inline void MoveFromConeOfFire::Archive( Archiver &arc ) + { + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_anim ); + + // Archive Components + arc.ArchiveObject ( &_chase ); + + // Archive Member Variables + arc.ArchiveInteger ( &_state ); + arc.ArchiveFloat ( &_nextsearch ); + arc.ArchiveVector ( &_newDestination ); + arc.ArchiveVector ( &_left ); + arc.ArchiveVector ( &_right ); + arc.ArchiveBool ( &_stuckOnPlayer ); + arc.ArchiveFloat ( &_oldTurnSpeed ); + arc.ArchiveVector ( &_destination ); + arc.ArchiveString ( &_torsoAnim ); + arc.ArchiveBool ( &_nextToObstacle ); + } + +inline void MoveFromConeOfFire::SetAnim( const str &anim ) + { + _anim = anim; + } + +//------------------------- CLASS ------------------------------ +// +// Name: Strafe +// Base Class: Behavior +// +// Description: Makes the Actor strafe +// +// Method of Use: State machine or another behavior +//-------------------------------------------------------------- +class Strafe : public Behavior +{ + public: // Modes + enum + { + STRAFE_LEFT, + STRAFE_RIGHT, + STRAFE_RANDOM, + STRAFE_NUMBER_OF_MODES + }; + + private: // Parameters + unsigned int mode; + + protected: + void _init ( Actor &self ); + void _setAnim ( Actor &self ); + + public: + CLASS_PROTOTYPE( Strafe ); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + // Accessors + void SetMode ( unsigned int strafeMode ); + + private: + str _anim; + bool _canStrafe; + bool _strafeComplete; + + +}; + +inline void Strafe::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Paramters + arc.ArchiveUnsigned ( &mode ); + + // Archive Components + + // Archive Member Variables + arc.ArchiveString ( &_anim ); + arc.ArchiveBool ( &_canStrafe ); + arc.ArchiveBool ( &_strafeComplete ); +} + + +//------------------------- CLASS ------------------------------ +// +// Name: CircleStrafeEntity +// Base Class: Behavior +// +// Description: Makes the Actor circle strafe +// +// Method of Use: State machine or another behavior +// +// ** Needs Additional Work ** +//-------------------------------------------------------------- +class CircleStrafeEntity : public Behavior + { + private: //Parameters + str _type; + float _radius; + str _legAnim; + bool _clockwise; + float _testDistance; + + + public: + CLASS_PROTOTYPE( CircleStrafeEntity ); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + void SetType ( const str &type ); + void SetRadius ( float radius ); + void SetAnim ( const str &anim ); + void SetClockwise ( bool clockwise ); + void SetTestDistance ( float testDistance ); + + protected: //Functions + Entity* _getStrafeTarget( Actor &self , const str &target ); + void _checkParameters(Event *ev); + qboolean _checkIfStuck(Actor &self); + void _init( Actor &self ); + + + private: //Member Vars + Wander _wander; + EntityPtr _strafeTarget; + Vector _lastPosition; + int _moveAttempts; + Vector _holdAngles; + bool _failed; + float _recheckTime; + + float _startWanderTime; + + + }; + +inline void CircleStrafeEntity::SetType( const str &type ) +{ + _type = type; +} + +inline void CircleStrafeEntity::SetRadius( float radius ) +{ + _radius = radius; +} + +inline void CircleStrafeEntity::SetAnim( const str &anim ) +{ + _legAnim = anim; +} + +inline void CircleStrafeEntity::SetClockwise( bool clockwise ) +{ + _clockwise = clockwise; +} + +inline void CircleStrafeEntity::SetTestDistance( float testDistance ) +{ + _testDistance = testDistance; +} + +inline void CircleStrafeEntity::Archive ( Archiver &arc ) +{ + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_type ); + arc.ArchiveFloat ( &_radius ); + arc.ArchiveString ( &_legAnim ); + arc.ArchiveBool ( &_clockwise ); + arc.ArchiveFloat ( &_testDistance ); + + // Archive Member Vars + arc.ArchiveObject ( &_wander ); + arc.ArchiveSafePointer ( &_strafeTarget ); + arc.ArchiveVector ( &_lastPosition ); + arc.ArchiveInteger ( &_moveAttempts ); + arc.ArchiveVector ( &_holdAngles ); + arc.ArchiveBool ( &_failed ); + arc.ArchiveFloat ( &_recheckTime ); + + arc.ArchiveFloat( &_startWanderTime ); +} + + + + +//------------------------- CLASS ------------------------------ +// +// Name: FollowInFormation +// Base Class: Behavior +// +// Description: Works with other followers to follow in a single file line +// +// Method Of Use: State Machine or other Behaviors +//--------------------------------------------------------------- +class FollowInFormation : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + FOLLOW_TARGET_STATE_SELECT_STATE, + FOLLOW_TARGET_STATE_FIND_FOLLOW_TARGET, + FOLLOW_TARGET_STATE_FOLLOW_TARGET, + FOLLOW_TARGET_STATE_FOLLOW_HOLD, + FOLLOW_TARGET_STATE_FOLLOW_WARP_TO_NEAREST_PATHNODE, + FOLLOW_TARGET_STATE_FOLLOW_WARP_TO_FOLLOW_TARGET, + FOLLOW_TARGET_STATE_FOLLOW_FAILED, + FOLLOW_TARGET_STATE_FOLLOW_NO_TARGET, + FOLLOW_TARGET_STATE_FOLLOW_SUCCESS, + FOLLOW_TARGET_NUMBER_OF_STATES + } followTargetStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _anim; + float _emergencyDistance; + float _catchupSpeed; + + public: + CLASS_PROTOTYPE( FollowInFormation ); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + void SetDefaultFollowTarget ( Actor &self ); + // Mutators + void SetAnim ( const str &anim ); + void SetEmergencyDistance ( float dist ); + void SetCatchupSpeed ( float speed ); + + + // Accessors + float GetFollowRange ( ); + unsigned int GetState ( ); + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void findFollowTarget ( Actor &self ); + + void selectState ( Actor &self ); + + void setupFollow ( Actor &self ); + void follow ( Actor &self ); + void followFailed ( Actor &self ); + + void setupWarpToPathNode ( Actor &self ); + void warpToPathNode ( Actor &self ); + + void setupWarpToTarget ( Actor &self ); + void warpToTarget ( Actor &self ); + + void setupHold ( Actor &self ); + void hold ( Actor &self ); + + void checkSpeed ( Actor &self ); + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + // Components + GotoEntity _followEntity; + WarpToEntity _warpToEntity; + WarpToPosition _warpToPosition; + + // Member Vars + unsigned int _state; + float _followFailureTime; + float _nextFollowAttemptTime; + float _followDist; + float _followDistMin; + float _nextTargetCheckTime; + float _endHold; + bool _selectedFollowTarget; + bool _attemptedWarpToPath; + bool _setFollowFailureTime; + float _oldForwardSpeed; + float _oldTurnSpeed; + bool _codeDriven; + + + }; + +inline void FollowInFormation::SetEmergencyDistance( float dist ) +{ + _emergencyDistance = dist; +} + +inline void FollowInFormation::SetCatchupSpeed( float speed ) +{ + _catchupSpeed = speed; +} + +inline void FollowInFormation::Archive( Archiver &arc ) + { + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_anim ); + arc.ArchiveFloat ( &_emergencyDistance ); + arc.ArchiveFloat ( &_catchupSpeed ); + + // Archive Components + arc.ArchiveObject ( &_followEntity ); + arc.ArchiveObject ( &_warpToEntity ); + arc.ArchiveObject ( &_warpToPosition ); + + // Archive Member Vars + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveFloat ( &_followFailureTime ); + arc.ArchiveFloat ( &_nextFollowAttemptTime ); + arc.ArchiveFloat ( &_followDist ); + arc.ArchiveFloat ( &_followDistMin ); + arc.ArchiveFloat ( &_nextTargetCheckTime ); + arc.ArchiveFloat ( &_endHold ); + arc.ArchiveBool ( &_selectedFollowTarget ); + arc.ArchiveBool ( &_attemptedWarpToPath ); + arc.ArchiveBool ( &_setFollowFailureTime ); + arc.ArchiveFloat ( &_oldForwardSpeed ); + arc.ArchiveFloat ( &_oldTurnSpeed ); + arc.ArchiveBool ( &_codeDriven ); + + } + +inline void FollowInFormation::SetAnim(const str &anim ) + { + _anim = anim; + } + +inline float FollowInFormation::GetFollowRange() + { + return _followDist; + } + +inline unsigned int FollowInFormation::GetState() + { + return _state; + } + + +//------------------------- CLASS ------------------------------ +// +// Name: GroupFollow +// Base Class: Behavior +// +// Description: Works with other followers to follow in a single file line +// +// Method Of Use: State Machine or other Behaviors +//--------------------------------------------------------------- +class GroupFollow : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + CLOSE_WITH_TARGET, + PACE_TARGET, + HOLD, + WANDER, + NUMBER_OF_STATES, + INVALID = -1, + } States; + + //------------------------------------ + // Parameters + //------------------------------------ + public: + CLASS_PROTOTYPE( GroupFollow ); + + void SetArgs( Event *ev ); + void AnimDone( Event *ev ); + void SetArgs ( const float stopDistance, const float paceDistance ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + + // Accessors + int GetState( void ); + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + BehaviorReturnCode_t FindFollowTarget( Actor &self ); + BehaviorReturnCode_t Hold( Actor &self ); + BehaviorReturnCode_t PaceTarget( Actor &self ); + BehaviorReturnCode_t CloseWithTarget( Actor &self ); + BehaviorReturnCode_t Wander(Actor &self ); + + float ComputePaceAnimationRateMultiplier( Actor &self ); + float ComputeAnimationRate( Actor &self, const str &animationName, const float scale ); + void GotoHoldState( Actor &self ); + void GotoPaceTargetState( Actor &self ); + void GotoCloseWithTargetState( Actor &self ); + void GotoWanderState(Actor &self); + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + static const float _minRangeMultiplier; + static const float _maxRangeMultiplier; + str _idleAnimation; + str _paceAnimation; + str _closeAnimation; + str _torsoAnimation; + GotoEntity _follow; + MoveRandomDirection _wander; + int _state; + bool _animationRateNeedsUpdate; + + float _stopDistance; + float _paceDistance; + float _endHold; + float _oldForwardSpeed; + float _nextFindFollowTime; + float _nextPathLenCheckTime; + float _nextPathLenCheckTime2; + + }; + +inline void GroupFollow::Archive( Archiver &arc ) + { + Behavior::Archive( arc ); + + arc.ArchiveString( &_idleAnimation ); + arc.ArchiveString( &_paceAnimation ); + arc.ArchiveString( &_closeAnimation ); + arc.ArchiveString( &_torsoAnimation ); + arc.ArchiveObject( &_follow ); + arc.ArchiveObject( &_wander ); + arc.ArchiveInteger( &_state ); + arc.ArchiveBool( &_animationRateNeedsUpdate ); + + arc.ArchiveFloat( &_stopDistance ); + arc.ArchiveFloat( &_paceDistance ); + + arc.ArchiveFloat( &_endHold ); + arc.ArchiveFloat( &_oldForwardSpeed ); + arc.ArchiveFloat( &_nextFindFollowTime ); + arc.ArchiveFloat( &_nextPathLenCheckTime ); + arc.ArchiveFloat( &_nextPathLenCheckTime2 ); + } + +inline int GroupFollow::GetState() + { + return _state; + } +//------------------------- CLASS ------------------------------ +// +// Name: MoveToDistanceFromEnemy +// Base Class: Behavior +// +// Description: Takes information from all the enemies in the +// actors hate list and uses them to get the +// best vector away from them. +// +// Method of Use: State Machine or other Behaviors +//-------------------------------------------------------------- +class MoveToDistanceFromEnemy : public Behavior + { + public: // States + typedef enum + { + MOVE_TO_DISTANCE_STATE_SEARCHING_FOR_SPOT, + MOVE_TO_DISTANCE_STATE_FOUND_SPOT, + MOVE_TO_DISTANCE_STATE_SEARCHING_FOR_NODE, + MOVE_TO_DISTANCE_STATE_FOUND_NODE, + MOVE_TO_DISTANCE_STATE_NO_SPOT, + MOVE_TO_DISTANCE_NUMBER_OF_STATES + } MoveToDistanceStates_t; + + private: + str _anim; + float _distance; + + public: + CLASS_PROTOTYPE( MoveToDistanceFromEnemy ); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + // Accessors + void SetAnim ( const str &anim ); + void SetDistance ( float distance ); + + + protected: + void _checkParameters ( Event *ev ); + PathNode *_FindNode ( Actor &self ); + + private: + // Components + FollowPathToPoint _chase; + + // Member Vars + int _state; + float _nextsearch; + Vector _away; + Vector _toTeam; + + }; + +inline void MoveToDistanceFromEnemy::Archive( Archiver &arc ) + { + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_anim ); + arc.ArchiveFloat ( &_distance ); + + // Archive Components + arc.ArchiveObject ( &_chase ); + + // Archive Member Vars + arc.ArchiveInteger( &_state ); + arc.ArchiveFloat ( &_nextsearch ); + arc.ArchiveVector ( &_away ); + arc.ArchiveVector ( &_toTeam ); + } + + + + +//Bleh +#include "MoveRandomDirection.hpp" + + +//------------------------- CLASS ------------------------------ +// +// Name: BackAwayFromEnemy +// Base Class: Behavior +// +// Description: Template that can be copied and pasted to +// generate new behavior +// +// Method of Use: State machine or another behavior +//-------------------------------------------------------------- + +class BackAwayFromEnemy : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + BAFE_SELECT_STATE, + BAFE_BACK_AWAY, + BAFE_BACK_AWAY_FAILED, + BAFE_BACK_AWAY_SUCCESS, + BAFE_NUMBER_OF_STATES + } BAFEStates_t; + + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _anim; + float _dist; + float _minDist; + + public: + CLASS_PROTOTYPE( BackAwayFromEnemy ); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + // Mutators + void SetAnim ( const str &anim ); + void SetDist ( float dist ); + void SetMinDist ( float minDist ); + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void selectState ( Actor &self ); + void setupMoveRandom ( Actor &self ); + void moveRandom ( Actor &self ); + void moveRandomFailed ( Actor &self ); + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + // Component Behaviors + MoveRandomDirection _moveRandom; //-- Behavior that will steer us to our desired position + + // Member Vars + int _state; //-- Maintains our Behavior's current State + + }; + + +inline void BackAwayFromEnemy::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_anim ); + arc.ArchiveFloat ( &_dist ); + arc.ArchiveFloat ( &_minDist ); + + // Archive Components + arc.ArchiveObject ( &_moveRandom ); + + // Archive Member Variables + arc.ArchiveInteger ( &_state ); + +} + +inline void BackAwayFromEnemy::SetAnim( const str &anim ) +{ + _anim = anim; +} + +inline void BackAwayFromEnemy::SetDist( float dist ) +{ + _dist = dist; +} + +inline void BackAwayFromEnemy::SetMinDist( float dist ) +{ + _minDist = dist; +} + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: AlertIdle +// Base Class: Behavior +// +// Description: The Actor is idle, but willing to get out of +// the way, follow the player, and (eventually) +// investigate. +// +// Method of Use: Statemachine or another behavior +//-------------------------------------------------------------- +class AlertIdle : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + ALERT_IDLE_SELECT_STATE, + ALERT_IDLE_IN_THE_WAY, + ALERT_IDLE_FOLLOW, + ALERT_IDLE_HOLD, + ALERT_IDLE_WANDER, + ALERT_IDLE_NUMBER_OF_STATES + } AlertIdleStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _followAnim; // Animation to use when following + str _torsoAnim; // Torso animation to play + float _baseIdleTime; // Our base time to idle, before wandering + float _emergencyDist; // Distance at which we switch to code driven speed to catch up + float _followDist; + float _wanderDist; + + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void _init ( Actor &self ); + void _selectState ( Actor &self ); + + void _setupGetOutOfTheWay ( Actor &self ); + void _doGetOutOfTheWay ( Actor &self ); + + void _setupFollow ( Actor &self ); + void _doFollow ( Actor &self ); + + bool _tryWander ( Actor &self ); + void _setupWander ( Actor &self ); + void _doWander ( Actor &self ); + + void _setupHold ( Actor &self ); + void _hold ( Actor &self ); + + void _setNextWanderTime ( Actor &self ); + bool _checkInTheWay ( Actor &self ); + + void _setTorsoAnim ( Actor &self ); + void checkDistance ( Actor &self ); + + public: + CLASS_PROTOTYPE( AlertIdle ); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + // Components + MoveFromConeOfFire _getOutOfTheWay; + FollowInFormation _follow; + MoveRandomDirection _wander; + + // Member Vars + float _nextFollowAttempt; + float _nextWanderTime; + + unsigned int _state; + bool _useTorsoAnim; + bool _unableToFollow; + Actor* _self; + }; + + +inline void AlertIdle::Archive( Archiver &arc ) + { + Behavior::Archive ( arc ); + + // Archive Parameters + arc.ArchiveString ( &_followAnim ); + arc.ArchiveString ( &_torsoAnim ); + arc.ArchiveFloat ( &_baseIdleTime ); + arc.ArchiveFloat ( &_emergencyDist ); + arc.ArchiveFloat ( &_followDist ); + arc.ArchiveFloat ( &_wanderDist ); + + // Archive Components + arc.ArchiveObject ( &_getOutOfTheWay ); + arc.ArchiveObject ( &_follow ); + arc.ArchiveObject ( &_wander ); + + + + // Archive Member Vars + arc.ArchiveFloat ( &_nextFollowAttempt ); + arc.ArchiveFloat ( &_nextWanderTime ); + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveBool ( &_useTorsoAnim ); + arc.ArchiveBool ( &_unableToFollow ); + + arc.ArchiveObjectPointer ( ( Class ** )&_self ); + + } + + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: DoBeamAttack +// Base Class: Behavior +// +// Description: Rotates the Actor towards its enemy then +// plays the specified attack animation +// Similar to the Do Attack Behavior, +// This one is setup to use and do beam/laser +// types of attacks +// +// +// Method of Use: State machine or called from another behavior +//-------------------------------------------------------------- +class DoBeamAttack : public Behavior + { + private: // Parameters + str tagName; + str beamShader; + str impactModel; + str flashModel; + str anim; + float damage; + float time; + float turnspeed; + bool trackEnemy; + int beamCount; + bool useRotation; + + + public: // States + enum + { + BEAMATTACK_SETUP, + BEAMATTACK_ROTATE, + BEAMATTACK_START_ANIM, + BEAMATTACK_START_ATTACK, + BEAMATTACK_ATTACKING, + BEAMATTACK_COMPLETE, + BEAMATTACK_FAILED, + }; + + protected: + void _setupRotate ( Actor &self ); + void _rotate ( Actor &self ); + void _playAttackAnim ( Actor &self ); + void _startAttack ( Actor &self ); + void _createBeam ( Actor &self ); + void _updateBeam ( Actor &self ); + void _attackFailed ( Actor &self ); + + bool _canAttack ( Actor &self ); + trace_t _beamAttackTrace( Actor &self , const Vector &startPos ); + + public: + CLASS_PROTOTYPE( DoBeamAttack ); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + private: + unsigned int _state; + RotateToEntity _rotateBehavior; + float _endTime; + bool _initialRotationComplete; + //EntityPtr _beam; + Vector _beamEndPos; + Container _beamList; + + + static const float BEAMATTACK_SPREADFACTOR; + + }; + +inline void DoBeamAttack::Archive( Archiver &arc ) +{ + int num , i; + Behavior::Archive( arc ); + EntityPtr beam; + + // Archive Parameters + arc.ArchiveString ( &tagName ); + arc.ArchiveString ( &beamShader ); + arc.ArchiveString ( &impactModel ); + arc.ArchiveString ( &flashModel ); + arc.ArchiveString ( &anim ); + arc.ArchiveFloat ( &damage ); + arc.ArchiveFloat ( &time ); + arc.ArchiveFloat ( &turnspeed ); + arc.ArchiveBool ( &trackEnemy ); + arc.ArchiveInteger ( &beamCount ); + arc.ArchiveBool ( &useRotation ); + + + // Archive Components + + // Archive Member Vars + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveObject ( &_rotateBehavior ); + arc.ArchiveFloat ( &_endTime ); + arc.ArchiveBool ( &_initialRotationComplete ); + + //arc.ArchiveSafePointer( &_beam ); + + arc.ArchiveVector ( &_beamEndPos ); + + if ( arc.Saving() ) + { + num = _beamList.NumObjects(); + arc.ArchiveInteger( &num ); + + + for ( i = 1 ; i <= num; i++ ) + { + beam = _beamList.ObjectAt( i ); + arc.ArchiveSafePointer( &beam ); + } + } + else + { + EntityPtr *beamPtr; + + arc.ArchiveInteger( &num ); + + _beamList.ClearObjectList(); + _beamList.Resize( num ); + + for ( i = 1 ; i <= num ; i++ ) + { + _beamList.AddObject( beam ); + + beamPtr = &_beamList.ObjectAt( i ); + + arc.ArchiveSafePointer( beamPtr ); + } + + } + +} + +//------------------------- CLASS ------------------------------ +// +// Name: FireWeapon +// Base Class: Behavior +// +// Description: Behavior that plays the specified Fire Animation +// +// Method of Use: Called From StateMachine or another behavior +//-------------------------------------------------------------- +class FireWeapon : public Behavior + { + private: //Parameters + str _anim; + + public: + CLASS_PROTOTYPE( FireWeapon ); + + FireWeapon(); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + void SetAnim ( const str &anim ); + void SetTarget ( Entity *target ) { _target = target; } + void SetTargetPosition( const Vector &targetPos ); + + + + protected: + void _stopFire ( Actor &self ); + + private: //Member Vars + int _totalShots; + int _frameCount; + float _currentYaw; + float _currentPitch; + Vector _aimAngles; + Vector _targetPosition; + EntityPtr _target; + qboolean _attacking; + bool _havePosition; + + + + + }; + +inline void FireWeapon::Archive( Archiver &arc ) + { + Behavior::Archive ( arc ); + + // Archive Parameters + arc.ArchiveString ( &_anim ); + + // Archive Components + + // Archive Member Vars + arc.ArchiveInteger ( &_totalShots ); + arc.ArchiveInteger ( &_frameCount ); + arc.ArchiveFloat ( &_currentYaw ); + arc.ArchiveFloat ( &_currentPitch ); + arc.ArchiveVector ( &_aimAngles ); + arc.ArchiveVector ( &_targetPosition ); + arc.ArchiveSafePointer ( &_target ); + arc.ArchiveBoolean ( &_attacking ); + arc.ArchiveBool ( &_havePosition ); + + } + + + +// +// Sigh +// +#include "closeInOnEnemy.hpp" +// +// ======================================================================== +// MetaBehaviors +// New ( Larger Scope ) Behaviors like "UseCover" or "GeneralCombat" +// The reason for these is that constraints with the state machine are +// preventing me from achieving the kinds of results that we need. +// +// I am shooting for a "building block" methodolgy here, I hope to utilize +// many of the behaviors we already have ( likely with a few accessor/mutator +// additions ) to build up these large meta behaviors +// ======================================================================== +// + + +//------------------------- CLASS ------------------------------ +// +// Name: SimpleMelee +// Base Class: Behavior +// +// Description: The Actor will rush at the enemy until it +// is within the specified melee range. Then +// it will do melee attacks until it can't +// see the enemy. If it can't see the enemy or +// can't get to the enemy, it will attempt to +// circle strafe until it can get in range +// +// Method of Use: Statemachine or another behavior +// +// Required Animations: +// -- Component Requirements + +// Component Behaviors: +// -- CloseInOnEnemy +// -- CircleStrafeEntity +// -- DoAttack +// +//-------------------------------------------------------------- +class SimpleMelee : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + enum + { + SIMPLE_MELEE_SELECT_STATE, + SIMPLE_MELEE_RUSH_ENEMY, + SIMPLE_MELEE_CIRCLE_STRAFE, + SIMPLE_MELEE_ATTACK, + SIMPLE_MELEE_HOLD + }; + + //------------------------------------ + // Parameters + //------------------------------------ + private: // Parameters + str rushAnim; + str attackAnim; + float meleeDist; + float turnSpeed; + + public: + CLASS_PROTOTYPE( SimpleMelee ); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + void SetRushAnim ( const str& anim ); + void SetAttackAnim ( const str& anim ); + void SetMeleeDist ( float dist ); + void SetTurnSpeed ( float turnspeed ); + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void _init ( Actor &self ); + void _selectState ( Actor &self ); + + void _setupRush ( Actor &self ); + void _rush ( Actor &self ); + + void _setupStrafe ( Actor &self ); + void _strafe ( Actor &self ); + + void _setupMeleeAttack ( Actor &self ); + void _meleeAttack ( Actor &self ); + + void _setupHold ( Actor &self ); + void _hold ( Actor &self ); + + + //------------------------------------- + // Member Variables + //------------------------------------- + + private: + // Component Behaviors + CloseInOnEnemy _rushEnemy; + CircleStrafeEntity _circleStrafe; + DoAttack _attack; + + // Member Variables + unsigned int _state; + float _holdTime; + int _holdCount; + bool _strafeClockwise; + int _strafeAttempts; + float _nextStrafeTime; + float _nextEnemyCheckTime; + Actor* _self; + + }; + + +inline void SimpleMelee::Archive( Archiver &arc ) + { + Behavior::Archive ( arc ); + + // Archive Parameters + arc.ArchiveString ( &rushAnim ); + arc.ArchiveString ( &attackAnim ); + arc.ArchiveFloat ( &meleeDist ); + arc.ArchiveFloat ( &turnSpeed ); + + // Archive Components + arc.ArchiveObject ( &_rushEnemy ); + arc.ArchiveObject ( &_circleStrafe ); + arc.ArchiveObject ( &_attack ); + + // Archive Member Vars + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveFloat ( &_holdTime ); + arc.ArchiveInteger ( &_holdCount ); + arc.ArchiveBool ( &_strafeClockwise ); + arc.ArchiveInteger ( &_strafeAttempts ); + arc.ArchiveFloat ( &_nextStrafeTime ); + arc.ArchiveFloat ( &_nextEnemyCheckTime ); + + arc.ArchiveObjectPointer ( ( Class ** )&_self ); + + } + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: FollowPathBlindly +// Base Class: Behavior +// +// Description: Makes the actor use FollowPathBlindly Helper Nodes +// +// Method of Use: Statemachine or another behavior +//-------------------------------------------------------------- +class FollowPathBlindly : public Behavior +{ +public: + CLASS_PROTOTYPE( FollowPathBlindly ); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + virtual void Archive ( Archiver &arc ); + +protected: + HelperNodePtr FindNextNode( Actor &self ); + HelperNodePtr FindNearestNode( Actor &self ); + void SetNode( Actor &self, HelperNodePtr node ); + const bool AdvanceNode( Actor &self ); + const Vector ComputeTargetPoint( const HelperNodePtr lastNode, const HelperNodePtr currentNode, const HelperNodePtr nextNode ) const; + +private: + // Component Behaviors + MoveDirectlyToPoint _gotoHelperNode; + + // Member Variables + str _animName; + HelperNodePtr _node; + HelperNodePtr _nextNode; + float _offset; + str _startNodeName; +}; + +inline void FollowPathBlindly::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Parmeters + + // Archive Components + arc.ArchiveObject ( &_gotoHelperNode ); + + // Archive Member Vars + arc.ArchiveString ( &_animName ); + arc.ArchiveSafePointer ( &_node ); + arc.ArchiveSafePointer ( &_nextNode ); + arc.ArchiveFloat ( &_offset ); + arc.ArchiveString ( &_startNodeName ); +} + + + + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: Hibernate +// Base Class: Behavior +// +// Description: Makes the Actor use Work Helper Nodes +// Note, this behavior makes extensive use of +// the CUSTOM helper node and REQUIRES that +// the Node's customType = "hibernate" +// +// Method of Use: Statemachine or another behavior +//-------------------------------------------------------------- +class Hibernate : public Behavior + { + public: // States + typedef enum + { + HIBERNATE_FIND_CLOSEST_NODE, + HIBERNATE_MOVING_TO_NODE, + HIBERNATE_AT_NODE, + HIBERNATE_START_HIBERNATE, + HIBERNATE_HIBERNATE, + HIBERNATE_END_HIBERNATE, + HIBERNATE_HOLD, + HIBERNATE_WAIT, + HIBERNATE_SUCCESSFUL, + HIBERNATE_FAILED + } hibernateStates_t; + + private: // Parameters + + protected: + void _init ( Actor &self ); + + bool _setupFindClosestHibernateNode ( Actor &self ); + void _setupFindClosestHibernateNodeFailed ( Actor &self ); + void _findClosestHibernateNode ( Actor &self ); + void _findClosestHibernateNodeFailed ( Actor &self ); + + bool _setupMovingToHibernateNode ( Actor &self ); + void _setupMovingToHibernateNodeFailed ( Actor &self ); + void _moveToHibernateNode ( Actor &self ); + void _moveToHibernateNodeFailed ( Actor &self ); + + bool _setupAtHibernateNode ( Actor &self ); + void _setupAtHibernateNodeFailed ( Actor &self ); + void _atHibernateNode ( Actor &self ); + + bool _setupHold ( Actor &self ); + void _setupHoldFailed ( Actor &self ); + void _hold ( Actor &self ); + + void _startHibernate ( Actor &self ); + void _hibernate ( Actor &self ); + void _endHibernate ( Actor &self ); + void _wait ( Actor &self ); + + + public: + CLASS_PROTOTYPE( Hibernate ); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + virtual void Archive ( Archiver &arc ); + + private: // Component Behaviors + GotoPoint _gotoHelperNode; + + private: // Member Variables + HelperNodePtr _node; + + unsigned int _state; + + int _moveFailures; + + float _nextMoveAttempt; + + + + }; + +inline void Hibernate::Archive( Archiver &arc ) + { + Behavior::Archive ( arc ); + + // Archive Parameters + + // Archive Components + arc.ArchiveObject ( &_gotoHelperNode ); + + // Archive Member Vars + arc.ArchiveSafePointer ( &_node ); + + arc.ArchiveUnsigned ( &_state ); + + arc.ArchiveInteger ( &_moveFailures ); + + arc.ArchiveFloat ( &_nextMoveAttempt ); + + + } + + + +//------------------------- CLASS ------------------------------ +// +// Name: GotoLiftPosition +// Base Class: Behavior +// +// Description: Makes the Actor use Lift Helper Nodes +// Note, this behavior makes extensive use of +// the CUSTOM helper node and REQUIRES that +// the Node's customType = "lift" +// +// Method of Use: Statemachine or another behavior +//-------------------------------------------------------------- +class GotoLiftPosition : public Behavior + { + public: // States + typedef enum + { + GLP_FIND_NODE, + GLP_MOVING_TO_NODE, + GLP_MOVE_FAILED, + GLP_AT_NODE, + GLP_SUCCESSFUL, + GLP_FAILED + } GotoLiftPositionStates_t; + + private: // Parameters + + protected: + void _init ( Actor &self ); + + void _findLiftNode ( Actor &self ); + + void _setupMovingToLiftNode ( Actor &self ); + void _moveToLiftNode ( Actor &self ); + + void _setupMoveToLiftFailed ( Actor &self ); + void _moveToLiftNodeFailed ( Actor &self ); + + + void _setupAtLiftNode ( Actor &self ); + void _atLiftNode ( Actor &self ); + + void _setupHold ( Actor &self ); + void _hold ( Actor &self ); + + void _warpToLiftNode ( Actor &self ); + void _warpToPathNode ( Actor &self ); + + + public: + CLASS_PROTOTYPE( GotoLiftPosition ); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + void FindNodes( Actor &self ); + + virtual void Archive ( Archiver &arc ); + + private: // Component Behaviors + GotoPoint _gotoHelperNode; + + private: // Member Variables + HelperNodePtr _node; + unsigned int _state; + int _moveFailures; + float _nextMoveAttempt; + Container _availableNodes; + Container _attemptedNodes; + + }; + +inline void GotoLiftPosition::Archive( Archiver &arc ) + { + Behavior::Archive ( arc ); + + // Archive Parameters + + // Archive Components + arc.ArchiveObject ( &_gotoHelperNode ); + + // Archive Member Vars + arc.ArchiveSafePointer ( &_node ); + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveInteger ( &_moveFailures ); + arc.ArchiveFloat ( &_nextMoveAttempt ); + + int num , i; + if ( arc.Saving() ) + { + num = _availableNodes.NumObjects(); + + arc.ArchiveInteger( &num ); + HelperNodePtr theNode; + for( i = 1 ; i <= num ; i++ ) + { + theNode = _availableNodes.ObjectAt(i); + arc.ArchiveSafePointer( &theNode ); + } + } + else + { + arc.ArchiveInteger( &num ); + + _availableNodes.Resize( num ); + + for ( i = 1; i <= num; i++ ) + { + HelperNodePtr theNode; + HelperNodePtr *ptrTheNode; + + _availableNodes.AddObject( theNode ); + + ptrTheNode = &_availableNodes.ObjectAt( i ); + + arc.ArchiveSafePointer( ptrTheNode ); + } + } + + if ( arc.Saving() ) + { + num = _attemptedNodes.NumObjects(); + + arc.ArchiveInteger( &num ); + HelperNodePtr theNode; + for( i = 1 ; i <= num ; i++ ) + { + theNode = _attemptedNodes.ObjectAt(i); + arc.ArchiveSafePointer( &theNode ); + } + } + else + { + arc.ArchiveInteger( &num ); + + for ( i = 1; i <= num; i++ ) + { + HelperNodePtr theNode; + arc.ArchiveSafePointer( &theNode ); + _attemptedNodes.AddObject( theNode ); + } + } + + } + +// +// Oh great googly moogly this is weak. Yes yes, I know this is a horrible +// horrible thing to do... But it's only temporary until I get all the +// behaviors split out into their own files. +// +#include "generalCombatWithRangedWeapon.hpp" + + + + + + +// +// ======================================================================== +// Global Functions +// ======================================================================== +// + +// Yes, Actor should be const, but find.heuristic stuff complains if it is +PathNode* FindNode ( Actor &self, FollowPath *chase, unsigned int mask , PathNode* (*check)(PathNode* node) ); + + + + + +/* + +//------------------------- CLASS ------------------------------ +// +// Name: Template +// Base Class: Behavior +// +// Description: Template that can be copied and pasted to +// generate new behavior +// +// Method of Use: State machine or another behavior +//-------------------------------------------------------------- + +class Template : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + TEMPLATE_NUMBER_OF_STATES + } MoveFCOFStates_t; + + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _anim; // --The animation that will be used ( Walk, Run, SideStep, etc ) + + public: + CLASS_PROTOTYPE( Template ); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + // Mutators + void SetAnim ( const str &anim ); + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + // Component Behaviors + GotoPoint _chase; //-- Behavior that will steer us to our desired position + + // Member Vars + int _state; //-- Maintains our Behavior's current State + + // Constants + static const float THE_CONSTANT; + }; + + +inline void Template::Archive( Archiver &arc ) + { + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_anim ); + + // Archive Components + arc.ArchiveObject ( &_chase ); + + // Archive Member Variables + arc.ArchiveInteger ( &_state ); + + } + +inline void Template::SetAnim( const str &anim ) + { + _anim = anim; + } + + +*/ + + + + + + + + + + + +#endif /* __BEHAVIORS_GENERAL_H__ */ diff --git a/dlls/game/behaviors_specific.cpp b/dlls/game/behaviors_specific.cpp new file mode 100644 index 0000000..0ccad34 --- /dev/null +++ b/dlls/game/behaviors_specific.cpp @@ -0,0 +1,1073 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/behaviors_specific.cpp $ +// $Revision:: 18 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +#include "_pch_cpp.h" +//#include "g_local.h" +#include "behavior.h" +#include "actor.h" +#include "doors.h" +#include "object.h" +#include "explosion.h" +#include "weaputils.h" +#include "player.h" +#include "behaviors_specific.h" + +extern Container HelperNodes; + + +//============================================================================== +// PatrolWorkHibernate +//============================================================================== + +// Init Static Vars +const float PatrolWorkHibernate::MIN_DISTANCE_TO_PATROL_NODE = 512.0f; +const float PatrolWorkHibernate::MIN_DISTANCE_TO_WORK_NODE = 512.0f; +const float PatrolWorkHibernate::MIN_DISTANCE_TO_HIBERNATE_NODE = 96.0f; + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, PatrolWorkHibernate, NULL ) + { + { &EV_Behavior_Args, &PatrolWorkHibernate::SetArgs }, + { &EV_Behavior_AnimDone, &PatrolWorkHibernate::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: PatrolWorkHibernate +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::SetArgs ( Event *ev) +{ + +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: PatrolWorkHibernate +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::AnimDone( Event *ev ) +{ + switch ( _state ) + { + case PATROLWORKHIBERNATE_HIBERNATE: + _hibernateComponent.AnimDone( ev ); + break; + + case PATROLWORKHIBERNATE_HACK: + _state = PATROLWORKHIBERNATE_SELECT_STATE; + break; + + case PATROLWORKHIBERNATE_HIBERNATE_HOLD: + _state = PATROLWORKHIBERNATE_SELECT_STATE; + break; + + case PATROLWORKHIBERNATE_PATROL: + _patrolComponent.AnimDone( ev ); + break; + + case PATROLWORKHIBERNATE_WORK: + _workComponent.AnimDone( ev ); + break; + } + +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: PatrolWorkHibernate +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::Begin( Actor &self ) +{ + _init( self ); + if ( self.GetActorFlag ( ACTOR_FLAG_IN_ALCOVE ) ) + _state = PATROLWORKHIBERNATE_HIBERNATE_HOLD; +} + + + +//-------------------------------------------------------------- +// Name: _init() +// Class: PatrolWorkHibernate +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::_init( Actor &self ) +{ + _nextStateCheck = 0.0f; + _nextWorkCheck = 0.0f; + _nextHibernateCheck = 0.0f; + + _state = PATROLWORKHIBERNATE_SELECT_STATE; +} + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: PatrolWorkHibernate +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t PatrolWorkHibernate::Evaluate ( Actor &self ) +{ + float tendencyToHibernate; + + switch ( _state ) + { + case PATROLWORKHIBERNATE_SELECT_STATE: + _selectState( self ); + break; + + case PATROLWORKHIBERNATE_PATROL: + _patrol( self ); + break; + + case PATROLWORKHIBERNATE_WORK: + _work( self ); + break; + + case PATROLWORKHIBERNATE_HIBERNATE: + tendencyToHibernate = self.personality->GetTendency( "hibernate" ); + + if ( G_Random() > tendencyToHibernate ) + { + self.SetAnim( "alcove_activate" , EV_Actor_NotifyBehavior ); + _state = PATROLWORKHIBERNATE_HACK; + return BEHAVIOR_EVALUATING; + } + + _hibernate( self ); + break; + + case PATROLWORKHIBERNATE_HOLD: + _hold( self ); + break; + + case PATROLWORKHIBERNATE_HIBERNATE_HOLD: + self.SetAnim( "alcove_idle" ); + tendencyToHibernate = self.personality->GetTendency( "hibernate" ); + + if ( G_Random() > tendencyToHibernate ) + { + self.SetAnim( "alcove_activate" , EV_Actor_NotifyBehavior ); + } + + break; + + case PATROLWORKHIBERNATE_FAILED: + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: PatrolWorkHibernate +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::End ( Actor &self ) +{ +} + + +//-------------------------------------------------------------- +// Name: _setupPatrol() +// Class: PatrolWorkHibernate +// +// Description: Sets up our Patrol Component Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::_setupPatrol( Actor &self ) +{ + _patrolComponent.SetInitialNode( self.currentHelperNode.node ); + _patrolComponent.Begin( self ); +} + + +//-------------------------------------------------------------- +// Name: _patrol() +// Class: PatrolWorkHibernate +// +// Description: Evaluates our Patrol Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::_patrol( Actor &self ) +{ + // See if we want to hibernate + if ( _wantsToHibernate( self ) ) + { + _state = PATROLWORKHIBERNATE_HIBERNATE; + _setupHibernate( self ); + _patrolComponent.End( self); + return; + } + + // See if we want to work + if ( _wantsToWork( self ) ) + { + _state = PATROLWORKHIBERNATE_WORK; + _setupWork( self ); + _patrolComponent.End( self); + return; + } + + + BehaviorReturnCode_t patrolResult; + patrolResult = _patrolComponent.Evaluate( self ); + + if ( patrolResult == BEHAVIOR_SUCCESS ) + _state = PATROLWORKHIBERNATE_SELECT_STATE; + + if ( patrolResult == BEHAVIOR_FAILED ) + _state = PATROLWORKHIBERNATE_FAILED; +} + + + +//-------------------------------------------------------------- +// Name: _setupHibernate() +// Class: PatrolWorkHibernate +// +// Description: Sets up our hibernate component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::_setupHibernate( Actor &self ) +{ + _hibernateComponent.Begin( self ); +} + + + +//-------------------------------------------------------------- +// Name: _hibernate() +// Class: PatrolWorkHibernate +// +// Description: Evaluates our Hibernate Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::_hibernate( Actor &self ) +{ + BehaviorReturnCode_t hibernateResult; + hibernateResult = _hibernateComponent.Evaluate( self ); + + if ( hibernateResult == BEHAVIOR_SUCCESS ) + _state = PATROLWORKHIBERNATE_SELECT_STATE; + + if ( hibernateResult == BEHAVIOR_FAILED ) + _state = PATROLWORKHIBERNATE_FAILED; +} + + +//-------------------------------------------------------------- +// Name: _setupWork() +// Class: PatrolWorkHibernate +// +// Description: Sets up our Work Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::_setupWork( Actor &self ) +{ + _workComponent.SetNode( self.currentHelperNode.node ); + _workComponent.Begin( self ); +} + + +//-------------------------------------------------------------- +// Name: _work() +// Class: PatrolWorkHibernate +// +// Description: Evaluates our Work Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::_work( Actor &self ) +{ + BehaviorReturnCode_t workResult; + workResult = _workComponent.Evaluate( self ); + + if ( workResult == BEHAVIOR_SUCCESS ) + _state = PATROLWORKHIBERNATE_SELECT_STATE; + + if ( workResult == BEHAVIOR_FAILED ) + _state = PATROLWORKHIBERNATE_FAILED; +} + + +void PatrolWorkHibernate::_setupHold( Actor &self ) +{ + self.SetAnim( "idle" ); + _nextStateCheck = level.time + G_Random() + 1.0f; +} + +void PatrolWorkHibernate::_hold( Actor &self ) +{ + if ( level.time >= _nextStateCheck ) + _state = PATROLWORKHIBERNATE_SELECT_STATE; +} + +bool PatrolWorkHibernate::_wantsToHibernate( Actor &self ) +{ + float tendencyToHibernate; + + if ( level.time > _nextHibernateCheck ) + { + _nextHibernateCheck = level.time + G_Random() + 0.25f; + + HelperNode* hibNode; + hibNode = HelperNode::FindClosestHelperNode( self , "hibernate" , MIN_DISTANCE_TO_HIBERNATE_NODE ); + + if ( !hibNode ) + return false; + + if ( !self.WithinDistance( hibNode->origin , MIN_DISTANCE_TO_HIBERNATE_NODE ) ) + return false; + + if ( hibNode == _lastHibernateNodeChecked ) + return false; + + _lastHibernateNodeChecked = hibNode; + + tendencyToHibernate = self.personality->GetTendency( "hibernate" ); + + if ( G_Random() <= tendencyToHibernate ) + return true; + + } + + return false; +} + + +bool PatrolWorkHibernate::_wantsToWork( Actor &self ) +{ + float tendencyToWork; + + if ( level.time > _nextWorkCheck ) + { + _nextWorkCheck = level.time + G_Random() + 0.25f; + + tendencyToWork = self.personality->GetTendency( "work" ); + if ( G_Random() > tendencyToWork ) + return false; + + HelperNode* workNode = HelperNode::FindClosestHelperNode( self , NODETYPE_WORK , MIN_DISTANCE_TO_WORK_NODE ); + + if ( !workNode ) + return false; + + if ( !self.WithinDistance( workNode->origin , MIN_DISTANCE_TO_WORK_NODE ) ) + return false; + + if ( workNode == _lastWorkNodeChecked ) + { + self.currentHelperNode.node->UnreserveNode(); + return false; + } + + _lastWorkNodeChecked = workNode; + + self.currentHelperNode.node->UnreserveNode(); + return true; + } + + return false; +} + +//-------------------------------------------------------------- +// Name: _selectState() +// Class: PatrolWorkHibernate +// +// Description: Selects the state of our behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkHibernate::_selectState( Actor &self ) +{ + // By default, we prefer to patrol. + // As we patrol, we'll keep looking for hibernate and work + // nodes, and change our state appropriately + HelperNode* patrolNode; + patrolNode = HelperNode::FindClosestHelperNode( self , NODETYPE_PATROL , MIN_DISTANCE_TO_PATROL_NODE ); + + if ( patrolNode && self.WithinDistance( patrolNode->origin , MIN_DISTANCE_TO_PATROL_NODE ) ) + { + _state = PATROLWORKHIBERNATE_PATROL; + _setupPatrol( self ); + return; + } + + // See if we want to hibernate + if ( _wantsToHibernate( self ) ) + { + _state = PATROLWORKHIBERNATE_HIBERNATE; + _setupHibernate( self ); + return; + } + + // See if we want to work + if ( _wantsToWork( self ) ) + { + _state = PATROLWORKHIBERNATE_WORK; + _setupWork( self ); + return; + } + + _state = PATROLWORKHIBERNATE_HOLD; + _setupHold( self ); + +} + + + + + +//============================================================================== +// PatrolWorkWander +//============================================================================== + +// Init Static Vars +const float PatrolWorkWander::MIN_DISTANCE_TO_PATROL_NODE = 512.0f; +const float PatrolWorkWander::MIN_DISTANCE_TO_WORK_NODE = 512.0f; + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, PatrolWorkWander, NULL ) + { + { &EV_Behavior_Args, &PatrolWorkWander::SetArgs }, + { &EV_Behavior_AnimDone, &PatrolWorkWander::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: PatrolWorkWander +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::SetArgs ( Event *ev) +{ +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: PatrolWorkWander +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::AnimDone( Event *ev ) +{ + switch ( _state ) + { + + case PATROLWORKWANDER_PATROL: + _patrolComponent.AnimDone( ev ); + break; + + case PATROLWORKWANDER_WORK: + _workComponent.AnimDone( ev ); + break; + + } +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: PatrolWorkWander +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::Begin( Actor &self ) +{ + _init( self ); +} + + + +//-------------------------------------------------------------- +// Name: _init() +// Class: PatrolWorkHibernate +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_init( Actor &self ) +{ + _nextStateCheck = 0.0f; + _nextWorkCheck = 0.0f; + _nextPatrolCheck = 0.0f; + + _wanderFailures = 0; + _patrolFailures = 0; + _workFailures = 0; + + + _state = PATROLWORKWANDER_SELECT_STATE; +} + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: PatrolWorkWander +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t PatrolWorkWander::Evaluate ( Actor &self ) +{ + switch ( _state ) + { + case PATROLWORKWANDER_SELECT_STATE: + _selectState( self ); + break; + + case PATROLWORKWANDER_PATROL: + _patrol ( self ); + break; + + case PATROLWORKWANDER_WORK: + _work( self ); + break; + + case PATROLWORKWANDER_WANDER: + _wander( self ); + break; + + case PATROLWORKWANDER_HOLD: + _hold( self ); + break; + + case PATROLWORKWANDER_FAILED: + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: PatrolWorkWander +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::End ( Actor &self ) +{ + +} + + +//-------------------------------------------------------------- +// Name: _setupWander() +// Class: PatrolWorkWander +// +// Description: Sets up our MoveRandomDirection Component Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_setupWander( Actor &self ) +{ + _wanderComponent.SetAnim( "walk" ); + _wanderComponent.SetDistance( 256.0f ); + _wanderComponent.SetMinDistance( 96.0f ); + _wanderComponent.SetMode( MoveRandomDirection::RANDOM_MOVE_ANYWHERE ); + _wanderComponent.Begin( self ); +} + + +//-------------------------------------------------------------- +// Name: _wander() +// Class: PatrolWorkWander +// +// Description: Evaluates our MoveRandomDirection Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_wander( Actor &self ) +{ + // See if we want to work + if ( _wantsToWork( self ) ) + { + _state = PATROLWORKWANDER_WORK; + _setupWork( self ); + _wanderComponent.End( self); + return; + } + + BehaviorReturnCode_t wanderResult; + wanderResult = _wanderComponent.Evaluate( self ); + + if ( wanderResult == BEHAVIOR_SUCCESS ) + { + _wanderFailures = 0; + _setupHold( self ); + _state = PATROLWORKWANDER_HOLD; + return; + } + + if ( wanderResult == BEHAVIOR_FAILED ) + { + _wanderFailed( self ); + return; + } + +} + + +//-------------------------------------------------------------- +// Name: _wanderFailed() +// Class: PatrolWorkWander +// +// Description: Failure Handler for the MoveRandomDirection Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_wanderFailed( Actor &self ) +{ + _wanderFailures++; + + _setupHold( self ); + _state = PATROLWORKWANDER_HOLD; +} + +//-------------------------------------------------------------- +// Name: _setupPatrol() +// Class: PatrolWorkWander +// +// Description: Sets up our Patrol Component Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_setupPatrol( Actor &self ) +{ + _patrolComponent.Begin( self ); +} + + +//-------------------------------------------------------------- +// Name: _patrol() +// Class: PatrolWorkWander +// +// Description: Evaluates our Patrol Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_patrol( Actor &self ) +{ + // See if we want to work + if ( _wantsToWork( self ) ) + { + _state = PATROLWORKWANDER_WORK; + _setupWork( self ); + _patrolComponent.End( self); + return; + } + + + BehaviorReturnCode_t patrolResult; + patrolResult = _patrolComponent.Evaluate( self ); + + if ( patrolResult == BEHAVIOR_SUCCESS ) + { + _patrolFailures = 0; + _setupHold( self ); + _state = PATROLWORKWANDER_HOLD; + return; + } + + + if ( patrolResult == BEHAVIOR_FAILED ) + { + _patrolFailed( self ); + return; + } + +} + +//-------------------------------------------------------------- +// Name: _patrolFailed() +// Class: PatrolWorkWander +// +// Description: Failure Handler for +// +// Parameters: +// +// Returns: +//-------------------------------------------------------------- +void PatrolWorkWander::_patrolFailed( Actor &self ) +{ + _patrolFailures++; + + _setupHold( self ); + _state = PATROLWORKWANDER_HOLD; +} + +//-------------------------------------------------------------- +// Name: _setupWork() +// Class: PatrolWorkWander +// +// Description: Sets up our Work Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_setupWork( Actor &self ) +{ + _workComponent.Begin( self ); +} + + +//-------------------------------------------------------------- +// Name: _work() +// Class: PatrolWorkWander +// +// Description: Evaluates our Work Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_work( Actor &self ) +{ + BehaviorReturnCode_t workResult; + workResult = _workComponent.Evaluate( self ); + + if ( workResult == BEHAVIOR_SUCCESS ) + { + _workFailures = 0; + _setupHold( self ); + _state = PATROLWORKWANDER_HOLD; + return; + } + + + if ( workResult == BEHAVIOR_FAILED ) + { + _workFailed( self ); + return; + } + +} + +//-------------------------------------------------------------- +// Name: _workFailed() +// Class: PatrolWorkWander +// +// Description: Failure Handler for the Work Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_workFailed( Actor &self ) +{ + _workFailures++; + + _setupHold( self ); + _state = PATROLWORKWANDER_HOLD; +} + +//-------------------------------------------------------------- +// Name: _setupHold() +// Class: PatrolWorkWander +// +// Description: Sets up Hold State +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_setupHold( Actor &self ) +{ + self.SetAnim( "idle" ); + _nextStateCheck = level.time + G_Random( 5.0f ) + 1.0f; +} + +//-------------------------------------------------------------- +// Name: _hold() +// Class: PatrolWorkWander +// +// Description: Holds us in place until we need to change +// states +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_hold( Actor &self ) +{ + if ( level.time >= _nextStateCheck ) + _state = PATROLWORKWANDER_SELECT_STATE; +} + +//-------------------------------------------------------------- +// Name: _wantsToWork() +// Class: PatrolWorkWander +// +// Description: Checks if the actor "wants" to work +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +bool PatrolWorkWander::_wantsToWork( Actor &self ) +{ + float tendencyToWork; + + if ( _workFailures > 5 ) + return false; + + if ( level.time > _nextWorkCheck ) + { + _nextWorkCheck = level.time + G_Random() + 0.25f; + + HelperNode* workNode; + workNode = HelperNode::FindClosestHelperNode( self , NODETYPE_WORK , MIN_DISTANCE_TO_WORK_NODE ); + + if ( !workNode ) + return false; + + if ( !self.WithinDistance( workNode->origin , MIN_DISTANCE_TO_WORK_NODE ) ) + return false; + + if ( workNode == _lastWorkNodeChecked ) + return false; + + _lastWorkNodeChecked = workNode; + + tendencyToWork = self.personality->GetTendency( "work" ); + + if ( G_Random() <= tendencyToWork ) + { + self.currentHelperNode.node->UnreserveNode(); + return true; + } + + + + } + + return false; +} + +//-------------------------------------------------------------- +// Name: _wantsToPatrol() +// Class: PatrolWorkWander +// +// Description: Checks if the Actor "wants" to patrol +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +bool PatrolWorkWander::_wantsToPatrol( Actor &self ) +{ + float tendencyToPatrol; + + if ( _patrolFailures > 5 ) + return false; + + if ( level.time >= _nextPatrolCheck ) + { + _nextPatrolCheck = level.time + G_Random() + 0.25f; + + HelperNode* patrolNode; + patrolNode = HelperNode::FindClosestHelperNode( self , NODETYPE_PATROL , MIN_DISTANCE_TO_PATROL_NODE ); + + if ( !patrolNode ) + return false; + + if ( !self.WithinDistance( patrolNode->origin , MIN_DISTANCE_TO_PATROL_NODE ) ) + return false; + + if ( patrolNode == _lastPatrolNodeChecked ) + return false; + + _lastPatrolNodeChecked = patrolNode; + + tendencyToPatrol = self.personality->GetTendency( "patrol" ); + + if ( G_Random() <= tendencyToPatrol ) + return true; + + } + + return false; +} + +//-------------------------------------------------------------- +// Name: _wantsToWander() +// Class: PatrolWorkWander +// +// Description: Checks if the Actor "wants" to Wander +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +bool PatrolWorkWander::_wantsToWander( Actor &self ) +{ + if ( _wanderFailures > 5 ) + return false; + + return true; +} +//-------------------------------------------------------------- +// Name: _selectState() +// Class: PatrolWorkWander +// +// Description: Selects the state of our behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void PatrolWorkWander::_selectState( Actor &self ) +{ + + if ( _wantsToPatrol( self ) ) + { + _setupPatrol( self ); + _state = PATROLWORKWANDER_PATROL; + return; + } + + if ( _wantsToWork( self ) ) + { + _setupWork( self ); + _state = PATROLWORKWANDER_WORK; + return; + } + + if ( _wantsToWander( self ) ) + { + _setupWander( self ); + _state = PATROLWORKWANDER_WANDER; + return; + } + + // Nothing else is working, so we'll just stand and play our + // idle animation + _state = PATROLWORKWANDER_HOLD; + _setupHold( self ); +} diff --git a/dlls/game/behaviors_specific.h b/dlls/game/behaviors_specific.h new file mode 100644 index 0000000..1f67868 --- /dev/null +++ b/dlls/game/behaviors_specific.h @@ -0,0 +1,247 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/behaviors_specific.h $ +// $Revision:: 12 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Specific Behaviors will go in this file. Specific, meaning, behaviors +// that are meant to be used by specific actors +// + +#ifndef __BEHAVIORS_SPECIFIC_H__ +#define __BEHAVIORS_SPECIFIC_H__ + +#include "behavior.h" +#include "behaviors_general.h" +#include "work.hpp" +#include "patrol.hpp" +#include "helper_node.h" +#include "actor.h" + + +//------------------------- CLASS ------------------------------ +// +// Name: PatrolWorkHibernate +// Base Class: Behavior +// +// Description: Big Behavior that looks at tendenencies +// on the actor to help decide if it is going +// to work, patrol, or hibernate +// +// Method of Use: Statemachine or another behavior +//-------------------------------------------------------------- +class PatrolWorkHibernate : public Behavior + { + private: // Parameters + + public: // States + enum + { + PATROLWORKHIBERNATE_SELECT_STATE, + PATROLWORKHIBERNATE_PATROL, + PATROLWORKHIBERNATE_WORK, + PATROLWORKHIBERNATE_HIBERNATE, + PATROLWORKHIBERNATE_HOLD, + PATROLWORKHIBERNATE_HIBERNATE_HOLD, + PATROLWORKHIBERNATE_FAILED, + PATROLWORKHIBERNATE_HACK + }; + + protected: + void _init ( Actor &self ); + void _selectState ( Actor &self ); + + void _setupPatrol ( Actor &self ); + void _patrol ( Actor &self ); + + void _setupHibernate ( Actor &self ); + void _hibernate ( Actor &self ); + + void _setupWork ( Actor &self ); + void _work ( Actor &self ); + + void _setupHold ( Actor &self ); + void _hold ( Actor &self ); + + bool _wantsToWork ( Actor &self ); + bool _wantsToHibernate ( Actor &self ); + + + public: + CLASS_PROTOTYPE( PatrolWorkHibernate ); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + virtual void Archive ( Archiver &arc ); + + private: // Component Behaviors + Patrol _patrolComponent; + Work _workComponent; + Hibernate _hibernateComponent; + + private: + // Member Variables + unsigned int _state; + HelperNodePtr _lastWorkNodeChecked; + HelperNodePtr _lastHibernateNodeChecked; + + float _nextStateCheck; + float _nextWorkCheck; + float _nextHibernateCheck; + + // Constants + static const float MIN_DISTANCE_TO_PATROL_NODE; + static const float MIN_DISTANCE_TO_WORK_NODE; + static const float MIN_DISTANCE_TO_HIBERNATE_NODE; + + }; + +inline void PatrolWorkHibernate::Archive( Archiver &arc ) + { + Behavior::Archive ( arc ); + + // Archive Parameters + + // Archive Components + arc.ArchiveObject ( &_patrolComponent ); + arc.ArchiveObject ( &_workComponent ); + arc.ArchiveObject ( &_hibernateComponent ); + + // Archive Memeber Vars + arc.ArchiveUnsigned( &_state ); + arc.ArchiveSafePointer( &_lastWorkNodeChecked ); + arc.ArchiveSafePointer( &_lastHibernateNodeChecked ); + + arc.ArchiveFloat( &_nextStateCheck ); + arc.ArchiveFloat( &_nextWorkCheck ); + arc.ArchiveFloat( &_nextHibernateCheck ); + } + + +//------------------------- CLASS ------------------------------ +// +// Name: PatrolWorkWander +// Base Class: Behavior +// +// Description: Big Behavior that looks at tendenencies +// on the actor to help decide if it is going +// to work, patrol. It will wander by default. +// +// Method of Use: Statemachine or another behavior +//-------------------------------------------------------------- +class PatrolWorkWander : public Behavior +{ + private: // Parameters + + public: // States + enum + { + PATROLWORKWANDER_SELECT_STATE, + PATROLWORKWANDER_PATROL, + PATROLWORKWANDER_WORK, + PATROLWORKWANDER_WANDER, + PATROLWORKWANDER_HOLD, + PATROLWORKWANDER_FAILED + }; + + protected: + void _init ( Actor &self ); + void _selectState ( Actor &self ); + + void _setupPatrol ( Actor &self ); + void _patrol ( Actor &self ); + void _patrolFailed ( Actor &self ); + + + void _setupWander ( Actor &self ); + void _wander ( Actor &self ); + void _wanderFailed ( Actor &self ); + + void _setupWork ( Actor &self ); + void _work ( Actor &self ); + void _workFailed ( Actor &self ); + + void _setupHold ( Actor &self ); + void _hold ( Actor &self ); + + bool _wantsToWork ( Actor &self ); + bool _wantsToPatrol ( Actor &self ); + bool _wantsToWander ( Actor &self ); + + + + public: + CLASS_PROTOTYPE( PatrolWorkWander ); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + virtual void Archive ( Archiver &arc ); + + private: // Component Behaviors + Patrol _patrolComponent; + Work _workComponent; + MoveRandomDirection _wanderComponent; + + private: // Member Variables + unsigned int _state; + HelperNodePtr _lastWorkNodeChecked; + HelperNodePtr _lastPatrolNodeChecked; + + float _nextStateCheck; + float _nextWorkCheck; + float _nextPatrolCheck; + + int _wanderFailures; + int _patrolFailures; + int _workFailures; + + // Constants + static const float MIN_DISTANCE_TO_PATROL_NODE; + static const float MIN_DISTANCE_TO_WORK_NODE; + +}; + +inline void PatrolWorkWander::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Parameters + + // Archive Components + arc.ArchiveObject ( &_patrolComponent ); + arc.ArchiveObject ( &_workComponent ); + arc.ArchiveObject ( &_wanderComponent ); + + // Archive Memeber Vars + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveSafePointer ( &_lastWorkNodeChecked ); + arc.ArchiveSafePointer ( &_lastPatrolNodeChecked ); + arc.ArchiveFloat ( &_nextStateCheck ); + arc.ArchiveFloat ( &_nextWorkCheck ); + arc.ArchiveFloat ( &_nextPatrolCheck ); + arc.ArchiveInteger ( &_wanderFailures ); + arc.ArchiveInteger ( &_patrolFailures ); + arc.ArchiveInteger ( &_workFailures ); + +} + +#endif /* __BEHAVIORS_SPECIFIC_H__ */ diff --git a/dlls/game/bg_local.h b/dlls/game/bg_local.h new file mode 100644 index 0000000..28f5fc1 --- /dev/null +++ b/dlls/game/bg_local.h @@ -0,0 +1,88 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/bg_local.h $ +// $Revision:: 5 $ +// $Date:: 10/13/03 9:11a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// bg_local.h -- local definitions for the bg (both games) files +// + +#ifndef __BG_LOCAL_H__ +#define __BG_LOCAL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define MIN_WALK_NORMAL 0.65 // can't walk on very steep slopes + +//#define STEPSIZE 18 + +#define JUMP_VELOCITY 270 + +#define TIMER_LAND 130 +#define TIMER_GESTURE (34*66+50) + +#define OVERCLIP 1.001 + +// all of the locals will be zeroed before each +// pmove, just to make sure we don't have +// any differences when running on client or server +typedef struct { + vec3_t forward, right, up; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + trace_t groundTrace; + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; +} pml_t; + +extern pmove_t *pm; +extern pml_t pml; + +// movement parameters +extern float pm_stopspeed; +extern float pm_duckScale; +extern float pm_swimScale; +extern float pm_wadeScale; + +extern float pm_accelerate; +extern float pm_airaccelerate; +extern float pm_wateraccelerate; +extern float pm_flyaccelerate; + +extern float pm_friction; +extern float pm_waterfriction; +extern float pm_flightfriction; +extern float pm_slipperyfriction; + +extern int c_pmove; + +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_AddTouchEnt( int entityNum ); +void PM_AddEvent( int newEvent ); + +qboolean PM_SlideMove( qboolean gravity ); +void PM_StepSlideMove( qboolean gravity ); + +#ifdef __cplusplus + } +#endif + +#endif /* !__BG_LOCAL_H__ */ \ No newline at end of file diff --git a/dlls/game/bg_misc.c b/dlls/game/bg_misc.c new file mode 100644 index 0000000..4d70f13 --- /dev/null +++ b/dlls/game/bg_misc.c @@ -0,0 +1,112 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/bg_misc.c $ +// $Revision:: 4 $ +// $Author:: Steven $ +// $Date:: 10/11/02 3:17a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Misc functions that are used in the client and server game dlls + +// included in both game dll and client + +#include "q_shared.h" +#include "bg_public.h" + + +char *gDLLNeededString = "xa37dd45ffe100bfffcc9753aabac325f07cb3fa231144fe2e33ae4783feead2b8a73ff021fac326df0ef9753ab9cdf6573ddff0312fab0b0ff39779eaff312x"; + +/* +================ +EvaluateTrajectory + +================ +*/ +void EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorCopy( tr->trBase, result ); + break; + case TR_LINEAR: + deltaTime = (float)( atTime - tr->trTime ) * 0.001f; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = sin( deltaTime * M_PI * 2.0 ); + VectorMA( tr->trBase, phase, tr->trDelta, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > ( tr->trTime + tr->trDuration ) ) { + atTime = tr->trTime + tr->trDuration; + } + deltaTime = (float)( atTime - tr->trTime ) * 0.001f; // milliseconds to seconds + if ( deltaTime < 0.0f ) { + deltaTime = 0.0f; + } + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = (float)( atTime - tr->trTime ) * 0.001f; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5f * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "EvaluateTrajectory: unknown trType: %i", tr->trType ); + break; + } +} + +/* +================ +EvaluateTrajectoryDelta + +================ +*/ +void EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorClear( result ); + break; + case TR_LINEAR: + VectorCopy( tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = (float)( atTime - tr->trTime ) / (float) tr->trDuration; + phase = cos( deltaTime * M_PI * 2.0 ); // derivative of sin = cos + phase *= 0.5f; + VectorScale( tr->trDelta, phase, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > ( tr->trTime + tr->trDuration ) ) { + VectorClear( result ); + return; + } + VectorCopy( tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = (float)( atTime - tr->trTime ) * 0.001f; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + diff --git a/dlls/game/bg_pmove.c b/dlls/game/bg_pmove.c new file mode 100644 index 0000000..d2bafb1 --- /dev/null +++ b/dlls/game/bg_pmove.c @@ -0,0 +1,2674 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/bg_pmove.c $ +// $Revision:: 62 $ +// $Date:: 10/13/03 9:42a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "q_shared.h" +#include "bg_public.h" + +// all of the locals will be zeroed before each +// pmove, just to make sure we don't have +// any differences when running on client or server +typedef struct { + vec3_t forward, left, up; + vec3_t flat_forward, flat_left, flat_up; + //float forward_speed, side_speed; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + trace_t groundTrace; + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; +} pml_t; + +pmove_t *pm; +pml_t pml; +qboolean slopeSlideFlag = qfalse; + +// movement parameters +const float pm_swimScale = 0.50f; +const float pm_wadeScale = 0.70f; +const float pm_slipperyfriction = 0.25f; +const float pm_leaninspeed = 75.0f; +const float pm_leanoutspeed = 150.0f; +const float pm_leanmaxdelta = 35.0f; + +int c_pmove = 0; + +void PM_CrashLand( void ); +qboolean PM_ShouldCrashLand( void ); + +/* +=============== +PM_AddTouchEnt +=============== +*/ +void PM_AddTouchEnt + ( + int entityNum + ) + + { + int i; + + if ( entityNum == ENTITYNUM_WORLD ) + { + return; + } + + if ( pm->numtouch == MAXTOUCH ) + { + return; + } + + // see if it is already added + for( i = 0; i < pm->numtouch; i++ ) + { + if ( pm->touchents[ i ] == entityNum ) + { + return; + } + } + + // add it + pm->touchents[ pm->numtouch ] = entityNum; + pm->numtouch++; + } + +/* +================== +PM_ClipVelocity + +Slide off of the impacting surface +================== +*/ +void PM_ClipVelocity + ( + const vec3_t in, + const vec3_t normal, + vec3_t out, + float overbounce + ) + + { + float backoff; + float change; + int i; + + backoff = DotProduct( in, normal ); + + if ( backoff < 0.0f ) + { + backoff *= overbounce; + } + else + { + backoff /= overbounce; + } + + for( i = 0; i < 3; i++ ) + { + change = normal[ i ] * backoff; + out[ i ] = in[ i ] - change; + } + } + +/* +================== +PM_SlideMove + +Returns qtrue if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 +qboolean PM_SlideMove( qboolean gravity ) { + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity; + vec3_t clipVelocity; + int i, j, k; + trace_t trace; + vec3_t end; + float time_left; + float into; + vec3_t endVelocity; + vec3_t endClipVelocity; + int gravtmp; + + numbumps = 4; + + VectorCopy (pm->ps->velocity, primal_velocity); + + if ( gravity ) { + if ( pm->ps->pm_flags & PMF_FLIGHT ) + gravtmp = 0; + else + gravtmp = pm->ps->gravity; + + VectorCopy( pm->ps->velocity, endVelocity ); + endVelocity[2] -= gravtmp * pml.frametime; + pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5f; + primal_velocity[2] = endVelocity[2]; + if ( pml.groundPlane ) { + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + + time_left = pml.frametime; + + // never turn against the ground plane + if ( pml.groundPlane ) { + numplanes = 1; + VectorCopy( pml.groundTrace.plane.normal, planes[0] ); + } else { + numplanes = 0; + } + + // never turn against original velocity + VectorNormalize2( pm->ps->velocity, planes[numplanes] ); + numplanes++; + + for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) { + + // calculate position we are trying to move to + VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + + // add exact pull from gravity + // if ( gravity && !(pm->ps->pm_flags & PMF_FLIGHT) ) { + // pm->ps->origin[2] += 0.5 * time_left * time_left; + // } + + // see if we can make it there + pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask, qtrue ); + + if ( trace.startsolid && trace.entityNum != ENTITYNUM_WORLD ) + { + // stuck in an entity, so try to pretend it's not there + pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, trace.entityNum, pm->tracemask, qtrue ); + } + + if (trace.allsolid) { + // entity is completely trapped in another solid + pm->ps->velocity[2] = 0.0f; // don't build up falling damage, but allow sideways acceleration + return qtrue; + } + + if ( pm->trypush && pm->trypush( trace.entityNum, pm->ps->origin, end ) ) + continue; + + if (trace.fraction > 0.0f) { + // actually covered some distance + VectorCopy (trace.endpos, pm->ps->origin); + } + + if (trace.fraction == 1.0f) { + break; // moved the entire distance + } + + /*if ( ( trace.plane.normal[ 2 ] < MIN_WALK_NORMAL ) && ( trace.plane.normal[ 2 ] > 0 ) ) + { + // treat steep walls as vertical + trace.plane.normal[ 2 ] = 0; + VectorNormalize( trace.plane.normal ); + }*/ + + // save entity for contact + PM_AddTouchEnt( trace.entityNum ); + + time_left -= time_left * trace.fraction; + + if (numplanes >= MAX_CLIP_PLANES) { + // this shouldn't really happen + VectorClear( pm->ps->velocity ); + return qtrue; + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + for ( i = 0 ; i < numplanes ; i++ ) { + if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99f ) { + VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + if ( i < numplanes ) { + continue; + } + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0 ; i < numplanes ; i++ ) { + into = DotProduct( pm->ps->velocity, planes[i] ); + if ( into >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // see how hard we are hitting things + if ( -into > pml.impactSpeed ) { + pml.impactSpeed = -into; + } + + // slide along the plane + PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); + + // slide along the plane + PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0 ; j < numplanes ; j++ ) { + if ( j == i ) { + continue; + } + if ( DotProduct( clipVelocity, planes[j] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); + PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); + + // see if it goes back into the first clip plane + if ( DotProduct( clipVelocity, planes[i] ) >= 0.0f ) { + continue; + } + + // slide the original velocity along the crease + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, pm->ps->velocity ); + VectorScale( dir, d, clipVelocity ); + + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + + // see if there is a third plane the the new move enters + for ( k = 0 ; k < numplanes ; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( DotProduct( clipVelocity, planes[k] ) >= 0.1f ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a triple plane interaction + VectorClear( pm->ps->velocity ); + return qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, pm->ps->velocity ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + } + + if ( gravity ) { + VectorCopy( endVelocity, pm->ps->velocity ); + } + + // don't change velocity if in a timer (FIXME: is this correct?) + if ( pm->ps->pm_time ) { + VectorCopy( primal_velocity, pm->ps->velocity ); + } + + return ( bumpcount != 0 ); +} +/* +void PM_StepSlideMove( qboolean gravity ) { + vec3_t start_o, start_v; + trace_t trace; +// float down_dist, up_dist; +// vec3_t delta, delta2; + vec3_t up, down; + vec3_t down_o, down_v; + + VectorCopy (pm->ps->origin, start_o); + VectorCopy (pm->ps->velocity, start_v); + + if ( PM_SlideMove( gravity ) == 0 ) { + return; // we got exactly where we wanted to go first try + } + + VectorCopy(start_o, down); + down[2] -= STEPSIZE; + pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask, qtrue); + VectorSet(up, 0, 0, 1); + // never step up when you still have up velocity + if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || + DotProduct(trace.plane.normal, up) < 0.7)) { + return; + } + + VectorCopy (start_o, up); + up[2] += STEPSIZE; + + // test the player position if they were a stepheight higher + pm->trace (&trace, up, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask, qtrue); + if ( trace.allsolid ) { + if ( pm->debugLevel ) { + Com_Printf("%i:bend can't step\n", c_pmove); + } + return; // can't step up + } + + // try slidemove from this position + VectorCopy (up, pm->ps->origin); + VectorCopy (start_v, pm->ps->velocity); + + PM_SlideMove( gravity ); + + VectorCopy (pm->ps->origin, down_o); + VectorCopy (pm->ps->velocity, down_v); + + // push down the final amount + VectorCopy (pm->ps->origin, down); + down[2] -= STEPSIZE; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask, qtrue ); + + if ( !trace.allsolid ) + { + if ( ( trace.fraction < 1.0 ) && trace.plane.normal[ 2 ] < MIN_WALK_NORMAL ) + { + slopeSlideFlag = qtrue; + //VectorCopy (start_o, pm->ps->origin); + //return; + } else + slopeSlideFlag = qfalse; + } + + + VectorCopy (trace.endpos, pm->ps->origin); + + if ( trace.fraction < 1.0 ) { + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + + // use the step move + pm->stepped = qtrue; + if ( pm->debugLevel ) { + Com_Printf("%i:stepped\n", c_pmove); + } +}*/ + +float PM_TryStepSlideMove( qboolean gravity, float stepSize ) +{ + trace_t trace; + vec3_t up, down; + vec3_t diff; + float distance; + + VectorCopy( pm->ps->origin, up ); + up[2] += stepSize; + + // test the player position if they were a stepheight higher + pm->trace( &trace, up, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask, qtrue ); + + if ( trace.allsolid ) + { + if ( pm->debugLevel ) + { + Com_WPrintf("%i:bend can't step\n", c_pmove); + } + return 0.0f; // can't step up + } + + // try slidemove from this position + VectorCopy( up, pm->ps->origin ); + //VectorCopy( start_v, pm->ps->velocity ); + + PM_SlideMove( gravity ); + + VectorSubtract( pm->ps->origin, up, diff ); + distance = VectorLength( diff ); + + // push down the final amount + VectorCopy (pm->ps->origin, down); + down[2] -= stepSize; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask, qtrue ); + + if ( !trace.allsolid ) + { + VectorCopy (trace.endpos, pm->ps->origin); + } + + if ( trace.fraction < 1.0f ) + { + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + + return distance; +} + +void PM_StepSlideMove( qboolean gravity ) +{ + vec3_t start_o, start_v; + float try1Distance, try2Distance, simpleTryDistance; + vec3_t try1_o, try1_v; + vec3_t simpleTry1_o, simpleTry1_v; + vec3_t simpleDiff; + + // Save off the starting info + + VectorCopy( pm->ps->origin, start_o ); + VectorCopy( pm->ps->velocity, start_v ); + + // Try the movement directly forward first + + if ( PM_SlideMove( gravity ) == 0 ) + { + return; // we got exactly where we wanted to go first try + } + + // Save off attempt + + VectorCopy( pm->ps->origin, simpleTry1_o ); + VectorCopy( pm->ps->velocity, simpleTry1_v ); + + VectorSubtract( simpleTry1_o, start_o, simpleDiff ); + simpleTryDistance = VectorLength( simpleDiff ); + + // Reset everything that could have changed so far + + VectorCopy( start_o, pm->ps->origin ); + VectorCopy( start_v, pm->ps->velocity ); + + // Try movement when stepping up a full stepsize + + try1Distance = PM_TryStepSlideMove( gravity, STEPSIZE ); + + // Save off attempt + + VectorCopy( pm->ps->origin, try1_o ); + VectorCopy( pm->ps->velocity, try1_v ); + + // Reset everything that could have changed so far + + VectorCopy( start_o, pm->ps->origin ); + VectorCopy( start_v, pm->ps->velocity ); + + // Try movement when stepping up half the stepsize + + try2Distance = PM_TryStepSlideMove( gravity, STEPSIZE / 2.0f ); + + // Pick the move that went the furthest forward + + if ( try1Distance > try2Distance ) + { + VectorCopy( try1_o, pm->ps->origin ); + VectorCopy( try1_v, pm->ps->velocity ); + } + + if ( ( try1Distance < 0.001f ) && ( try2Distance < 0.001f ) && ( simpleTryDistance > 0.0f ) ) + { + VectorCopy( simpleTry1_o, pm->ps->origin ); + VectorCopy( simpleTry1_v, pm->ps->velocity ); + } + + pm->stepped = qtrue; // allow client to smooth out the step + + if ( pm->debugLevel ) + { + Com_Printf("%i:stepped\n", c_pmove); + } +} + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +void PM_Friction + ( + void + ) + + { + vec3_t vec; + float *vel; + float speed; + float newspeed; + float control; + float drop; + + vel = pm->ps->velocity; + + VectorCopy( vel, vec ); + if ( pml.walking ) + { + // ignore slope movement + vec[ 2 ] = 0.0f; + } + + speed = VectorLength( vec ); + if ( speed < 1.0f ) + { + // allow sinking underwater + vel[ 0 ] = 0; + vel[ 1 ] = 0; + + return; + } + + drop = 0; + + // apply ground friction + if ( pml.walking ) + { + // if getting knocked back, no friction + if ( !( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) ) + { + control = ( speed < (float)pm->ps->pm_stopspeed ) ? (float)pm->ps->pm_stopspeed : speed; + if ( pml.groundTrace.surfaceFlags & SURF_SLICK ) + { + drop += control * pm_slipperyfriction * pml.frametime; + } + else + { + drop += control * (float)pm->ps->pm_friction * pml.frametime; + } + } + } + + // apply water friction + if ( pm->waterlevel ) + { + if ( pm->watertype & CONTENTS_SLIME ) + { + drop += speed * (float)pm->ps->pm_waterfriction * 5.0f * pm->waterlevel * pml.frametime; + //drop += speed * pm->ps->pm_waterfriction * 2 * pml.frametime; + } + else + { + drop += speed * (float)pm->ps->pm_waterfriction * (float)pm->waterlevel * pml.frametime; + } + } + + // scale the velocity + newspeed = speed - drop; + if ( newspeed < 0.0f ) + { + newspeed = 0; + } + + newspeed /= speed; + + vel[0] = vel[ 0 ] * newspeed; + vel[1] = vel[ 1 ] * newspeed; + vel[2] = vel[ 2 ] * newspeed; + } + + +/* +============== +PM_Accelerate + +Handles user intended acceleration +============== +*/ +void PM_Accelerate + ( + const vec3_t wishdir, + float wishspeed, + float accel + ) + + { + int i; + float addspeed; + float accelspeed; + float currentspeed; + + currentspeed = DotProduct( pm->ps->velocity, wishdir ); + addspeed = wishspeed - currentspeed; + if ( addspeed <= 0.0f ) + { + return; + } + + accelspeed = accel * pml.frametime * wishspeed; + if ( accelspeed > addspeed ) + { + accelspeed = addspeed; + } + + for( i = 0; i < 3; i++ ) + { + pm->ps->velocity[ i ] += accelspeed * wishdir[ i ]; + } + } + + +/* +============ +PM_CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ + +float PM_CmdScale + ( + const usercmd_t *cmd + ) + + { + int max; + float total; + float scale; + + max = abs( cmd->forwardmove ); + if ( abs( cmd->rightmove ) > max ) + { + max = abs( cmd->rightmove ); + } + + if ( abs( cmd->upmove ) > max ) + { + max = abs( cmd->upmove ); + } + + if ( !max ) + { + return 0; + } + + if ( ( pm->ps->pm_flags & PMF_DUCKED ) && ( cmd->upmove >= 0 ) ) + { + // This means we are being forced to duck (take into account this forced down movement) + total = sqrt( ( cmd->forwardmove * cmd->forwardmove ) + ( cmd->rightmove * cmd->rightmove ) + ( -127 * -127 ) ); + } + else + { + // This is the normal case + total = sqrt( ( cmd->forwardmove * cmd->forwardmove ) + ( cmd->rightmove * cmd->rightmove ) + ( cmd->upmove * cmd->upmove ) ); + } + + scale = (float)pm->ps->speed * (float)max / ( 127.0f * total ); + + return scale; + } + +/* +============= +PM_CheckJump +============= +*/ + +qboolean PM_CheckJump( void ) { + + //If we are ducked, then we can't jump + //PMF_DUCKED means the player is either + //pressing duck, or the player is ducked in + //an area where they cannot stand up. + if(pm->ps->pm_flags & PMF_DUCKED) + return qfalse; + +/* jhefty/jwaters -- another strafe-jump fix + if (pm->ps->commandTime < pm->ps->pm_landtime) + return qfalse; +*/ + /* if ( pm->ps->pm_flags & PMF_TIME_LAND ) { + // hasn't been long enough since landing to jump again + return qfalse; + } */ + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + return qfalse; + } + + // must wait for jump to be released + if ( pm->ps->pm_flags & PMF_JUMP_HELD ) { + return qfalse; + } + + pml.groundPlane = qfalse; // jumping away + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pm->ps->velocity[2] = pm->ps->jumpvelocity; + + return qtrue; +} + +/* +============= +PM_CheckWaterJump +============= +*/ +qboolean PM_CheckWaterJump + ( + void + ) + + { + vec3_t spot; + int cont; + + if ( pm->ps->pm_time ) + { + return qfalse; + } + + // check for water jump + if ( ( pm->waterlevel < 2 ) ) + { + return qfalse; + } + // if we are below the surface and not in slime, return + if ( !( pm->watertype & CONTENTS_SLIME ) && ( pm->waterlevel == 3 ) ) + { + return qfalse; + } + + VectorMA( pm->ps->origin, 80.0f, pml.flat_forward, spot ); + spot[ 2 ] += pm->ps->viewheight - 16.0f; + cont = pm->pointcontents( spot, pm->ps->clientNum ); + if ( !( cont & pm->tracemask ) ) + { + return qfalse; + } + + spot[ 2 ] += 48.0f; + cont = pm->pointcontents( spot, pm->ps->clientNum ); + if ( cont ) + { + return qfalse; + } + + // jump out of water + VectorScale( pml.flat_forward, 150, pm->ps->velocity ); + pm->ps->velocity[ 2 ] = 600; + + pm->ps->pm_flags |= PMF_TIME_WATERJUMP; + pm->ps->pm_time = 2000; + + return qtrue; + } + +//============================================================================ + + +/* +=================== +PM_WaterJumpMove + +Flying out of the water +=================== +*/ +void PM_WaterJumpMove + ( + void + ) + + { + // waterjump has no control, but falls + PM_StepSlideMove( !( pm->ps->pm_flags & PMF_NO_GRAVITY ) ); + + pm->ps->velocity[ 2 ] -= (float)pm->ps->gravity * pml.frametime; + if ( pm->ps->velocity[ 2 ] < 0.0f ) + { + // cancel as soon as we are falling down again + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } + } + +#define SLIME_SINK_SPEED -10.0f +/* +=================== +PM_WaterMove + +=================== +*/ +void PM_WaterMove + ( + void + ) + + { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + + if ( PM_CheckWaterJump() ) + { + PM_WaterJumpMove(); + return; + } + + // + // clamp our speed if we are in slime + // + if ( pm->watertype & CONTENTS_SLIME ) + { + if ( pm->ps->velocity[ 2 ] < SLIME_SINK_SPEED ) + { + pm->ps->velocity[ 2 ] = SLIME_SINK_SPEED; + } + } + + PM_Friction(); + + scale = PM_CmdScale( &pm->cmd ); + + // + // user intentions + // + if ( !scale ) + { + wishvel[ 0 ] = 0; + wishvel[ 1 ] = 0; + if ( pm->watertype & CONTENTS_SLIME ) + { + wishvel[ 2 ] = SLIME_SINK_SPEED; // sink towards bottom + } + else + { + wishvel[ 2 ] = -60; // sink towards bottom + } + } + else + { + for( i = 0; i < 3; i++ ) + { + wishvel[ i ] = ( scale * pml.flat_forward[ i ] * pm->cmd.forwardmove ) - ( scale * pml.flat_left[ i ] * pm->cmd.rightmove ); + } + + wishvel[ 2 ] += scale * pm->cmd.upmove; + } + if ( ( pm->watertype & CONTENTS_SLIME ) && ( pm->waterlevel > 2 ) && ( wishvel[ 2 ] < 0.0f ) ) + { + wishvel[ 2 ] = 0; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + if ( wishspeed > ( pm->ps->speed * pm_swimScale ) ) + { + wishspeed = pm->ps->speed * pm_swimScale; + } + + PM_Accelerate( wishdir, wishspeed, pm->ps->pm_wateraccelerate ); + + PM_SlideMove( qfalse ); + } + +/* +=================== +PM_StuckJumpMove + +Flying out of someplace we were stuck +=================== +*/ +void PM_StuckJumpMove + ( + void + ) + + { + // stuckjump has no control, but falls + PM_StepSlideMove( !( pm->ps->pm_flags & PMF_NO_GRAVITY ) ); + + pm->ps->velocity[ 2 ] -= pm->ps->gravity * pml.frametime; + if ( pm->ps->velocity[ 2 ] < -48.0f ) + { + // cancel as soon as we are falling at decent clip again + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } + } + +/* +=================== +PM_FlyMove + +Only with the flight powerup +=================== +*/ +void PM_FlyMove( void ) { + float speed; + float drop; + float friction; + float control; + float newspeed; + int i; + vec3_t wishvel; + float fmove; + float smove; + vec3_t wishdir; + float wishspeed; + float scale; + + // friction + + speed = VectorLength( pm->ps->velocity ); + if ( speed < 1.0f ) + { + VectorCopy( vec3_origin, pm->ps->velocity ); + } + else + { + drop = 0; + + // extra friction + friction = pm->ps->pm_friction * 1.5f; + + control = speed < pm->ps->pm_stopspeed ? pm->ps->pm_stopspeed : speed; + drop += control * friction * pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if ( newspeed < 0.0f ) + { + newspeed = 0; + } + newspeed /= speed; + + VectorScale( pm->ps->velocity, newspeed, pm->ps->velocity ); + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + pm->ps->pm_runtime = 0; + + for( i = 0; i < 3; i++ ) + { + wishvel[ i ] = ( pml.flat_forward[ i ] * fmove ) - ( pml.flat_left[ i ] * smove ); + } + + wishvel[ 2 ] += pm->cmd.upmove; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm->ps->pm_accelerate ); + + // move + PM_StepSlideMove( qtrue ); + //VectorMA( pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin ); +} + +/* +============= +PM_CheckStuckJump +============= +*/ +//#define MAX_XY_VELOCITY 50 +qboolean PM_CheckStuckJump + ( + void + ) + + { + vec3_t diff; + + if ( pm->ps->pm_time ) + { + return qfalse; + } + + if ( pm->waterlevel > 1 ) + { + return qfalse; + } + + VectorSubtract( pm->ps->origin, pml.previous_origin, diff ); + if ( VectorLength( diff ) ) + { + return qfalse; + } + if ( VectorLength( pm->ps->velocity ) < 100.0f ) + { + return qfalse; + } + + // we have been falling, we haven't moved and our velocity is getting dangerously high + // let's give ourselves a boost straight up and opposite our current velocity + /* pm->ps->velocity[ 0 ] = -pm->ps->velocity[ 0 ]; + if ( pm->ps->velocity[ 0 ] > MAX_XY_VELOCITY ) + pm->ps->velocity[ 0 ] = MAX_XY_VELOCITY; + else if ( pm->ps->velocity[ 0 ] < -MAX_XY_VELOCITY ) + pm->ps->velocity[ 0 ] = -MAX_XY_VELOCITY; + + pm->ps->velocity[ 1 ] = -pm->ps->velocity[ 1 ]; + if ( pm->ps->velocity[ 1 ] > MAX_XY_VELOCITY ) + pm->ps->velocity[ 1 ] = MAX_XY_VELOCITY; + else if ( pm->ps->velocity[ 1 ] < -MAX_XY_VELOCITY ) + pm->ps->velocity[ 1 ] = -MAX_XY_VELOCITY; + + pm->ps->velocity[ 2 ] = 500; + + pm->ps->pm_flags |= PMF_TIME_STUCKJUMP; + pm->ps->pm_time = 2000;*/ + + return qtrue; + } + +/* +============= +PM_CheckTerminalVelocity +============= +*/ +#define TERMINAL_VELOCITY 1200.0f + +void PM_CheckTerminalVelocity + ( + void + ) + { + float oldspeed; + float speed; + + // + // how fast were we falling + // + oldspeed = -pml.previous_velocity[ 2 ]; + + // + // how fast are we falling + // + speed = -pm->ps->velocity[ 2 ]; + + if ( speed <= 0.0f ) + { + return; + } + + if ( ( oldspeed <= TERMINAL_VELOCITY ) && ( speed > TERMINAL_VELOCITY ) ) + { + pm->pmoveEvent = EV_TERMINAL_VELOCITY; + } + } + +/* +=================== +PM_AirMove + +=================== +*/ +void PM_AirMove + ( + void + ) + + { + vec3_t wishvel; + float fmove; + float smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + + vec3_t original_start; + vec3_t original_end; + float old_speed; + vec3_t original_diff; + float original_dist; + vec3_t new_diff; + float new_dist; + float new_speed; + float max_new_speed; + + // Save some information about original state of things + + VectorCopy( pm->ps->origin, original_start ); + VectorMA( pm->ps->origin, pml.frametime, pm->ps->velocity, original_end ); + old_speed = VectorLength( pm->ps->velocity ); + + //PM_Friction(); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + pm->ps->pm_runtime = 0; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + wishvel[ 0 ] = ( pml.flat_forward[ 0 ] * fmove ) - ( pml.flat_left[ 0 ] * smove ); + wishvel[ 1 ] = ( pml.flat_forward[ 1 ] * fmove ) - ( pml.flat_left[ 1 ] * smove ); + wishvel[ 2 ] = 0.0f; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + // not on ground, so little effect on velocity + PM_Accelerate( wishdir, wishspeed, pm->ps->pm_airaccelerate ); + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if ( pml.groundPlane ) + { + PM_ClipVelocity( pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + + if ( !pml.walking && pml.groundPlane ) + { + vec3_t vel; + + VectorCopy( pm->ps->velocity, vel ); + vel[ 2 ] -= pm->ps->gravity * pml.frametime; + pm->ps->velocity[ 2 ] = ( pm->ps->velocity[ 2 ] + vel[ 2 ] ) * 0.5f; + PM_SlideMove( qfalse ); + VectorCopy( vel, pm->ps->velocity ); + } + else + { + PM_SlideMove( !( pm->ps->pm_flags & PMF_NO_GRAVITY ) ); + } + + if ( PM_CheckStuckJump() ) + { + PM_StuckJumpMove(); + } + + // Get the distance we tried to move + + VectorSubtract( original_start, original_end, original_diff ); + original_dist = VectorLength( original_diff ); + + // Get the distance we actually moved + + VectorSubtract( original_start, pm->ps->origin, new_diff ); + new_dist = VectorLength( new_diff ); + + if ( ( new_dist < original_dist ) && ( new_dist > 0 ) ) + { + // Modify our velocity based on how far we got to move + + max_new_speed = old_speed * ( new_dist / original_dist ); + + new_speed = VectorLength( pm->ps->velocity ); + + if ( new_speed > max_new_speed ) + { + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, max_new_speed, pm->ps->velocity ); + } + } + + PM_CheckTerminalVelocity(); + } + +void AddPlane + ( + const vec3_t norm, + vec3_t planes[ MAX_CLIP_PLANES ], + int *numplanes + ) + + { + int i; + + if ( *numplanes >= MAX_CLIP_PLANES ) + { + return; + } + + for( i = 0; i < *numplanes; i++ ) + { + if ( VectorCompare( planes[ i ], norm ) ) + { + // don't add the plane twice + return; + } + } + + VectorCopy( norm, planes[ *numplanes ] ); + ( *numplanes )++; + } + +void PM_StepMove + ( + void + ) + + { + trace_t trace; + vec3_t up; + vec3_t down; + vec3_t oldvelocity; + vec3_t oldorigin; + vec3_t velocity1; + vec3_t origin1; + + VectorCopy( pm->ps->velocity, oldvelocity ); + VectorCopy( pm->ps->origin, oldorigin ); + + if ( PM_SlideMove( !( pm->ps->pm_flags & PMF_NO_GRAVITY ) ) ) + { + VectorCopy( pm->ps->velocity, velocity1 ); + VectorCopy( pm->ps->origin, origin1 ); + + VectorCopy( oldvelocity, pm->ps->velocity ); + + VectorCopy( oldorigin, up ); + up[ 2 ] += STEPSIZE; + + //pm->trace( &trace, oldorigin, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask, qtrue ); + pm->trace( &trace, up, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask, qtrue ); + VectorCopy( trace.endpos, pm->ps->origin ); + + PM_SlideMove( !( pm->ps->pm_flags & PMF_NO_GRAVITY ) ); + + VectorCopy( pm->ps->origin, down ); + down[ 2 ] = oldorigin[ 2 ]; + + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask, qtrue ); + if ( trace.plane.normal[ 2 ] < MIN_WALK_NORMAL ) + { + // use the first move + VectorCopy( velocity1, pm->ps->velocity ); + VectorCopy( origin1, pm->ps->origin ); + } + else + { + VectorCopy( trace.endpos, pm->ps->origin ); + pm->stepped = qtrue; + } + } + } + +/* +=================== +PM_WalkMove + +=================== +*/ + +void PM_WalkMove + ( + void + ) + + { + int i; + vec3_t wishvel; + float fmove; + float smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float accelerate; + float waterScale; + + if ( ( pm->waterlevel > 2 ) && ( DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0.0f ) ) + { + // begin swimming + PM_WaterMove(); + + return; + } + + if ( pm->ps->instantJump ) + if ( PM_CheckJump () ) { + // jumped away + pm->ps->jumped = qtrue; + pm->ps->pm_time = 150; + pm->ps->pm_flags |= PMF_TIME_JUMP_START; + if ( pm->waterlevel > 1 ) { + PM_WaterMove(); + } else { + PM_AirMove(); + } + return; + } + + PM_Friction(); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + if ( ( pm->cmd.buttons & BUTTON_RUN ) && fmove && !smove ) + { + pm->ps->pm_runtime += pml.msec; + } + else + { + pm->ps->pm_runtime = 0; + } + + // + // only run faster if we have exceeded our running time + // +/* if ( ( pm->ps->stats[STAT_WATER_LEVEL] >= MINIMUM_WATER_FOR_TURBO ) && ( pm->ps->pm_runtime > WATER_TURBO_TIME ) ) + { + scale *= WATER_TURBO_SPEED; + }*/ + + // project the forward and right directions onto the ground plane + PM_ClipVelocity (pml.flat_forward, pml.groundTrace.plane.normal, pml.flat_forward, OVERCLIP ); + PM_ClipVelocity (pml.flat_left, pml.groundTrace.plane.normal, pml.flat_left, OVERCLIP ); + + VectorNormalize (pml.flat_forward); + VectorNormalize (pml.flat_left); + + for( i = 0 ; i < 3 ; i++ ) + { + wishvel[ i ] = pml.flat_forward[ i ] * fmove - pml.flat_left[ i ] * smove; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + // clamp the speed lower if wading or walking on the bottom + if ( pm->waterlevel ) + { + waterScale = pm->waterlevel / 3.0f; + waterScale = 1.0f - ( 1.0f - pm_swimScale ) * waterScale; + if ( wishspeed > ( pm->ps->speed * waterScale ) ) + { + wishspeed = pm->ps->speed * waterScale; + } + } + + // when a player gets hit, they temporarily lose + // full control, which allows them to be moved a bit + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || ( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) ) + { + accelerate = pm->ps->pm_airaccelerate; + } + else + { + accelerate = pm->ps->pm_accelerate; + } + + PM_Accelerate( wishdir, wishspeed, accelerate ); + + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || ( pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) ) + { + pm->ps->velocity[ 2 ] -= pm->ps->gravity * pml.frametime; + } + + // don't do anything if standing still + if ( !pm->ps->velocity[ 0 ] && !pm->ps->velocity[ 1 ] && !pm->ps->velocity[ 2 ] ) + return; + + PM_StepSlideMove ( !( pm->ps->pm_flags & PMF_NO_GRAVITY ) ); + } + +/* +============== +PM_DeadMove +============== +*/ +void PM_DeadMove + ( + void + ) + + { + float forward; + + if ( !pml.walking ) + { + return; + } + + // extra friction + forward = VectorLength( pm->ps->velocity ); + forward -= 20.0f; + if ( forward <= 0.0f ) + { + VectorClear( pm->ps->velocity ); + } + else + { + VectorNormalize( pm->ps->velocity ); + VectorScale( pm->ps->velocity, forward, pm->ps->velocity ); + } + } + + +/* +=============== +PM_NoclipMove +=============== +*/ +void PM_VehicleMove + ( + void + ) + + { + + vec3_t wishvel; + //float fmove; + //float smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float accelerate; + + //fmove = pm->cmd.forwardmove; + //smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + pm->ps->pm_runtime = 0; + + wishvel[0] = pm->ps->velocity[0]; + wishvel[1] = pm->ps->velocity[1]; + wishvel[2] = pm->ps->velocity[2]; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + accelerate = pm->ps->pm_accelerate; + PM_Accelerate( wishdir, wishspeed, accelerate ); + + // don't do anything if standing still + if ( !pm->ps->velocity[ 0 ] && !pm->ps->velocity[ 1 ] ) + return; + + //PM_StepSlideMove ( !( pm->ps->pm_flags & PMF_NO_GRAVITY ) ); + } + + + +/* +=============== +PM_NoclipMove +=============== +*/ +void PM_NoclipMove + ( + void + ) + + { + float speed; + float drop; + float friction; + float control; + float newspeed; + int i; + vec3_t wishvel; + float fmove; + float smove; + vec3_t wishdir; + float wishspeed; + float scale; + + pm->ps->viewheight = pm->ps->pm_defaultviewheight; + + // friction + + speed = VectorLength( pm->ps->velocity ); + if ( speed < 1.0f ) + { + VectorCopy( vec3_origin, pm->ps->velocity ); + } + else + { + drop = 0; + + // extra friction + friction = pm->ps->pm_friction * 1.5f; + + control = speed < pm->ps->pm_stopspeed ? pm->ps->pm_stopspeed : speed; + drop += control * friction * pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if ( newspeed < 0.0f ) + { + newspeed = 0; + } + newspeed /= speed; + + VectorScale( pm->ps->velocity, newspeed, pm->ps->velocity ); + } + + // accelerate + // allow the player to move twice as fast in noclip + scale = PM_CmdScale( &pm->cmd ) * 2.0f; + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + pm->ps->pm_runtime = 0; + + for( i = 0; i < 3; i++ ) + { + wishvel[ i ] = ( pml.flat_forward[ i ] * fmove ) - ( pml.flat_left[ i ] * smove ); + } + + wishvel[ 2 ] += pm->cmd.upmove; + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm->ps->pm_accelerate ); + + // move + VectorMA( pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin ); + } + +//============================================================================ + +/* +============= +PM_CorrectAllSolid +============= +*/ +void PM_CorrectAllSolid + ( + void + ) + + { + if ( pm->debugLevel ) + { + Com_Printf( "%i:allsolid\n", c_pmove ); + } + + // FIXME: jitter around + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + } + +/* +============= +PM_GroundTrace +============= +*/ +void PM_GroundTrace + ( + qboolean onlyTrace + ) + + { + vec3_t point; + vec3_t tmporg; + trace_t trace; + + point[ 0 ] = pm->ps->origin[ 0 ]; + point[ 1 ] = pm->ps->origin[ 1 ]; + point[ 2 ] = pm->ps->origin[ 2 ] - 9.0f; // long trace to avoid potential terrain hitches + + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask, qtrue ); + + if (!(trace.surfaceFlags & SURF_TERRAIN )) // if we're not on terrain, go back to 0.25f trace distance + { + point[ 2 ] = pm->ps->origin[ 2 ] - 0.25f; + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask, qtrue ); + } + + pml.groundTrace = trace; + pm->ps->groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if ( trace.allsolid ) + { + // We're gonna start with our origin a tad higher and see if it works + tmporg[ 0 ] = pm->ps->origin[ 0 ]; + tmporg[ 1 ] = pm->ps->origin[ 1 ]; + tmporg[ 2 ] = pm->ps->origin[ 2 ] + 2.0f; + + // Trace down to the same point + pm->trace( &trace, tmporg, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask, qtrue ); + pml.groundTrace = trace; + pm->ps->groundTrace = trace; + + // It's hopeless if we start all solid again + if ( trace.allsolid ) + { + PM_CorrectAllSolid(); + pm->ps->walking = pml.walking; + pm->ps->groundPlane = pml.groundPlane; + + return; + } + } + + // if the trace didn't hit anything, we are in free fall + if ( trace.fraction == 1.0f ) + { + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + if ( pm->debugLevel ) + { + Com_Printf( "%i:lift\n", c_pmove ); + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + + pm->ps->walking = pml.walking; + pm->ps->groundPlane = pml.groundPlane; + + return; + } + + // slopes that are too steep will not be considered onground + // check slopeSlideFlag to avoid the "falling" bug + if ( trace.plane.normal[ 2 ] < MIN_WALK_NORMAL ) + { + vec3_t oldvel; + float d; + + if ( pm->debugLevel ) + { + Com_Printf( "%i:steep\n", c_pmove ); + } + + // if they can't slide down the slope, let them + // walk (sharp crevices) + VectorCopy( pm->ps->velocity, oldvel ); + VectorSet( pm->ps->velocity, 0.0f, 0.0f, -1.0f / pml.frametime ); + PM_SlideMove( qfalse ); + + d = VectorLength( pm->ps->velocity ); + VectorCopy( oldvel, pm->ps->velocity ); + if ( d > ( 0.1f / pml.frametime ) ) + { + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qtrue; + pml.walking = qfalse; + + pm->ps->walking = pml.walking; + pm->ps->groundPlane = pml.groundPlane; + + return; + } + } + + // check if getting thrown off the ground + if ( ( pm->ps->velocity[ 2 ] > 0.0f ) && ( DotProduct( pm->ps->velocity, trace.plane.normal ) > 10.0f ) ) + { + if ( pm->debugLevel ) + { + Com_Printf( "%i:kickoff\n", c_pmove ); + } + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + + pm->ps->walking = pml.walking; + pm->ps->groundPlane = pml.groundPlane; + + return; + } + + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // hitting solid ground will end a waterjump + if ( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) + { + //pm->ps->pm_flags &= ~( PMF_TIME_WATERJUMP | PMF_TIME_LAND); + pm->ps->pm_flags &= ~( PMF_TIME_WATERJUMP ); + pm->ps->pm_time = 0; + } + + // hitting solid ground will end a stuckjump + if ( pm->ps->pm_flags & PMF_TIME_STUCKJUMP ) + { + pm->ps->pm_flags &= ~( PMF_TIME_STUCKJUMP ); + pm->ps->pm_time = 0; + } + + if ( !onlyTrace && !( pml.groundTrace.surfaceFlags & SURF_SLICK ) ) + pm->ps->velocity[ 2 ] = 0; + + pm->ps->groundEntityNum = trace.entityNum; + + PM_AddTouchEnt( trace.entityNum ); + + pm->ps->walking = pml.walking; + pm->ps->groundPlane = pml.groundPlane; + } + + +/* +============= +PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving +============= +*/ +void PM_SetWaterLevel + ( + void + ) + + { + vec3_t point; + int cont; + int sample1; + int sample2; + + // + // get waterlevel, accounting for ducking + // + pm->waterlevel = 0; + pm->watertype = 0; + + sample2 = pm->ps->viewheight - MINS_Z; + sample1 = sample2 * 3 / 4; + + VectorCopy( pm->ps->origin, point ); + point[ 2 ] += MINS_Z + 1.0f; + cont = pm->pointcontents( point, pm->ps->clientNum ); + if ( cont & MASK_WATER ) + { + pm->watertype = cont; + pm->waterlevel = 1; + + point[ 2 ] = pm->ps->origin[ 2 ] + MINS_Z + sample1; + cont = pm->pointcontents( point, 0 ); + if ( cont & MASK_WATER ) + { + pm->waterlevel = 2; + point[ 2 ] = pm->ps->origin[ 2 ] + MINS_Z + sample2; + cont = pm->pointcontents( point, 0 ); + if ( cont & MASK_WATER ) + { + pm->waterlevel = 3; + } + } + } + } + + +/* +============== +PM_CheckDuck + +Sets mins, maxs, and pm->ps->viewheight +============== +*/ +void PM_CheckDuck + ( + void + ) + + { + pm->mins[ 0 ] = MINS_X; + pm->mins[ 1 ] = MINS_Y; + pm->mins[ 2 ] = MINS_Z; + pm->maxs[ 0 ] = MAXS_X; + pm->maxs[ 1 ] = MAXS_Y; + pm->maxs[ 2 ] = MAXS_Z; + + pm->ps->viewheight = pm->ps->pm_defaultviewheight; + + if ( pm->ps->pm_type == PM_DEAD ) + { + pm->maxs[ 2 ] = DEAD_MINS_Z; + return; + } + + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + + pm->maxs[ 2 ] = CROUCH_MAXS_Z; + pm->ps->viewheight = CROUCH_VIEWHEIGHT; + } + } + + +//----------------------------------------------------- +// +// Name: PM_CheckCrouchJump +// Class: None +// +// Description: Checks to see if the player is ducking and jumping, which is a crouch jump. +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void PM_CheckCrouchJump(void) +{ + //if the player is not on the ground and is ducking, + //set the crouch jump flag. + if ( pm->ps->jumped && + ( ( ( pm->ps->pm_flags & PMF_DUCKED ) && ( pm->ps->pm_flags & PMF_TIME_JUMP_START ) ) || + ( !( pm->ps->pm_flags & PMF_TIME_JUMP_START ) && ( pm->ps->pm_flags & PMF_JUMP_HELD ) ) ) ) + { + if(pm->ps->crouchjumpset == qfalse) + { + pm->ps->pm_flags |= PMF_TIME_CROUCH_JUMP; + pm->ps->crouchjumpset = qtrue; + } + } + + //This really sucks to put this code here, since the + //primary function of this procedure is to check if + //crouch jump is active. Since CheckDuck sets the + //default height down if duck is pressed, this moves + //it back up if the player is crouch jumping + if(pm->ps->crouchjumpset == qtrue) + { + pm->ps->viewheight = pm->ps->pm_defaultviewheight; + pm->maxs[ 2 ] = MAXS_Z; + } + + if(pm->ps->groundPlane) + { + pm->ps->crouchjumpset = qfalse; + } +} + + +//----------------------------------------------------- +// +// Name: PM_CrouchJumpMove +// Class: None +// +// Description: Allows the player to clear objects a little higher than a normal jump could. +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void PM_CrouchJumpMove(void) +{ + PM_StepSlideMove( !( pm->ps->pm_flags & PMF_NO_GRAVITY ) ); + pm->ps->velocity[2] += pm->ps->crouchjumpvelocity; + + //turn off the jump crouch flag. + pm->ps->pm_flags &= ~PMF_TIME_CROUCH_JUMP; +} + +//=================================================================== + +qboolean PM_ShouldCrashLand( void ) +{ + + +/* jhefty/jwaters -- another strafe-jump fix + if (pml.impactSpeed) + pm->ps->pm_landtime = pm->ps->commandTime + 175; +*/ + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + //vec3_t diff; + float delta; + + // We're on the ground, see if we hit it hard enough for a crash land + + //VectorSubtract( pm->ps->velocity, pml.previous_velocity, diff ); + //delta = VectorLength( diff ); + //delta = abs( pm->ps->velocity[ 2 ] - pml.previous_velocity[ 2 ] ); + delta = pm->ps->velocity[ 2 ] - pml.previous_velocity[ 2 ]; + + if ( delta < 0.0f || pml.previous_velocity[ 2 ] > -400 ) + return qfalse; + + if ( delta > 600.0f ) + { + return qtrue; + } + } + + return qfalse; +} + +/* +================= +PM_CrashLand + +Check for hard landings that generate sound events + + fall from 128: 400 = 160000 + fall from 256: 580 = 336400 + fall from 384: 720 = 518400 + fall from 512: 800 = 640000 + fall from 640: 960 = + + damage = deltavelocity*deltavelocity * 0.0001 + +================= +*/ +void PM_CrashLand + ( + void + ) + + { + float delta; + /* float dist; + float vel; + float acc; + float t; + float a, b, c, den; */ + //vec3_t diff; + + // calculate the exact velocity on landing + /* dist = pm->ps->origin[ 2 ] - pml.previous_origin[ 2 ]; + vel = pml.previous_velocity[ 2 ]; + acc = -pm->ps->gravity; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if ( den < 0 ) + { + return; + } + + t = ( -b - sqrt( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + + if (delta < -200) + { + pm->ps->pm_flags |= PMF_TIME_LAND; + // don't allow another jump for a little while + if (delta < -400) + pm->ps->pm_time = 200; + else + pm->ps->pm_time = 144; + } */ + + // Get the change in speed + + //VectorSubtract( pm->ps->velocity, pml.previous_velocity, diff ); + //delta = VectorLength( diff ); + //delta = abs( pm->ps->velocity[ 2 ] - pml.previous_velocity[ 2 ] ); + + delta = pm->ps->velocity[ 2 ] - pml.previous_velocity[ 2 ]; + + if ( delta < 0.0f || pml.previous_velocity[ 2 ] > -400 ) + return; + + //delta = delta * delta * 0.0001f; + + // never take falling damage if completely underwater + if ( pm->waterlevel == 3 ) + { + return; + } + + // reduce falling damage if there is standing water + if ( pm->waterlevel == 2 ) + { + delta *= 0.25f; + } + + if ( pm->waterlevel == 1 ) + { + delta *= 0.5f; + } + + if ( delta < 1.0f ) + { + return; + } + + pm->landed = qtrue; + pm->landedVelocity = pml.previous_velocity[2]; + + // SURF_NODAMAGE is used for bounce pads where you don't ever + // want to take damage or play a crunch sound + if ( !( pml.groundTrace.surfaceFlags & SURF_NODAMAGE ) ) + { + if ( delta > 1500.0f ) + { + pm->pmoveEvent = EV_FALL_FATAL; + } + else if ( delta > 1300.0f ) + { + pm->pmoveEvent = EV_FALL_VERY_FAR; + } + else if ( delta > 1100.0f ) + { + pm->pmoveEvent = EV_FALL_FAR; + } + else if ( delta > 900.0f ) + { + pm->pmoveEvent = EV_FALL_MEDIUM; + } + else if ( delta > 750.0f ) + { + pm->pmoveEvent = EV_FALL_SHORT; + } + else if ( delta > 600.0f ) + { + pm->pmoveEvent = EV_FALL_VERY_SHORT; + } + } + + } + +/* +============== +PM_WaterEvents + +Generate sound events for entering and leaving water +============== +*/ +void PM_WaterEvents + ( + void + ) + + { + + if ( ( pm->ps->pm_type == PM_SPECTATOR ) || ( pm->ps->pm_type == PM_SPECTATOR_FOLLOW ) ) + return; + + + // FIXME? + // + // if just entered a water volume, play a sound + // + if ( !pml.previous_waterlevel && pm->waterlevel ) + { + pm->pmoveEvent = EV_WATER_TOUCH; + } + + // + // if just completely exited a water volume, play a sound + // + if ( pml.previous_waterlevel && ! pm->waterlevel ) + { + pm->pmoveEvent = EV_WATER_LEAVE; + } + + // + // check for head just going under water + // + if ( ( pml.previous_waterlevel != 3 ) && ( pm->waterlevel == 3 ) ) + { + pm->pmoveEvent = EV_WATER_UNDER; + } + + // + // check for head just coming out of water + // + if ( ( pml.previous_waterlevel == 3 ) && ( pm->waterlevel != 3 ) ) + { + pm->pmoveEvent = EV_WATER_CLEAR; + } + } + +/* +================ +PM_DropTimers +================ +*/ +void PM_DropTimers + ( + void + ) + + { + // drop misc timing counter + if ( pm->ps->pm_time ) + { + if ( pml.msec >= pm->ps->pm_time ) + { + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } + else + { + pm->ps->pm_time -= pml.msec; + } + } + } + + + +//----------------------------------------------------- +// +// Name: PM_UpdateLeanIn +// Class: None +// +// Description: Calculates the lean in movement +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PM_UpdateLeanIn( playerState_t* ps, const usercmd_t* cmd ) +{ + vec3_t viewOrigin; + vec3_t leanTraceMins; + vec3_t leanTraceMaxs; + vec3_t leftVector; + vec3_t leanTraceEnd; + vec3_t playerViewAngles; + trace_t leanTrace; + + float leanDelta = ps->leanDelta; + + if( cmd->lean > 0 ) + { + //We are leaning left, so calculate the new lean delta. + //If lean delta is greater then the max lean delta + //then set lean delta to be the max. + leanDelta += ((( float ) pml.msec / ( float ) pm_leaninspeed) * pm_leanmaxdelta); + if( leanDelta > pm_leanmaxdelta ) + leanDelta = pm_leanmaxdelta; + } + else + { + //We are leaning right, so calculate the new lean delta. + //If lean delta is less than the negative max lean delta + //then set lean delta to be the negative max lean delta. + leanDelta -= ((( float ) pml.msec / ( float ) pm_leaninspeed) * pm_leanmaxdelta); + if( leanDelta < -pm_leanmaxdelta ) + leanDelta = -pm_leanmaxdelta; + } + + //We calculate our lean by scaling our view origin vector by the amount of the lean delta, + //then adding the left vector to get the delta amount of the lean. + + //Get the view origin + VectorCopy( ps->origin, viewOrigin ); + viewOrigin[2] += ps->viewheight; + + //Get the view angles, remove all roll from the view angles. + VectorCopy( ps->viewangles, playerViewAngles ); + playerViewAngles[ROLL] = 0; + + AngleVectors( ps->viewangles, NULL, leftVector, NULL ); + VectorMA( viewOrigin, leanDelta, leftVector, leanTraceEnd ); + + + //Run a trace to check if we collide with any object when we lean. + VectorSet( leanTraceMins, -5, -5, -4 ); + VectorSet( leanTraceMaxs, 5, 5, 4 ); + + if( pm != 0) + { + pm->trace(&leanTrace, viewOrigin, leanTraceMins, leanTraceMaxs, + leanTraceEnd, ps->clientNum, MASK_PLAYERSOLID, qfalse); + + //Scale the lean delta by the end result of the trace. + //This will scale our delta to the object that the trace has hit. + leanDelta *= leanTrace.fraction; + } + + ps->leanDelta = leanDelta; +} + + +//----------------------------------------------------- +// +// Name: PM_UpdateLeanOut +// Class: +// +// Description: Calculates the lean out movement. +// +// Parameters: ps - the current player state. +// +// Returns: +//----------------------------------------------------- +void PM_UpdateLeanOut( playerState_t* ps ) +{ + float leanDelta = ps->leanDelta; + + if( leanDelta > 0 ) + { + leanDelta -= (( (float) pml.msec / (float) pm_leanoutspeed) * pm_leanmaxdelta); + if( leanDelta < 0 ) + leanDelta = 0; + } + else if ( leanDelta < 0 ) + { + leanDelta += (((float)pml.msec / ( float ) pm_leanoutspeed) * pm_leanmaxdelta); + if( leanDelta > 0) + leanDelta = 0; + } + + ps->leanDelta = leanDelta; +} + + +//----------------------------------------------------- +// +// Name: PM_UpdateLeanView +// Class: +// +// Description: Calculates the lean movement. +// +// Parameters: ps - the player state +// cmd - the input commands struct. +// +// Returns: None +//----------------------------------------------------- +void PM_UpdateLeanView( playerState_t* ps, const usercmd_t* cmd ) +{ + // We are not leaning, so move back to center position + if( cmd->lean == 0 ) + { + PM_UpdateLeanOut(ps); + } + else + { + PM_UpdateLeanIn(ps, cmd); + } +} + + +/* +================ +PM_UpdateViewAngles + +This can be used as another entry point when only the viewangles +are being updated isntead of a full move +================ +*/ +void PM_UpdateViewAngles + ( + playerState_t *ps, + const usercmd_t *cmd + ) + + { + short temp; + int i; + + if ( ps->pm_flags & PMF_FROZEN ) + { + // no view changes at all + return; + } + + /* + if ( ps->stats[ STAT_HEALTH ] <= 0 ) + { + // no view changes at all + return; + } + */ + + // circularly clamp the angles with deltas + for( i = 0; i < 3; i++ ) + { + temp = cmd->angles[ i ] + ps->delta_angles[ i ]; + if ( i == PITCH ) + { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) + { + ps->delta_angles[ i ] = 16000 - cmd->angles[ i ]; + temp = 16000; + } + else if ( temp < -16000 ) + { + ps->delta_angles[ i ] = -16000 - cmd->angles[ i ]; + temp = -16000; + } + } + + ps->viewangles[ i ] = SHORT2ANGLE( temp ); + } + + + PM_UpdateLeanView( ps, cmd ); + } + + +void Pmove_GroundTrace + ( + pmove_t *pmove + ) + + { + memset (&pml, 0, sizeof(pml)); + pml.msec = 1; + pml.frametime = 0.001f; + pm = pmove; + if ( !( pm->ps->pm_flags & PMF_NO_MOVE ) ) + { + PM_CheckDuck(); + PM_CheckCrouchJump(); + } + + PM_GroundTrace( qtrue ); + } + + +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +void PmoveSingle (pmove_t *pmove) + { + vec3_t tempVec; + qboolean walking; + vec3_t temp; + + pm = pmove; + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint fot the previous frame + c_pmove++; + + // clear results + pm->numtouch = 0; + pm->watertype = 0; + pm->waterlevel = 0; + pm->landed = qfalse; + + if(pm->ps->groundPlane) + { + //only set this to false if we are on the ground. + //when jump is hit, this is set to true. This + //allows the code to know if the player jumped or not. + pm->ps->jumped = qfalse; + } + + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) + { + pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies + } + + // adding fake talk balloons + /* if ( pmove->cmd.buttons & BUTTON_TALK ) + { + pmove->cmd.buttons = 0; + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + pmove->cmd.upmove = 0; + pmove->cmd.lean = 0; + } */ + + // clear all pmove local vars + memset (&pml, 0, sizeof(pml)); + + // determine the time + pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; + if ( pml.msec < 1 ) + { + pml.msec = 1; + } + else if ( pml.msec > 200 ) + { + pml.msec = 200; + } + + pm->ps->commandTime = pmove->cmd.serverTime; + + // save old org in case we get stuck + VectorCopy( pm->ps->origin, pml.previous_origin ); + + // save old velocity for crashlanding + VectorCopy( pm->ps->velocity, pml.previous_velocity ); + VectorCopy( pm->ps->velocity, temp ); + + pml.frametime = pml.msec * 0.001f; + + // update the viewangles + if (!pm->ps->in_vehicle ) + { + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + AngleVectors( pm->ps->viewangles, pml.forward, pml.left, pml.up ); + VectorClear( tempVec ); + tempVec[ YAW ] = pm->ps->viewangles[ YAW ]; + AngleVectors( tempVec, pml.flat_forward, pml.flat_left, pml.flat_up ); + } + + if ( pm->ps->in_vehicle ) + { + PM_VehicleMove(); + return; + } + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + pm->ps->pm_flags &= ~PMF_JUMP_HELD; + } + + if ( pm->ps->pm_type == PM_DEAD ) + { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + pm->cmd.lean = 0; + } + + if ( pm->ps->pm_type == PM_NOCLIP ) + { + PM_NoclipMove (); + PM_DropTimers (); + return; + } + + if ( ( pm->ps->pm_flags & PMF_FROZEN ) || ( pm->ps->pm_flags & PMF_NO_MOVE ) ) + { + return; // no movement at all + } + + PM_CheckDuck(); + + PM_CheckCrouchJump(); + + // set watertype, and waterlevel + PM_SetWaterLevel(); + pml.previous_waterlevel = pmove->waterlevel; + + // set groundentity + PM_GroundTrace( qfalse ); + + if ( pm->ps->pm_type == PM_DEAD ) + { + PM_DeadMove(); + } + + PM_DropTimers(); + + if ( pm->ps->pm_flags & PMF_FLIGHT ) + // flight powerup doesn't allow jump and has different friction + PM_FlyMove(); + else if ( pm->ps->pm_flags & PMF_TIME_WATERJUMP ) + { + PM_WaterJumpMove(); + } + else if ( pml.walking ) + { + // walking on ground + PM_WalkMove(); + } + else if ( pm->waterlevel > 1 ) + { + // swimming + PM_WaterMove(); + } + else if ( pm->ps->pm_flags & PMF_TIME_STUCKJUMP ) + { + PM_StuckJumpMove(); + } + else if( pm->ps->pm_flags & PMF_TIME_CROUCH_JUMP) + { + PM_CrouchJumpMove(); + } + else + { + // airborne + if ( !pm->ps->in_vehicle ) + PM_AirMove(); + } + + walking = pml.walking; + + // set groundentity, watertype, and waterlevel + PM_GroundTrace( qfalse ); + PM_SetWaterLevel(); + + // don't fall down stairs or do really short falls + if ( !pml.walking && ( walking || ( ( pml.previous_velocity[ 2 ] >= 0.0f ) && ( pm->ps->velocity[ 2 ] <= 0.0f ) ) ) ) + { + vec3_t point; + trace_t trace; + + point[ 0 ] = pm->ps->origin[ 0 ]; + point[ 1 ] = pm->ps->origin[ 1 ]; + point[ 2 ] = pm->ps->origin[ 2 ] - 0.5f; + + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask, qtrue ); + if ( ( trace.fraction < 1.0f ) && ( !trace.allsolid ) ) + { + VectorCopy( trace.endpos, pm->ps->origin ); + + // allow client to smooth out the step + pm->stepped = qtrue; + + // requantify the player's position + PM_GroundTrace( qfalse ); + PM_SetWaterLevel(); + } + } + + // entering / leaving water splashes + PM_WaterEvents(); + + + // test stuff + + // snap some parts of playerstate to save network bandwidth + SnapVector( pm->ps->velocity ); + + // If the player is off the ground cap his xy velocity so he can't strafe-jump and use other cheats + // to go too fast + + if ( !pm->ps->strafeJumpingAllowed && ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) && ( pm->ps->groundEntityNum == ENTITYNUM_WORLD ) ) + { + float speed; + vec3_t xyVelocity; + + // Get xy velocity + + //jhefty/jwaters modifications + + VectorCopy( pm->ps->velocity, xyVelocity ); + //xyVelocity[ 2 ] = 0.0f; + + // See if the xy velocity is too fast + + speed = VectorLength( xyVelocity ); + + if ( speed > pm->ps->speed ) + { + // Cap the real velocity + + VectorNormalize( xyVelocity ); + VectorScale( xyVelocity, pm->ps->speed, pm->ps->velocity ); + + + //pm->ps->velocity[ 0 ] = xyVelocity[ 0 ]; + //pm->ps->velocity[ 1 ] = xyVelocity[ 1 ]; + + } + } + + if ( PM_ShouldCrashLand() ) + { + // just hit the ground + if ( pm->debugLevel ) + { + Com_Printf( "%i:Land\n", c_pmove ); + } + + PM_CrashLand(); + } + + } + +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +void Pmove (pmove_t *pmove) { + int finalTime; + + finalTime = pmove->cmd.serverTime; + + if ( finalTime < pmove->ps->commandTime ) { + return; // should not happen + } + + if ( finalTime > ( pmove->ps->commandTime + 1000 ) ) { + pmove->ps->commandTime = finalTime - 1000; + } + + // chop the move up if it is too long, to prevent framerate + // dependent behavior + while ( pmove->ps->commandTime != finalTime ) { + int msec; + + msec = finalTime - pmove->ps->commandTime; + + if ( msec > 50 ) { + msec = 50; + } + pmove->cmd.serverTime = pmove->ps->commandTime + msec; + PmoveSingle( pmove ); + //Com_Printf("Vel: %f\n",pm->ps->velocity[2]); + } + +} diff --git a/dlls/game/bg_public.h b/dlls/game/bg_public.h new file mode 100644 index 0000000..637e314 --- /dev/null +++ b/dlls/game/bg_public.h @@ -0,0 +1,587 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/bg_public.h $ +// $Revision:: 112 $ +// $Author:: Steven $ +// $Date:: 5/10/03 2:02p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Definitions shared by both the server game and client game modules + +#ifndef __BG_PUBLIC_H__ +#define __BG_PUBLIC_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +// because games can change separately from the main system version, we need a +// second version that must match between game and cgame +#define GAME_VERSION "EF2-base-1" + +#define DEFAULT_GRAVITY 800 + +// CS_SERVERINFO and CS_SYSTEMINFO and CS_NAME are defined in q_shared.h +#define CS_SOUNDTRACK 8 +#define CS_FOGINFO 9 +#define CS_SKYINFO 10 +#define CS_GAME_VERSION 11 +#define CS_LEVEL_START_TIME 12 // so the timer only shows the current level +#define CS_TERRAININFO 13 +#define CS_ENTITYFADEINFO 14 +#define CS_DEBUGINFO 15 +#define CS_MODELS 32 +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) +#define CS_IMAGES (CS_SOUNDS+MAX_SOUNDS) +#define CS_LIGHTSTYLES (CS_IMAGES+MAX_IMAGES) +#define CS_PLAYERS (CS_LIGHTSTYLES+MAX_LIGHTSTYLES) +#define CS_ITEMS (CS_PLAYERS+MAX_CLIENTS) // strings for item names +#define CS_AMMO (CS_ITEMS+MAX_ITEMS) // strings for ammo names +#define CS_LOCATIONS (CS_AMMO+MAX_AMMO) +#define CS_NUM_ARENAS (CS_LOCATIONS+MAX_LOCATIONS) +#define CS_ARENA_INFO (CS_NUM_ARENAS+1) +#define CS_NUM_TEAMS (CS_ARENA_INFO+MAX_ARENA_INFO) +#define CS_TEAM_INFO (CS_NUM_TEAMS+1) +#define CS_ARCHETYPE (CS_TEAM_INFO + MAX_TEAM_INFO) +#define CS_FAILEDREASON (CS_ARCHETYPE + MAX_ARCHETYPES) +#define CS_OBJECTIVE_NAME (CS_FAILEDREASON+1) +#define CS_BOTINFO (CS_OBJECTIVE_NAME + MAX_OBJECTIVE_NAMES) // used for bot code +#define CS_GENERAL_STRINGS (CS_BOTINFO + 2) +#define CS_MAX (CS_GENERAL_STRINGS + MAX_GENERAL_STRINGS) // 2 because that's what q3ta defined, haven't looked why yet + +#if (CS_MAX) > MAX_CONFIGSTRINGS +#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +#endif + +// BOTLIB needed for bot code +typedef enum { + TEAM_FREE, + TEAM_RED, + TEAM_BLUE, + TEAM_SPECTATOR, + + TEAM_NUM_TEAMS +} team_t; + + +// +// scale to use when evaluating constantLight scale +// +#define CONSTANTLIGHT_RADIUS_SCALE 8 + +/* +=================================================================================== + +PMOVE MODULE + +The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t +and some other output data. Used for local prediction on the client game and true +movement on the server game. +=================================================================================== +*/ + +#define MAX_CLIP_PLANES 5 +#define MIN_WALK_NORMAL 0.65f // can't walk on very steep slopes + +#define STEPSIZE 33.0f + +#define MINS_X -22 +#define MINS_Y -22 +#define MAXS_X 22 +#define MAXS_Y 22 + +#define MINS_Z 0 +#define MAXS_Z 96 + +#define DEAD_MINS_Z 32 +#define CROUCH_MAXS_Z 49 +//#define DEFAULT_VIEWHEIGHT 85 +#define CROUCH_VIEWHEIGHT ( CROUCH_MAXS_Z - 6 ) +#define DEAD_VIEWHEIGHT ( MAXS_Z - 6 ) + +#define WATER_TURBO_SPEED 1.35f +#define WATER_TURBO_TIME 1200 +#define MINIMUM_RUNNING_TIME 800 +#define MINIMUM_WATER_FOR_TURBO 90 + +#define OVERCLIP 1.001f + +typedef enum { + PM_NORMAL, // normal movement mode + PM_NOCLIP, // noclip movement + PM_DEAD, // no acceleration or turning, but free falling + PM_SIDESTRAFE, // Player can only strafe side to side + PM_FORWARDSTRAFE, // Player can only move forward and back, but the strafe keys will be overloaded to do so + PM_SPECTATOR, + PM_SPECTATOR_FOLLOW, + PM_SECRET_MOVE_MODE, + PM_3RD_PERSON, + PM_NONE +} pmtype_t; + +// entityState_t->event values +// entity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. +typedef enum { + EV_NONE, + + EV_FALL_VERY_SHORT, + EV_FALL_SHORT, + EV_FALL_MEDIUM, + EV_FALL_FAR, + EV_FALL_VERY_FAR, + EV_FALL_FATAL, + EV_TERMINAL_VELOCITY, + + EV_WATER_TOUCH, // foot touches + EV_WATER_LEAVE, // foot leaves + EV_WATER_UNDER, // head touches + EV_WATER_CLEAR, // head leaves + + EV_LAST_PREDICTED // just a marker point + + // events generated by non-players or never predicted +} entity_event_t; + +// pmove->pm_flags +#define PMF_DUCKED ( 1<<0 ) // player is ducked +#define PMF_TIME_JUMP_START ( 1<<1 ) +#define PMF_TIME_KNOCKBACK ( 1<<2 ) // pm_time is an air-accelerate only time +#define PMF_TIME_WATERJUMP ( 1<<3 ) // pm_time is waterjump +#define PMF_TIME_TELEPORT ( 1<<4 ) // pm_time is teleport +#define PMF_NO_PREDICTION ( 1<<5 ) // no prediction +#define PMF_FROZEN ( 1<<6 ) // player cannot move or look around +#define PMF_INTERMISSION ( 1<<7 ) // intermission view + +// +// the following flag is required by the server and cannot be changed +// +#define PMF_CAMERA_VIEW ( 1<<8 ) // use camera view instead of ps view +#define PMF_NO_MOVE ( 1<<9 ) // player cannot move but can still look around +#define PMF_HAVETARGET ( 1<<10 ) // player has a target in the crosshairs +#define PMF_TIME_STUCKJUMP ( 1<<11 ) // pm_time is stuckjump +#define PMF_LEVELEXIT ( 1<<12 ) // player is near an exit +#define PMF_NO_GRAVITY ( 1<<13 ) // do not apply gravity to the player +#define PMF_JUMP_HELD ( 1<<14 ) // Jump held +#define PMF_FLIGHT ( 1<<15 ) // Flight Powerup +#define PMF_ENEMY_TARGETED ( 1<<16 ) // An enemy is targeted. +#define PMF_RADAR_MODE ( 1<<17 ) // the mode of the radar ( simple: 0, detailed : 1) +#define PMF_DISABLE_INVENTORY ( 1<<18 ) // disables the player's inventory +#define PMF_SCANNER ( 1<<22 ) // has a scanner +#define PMF_ZOOM ( 1<<23 ) // zoom is active +#define PMF_LEAN_LEFT ( 1<<24 ) // player is leaning left +#define PMF_LEAN_RIGHT ( 1<<25 ) // player is leaning right +#define PMF_TIME_CROUCH_JUMP ( 1<<26 ) // crouch jump +#define PMF_NIGHTVISION ( 1<<27 ) // night vision +#define PMF_DAMAGE_FRONT ( 1<<28 ) +#define PMF_DAMAGE_BACK ( 1<<29 ) +#define PMF_DAMAGE_LEFT ( 1<<30 ) +#define PMF_DAMAGE_RIGHT ( 1<<31 ) + +#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_JUMP_START|PMF_TIME_KNOCKBACK|PMF_TIME_TELEPORT|PMF_TIME_STUCKJUMP|PMF_TIME_CROUCH_JUMP) + +#define MAXTOUCH 32 + +#define MOVERESULT_NONE 0 // nothing blocking +#define MOVERESULT_TURNED 1 // move blocked, but player turned to avoid it +#define MOVERESULT_BLOCKED 2 // move blocked by slope or wall +#define MOVERESULT_HITWALL 3 // player ran into wall + +typedef struct { + // state (in / out) + playerState_t *ps; + + // command (in) + usercmd_t cmd; + int tracemask; // collide against these types of surfaces + int debugLevel; // if set, diagnostic output will be printed + qboolean noFootsteps; // if the game is setup for no footsteps by the server + + // results (out) + int numtouch; + int touchents[MAXTOUCH]; + + int moveresult; // indicates whether 2the player's movement was blocked and how + + qboolean landed; + float landedVelocity; + + qboolean stepped; // made a non-smooth step that can be + // smoothed on the client side + + int pmoveEvent; // events predicted on client side + vec3_t mins, maxs; // bounding box size + int watertype; + int waterlevel; + + // callbacks to test the world + // these will be different functions during game and cgame + void (*trace)( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask, qboolean cylinder ); + int (*pointcontents)( const vec3_t point, int passEntityNum ); + qboolean (*trypush)( int entnum, vec3_t move_origin, vec3_t move_end ); +} pmove_t; + +// if a full pmove isn't done on the client, you can just update the angles +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); +void Pmove_GroundTrace( pmove_t *pmove ); +void Pmove (pmove_t *pmove); + +//=================================================================================== + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID) +#define MASK_USABLE (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_USABLE) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY|CONTENTS_SETCLIP) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_SETCLIP) +#define MASK_MONSTERSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BODY|CONTENTS_SETCLIP) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_CAMERACLIP) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE|CONTENTS_WEAPONCLIP|CONTENTS_SHOOTABLE_ONLY|CONTENTS_SETCLIP) +#define MASK_PROJECTILE (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_WEAPONCLIP|CONTENTS_SHOOTABLE_ONLY) +#define MASK_PROJECTILE_NOTSHOOTABLE (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_WEAPONCLIP) +#define MASK_MELEE (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE|CONTENTS_WEAPONCLIP|CONTENTS_SHOOTABLE_ONLY) +#define MASK_PATHSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_SETCLIP) +#define MASK_CAMERASOLID (CONTENTS_SOLID|CONTENTS_CAMERACLIP|CONTENTS_BODY|CONTENTS_SETCLIP|MASK_WATER) +#define MASK_SETCLIP (CONTENTS_BODY) +#define MASK_CURRENT (CONTENTS_CURRENT_0|CONTENTS_CURRENT_90|CONTENTS_CURRENT_180|CONTENTS_CURRENT_270|CONTENTS_CURRENT_UP|CONTENTS_CURRENT_DOWN) + +// player_state->persistant[] indexes +// these fields are the only part of player_state that isn't +// cleared on respawn +typedef enum { + PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + PERS_TEAM +} persEnum_t; + +// entityState_t->eFlags +#define EF_EVENT_BIT1 0x00000001 // toggled every time an event changes +#define EF_EVENT_BIT2 0x00000002 // toggled every time an event changes +#define EF_EVENT_BITS (EF_EVENT_BIT1|EF_EVENT_BIT2) +#define EF_TELEPORT_BIT (1<<2) // toggled every time the origin abruptly changes +#define EF_EVERYFRAME (1<<3) // def commands will be run every client frame +#define EF_FRIEND (1<<4) // the entity is a friend +#define EF_ENEMY (1<<5) // the entity is a enemy +#define EF_DONT_PROCESS_COMMANDS (1<<6) // don't process client commands for this entity +#define EF_EFFECT_CUSTOM (1<<7) // Custom effect, look at the entitystate for the shader to use +#define EF_EMITTER_CUSTOM (1<<8) // Custom emitter, look at the entitystate for the emitter name to use + +#define EF_DISPLAY_INFO (1<<9) +#define EF_DISPLAY_DESC1 (1<<10) // Display description 1 +#define EF_DISPLAY_DESC2 (1<<11) // Display description 2 +#define EF_DISPLAY_DESC3 (1<<12) // Display description 3 + +#define EF_EFFECT_ELECTRIC (1<<14) // Transport effect +#define EF_BEHAVIOR_FAILURE (1<<15) // Behavior Failure Effect + +#define EF_EFFECTS ( EF_EFFECT_CUSTOM | EF_EFFECT_ELECTRIC | EF_BEHAVIOR_FAILURE ) + +// flip the togglebit every time an animation +// changes so a restart of the same anim can be detected +#define ANIM_TOGGLEBIT (1<<9) +#define ANIM_BLEND (1<<10) +#define ANIM_NUMBITS 11 + +// server side anim bits +#define ANIM_SERVER_EXITCOMMANDS_PROCESSED (1<<12) + +#define ANIM_MASK ( ~( ANIM_TOGGLEBIT | ANIM_BLEND | ANIM_SERVER_EXITCOMMANDS_PROCESSED ) ) + +// if FRAME_EXPLICIT is set, don't auto animate +#define FRAME_NUMBITS 14 +#define FRAME_EXPLICIT ( 1 << 13 ) +#define FRAME_MASK ( ~FRAME_EXPLICIT ) + +// +// Tag specific flags +// +#define TAG_NUMBITS 10 // number of bits required to send over network +#define TAG_MASK ( ( 1 << 10 ) - 1 ) + + +// +// Camera Flags +// +#define CF_CAMERA_ANGLES_ABSOLUTE ( 1 << 0 ) +#define CF_CAMERA_ANGLES_IGNORE_PITCH ( 1 << 1 ) +#define CF_CAMERA_ANGLES_IGNORE_YAW ( 1 << 2 ) +#define CF_CAMERA_ANGLES_ALLOWOFFSET ( 1 << 3 ) +#define CF_CAMERA_CUT_BIT ( 1 << 7 ) // this bit gets toggled everytime we do a hard camera cut + +typedef enum { + MOD_NONE, + MOD_DROWN, + MOD_SUICIDE, + MOD_CRUSH, + MOD_CRUSH_EVERY_FRAME, + MOD_TELEFRAG, + MOD_LAVA, + MOD_SLIME, + MOD_FALLING, + MOD_LAST_SELF_INFLICTED, + MOD_EXPLOSION, + MOD_EXPLODEWALL, + MOD_ELECTRIC, + MOD_ELECTRICWATER, + MOD_THROWNOBJECT, + MOD_BEAM, + MOD_ROCKET, + MOD_IMPACT, + MOD_GAS, + MOD_GAS_BLOCKABLE, + MOD_ACID, + MOD_SWORD, + MOD_PLASMA, + MOD_PLASMABEAM, + MOD_PLASMASHOTGUN, + MOD_STING, + MOD_STING2, + MOD_SLING, + MOD_BULLET, + MOD_FAST_BULLET, + MOD_VEHICLE, + MOD_FIRE, + MOD_FIRE_BLOCKABLE, + MOD_VORTEX, + MOD_LIFEDRAIN, + MOD_FLASHBANG, + MOD_POO_EXPLOSION, + MOD_AXE, + MOD_CHAINSWORD, + MOD_ON_FIRE, + MOD_FIRESWORD, + MOD_ELECTRICSWORD, + MOD_CIRCLEOFPROTECTION, + MOD_RADIATION, + MOD_LIGHTSWORD, + MOD_GIB, + MOD_IMPALE, + MOD_UPPERCUT, + MOD_POISON, + MOD_EAT, + MOD_REDEMPTION, + MOD_STASIS, + + // Added for EF + MOD_PHASER, + MOD_VAPORIZE, + MOD_COMP_RIFLE, + MOD_VAPORIZE_COMP, + MOD_IMOD_PRIMARY, + MOD_IMOD_SECONDARY, + MOD_SMALL_EXPLOSION, + MOD_TETRYON, + MOD_DISRUPTOR, + MOD_VAPORIZE_DISRUPTOR, + MOD_VAPORIZE_PHOTON, + MOD_SNIPER, + MOD_MELEE, + MOD_ALIEN_MELEE, + MOD_KLINGON_MELEE, + MOD_TURRET, + + // Powerups/runes + + MOD_DEATH_QUAD, + MOD_EMPATHY_SHIELD, + MOD_ARMOR_PIERCING, + + MOD_TOTAL_NUMBER + } meansOfDeath_t; + +// If you add to the enum above go add a string to the means_of_death_strings in g_utils.cpp +extern char means_of_death_strings[ MOD_TOTAL_NUMBER ][ 32 ]; + + +// Context Dialog +// To add a new context, you must add the context in 4 places ( Yes, I know ). You must add +// an enumeration to the dialogContexts_t and a corresponding string in context_strings ( inside g_utils.cpp ) +// That will "create" the context ( though you still need to implement the usage ). To make the context useful +// you will need to add a SOUNDTYPE for it as well -- Which means adding a string to soundtype_strings +// ( also in g_utils.cpp ). +// +// In short, to add a context put it in the following places +// 1: dialogContexts_t ( enumerated ID ) +// 2: context_strings ( string form of ID ) +// 3: soundType ( soundType for context ) +// 4 soundtype_strings ( string of the soundType ) +// +typedef enum + { + CONTEXT_SPOTTED_ENEMY, + CONTEXT_INJURED, + CONTEXT_IN_COMBAT, + CONTEXT_WEAPON_USELESS, + CONTEXT_INVESTIGATING, + + CONTEXT_TOTAL_NUMBER + } dialogContexts_t; + +typedef enum + { + // General Sound Types + SOUNDTYPE_NONE, + SOUNDTYPE_GENERAL, + SOUNDTYPE_EXPLOSION, + SOUNDTYPE_WEAPONFIRE, + SOUNDTYPE_ALERT, + SOUNDTYPE_FOOTSTEPS_WALK, + SOUNDTYPE_FOOTSTEPS_RUN, + SOUNDTYPE_FALL, + + // Context Dialog Sound Types + SOUNDTYPE_DIALOG_CONTEXT_SPOTTED_ENEMY, + SOUNDTYPE_DIALOG_CONTEXT_INJURED, + SOUNDTYPE_DIALOG_CONTEXT_IN_COMBAT, + SOUNDTYPE_DIALOG_CONTEXT_WEAPON_USELESS, + SOUNDTYPE_DIALOG_CONTEXT_INVESTIGATING, + + SOUNDTYPE_TOTAL_NUMBER + } soundType; + +// If you add to the enum above go add a string to the soundtype_strings in g_utils.cpp +extern char soundtype_strings[ SOUNDTYPE_TOTAL_NUMBER ][ 32 ]; +extern char context_strings[CONTEXT_TOTAL_NUMBER][32]; + +//--------------------------------------------------------- + + +// mp_flags->integer flags + +#define MP_FLAG_NO_HEALTH (1<<0) +#define MP_FLAG_NO_POWERUPS (1<<1) +#define MP_FLAG_WEAPONS_STAY (1<<2) +#define MP_FLAG_NO_FALLING (1<<3) +#define MP_FLAG_INSTANT_ITEMS (1<<4) +#define MP_FLAG_SAME_LEVEL (1<<5) +#define MP_FLAG_SKINTEAMS (1<<6) +#define MP_FLAG_MODELTEAMS (1<<7) +#define MP_FLAG_FRIENDLY_FIRE (1<<8) +#define MP_FLAG_SPAWN_FARTHEST (1<<9) +#define MP_FLAG_FORCE_RESPAWN (1<<10) +#define MP_FLAG_NO_ARMOR (1<<11) +#define MP_FLAG_FAST_WEAPONS (1<<12) +#define MP_FLAG_NOEXIT (1<<13) +#define MP_FLAG_INFINITE_AMMO (1<<14) +#define MP_FLAG_FIXED_FOV (1<<15) +#define MP_FLAG_NO_DROP_WEAPONS (1<<16) +#define MP_FLAG_NO_FOOTSTEPS (1<<17) +#define MP_FLAG_DONT_ALLOW_VOTE (1<<18) +#define MP_FLAG_FULL_COLLISION (1<<19) +#define MP_FLAG_NO_AUTO_JOIN_TEAM (1<<20) +#define MP_FLAG_AUTO_BALANCE_TEAMS (1<<21) +#define MP_FLAG_FORCE_DEFAULT_MODEL (1<<22) + +// teamflags->integer flags +#define TF_TEAMPLAY 1 +#define TF_NO_FRIENDLY_FIRE 2 + +// +// entityState_t->eType +// +typedef enum { + ET_MODELANIM, + ET_PLAYER, + ET_ITEM, + ET_GENERAL, + ET_MISSILE, + ET_MOVER, + ET_BEAM, + ET_MULTIBEAM, + ET_SPRITE, + ET_PORTAL, + ET_EVENT_ONLY, + ET_RAIN, + ET_LEAF, + ET_SPEAKER, + ET_PUSH_TRIGGER, + ET_TELEPORT_TRIGGER, + ET_DECAL, + ET_EMITTER, + ET_ROPE, + ET_EVENTS +} entityType_t; + +void EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); +void EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); + +#define MAX_LETTERBOX_SIZE 0x7fff + +#define ITEM_NAME_AMMO_LEFT 0 +#define ITEM_NAME_AMMO_RIGHT 1 +#define ITEM_NAME_WEAPON_LEFT 2 +#define ITEM_NAME_WEAPON_RIGHT 3 +#define ITEM_NAME_WEAPON_DUAL 4 + +// added for bot code BOTLIB +#define ARENAS_PER_TIER 4 +#define MAX_ARENAS 1024 +#define MAX_ARENAS_TEXT 8192 + +#define MAX_BOTS 1024 +#define MAX_BOTS_TEXT 8192 + +typedef enum { + GTS_RED_CAPTURE, + GTS_BLUE_CAPTURE, + GTS_RED_RETURN, + GTS_BLUE_RETURN, + GTS_RED_TAKEN, + GTS_BLUE_TAKEN, + GTS_REDOBELISK_ATTACKED, + GTS_BLUEOBELISK_ATTACKED, + GTS_REDTEAM_SCORED, + GTS_BLUETEAM_SCORED, + GTS_REDTEAM_TOOK_LEAD, + GTS_BLUETEAM_TOOK_LEAD, + GTS_TEAMS_ARE_TIED, + GTS_KAMIKAZE +} global_team_sound_t; + +//team task +typedef enum { + TEAMTASK_NONE, + TEAMTASK_OFFENSE, + TEAMTASK_DEFENSE, + TEAMTASK_PATROL, + TEAMTASK_FOLLOW, + TEAMTASK_RETRIEVE, + TEAMTASK_ESCORT, + TEAMTASK_CAMP +} teamtask_t; + +// end bot additions + +typedef enum { + UNKNOWN, + OBJECT_LOCATION, + OBJECT_NONINTERACTIVE, + OBJECT_USABLE, + OBJECT_DESTRUCTABLE, + ACTOR_FRIENDLY, + ACTOR_ENEMY, + ACTOR_TEAMMATE +} EntityType; + +typedef enum +{ + HEALTHY_STATUS = 0, + INJURED_STATUS, + CRITICAL_STATUS +} TeammateStatus; + + +#define MAX_GAMEPLAY_STRING_LENGTH 256 + +#ifdef __cplusplus + } +#endif + +#endif // __BG_PUBLIC_H__ diff --git a/dlls/game/bit_vector.h b/dlls/game/bit_vector.h new file mode 100644 index 0000000..1fa188f --- /dev/null +++ b/dlls/game/bit_vector.h @@ -0,0 +1,762 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/bit_vector.h $ +// $Revision:: 4 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// This class is to used to manage arrays of bits. + + +//======================================= +//Forward Declarations +//======================================= + +class BitVector; +class BitReference; + +#ifndef __BIT_VECTOR_H__ +#define __BIT_VECTOR_H__ + +//================================================================ +// +// Class: BitVector +// +// Description: The BitVector class is basically a class made to simplify +// working with an array/vector of bits. +// +//================================================================ + +class BitVector + { + public: + + // Constructors / destructors + + BitVector( unsigned int numBits, bool value = false ); + BitVector( const BitVector &old ); + ~BitVector(); + + // Bit manipulation + + void SetBit( unsigned int index, bool value = true ); + void ClearBit( unsigned int index ); + void FlipBit( unsigned int index ); + bool GetBit( unsigned int index ) const; + + // Bit array manipulation + + void Set( void ); + void Clear( void ); + void Flip( void ); + + // Bit range manipulation + + void SetRange( unsigned int first_index, unsigned int last_index, bool value = true ); + void ClearRange( unsigned int first_index, unsigned int last_index ); + + // Info + + unsigned int BitSize( void ) const; + unsigned int ByteSize( void ) const; + unsigned int Count( void ) const; + bool Any( void ) const; + bool None( void ) const; + + // Operators + + bool operator[]( unsigned int index ) const; + BitReference operator[]( unsigned int index ); + bool operator== ( const BitVector &other ); + bool operator!= ( const BitVector &other ); + void operator|= ( const BitVector &other ); + void operator&= ( const BitVector &other ); + BitVector operator~ ( void ); + + private: + + // Data + + unsigned char *_bytes; + unsigned int _numBytes; + unsigned int _numBits; + + // Friends + + friend BitVector operator| ( const BitVector &left, const BitVector &right ); + friend BitVector operator& ( const BitVector &left, const BitVector &right ); + + friend BitReference; + }; + +//================================================================ +// +// Class: BitReference +// +// Description: The BitReference class is a go between for the BitVector +// class to allow the operator[] to work exactly as you +// would expect it to for an array. This makes it possible +// to do the following: +// bits[ 2 ] = true; +// bit = bits[ 2 ]; +// bits[ 2 ] = other_bits[ 5 ]; +// +//================================================================ + +class BitReference + { + public: + + // Operators + + BitReference& operator= ( bool b ); + BitReference& operator= ( const BitReference &other ); + operator bool() const; + + private: + + // Data + + BitVector *_vector; + unsigned int _index; + + // Setup only called by BitVector class + + void Setup( BitVector *vector, unsigned int index ); + + // Friends + + friend BitVector; + }; + +//================================================================ +// Name: BitVector +// Class: BitVector +// +// Description: Constructor +// +// Parameters: unsigned int numBits - number of bits in the array +// bool value - value to default all bits to (defaults to false) +// +// Returns: None +//================================================================ + +BitVector::BitVector( unsigned int numBits, bool value ) + { + // Setup data + + _numBits = numBits; + _numBytes = ( _numBits + 7 ) >> 3; + _bytes = new unsigned char[ _numBytes ]; + + // Initialize all bits + + if ( value ) + Set(); + else + Clear(); + } + +//================================================================ +// Name: BitVector +// Class: BitVector +// +// Description: Constructor (copy) +// +// Parameters: const BitVector &old - bit array to copy +// +// Returns: None +//================================================================ + +BitVector::BitVector( const BitVector &old ) + { + int i; + + // Setup data + + _numBits = old._numBits; + _numBytes = old._numBytes; + _bytes = new unsigned char[ _numBytes ]; + + // Copy bits + + for ( i = 0 ; i < _numBytes ; i++ ) + _bytes[ i ] = old._bytes[ i ]; + } + +//================================================================ +// Name: ~BitVector +// Class: BitVector +// +// Description: Deconstructor +// +// Parameters: None +// +// Returns: None +//================================================================ + +BitVector::~BitVector() + { + // Free the bit array + + delete[] _bytes; + } + +//================================================================ +// Name: SetBit +// Class: BitVector +// +// Description: Sets the specified bit with the value +// +// Parameters: unsigned int index - index of bit +// bool value - value of bit to set (defaults to true) +// +// Returns: None +//================================================================ + +inline void BitVector::SetBit( unsigned int index, bool value ) + { + if ( index < _numBits ) + { + if ( value ) + _bytes[ index >> 3 ] |= ( 1 << ( index & 7 ) ); // Set the bit + else + _bytes[ index >> 3 ] &= ~( 1 << ( index & 7 ) ); // Clear the bit + } + } + +//================================================================ +// Name: ClearBit +// Class: BitVector +// +// Description: Clears the specified bit (sets it to 0) +// +// Parameters: unsigned int index - index of bit +// +// Returns: None +//================================================================ + +inline void BitVector::ClearBit( unsigned int index ) + { + SetBit( index, false ); + } + +//================================================================ +// Name: FlipBit +// Class: BitVector +// +// Description: Flips the specified bit +// +// Parameters: unsigned int index - index of bit +// +// Returns: None +//================================================================ + +inline void BitVector::FlipBit( unsigned int index ) + { + bool value; + + value = GetBit( index ); + SetBit( index, !value ); + } + +//================================================================ +// Name: GetBit +// Class: BitVector +// +// Description: Returns the specified bit +// +// Parameters: unsigned int index - index of bit +// +// Returns: None +//================================================================ + +inline bool BitVector::GetBit( unsigned int index ) const + { + if ( index < _numBits ) + return ( ( _bytes[ index >> 3 ] & ( 1 << ( index & 7 ) ) ) != 0 ); + else + return false; + } + +//================================================================ +// Name: Set +// Class: BitVector +// +// Description: Sets every bit in bit array to 1 +// +// Parameters: None +// +// Returns: None +//================================================================ + +inline void BitVector::Set( void ) + { + unsigned int i; + + for ( i = 0 ; i < _numBytes ; i++ ) + _bytes[ i ] = (unsigned char)0xFF; + } + +//================================================================ +// Name: Clear +// Class: BitVector +// +// Description: Clears every bit in array (sets to 0) +// +// Parameters: None +// +// Returns: None +//================================================================ + +inline void BitVector::Clear( void ) + { + unsigned int i; + + for ( i = 0 ; i < _numBytes ; i++ ) + _bytes[ i ] = 0; + } + +//================================================================ +// Name: Flip +// Class: BitVector +// +// Description: Flips every bit in array +// +// Parameters: None +// +// Returns: None +//================================================================ + +inline void BitVector::Flip( void ) + { + unsigned int i; + + for ( i = 0 ; i < _numBytes ; i++ ) + { + _bytes[ i ] = ~_bytes[ i ]; + } + } + +//================================================================ +// Name: SetRange +// Class: BitVector +// +// Description: Sets the specified range of bits to the value +// +// Parameters: unsigned int first_index - index of first bit +// unsigned int last_index - index of last bit +// bool value - value to set bits to (defaults to true) +// +// Returns: None +//================================================================ + +inline void BitVector::SetRange( unsigned int first_index, unsigned int last_index, bool value ) + { + unsigned int i; + + for ( i = first_index ; i < last_index ; i++ ) + SetBit( i, value ); + } + +//================================================================ +// Name: ClearRange +// Class: BitVector +// +// Description: Clears a range of bits (sets to 0) +// +// Parameters: unsigned int first_index - index of first bit +// unsigned int last_index - index of last bit +// +// Returns: None +//================================================================ + +inline void BitVector::ClearRange( unsigned int first_index, unsigned int last_index ) + { + SetRange( first_index, last_index, false ); + } + +//================================================================ +// Name: BitSize +// Class: BitVector +// +// Description: Returns the number of bits +// +// Parameters: None +// +// Returns: unsigned int - number of bits +//================================================================ + +inline unsigned int BitVector::BitSize( void ) const + { + return _numBits; + } + +//================================================================ +// Name: ByteSize +// Class: BitVector +// +// Description: Returns the number of bytes in the array +// +// Parameters: None +// +// Returns: unsigned int - number of bytes in array +//================================================================ + +inline unsigned int BitVector::ByteSize( void ) const + { + return _numBytes; + } + +//================================================================ +// Name: Count +// Class: BitVector +// +// Description: Returns the number of bits set to 1 +// +// Parameters: None +// +// Returns: unsigned int - number of bits set to 1 +//================================================================ + +inline unsigned int BitVector::Count( void ) const + { + unsigned int i; + unsigned count; + + count = 0; + + for ( i = 0 ; i < _numBits ; i++ ) + { + if ( GetBit( i ) ) + count++; + } + + return count; + } + +//================================================================ +// Name: Any +// Class: BitVector +// +// Description: Returns whether or not any bits are set to 1 +// +// Parameters: None +// +// Returns: bool - if any bits are set to 1 +//================================================================ + +inline bool BitVector::Any( void ) const + { + unsigned int i; + + for ( i = 0 ; i < _numBits ; i++ ) + { + if ( GetBit( i ) ) + return true; + } + + return false; + } + +//================================================================ +// Name: None +// Class: BitVector +// +// Description: Returns whether or not none of the bits are set to 1 +// +// Parameters: None +// +// Returns: bool - if none of the bits are set to 1 +//================================================================ + +inline bool BitVector::None( void ) const + { + return !Any(); + } + +//================================================================ +// Name: operator[] +// Class: BitVector +// +// Description: Returns the referenced bit +// +// Parameters: unsigned int index - index of bit +// +// Returns: bool - bit +//================================================================ + +inline bool BitVector::operator[]( unsigned int index ) const + { + return GetBit( index ); + } + +//================================================================ +// Name: operator[] +// Class: BitVector +// +// Description: Returns a reference to the bit in question +// +// Parameters: unsigned int index - index of bit +// +// Returns: BitReference - reference of bit +//================================================================ + +inline BitReference BitVector::operator[]( unsigned int index ) + { + BitReference bitRef; + + bitRef.Setup( this, index ); + return bitRef; + } + +//================================================================ +// Name: operator== +// Class: BitVector +// +// Description: Returns whether or not this bitvector equals another +// +// Parameters: const BitVector &other - the other BitVector to compare to +// +// Returns: bool - if equal +//================================================================ + +inline bool BitVector::operator== ( const BitVector &other ) + { + unsigned int i; + + if ( BitSize() != other.BitSize() ) + return false; + + for ( i = 0 ; i < _numBytes ; i++ ) + { + if ( _bytes[ i ] != other._bytes[ i ] ) + return false; + } + + return true; + } + +//================================================================ +// Name: operator!= +// Class: BitVector +// +// Description: Returns whether or not this bitvector doesn't equal another +// +// Parameters: const BitVector &other - the other BitVector to compare to +// +// Returns: bool - if not equal +//================================================================ + +inline bool BitVector::operator!= ( const BitVector &other ) + { + unsigned int i; + + if ( BitSize() != other.BitSize() ) + return true; + + for ( i = 0 ; i < _numBytes ; i++ ) + { + if ( _bytes[ i ] != other._bytes[ i ] ) + return true; + } + + return false; + } + +//================================================================ +// Name: operator|= +// Class: BitVector +// +// Description: Ors together this BitVector with the specified BitVector (bit or) +// +// Parameters: const BitVector &other - the other BitVector to or with +// +// Returns: None +//================================================================ + +inline void BitVector::operator|= ( const BitVector &other ) + { + unsigned int i; + + if ( BitSize() != other.BitSize() ) + return; + + for ( i = 0 ; i < _numBytes ; i++ ) + { + _bytes[ i ] |= other._bytes[ i ]; + } + } + +//================================================================ +// Name: operator&= +// Class: BitVector +// +// Description: Ands together this BitVector with the specified BitVector (bit and) +// +// Parameters: const BitVector &other - the other BitVector to and with +// +// Returns: None +//================================================================ + +inline void BitVector::operator&= ( const BitVector &other ) + { + unsigned int i; + + if ( BitSize() != other.BitSize() ) + return; + + for ( i = 0 ; i < _numBytes ; i++ ) + { + _bytes[ i ] &= other._bytes[ i ]; + } + } + +//================================================================ +// Name: operator~ +// Class: BitVector +// +// Description: Returns a not'ed version of the bitarray +// +// Parameters: None +// +// Returns: BitVector - bitwise xor'ed version +//================================================================ + +inline BitVector BitVector::operator~ ( void ) + { + BitVector temp_bits( _numBits ); + unsigned int i; + + for ( i = 0 ; i < _numBytes ; i++ ) + { + temp_bits._bytes[ i ] = ~_bytes[ i ]; + } + + return temp_bits; + } + +//================================================================ +// Name: operator| +// Class: +// +// Description: Ors together 2 BitVectors +// +// Parameters: const BitVector &left - BitVector on left of | +// const BitVector &right - BitVector on right of | +// +// Returns: None +//================================================================ + +inline BitVector operator| ( const BitVector &left, const BitVector &right ) + { + BitVector temp_bits( left ); + + temp_bits |= right; + return temp_bits; + } + +//================================================================ +// Name: operator& +// Class: +// +// Description: Ands together 2 BitVectors +// +// Parameters: const BitVector &left - BitVector on left of & +// const BitVector &right - BitVector on right of & +// +// Returns: None +//================================================================ + +inline BitVector operator& ( const BitVector &left, const BitVector &right ) + { + BitVector temp_bits( left ); + + temp_bits &= right; + return temp_bits; + } + +//================================================================ +// Name: operator= +// Class: BitReference +// +// Description: Sets the saved indexed bit to the value +// +// Parameters: bool b - value to set bit to +// +// Returns: BitReference& - the reference of this bit +//================================================================ + +inline BitReference& BitReference::operator= ( bool b ) + { + if ( _vector ) + _vector->SetBit( _index, b ); + + return *this; + } + +//================================================================ +// Name: operator= +// Class: BitReference +// +// Description: Sets the saved indexed bit to the saved index bit in the passed in BitReference +// +// Parameters: const BitReference &other - reference of the bit/BitVector to get bit from +// +// Returns: BitReference& - the reference of this bit +//================================================================ + +inline BitReference& BitReference::operator= ( const BitReference &other ) + { + if ( _vector ) + _vector->SetBit( _index, other._vector->GetBit( other._index ) ); + + return *this; + } + +//================================================================ +// Name: operator bool +// Class: BitVector +// +// Description: Returns bit that is referenced +// +// Parameters: None +// +// Returns: bool - referenced bit +//================================================================ + +inline BitReference::operator bool() const + { + if ( _vector ) + return _vector->GetBit( _index ); + else + return false; + } + +//================================================================ +// Name: Setup +// Class: BitVector +// +// Description: Sets up the BitReference +// +// Parameters: BitVector *vector - vector to reference +// unsigned int index - index of the bit to reference +// +// Returns: None +//================================================================ + +inline void BitReference::Setup( BitVector *vector, unsigned int index ) + { + _vector = vector; + _index = index; + } + +#endif // __BIT_VECTOR_H__ diff --git a/dlls/game/body.cpp b/dlls/game/body.cpp new file mode 100644 index 0000000..a4eba0c --- /dev/null +++ b/dlls/game/body.cpp @@ -0,0 +1,98 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/body.cpp $ +// $Revision:: 13 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:35p $ +// +// Copyright (C) 2000 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Dead bodies + +#include "_pch_cpp.h" +#include "animate.h" +#include "gibs.h" +#include "body.h" + +CLASS_DECLARATION( Entity, Body, NULL ) +{ + { &EV_Damage, &Body::Damage }, + + { NULL, NULL } +}; + +//============================================================= +//Body::Body +//============================================================= +Body::Body() +{ + Event *newEvent; + + takedamage = DAMAGE_YES; + edict->s.eType = ET_MODELANIM; + health = 10; + edict->clipmask = MASK_DEADSOLID; + edict->svflags |= SVF_DEADMONSTER; + + setSolidType( SOLID_NOT ); + //setSolidType( SOLID_BBOX ); + //setContents( CONTENTS_CORPSE ); + setMoveType( MOVETYPE_NONE ); + + //PostEvent( EV_FadeOut, 6.0f ); + + newEvent = new Event( EV_DisplayEffect ); + newEvent->AddString( "TransportOut" ); + newEvent->AddString( "Multiplayer" ); + PostEvent( newEvent, 0.0f ); + + PostEvent( EV_Remove, 5.0f ); + + //Event *transport_event = new Event( EV_DisplayEffect ); + //transport_event->AddString( "transport_out" ); + //ProcessEvent( transport_event ); + + animate = new Animate( this ); +} + +//============================================================= +//Body::Damage +//============================================================= +void Body::Damage( Event *ev ) +{ + /* str gib_name; + int number_of_gibs; + float scale; + Entity *ent; + str real_gib_name; + + if ( !com_blood->integer ) + return; + + gib_name = "fx_rgib"; + number_of_gibs = 5; + scale = 1.2; + + // Spawn the gibs + real_gib_name = gib_name; + real_gib_name += number_of_gibs; + real_gib_name += ".tik"; + + ent = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + ent->setModel( real_gib_name.c_str() ); + ent->setScale( scale ); + ent->setOrigin( centroid ); + ent->animate->RandomAnimate( "idle" ); + ent->PostEvent( EV_Remove, 1.0f ); + + Sound( "snd_decap", CHAN_BODY, 1.0f, 300.0f ); + + this->hideModel(); + this->takedamage = DAMAGE_NO; */ +} diff --git a/dlls/game/body.h b/dlls/game/body.h new file mode 100644 index 0000000..1926bad --- /dev/null +++ b/dlls/game/body.h @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/body.h $ +// $Revision:: 4 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 2000 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Dead bodies + +#ifndef __BODY_H__ +#define __BODY_H__ + +//============================================================= +//class Body +//============================================================= +class Body : public Entity + { + private: + void Damage( Event *ev ); + + public: + CLASS_PROTOTYPE( Body ); + Body(); + }; + +#endif //__BODY_H__ diff --git a/dlls/game/botlib.h b/dlls/game/botlib.h new file mode 100644 index 0000000..c49455a --- /dev/null +++ b/dlls/game/botlib.h @@ -0,0 +1,506 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: botlib.h + * + * desc: bot AI library + * + * $Archive: /EF2/Code/DLLs/game/botlib.h $ + * $Author: Singlis $ + * $Revision: 5 $ + * $Modtime: 9/24/03 3:09p $ + * $Date: 9/26/03 2:35p $ + * + *****************************************************************************/ + +#ifndef __BOTLIB_H__ +#define __BOTLIB_H__ + +#define BOTLIB_API_VERSION 2 + +struct aas_clientmove_s; +struct aas_entityinfo_s; +struct aas_areainfo_s; +struct aas_altroutegoal_s; +struct aas_predictroute_s; +struct bot_consolemessage_s; +struct bot_match_s; +struct bot_goal_s; +struct bot_moveresult_s; +struct bot_initmove_s; +struct weaponinfo_s; + +#define BOTFILESBASEFOLDER "botfiles" +//debug line colors +#define LINECOLOR_NONE -1 +#define LINECOLOR_RED 1//0xf2f2f0f0L +#define LINECOLOR_GREEN 2//0xd0d1d2d3L +#define LINECOLOR_BLUE 3//0xf3f3f1f1L +#define LINECOLOR_YELLOW 4//0xdcdddedfL +#define LINECOLOR_ORANGE 5//0xe0e1e2e3L + +//Print types +#define PRT_MESSAGE 1 +#define PRT_WARNING 2 +#define PRT_ERROR 3 +#define PRT_FATAL 4 +#define PRT_EXIT 5 + +//console message types +#define CMS_NORMAL 0 +#define CMS_CHAT 1 + +//botlib error codes +#define BLERR_NOERROR 0 //no error +#define BLERR_LIBRARYNOTSETUP 1 //library not setup +#define BLERR_INVALIDENTITYNUMBER 2 //invalid entity number +#define BLERR_NOAASFILE 3 //no AAS file available +#define BLERR_CANNOTOPENAASFILE 4 //cannot open AAS file +#define BLERR_WRONGAASFILEID 5 //incorrect AAS file id +#define BLERR_WRONGAASFILEVERSION 6 //incorrect AAS file version +#define BLERR_CANNOTREADAASLUMP 7 //cannot read AAS file lump +#define BLERR_CANNOTLOADICHAT 8 //cannot load initial chats +#define BLERR_CANNOTLOADITEMWEIGHTS 9 //cannot load item weights +#define BLERR_CANNOTLOADITEMCONFIG 10 //cannot load item config +#define BLERR_CANNOTLOADWEAPONWEIGHTS 11 //cannot load weapon weights +#define BLERR_CANNOTLOADWEAPONCONFIG 12 //cannot load weapon config + +//action flags +#define ACTION_ATTACK 0x0000001 +#define ACTION_ATTACKRIGHT 0x0000002 +#define ACTION_USE 0x0000004 +#define ACTION_RESPAWN 0x0000008 +#define ACTION_JUMP 0x0000010 +#define ACTION_MOVEUP 0x0000020 +#define ACTION_CROUCH 0x0000080 +#define ACTION_MOVEDOWN 0x0000100 +#define ACTION_MOVEFORWARD 0x0000200 +#define ACTION_MOVEBACK 0x0000800 +#define ACTION_MOVELEFT 0x0001000 +#define ACTION_MOVERIGHT 0x0002000 +#define ACTION_DELAYEDJUMP 0x0008000 +#define ACTION_TALK 0x0010000 +#define ACTION_GESTURE 0x0020000 +#define ACTION_WALK 0x0080000 +#define ACTION_AFFIRMATIVE 0x0100000 +#define ACTION_NEGATIVE 0x0200000 +#define ACTION_GETFLAG 0x0800000 +#define ACTION_GUARDBASE 0x1000000 +#define ACTION_PATROL 0x2000000 +#define ACTION_FOLLOWME 0x8000000 + +//the bot input, will be converted to an usercmd_t +typedef struct bot_input_s +{ + float thinktime; //time since last output (in seconds) + vec3_t dir; //movement direction + float speed; //speed in the range [0, 400] + vec3_t viewangles; //the view angles + int actionflags; //one of the ACTION_? flags + int weapon,latchweapon; //weapon to use + int firestate; //right/left hand fire state +} bot_input_t; + +#ifndef BSPTRACE + +#define BSPTRACE + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//remove the bsp_trace_s structure definition l8r on +//a trace is returned when a box is swept through the world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // the hit point surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; + +#endif // BSPTRACE + +//entity state +typedef struct bot_entitystate_s +{ + int type; // entity type + int flags; // entity flags + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +} bot_entitystate_t; + +//bot AI library exported functions +typedef struct botlib_import_s +{ + //print messages from the bot library + void (QDECL *Print)(int type, char *fmt, ...); + //trace a bbox through the world + void (*Trace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); + //trace a bbox against a specific entity + void (*EntityTrace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask); + //retrieve the contents at the given point + int (*PointContents)(vec3_t point); + //check if the point is in potential visible sight + int (*inPVS)(vec3_t p1, vec3_t p2); + //retrieve the BSP entity data lump + char *(*BSPEntityData)(void); + // + void (*BSPModelMinsMaxsOrigin)(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); + //send a bot client command + void (*BotClientCommand)(int client, const char *command); // was char *, changed for c++ compilation + //memory allocation + void *(*GetMemory)(int size); // allocate from Zone + void (*FreeMemory)(void *ptr); // free memory from Zone + int (*AvailableMemory)(void); // available Zone memory + void *(*HunkAlloc)(int size); // allocate from hunk + //file system access + int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); + int (*FS_Read)( void *buffer, int len, fileHandle_t f ); + int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); + void (*FS_FCloseFile)( fileHandle_t f ); + int (*FS_Seek)( fileHandle_t f, long offset, int origin ); + //debug visualisation stuff + int (*DebugLineCreate)(void); + void (*DebugLineDelete)(int line); + void (*DebugLineShow)(int line, vec3_t start, vec3_t end, int color); + // + int (*DebugPolygonCreate)(int color, int numPoints, vec3_t *points); + void (*DebugPolygonDelete)(int id); +} botlib_import_t; + +typedef struct aas_export_s +{ + //----------------------------------- + // be_aas_entity.h + //----------------------------------- + void (*AAS_EntityInfo)(int entnum, struct aas_entityinfo_s *info); + //----------------------------------- + // be_aas_main.h + //----------------------------------- + int (*AAS_Initialized)(void); + void (*AAS_PresenceTypeBoundingBox)(int presencetype, vec3_t mins, vec3_t maxs); + float (*AAS_Time)(void); + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + int (*AAS_PointAreaNum)(vec3_t point); + int (*AAS_PointReachabilityAreaIndex)( vec3_t point ); + int (*AAS_TraceAreas)(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); + int (*AAS_BBoxAreas)(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); + int (*AAS_AreaInfo)( int areanum, struct aas_areainfo_s *info ); + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + int (*AAS_PointContents)(vec3_t point); + int (*AAS_NextBSPEntity)(int ent); + int (*AAS_ValueForBSPEpairKey)(int ent, char *key, char *value, int size); + int (*AAS_VectorForBSPEpairKey)(int ent, char *key, vec3_t v); + int (*AAS_FloatForBSPEpairKey)(int ent, char *key, float *value); + int (*AAS_IntForBSPEpairKey)(int ent, char *key, int *value); + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + int (*AAS_AreaReachability)(int areanum); + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + int (*AAS_AreaTravelTimeToGoalArea)(int areanum, vec3_t origin, int goalareanum, int travelflags); + int (*AAS_EnableRoutingArea)(int areanum, int enable); + int (*AAS_PredictRoute)(struct aas_predictroute_s *route, int areanum, vec3_t origin, + int goalareanum, int travelflags, int maxareas, int maxtime, + int stopevent, int stopcontents, int stoptfl, int stopareanum); + //-------------------------------------------- + // be_aas_altroute.c + //-------------------------------------------- + int (*AAS_AlternativeRouteGoals)(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + struct aas_altroutegoal_s *altroutegoals, int maxaltroutegoals, + int type); + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + int (*AAS_Swimming)(vec3_t origin); + int (*AAS_PredictClientMovement)(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize); +} aas_export_t; + +typedef struct ea_export_s +{ + //ClientCommand elementary actions + void (*EA_Command)(int client, const char *command ); + void (*EA_Say)(int client, char *str); + void (*EA_SayTeam)(int client, char *str); + // + void (*EA_Action)(int client, int action); + void (*EA_Gesture)(int client); + void (*EA_Talk)(int client); + void (*EA_ToggleFireState)(int client); + void (*EA_Attack)(int client, int primarydangerous, int altdangerous); + void (*EA_Use)(int client); + void (*EA_Respawn)(int client); + void (*EA_MoveUp)(int client); + void (*EA_MoveDown)(int client); + void (*EA_MoveForward)(int client); + void (*EA_MoveBack)(int client); + void (*EA_MoveLeft)(int client); + void (*EA_MoveRight)(int client); + void (*EA_Crouch)(int client); + + void (*EA_SelectWeapon)(int client, int weapon); + void (*EA_Jump)(int client); + void (*EA_DelayedJump)(int client); + void (*EA_Move)(int client, vec3_t dir, float speed); + void (*EA_View)(int client, vec3_t viewangles); + //send regular input to the server + void (*EA_EndRegular)(int client, float thinktime); + void (*EA_GetInput)(int client, float thinktime, bot_input_t *input); + void (*EA_ResetInput)(int client); +} ea_export_t; + +typedef struct ai_export_s +{ + //----------------------------------- + // be_ai_char.h + //----------------------------------- + int (*BotLoadCharacter)(char *charfile, float skill); + void (*BotFreeCharacter)(int character); + float (*Characteristic_Float)(int character, int index); + float (*Characteristic_BFloat)(int character, int index, float min, float max); + int (*Characteristic_Integer)(int character, int index); + int (*Characteristic_BInteger)(int character, int index, int min, int max); + void (*Characteristic_String)(int character, int index, char *buf, int size); + //----------------------------------- + // be_ai_chat.h + //----------------------------------- + int (*BotAllocChatState)(void); + void (*BotFreeChatState)(int handle); + void (*BotQueueConsoleMessage)(int chatstate, int type, char *message); + void (*BotRemoveConsoleMessage)(int chatstate, int handle); + int (*BotNextConsoleMessage)(int chatstate, struct bot_consolemessage_s *cm); + int (*BotNumConsoleMessages)(int chatstate); + void (*BotInitialChat)(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotNumInitialChats)(int chatstate, char *type); + int (*BotReplyChat)(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotChatLength)(int chatstate); + void (*BotEnterChat)(int chatstate, int client, int sendto); + void (*BotGetChatMessage)(int chatstate, char *buf, int size); + int (*StringContains)(char *str1, char *str2, int casesensitive); + int (*BotFindMatch)(char *str, struct bot_match_s *match, unsigned long int context); + void (*BotMatchVariable)(struct bot_match_s *match, int variable, char *buf, int size); + void (*UnifyWhiteSpaces)(char *string); + void (*BotReplaceSynonyms)(char *string, unsigned long int context); + int (*BotLoadChatFile)(int chatstate, char *chatfile, char *chatname); + void (*BotSetChatGender)(int chatstate, int gender); + void (*BotSetChatName)(int chatstate, char *name, int client); + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + void (*BotResetGoalState)(int goalstate); + void (*BotResetAvoidGoals)(int goalstate); + void (*BotRemoveFromAvoidGoals)(int goalstate, int number); + void (*BotPushGoal)(int goalstate, struct bot_goal_s *goal); + void (*BotPopGoal)(int goalstate); + void (*BotEmptyGoalStack)(int goalstate); + void (*BotDumpAvoidGoals)(int goalstate); + void (*BotDumpGoalStack)(int goalstate); + void (*BotGoalName)(int number, char *name, int size); + int (*BotGetTopGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotGetSecondGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotChooseLTGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags); + int (*BotChooseNBGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags, + struct bot_goal_s *ltg, float maxtime); + int (*BotTouchingGoal)(vec3_t origin, struct bot_goal_s *goal); + int (*BotItemGoalInVisButNotVisible)(int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal); + int (*BotGetLevelItemGoal)(int index, char *classname, struct bot_goal_s *goal); + int (*BotGetNextCampSpotGoal)(int num, struct bot_goal_s *goal); + int (*BotGetMapLocationGoal)(char *name, struct bot_goal_s *goal); + float (*BotAvoidGoalTime)(int goalstate, int number); + void (*BotSetAvoidGoalTime)(int goalstate, int number, float avoidtime); + void (*BotInitLevelItems)(void); + void (*BotUpdateEntityItems)(void); + int (*BotLoadItemWeights)(int goalstate, char *filename); + void (*BotFreeItemWeights)(int goalstate); + void (*BotInterbreedGoalFuzzyLogic)(int parent1, int parent2, int child); + void (*BotSaveGoalFuzzyLogic)(int goalstate, char *filename); + void (*BotMutateGoalFuzzyLogic)(int goalstate, float range); + int (*BotAllocGoalState)(int client); + void (*BotFreeGoalState)(int handle); + //----------------------------------- + // be_ai_move.h + //----------------------------------- + void (*BotResetMoveState)(int movestate); + void (*BotMoveToGoal)(struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags); + int (*BotMoveInDirection)(int movestate, vec3_t dir, float speed, int type); + void (*BotResetAvoidReach)(int movestate); + void (*BotResetLastAvoidReach)(int movestate); + int (*BotReachabilityArea)(vec3_t origin, int testground); + int (*BotMovementViewTarget)(int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target); + int (*BotPredictVisiblePosition)(vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target); + int (*BotAllocMoveState)(void); + void (*BotFreeMoveState)(int handle); + void (*BotInitMoveState)(int handle, struct bot_initmove_s *initmove); + void (*BotAddAvoidSpot)(int movestate, vec3_t origin, float radius, int type); + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + int (*BotChooseBestFightWeapon)(int weaponstate, int *inventory); + void (*BotGetWeaponInfo)(int weaponstate, int weapon, struct weaponinfo_s *weaponinfo); + int (*BotLoadWeaponWeights)(int weaponstate, char *filename); + int (*BotAllocWeaponState)(void); + void (*BotFreeWeaponState)(int weaponstate); + void (*BotResetWeaponState)(int weaponstate); + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + int (*GeneticParentsAndChildSelection)(int numranks, float *ranks, int *parent1, int *parent2, int *child); +} ai_export_t; + +//bot AI library imported functions +typedef struct botlib_export_s +{ + //Area Awareness System functions + aas_export_t aas; + //Elementary Action functions + ea_export_t ea; + //AI functions + ai_export_t ai; + //setup the bot library, returns BLERR_ + int (*BotLibSetup)(void); + //shutdown the bot library, returns BLERR_ + int (*BotLibShutdown)(void); + //sets a library variable returns BLERR_ + int (*BotLibVarSet)(char *var_name, char *value); + //gets a library variable returns BLERR_ + int (*BotLibVarGet)(char *var_name, char *value, int size); + + //sets a C-like define returns BLERR_ + int (*PC_AddGlobalDefine)(char *string); + int (*PC_LoadSourceHandle)(const char *filename); + int (*PC_FreeSourceHandle)(int handle); +// int (*PC_ReadTokenHandle)(int handle, pc_token_t *pc_token); + int (*PC_SourceFileAndLine)(int handle, char *filename, int *line); + + //start a frame in the bot library + int (*BotLibStartFrame)(float time); + //load a new map in the bot library + int (*BotLibLoadMap)(const char *mapname); + //entity updates + int (*BotLibUpdateEntity)(int ent, bot_entitystate_t *state); + //just for testing + int (*Test)(int parm0, int parm1, vec3_t parm2, vec3_t parm3); +} botlib_export_t; + +//linking of bot library +botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ); + +/* Library variables: + +name: default: module(s): description: + +"basedir" "" l_utils.c base directory +"gamedir" "" l_utils.c game directory +"cddir" "" l_utils.c CD directory + +"log" "0" l_log.c enable/disable creating a log file +"maxclients" "4" be_interface.c maximum number of clients +"maxentities" "1024" be_interface.c maximum number of entities +"bot_developer" "0" be_interface.c bot developer mode + +"phys_friction" "6" be_aas_move.c ground friction +"phys_stopspeed" "100" be_aas_move.c stop speed +"phys_gravity" "800" be_aas_move.c gravity value +"phys_waterfriction" "1" be_aas_move.c water friction +"phys_watergravity" "400" be_aas_move.c gravity in water +"phys_maxvelocity" "320" be_aas_move.c maximum velocity +"phys_maxwalkvelocity" "320" be_aas_move.c maximum walk velocity +"phys_maxcrouchvelocity" "100" be_aas_move.c maximum crouch velocity +"phys_maxswimvelocity" "150" be_aas_move.c maximum swim velocity +"phys_walkaccelerate" "10" be_aas_move.c walk acceleration +"phys_airaccelerate" "1" be_aas_move.c air acceleration +"phys_swimaccelerate" "4" be_aas_move.c swim acceleration +"phys_maxstep" "18" be_aas_move.c maximum step height +"phys_maxsteepness" "0.7" be_aas_move.c maximum floor steepness +"phys_maxbarrier" "32" be_aas_move.c maximum barrier height +"phys_maxwaterjump" "19" be_aas_move.c maximum waterjump height +"phys_jumpvel" "270" be_aas_move.c jump z velocity +"phys_falldelta5" "40" be_aas_move.c +"phys_falldelta10" "60" be_aas_move.c +"rs_waterjump" "400" be_aas_move.c +"rs_teleport" "50" be_aas_move.c +"rs_barrierjump" "100" be_aas_move.c +"rs_startcrouch" "300" be_aas_move.c +"rs_startgrapple" "500" be_aas_move.c +"rs_startwalkoffledge" "70" be_aas_move.c +"rs_startjump" "300" be_aas_move.c +"rs_rocketjump" "500" be_aas_move.c +"rs_bfgjump" "500" be_aas_move.c +"rs_jumppad" "250" be_aas_move.c +"rs_aircontrolledjumppad" "300" be_aas_move.c +"rs_funcbob" "300" be_aas_move.c +"rs_startelevator" "50" be_aas_move.c +"rs_falldamage5" "300" be_aas_move.c +"rs_falldamage10" "500" be_aas_move.c +"rs_maxjumpfallheight" "450" be_aas_move.c + +"max_aaslinks" "4096" be_aas_sample.c maximum links in the AAS +"max_routingcache" "4096" be_aas_route.c maximum routing cache size in KB +"forceclustering" "0" be_aas_main.c force recalculation of clusters +"forcereachability" "0" be_aas_main.c force recalculation of reachabilities +"forcewrite" "0" be_aas_main.c force writing of aas file +"aasoptimize" "0" be_aas_main.c enable aas optimization +"sv_mapChecksum" "0" be_aas_main.c BSP file checksum +"bot_visualizejumppads" "0" be_aas_reach.c visualize jump pads + +"bot_reloadcharacters" "0" - reload bot character files +"ai_gametype" "0" be_ai_goal.c game type +"droppedweight" "1000" be_ai_goal.c additional dropped item weight +"weapindex_rocketlauncher" "5" be_ai_move.c rl weapon index for rocket jumping +"weapindex_bfg10k" "9" be_ai_move.c bfg weapon index for bfg jumping +"weapindex_grapple" "10" be_ai_move.c grapple weapon index for grappling +"entitytypemissile" "3" be_ai_move.c ET_MISSILE +"offhandgrapple" "0" be_ai_move.c enable off hand grapple hook +"cmd_grappleon" "grappleon" be_ai_move.c command to activate off hand grapple +"cmd_grappleoff" "grappleoff" be_ai_move.c command to deactivate off hand grapple +"itemconfig" "items.c" be_ai_goal.c item configuration file +"weaponconfig" "weapons.c" be_ai_weap.c weapon configuration file +"synfile" "syn.c" be_ai_chat.c file with synonyms +"rndfile" "rnd.c" be_ai_chat.c file with random strings +"matchfile" "match.c" be_ai_chat.c file with match strings +"nochat" "0" be_ai_chat.c disable chats +"max_messages" "1024" be_ai_chat.c console message heap size +"max_weaponinfo" "32" be_ai_weap.c maximum number of weapon info +"max_projectileinfo" "32" be_ai_weap.c maximum number of projectile info +"max_iteminfo" "256" be_ai_goal.c maximum number of item info +"max_levelitems" "256" be_ai_goal.c maximum number of level items + +*/ +#endif diff --git a/dlls/game/botmenudef.h b/dlls/game/botmenudef.h new file mode 100644 index 0000000..ef23567 --- /dev/null +++ b/dlls/game/botmenudef.h @@ -0,0 +1,287 @@ + +#define ITEM_TYPE_TEXT 0 // simple text +#define ITEM_TYPE_BUTTON 1 // button, basically text with a border +#define ITEM_TYPE_RADIOBUTTON 2 // toggle button, may be grouped +#define ITEM_TYPE_CHECKBOX 3 // check box +#define ITEM_TYPE_EDITFIELD 4 // editable text, associated with a cvar +#define ITEM_TYPE_COMBO 5 // drop down list +#define ITEM_TYPE_LISTBOX 6 // scrollable list +#define ITEM_TYPE_MODEL 7 // model +#define ITEM_TYPE_OWNERDRAW 8 // owner draw, name specs what it is +#define ITEM_TYPE_NUMERICFIELD 9 // editable text, associated with a cvar +#define ITEM_TYPE_SLIDER 10 // mouse speed, volume, etc. +#define ITEM_TYPE_YESNO 11 // yes no cvar setting +#define ITEM_TYPE_MULTI 12 // multiple list setting, enumerated +#define ITEM_TYPE_BIND 13 // multiple list setting, enumerated + +#define ITEM_ALIGN_LEFT 0 // left alignment +#define ITEM_ALIGN_CENTER 1 // center alignment +#define ITEM_ALIGN_RIGHT 2 // right alignment + +#define ITEM_TEXTSTYLE_NORMAL 0 // normal text +#define ITEM_TEXTSTYLE_BLINK 1 // fast blinking +#define ITEM_TEXTSTYLE_PULSE 2 // slow pulsing +#define ITEM_TEXTSTYLE_SHADOWED 3 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINED 4 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_OUTLINESHADOWED 5 // drop shadow ( need a color for this ) +#define ITEM_TEXTSTYLE_SHADOWEDMORE 6 // drop shadow ( need a color for this ) + +#define WINDOW_BORDER_NONE 0 // no border +#define WINDOW_BORDER_FULL 1 // full border based on border color ( single pixel ) +#define WINDOW_BORDER_HORZ 2 // horizontal borders only +#define WINDOW_BORDER_VERT 3 // vertical borders only +#define WINDOW_BORDER_KCGRADIENT 4 // horizontal border using the gradient bars + +#define WINDOW_STYLE_EMPTY 0 // no background +#define WINDOW_STYLE_FILLED 1 // filled with background color +#define WINDOW_STYLE_GRADIENT 2 // gradient bar based on background color +#define WINDOW_STYLE_SHADER 3 // gradient bar based on background color +#define WINDOW_STYLE_TEAMCOLOR 4 // team color +#define WINDOW_STYLE_CINEMATIC 5 // cinematic + +#define MENU_TRUE 1 // uh.. true +#define MENU_FALSE 0 // and false + +#define HUD_VERTICAL 0x00 +#define HUD_HORIZONTAL 0x01 + +// list box element types +#define LISTBOX_TEXT 0x00 +#define LISTBOX_IMAGE 0x01 + +// list feeders +#define FEEDER_HEADS 0x00 // model heads +#define FEEDER_MAPS 0x01 // text maps based on game type +#define FEEDER_SERVERS 0x02 // servers +#define FEEDER_CLANS 0x03 // clan names +#define FEEDER_ALLMAPS 0x04 // all maps available, in graphic format +#define FEEDER_REDTEAM_LIST 0x05 // red team members +#define FEEDER_BLUETEAM_LIST 0x06 // blue team members +#define FEEDER_PLAYER_LIST 0x07 // players +#define FEEDER_TEAM_LIST 0x08 // team members for team voting +#define FEEDER_MODS 0x09 // team members for team voting +#define FEEDER_DEMOS 0x0a // team members for team voting +#define FEEDER_SCOREBOARD 0x0b // team members for team voting +#define FEEDER_Q3HEADS 0x0c // model heads +#define FEEDER_SERVERSTATUS 0x0d // server status +#define FEEDER_FINDPLAYER 0x0e // find player +#define FEEDER_CINEMATICS 0x0f // cinematics + +// display flags +#define CG_SHOW_BLUE_TEAM_HAS_REDFLAG 0x00000001 +#define CG_SHOW_RED_TEAM_HAS_BLUEFLAG 0x00000002 +#define CG_SHOW_ANYTEAMGAME 0x00000004 +#define CG_SHOW_HARVESTER 0x00000008 +#define CG_SHOW_ONEFLAG 0x00000010 +#define CG_SHOW_CTF 0x00000020 +#define CG_SHOW_OBELISK 0x00000040 +#define CG_SHOW_HEALTHCRITICAL 0x00000080 +#define CG_SHOW_SINGLEPLAYER 0x00000100 +#define CG_SHOW_TOURNAMENT 0x00000200 +#define CG_SHOW_DURINGINCOMINGVOICE 0x00000400 +#define CG_SHOW_IF_PLAYER_HAS_FLAG 0x00000800 +#define CG_SHOW_LANPLAYONLY 0x00001000 +#define CG_SHOW_MINED 0x00002000 +#define CG_SHOW_HEALTHOK 0x00004000 +#define CG_SHOW_TEAMINFO 0x00008000 +#define CG_SHOW_NOTEAMINFO 0x00010000 +#define CG_SHOW_OTHERTEAMHASFLAG 0x00020000 +#define CG_SHOW_YOURTEAMHASENEMYFLAG 0x00040000 +#define CG_SHOW_ANYNONTEAMGAME 0x00080000 +#define CG_SHOW_2DONLY 0x10000000 + + +#define UI_SHOW_LEADER 0x00000001 +#define UI_SHOW_NOTLEADER 0x00000002 +#define UI_SHOW_FAVORITESERVERS 0x00000004 +#define UI_SHOW_ANYNONTEAMGAME 0x00000008 +#define UI_SHOW_ANYTEAMGAME 0x00000010 +#define UI_SHOW_NEWHIGHSCORE 0x00000020 +#define UI_SHOW_DEMOAVAILABLE 0x00000040 +#define UI_SHOW_NEWBESTTIME 0x00000080 +#define UI_SHOW_FFA 0x00000100 +#define UI_SHOW_NOTFFA 0x00000200 +#define UI_SHOW_NETANYNONTEAMGAME 0x00000400 +#define UI_SHOW_NETANYTEAMGAME 0x00000800 +#define UI_SHOW_NOTFAVORITESERVERS 0x00001000 + + + + +// owner draw types +// ideally these should be done outside of this file but +// this makes it much easier for the macro expansion to +// convert them for the designers ( from the .menu files ) +#define CG_OWNERDRAW_BASE 1 +#define CG_PLAYER_ARMOR_ICON 1 +#define CG_PLAYER_ARMOR_VALUE 2 +#define CG_PLAYER_HEAD 3 +#define CG_PLAYER_HEALTH 4 +#define CG_PLAYER_AMMO_ICON 5 +#define CG_PLAYER_AMMO_VALUE 6 +#define CG_SELECTEDPLAYER_HEAD 7 +#define CG_SELECTEDPLAYER_NAME 8 +#define CG_SELECTEDPLAYER_LOCATION 9 +#define CG_SELECTEDPLAYER_STATUS 10 +#define CG_SELECTEDPLAYER_WEAPON 11 +#define CG_SELECTEDPLAYER_POWERUP 12 + +#define CG_FLAGCARRIER_HEAD 13 +#define CG_FLAGCARRIER_NAME 14 +#define CG_FLAGCARRIER_LOCATION 15 +#define CG_FLAGCARRIER_STATUS 16 +#define CG_FLAGCARRIER_WEAPON 17 +#define CG_FLAGCARRIER_POWERUP 18 + +#define CG_PLAYER_ITEM 19 +#define CG_PLAYER_SCORE 20 + +#define CG_BLUE_FLAGHEAD 21 +#define CG_BLUE_FLAGSTATUS 22 +#define CG_BLUE_FLAGNAME 23 +#define CG_RED_FLAGHEAD 24 +#define CG_RED_FLAGSTATUS 25 +#define CG_RED_FLAGNAME 26 + +#define CG_BLUE_SCORE 27 +#define CG_RED_SCORE 28 +#define CG_RED_NAME 29 +#define CG_BLUE_NAME 30 +#define CG_HARVESTER_SKULLS 31 // only shows in harvester +#define CG_ONEFLAG_STATUS 32 // only shows in one flag +#define CG_PLAYER_LOCATION 33 +#define CG_TEAM_COLOR 34 +#define CG_CTF_POWERUP 35 + +#define CG_AREA_POWERUP 36 +#define CG_AREA_LAGOMETER 37 // painted with old system +#define CG_PLAYER_HASFLAG 38 +#define CG_GAME_TYPE 39 // not done + +#define CG_SELECTEDPLAYER_ARMOR 40 +#define CG_SELECTEDPLAYER_HEALTH 41 +#define CG_PLAYER_STATUS 42 +#define CG_FRAGGED_MSG 43 // painted with old system +#define CG_PROXMINED_MSG 44 // painted with old system +#define CG_AREA_FPSINFO 45 // painted with old system +#define CG_AREA_SYSTEMCHAT 46 // painted with old system +#define CG_AREA_TEAMCHAT 47 // painted with old system +#define CG_AREA_CHAT 48 // painted with old system +#define CG_GAME_STATUS 49 +#define CG_KILLER 50 +#define CG_PLAYER_ARMOR_ICON2D 51 +#define CG_PLAYER_AMMO_ICON2D 52 +#define CG_ACCURACY 53 +#define CG_ASSISTS 54 +#define CG_DEFEND 55 +#define CG_EXCELLENT 56 +#define CG_IMPRESSIVE 57 +#define CG_PERFECT 58 +#define CG_GAUNTLET 59 +#define CG_SPECTATORS 60 +#define CG_TEAMINFO 61 +#define CG_VOICE_HEAD 62 +#define CG_VOICE_NAME 63 +#define CG_PLAYER_HASFLAG2D 64 +#define CG_HARVESTER_SKULLS2D 65 // only shows in harvester +#define CG_CAPFRAGLIMIT 66 +#define CG_1STPLACE 67 +#define CG_2NDPLACE 68 +#define CG_CAPTURES 69 + + + + +#define UI_OWNERDRAW_BASE 200 +#define UI_HANDICAP 200 +#define UI_EFFECTS 201 +#define UI_PLAYERMODEL 202 +#define UI_CLANNAME 203 +#define UI_CLANLOGO 204 +#define UI_GAMETYPE 205 +#define UI_MAPPREVIEW 206 +#define UI_SKILL 207 +#define UI_BLUETEAMNAME 208 +#define UI_REDTEAMNAME 209 +#define UI_BLUETEAM1 210 +#define UI_BLUETEAM2 211 +#define UI_BLUETEAM3 212 +#define UI_BLUETEAM4 213 +#define UI_BLUETEAM5 214 +#define UI_REDTEAM1 215 +#define UI_REDTEAM2 216 +#define UI_REDTEAM3 217 +#define UI_REDTEAM4 218 +#define UI_REDTEAM5 219 +#define UI_NETSOURCE 220 +#define UI_NETMAPPREVIEW 221 +#define UI_NETFILTER 222 +#define UI_TIER 223 +#define UI_OPPONENTMODEL 224 +#define UI_TIERMAP1 225 +#define UI_TIERMAP2 226 +#define UI_TIERMAP3 227 +#define UI_PLAYERLOGO 228 +#define UI_OPPONENTLOGO 229 +#define UI_PLAYERLOGO_METAL 230 +#define UI_OPPONENTLOGO_METAL 231 +#define UI_PLAYERLOGO_NAME 232 +#define UI_OPPONENTLOGO_NAME 233 +#define UI_TIER_MAPNAME 234 +#define UI_TIER_GAMETYPE 235 +#define UI_ALLMAPS_SELECTION 236 +#define UI_OPPONENT_NAME 237 +#define UI_VOTE_KICK 238 +#define UI_BOTNAME 239 +#define UI_BOTSKILL 240 +#define UI_REDBLUE 241 +#define UI_CROSSHAIR 242 +#define UI_SELECTEDPLAYER 243 +#define UI_MAPCINEMATIC 244 +#define UI_NETGAMETYPE 245 +#define UI_NETMAPCINEMATIC 246 +#define UI_SERVERREFRESHDATE 247 +#define UI_SERVERMOTD 248 +#define UI_GLINFO 249 +#define UI_KEYBINDSTATUS 250 +#define UI_CLANCINEMATIC 251 +#define UI_MAP_TIMETOBEAT 252 +#define UI_JOINGAMETYPE 253 +#define UI_PREVIEWCINEMATIC 254 +#define UI_STARTMAPCINEMATIC 255 +#define UI_MAPS_SELECTION 256 + +#define VOICECHAT_GETFLAG "getflag" // command someone to get the flag +#define VOICECHAT_OFFENSE "offense" // command someone to go on offense +#define VOICECHAT_DEFEND "defend" // command someone to go on defense +#define VOICECHAT_DEFENDFLAG "defendflag" // command someone to defend the flag +#define VOICECHAT_PATROL "patrol" // command someone to go on patrol (roam) +#define VOICECHAT_CAMP "camp" // command someone to camp (we don't have sounds for this one) +#define VOICECHAT_FOLLOWME "followme" // command someone to follow you +#define VOICECHAT_RETURNFLAG "returnflag" // command someone to return our flag +#define VOICECHAT_FOLLOWFLAGCARRIER "followflagcarrier" // command someone to follow the flag carrier +#define VOICECHAT_YES "yes" // yes, affirmative, etc. +#define VOICECHAT_NO "no" // no, negative, etc. +#define VOICECHAT_ONGETFLAG "ongetflag" // I'm getting the flag +#define VOICECHAT_ONOFFENSE "onoffense" // I'm on offense +#define VOICECHAT_ONDEFENSE "ondefense" // I'm on defense +#define VOICECHAT_ONPATROL "onpatrol" // I'm on patrol (roaming) +#define VOICECHAT_ONCAMPING "oncamp" // I'm camping somewhere +#define VOICECHAT_ONFOLLOW "onfollow" // I'm following +#define VOICECHAT_ONFOLLOWCARRIER "onfollowcarrier" // I'm following the flag carrier +#define VOICECHAT_ONRETURNFLAG "onreturnflag" // I'm returning our flag +#define VOICECHAT_INPOSITION "inposition" // I'm in position +#define VOICECHAT_IHAVEFLAG "ihaveflag" // I have the flag +#define VOICECHAT_BASEATTACK "baseattack" // the base is under attack +#define VOICECHAT_ENEMYHASFLAG "enemyhasflag" // the enemy has our flag (CTF) +#define VOICECHAT_STARTLEADER "startleader" // I'm the leader +#define VOICECHAT_STOPLEADER "stopleader" // I resign leadership +#define VOICECHAT_TRASH "trash" // lots of trash talk +#define VOICECHAT_WHOISLEADER "whoisleader" // who is the team leader +#define VOICECHAT_WANTONDEFENSE "wantondefense" // I want to be on defense +#define VOICECHAT_WANTONOFFENSE "wantonoffense" // I want to be on offense +#define VOICECHAT_KILLINSULT "kill_insult" // I just killed you +#define VOICECHAT_TAUNT "taunt" // I want to taunt you +#define VOICECHAT_DEATHINSULT "death_insult" // you just killed me +#define VOICECHAT_KILLGAUNTLET "kill_gauntlet" // I just killed you with the gauntlet +#define VOICECHAT_PRAISE "praise" // you did something good diff --git a/dlls/game/bspline.cpp b/dlls/game/bspline.cpp new file mode 100644 index 0000000..a9b29d0 --- /dev/null +++ b/dlls/game/bspline.cpp @@ -0,0 +1,939 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/bspline.cpp $ +// $Revision:: 12 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:35p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// Uniform non-rational bspline class. +// + +#include "_pch_cpp.h" +#include "bspline.h" + +void BSpline::Set( const Vector *control_points_, int num_control_points_, splinetype_t type ) +{ + int i; + + SetType( type ); + + has_orientation = false; + + if ( control_points ) + { + delete [] control_points; + control_points = NULL; + } + + num_control_points = num_control_points_; + if ( num_control_points ) + { + control_points = new BSplineControlPoint[ num_control_points ]; + assert( control_points ); + + for( i = 0; i < num_control_points; i++ ) + { + control_points[ i ].Set( control_points_[ i ] ); + } + } +} + +void BSpline::Set( const Vector *control_points_, const Vector *control_orients_, const float *control_speeds_, + int num_control_points_, splinetype_t type ) +{ + int i; + + SetType( type ); + + has_orientation = true; + + if ( control_points ) + { + delete [] control_points; + control_points = NULL; + } + + num_control_points = num_control_points_; + if ( num_control_points ) + { + control_points = new BSplineControlPoint[ num_control_points ]; + assert( control_points ); + + for( i = 0; i < num_control_points; i++ ) + { + control_points[ i ].Set( control_points_[ i ], control_orients_[ i ], control_speeds_[ i ] ); + } + } +} + +void BSpline::Clear( void ) +{ + if( control_points ) + { + delete [] control_points; + control_points = NULL; + } + num_control_points = 0; + has_orientation = false; +} + +inline float BSpline::EvalNormal( float u, Vector& pos, Vector& orient ) +{ + int segment_id; + float B[ 4 ]; + float tmp; + float u_2; + float u_3; + Vector ang; + float roll; + float speed; + + segment_id = ( int )u; + if ( segment_id < 0 ) + { + segment_id = 0; + } + if ( segment_id > ( num_control_points - 4 ) ) + { + segment_id = num_control_points - 4; + } + u -= ( float )segment_id; + + u_2 = u * u; + u_3 = u * u_2; + + tmp = 1.0f - u; + B[ 0 ] = ( tmp * tmp * tmp ) * ( 1.0f / 6.0f ); + B[ 1 ] = ( ( 3.0f * u_3 ) - ( 6.0f * u_2 ) + 4.0f ) * ( 1.0f / 6.0f ); + B[ 2 ] = ( ( -3.0f * u_3 ) + ( 3.0f * u_2 ) + ( 3.0f * u ) + 1 ) * ( 1.0f / 6.0f ); + B[ 3 ] = u_3 * ( 1.0f / 6.0f ); + + pos = + ( *control_points[ 0 + segment_id ].GetPosition() * B[ 0 ] ) + + ( *control_points[ 1 + segment_id ].GetPosition() * B[ 1 ] ) + + ( *control_points[ 2 + segment_id ].GetPosition() * B[ 2 ] ) + + ( *control_points[ 3 + segment_id ].GetPosition() * B[ 3 ] ); + + ang = + ( *control_points[ 0 + segment_id ].GetOrientation() * B[ 0 ] ) + + ( *control_points[ 1 + segment_id ].GetOrientation() * B[ 1 ] ) + + ( *control_points[ 2 + segment_id ].GetOrientation() * B[ 2 ] ) + + ( *control_points[ 3 + segment_id ].GetOrientation() * B[ 3 ] ); + + roll = + ( *control_points[ 0 + segment_id ].GetRoll() * B[ 0 ] ) + + ( *control_points[ 1 + segment_id ].GetRoll() * B[ 1 ] ) + + ( *control_points[ 2 + segment_id ].GetRoll() * B[ 2 ] ) + + ( *control_points[ 3 + segment_id ].GetRoll() * B[ 3 ] ); + + speed = + ( *control_points[ 0 + segment_id ].GetSpeed() * B[ 0 ] ) + + ( *control_points[ 1 + segment_id ].GetSpeed() * B[ 1 ] ) + + ( *control_points[ 2 + segment_id ].GetSpeed() * B[ 2 ] ) + + ( *control_points[ 3 + segment_id ].GetSpeed() * B[ 3 ] ); + + orient = ang.toAngles(); + orient[ ROLL ] = roll; + + return speed; +} + +inline float BSpline::EvalLoop( float t, Vector& pos, Vector& orient ) +{ + Vector retval; + Vector ang; + float speed; + float roll; + int segment_id; + int next_id; + float B[ 4 ]; + float tmp; + float u; + float u_2; + float u_3; + int i; + int j; + + segment_id = ( int )floor( t ); + u = t - floor( t ); + + segment_id %= num_control_points; + if ( segment_id < 0 ) + { + segment_id += num_control_points; + } + + u_2 = u * u; + u_3 = u * u_2; + + tmp = 1.0f - u; + B[ 0 ] = ( tmp * tmp * tmp ) * ( 1.0f / 6.0f ); + B[ 1 ] = ( ( 3.0f * u_3 ) - ( 6.0f * u_2 ) + 4.0f ) * ( 1.0f / 6.0f ); + B[ 2 ] = ( ( -3.0f * u_3 ) + ( 3.0f * u_2 ) + ( 3.0f * u ) + 1 ) * ( 1.0f / 6.0f ); + B[ 3 ] = u_3 * ( 1.0f / 6.0f ); + + speed = 0; + roll = 0; + + for( i = 0, j = segment_id; i < 4; i++, j++ ) + { + if ( j >= num_control_points ) + { + j -= ( num_control_points - loop_control_point ); + } + + retval += *control_points[ j ].GetPosition() * B[ i ]; + ang += *control_points[ j ].GetOrientation() * B[ i ]; + speed += *control_points[ j ].GetSpeed() * B[ i ]; + roll += *control_points[ j ].GetRoll() * B[ i ]; + } + + pos = retval; + + next_id = segment_id + 1; + if ( next_id >= num_control_points ) + { + next_id -= ( num_control_points - loop_control_point ); + } + orient = ang.toAngles(); + orient[ ROLL ] = roll; + + return speed; +} + +inline float BSpline::EvalClamp( float t, Vector& pos, Vector& orient ) +{ + Vector retval; + Vector ang; + int segment_id; + int next_id; + float B[ 4 ]; + float tmp; + float u; + float u_2; + float u_3; + int i; + int j; + float speed; + float roll; + + segment_id = ( int )floor( t ); + u = t - floor( t ); + + u_2 = u * u; + u_3 = u * u_2; + + tmp = 1.0f - u; + B[ 0 ] = ( tmp * tmp * tmp ) * ( 1.0f / 6.0f ); + B[ 1 ] = ( ( 3.0f * u_3 ) - ( 6.0f * u_2 ) + 4.0f ) * ( 1.0f / 6.0f ); + B[ 2 ] = ( ( -3.0f * u_3 ) + ( 3.0f * u_2 ) + ( 3.0f * u ) + 1 ) * ( 1.0f / 6.0f ); + B[ 3 ] = u_3 * ( 1.0f / 6.0f ); + + speed = 0.0f; + roll = 0.0f; + for( i = 0; i < 4; i++, segment_id++ ) + { + j = segment_id; + if ( j < 0 ) + { + j = 0; + } + else if ( j >= num_control_points ) + { + j = num_control_points - 1; + } + + retval += *control_points[ j ].GetPosition() * B[ i ]; + ang += *control_points[ j ].GetOrientation() * B[ i ]; + speed += *control_points[ j ].GetSpeed() * B[ i ]; + roll += *control_points[ j ].GetRoll() * B[ i ]; + } + + pos = retval; + + next_id = segment_id + 1; + if ( segment_id < 0 ) + { + segment_id = 0; + } + if ( segment_id >= num_control_points ) + { + segment_id = num_control_points - 1; + } + if ( next_id < 0 ) + { + next_id = 0; + } + if ( next_id >= num_control_points ) + { + next_id = num_control_points - 1; + } + orient = ang.toAngles(); + orient[ ROLL ] = roll; + + return speed; +} + + +Vector BSpline::Eval( float u ) +{ + Vector pos; + Vector orient; + + switch( curvetype ) + { + default: + case SPLINE_NORMAL : + EvalNormal( u, pos, orient ); + break; + + case SPLINE_CLAMP: + EvalClamp( u, pos, orient ); + break; + + case SPLINE_LOOP: + if ( u < 0.0f ) + { + EvalClamp( u, pos, orient ); + } + else + { + EvalLoop( u, pos, orient ); + } + break; + } + return pos; +} + +float BSpline::Eval( float u, Vector &pos, Vector &orient ) +{ + switch( curvetype ) + { + default: + case SPLINE_NORMAL : + return EvalNormal( u, pos, orient ); + break; + + case SPLINE_CLAMP: + return EvalClamp( u, pos, orient ); + break; + + case SPLINE_LOOP: + if ( u < 0.0f ) + { + return EvalClamp( u, pos, orient ); + } + else + { + return EvalLoop( u, pos, orient ); + } + break; + } +} + +void BSpline::DrawControlSegments( void ) +{ + int i; + + G_BeginLine(); + for( i = 0; i < num_control_points; i++ ) + { + G_Vertex( *control_points[ i ].GetPosition() ); + } + G_EndLine(); +} + +void BSpline::DrawCurve( int num_subdivisions ) +{ + float u; + float du; + + if ( !num_control_points ) + { + return; + } + + du = 1.0f / ( float )num_subdivisions; + + G_BeginLine(); + for( u = -2.0f; u <= ( float )num_control_points; u += du ) + { + G_Vertex( ( Vector )Eval( u ) ); + } + G_EndLine(); +} + +void BSpline::DrawCurve( const Vector &offset, int num_subdivisions ) +{ + float u; + float du; + + du = 1.0f / ( float )num_subdivisions; + + G_BeginLine(); + for( u = -2.0f; u <= ( float )num_control_points; u += du ) + { + G_Vertex( offset + ( Vector )Eval( u ) ); + } + G_EndLine(); +} + +void BSpline::AppendControlPoint( const Vector& new_control_point ) +{ + BSplineControlPoint *old_control_points; + int i; + + old_control_points = control_points; + num_control_points++; + + control_points = new BSplineControlPoint[num_control_points]; + assert( control_points ); + + if ( old_control_points ) + { + for( i = 0; i < ( num_control_points - 1 ) ; i++ ) + { + control_points[ i ] = old_control_points[ i ]; + } + delete [] old_control_points; + } + + control_points[ num_control_points - 1 ].Set( new_control_point ); +} + +void BSpline::AppendControlPoint( const Vector& new_control_point, const float& speed ) +{ + BSplineControlPoint *old_control_points; + int i; + + old_control_points = control_points; + num_control_points++; + + control_points = new BSplineControlPoint[num_control_points]; + assert( control_points ); + + if ( old_control_points ) + { + for( i = 0; i < ( num_control_points - 1 ); i++ ) + { + control_points[ i ] = old_control_points[ i ]; + } + delete [] old_control_points; + } + + control_points[ num_control_points - 1 ].Set( new_control_point, speed ); +} + +void BSpline::AppendControlPoint( const Vector& new_control_point, const Vector& new_control_orient, + const float& new_control_speed ) +{ + BSplineControlPoint *old_control_points; + int i; + + has_orientation = true; + + old_control_points = control_points; + num_control_points++; + + control_points = new BSplineControlPoint[num_control_points]; + assert( control_points ); + + if ( old_control_points ) + { + for( i = 0; i < ( num_control_points - 1 ) ; i++ ) + { + control_points[ i ] = old_control_points[ i ]; + } + delete [] old_control_points; + } + + control_points[ num_control_points - 1 ].Set( new_control_point, new_control_orient, new_control_speed ); +} + +void BSpline::SetLoopPoint( const Vector& pos ) +{ + int i; + + for( i = 0; i < num_control_points; i++ ) + { + if ( pos == *control_points[ i ].GetPosition() ) + { + loop_control_point = i; + break; + } + } +} + +int BSpline::PickControlPoint( const Vector& window_point, float pick_size ) +{ + int i; + float closest_dist_2; + int closest_index; + float dist_2; + Vector delta; + + closest_index = -1; + closest_dist_2 = 1000000.0f; + for( i = 0; i < num_control_points; i++ ) + { + delta = window_point - *control_points[ i ].GetPosition(); + dist_2 = delta * delta; + if ( dist_2 < closest_dist_2 ) + { + closest_dist_2 = dist_2; + closest_index = i; + } + } + + if ( pick_size * pick_size >= closest_dist_2 ) + { + return closest_index; + } + else + { + return -1; + } +} + +Event EV_SplinePath_Create +( + "SplinePath_create", + EV_SCRIPTONLY, + NULL, + NULL, + "Creates the spline path from the target list." +); +Event EV_SplinePath_Loop +( + "loopSpline", + EV_SCRIPTONLY, + "s", + "loop_name", + "Sets the loop name." +); +Event EV_SplinePath_Speed +( + "speed", + EV_DEFAULT, + "f", + "speed", + "Sets the path speed." +); +Event EV_SplinePath_SetTriggerTarget +( + "triggertarget", + EV_SCRIPTONLY, + "s", + "target", + "Sets the trigger target." +); +Event EV_SplinePath_SetThread +( + "thread", + EV_SCRIPTONLY, + "s", + "thread", + "Sets the thread." +); +Event EV_SplinePath_SetWatch +( + "watch", + EV_SCRIPTONLY, + "s", + "watchEntity", + "Sets the entity to watch at this node." +); +Event EV_SplinePath_SetFov +( + "fov", + EV_CONSOLE, + "f", + "cameraFOV", + "Sets the fov at this node." +); +Event EV_SplinePath_SetFadeTime +( + "fadetime", + EV_SCRIPTONLY, + "f", + "fadeTime", + "Sets the fadetime at this node." +); +Event EV_SplinePath_MoveSpline +( + "movespline", + EV_SCRIPTONLY, + "v", + "new_position", + "Sets the new position of the spline." +); +Event EV_SplinePath_OffsetSpline +( + "offsetspline", + EV_SCRIPTONLY, + "v", + "offset", + "Offsets the position of the spline." +); +Event EV_SplinePath_TurnSpline +( + "turnspline", + EV_SCRIPTONLY, + "v", + "angle_offset", + "Turns the spline." +); + +/*QUAKED info_splinepath (0 0 .5) (-8 -8 -8) (8 8 8) x x x x x x x LADYBUG +Spline Path Node +"angles" camera will use that orientation +"loop" set a loop node +"triggertarget" when node is reached fire these targets +"thread" fire this thread when node is reached +"speed" speed multiplier for this segment, normally each\ +segment will take 1 second to traverse, use 2 for double speed\ +and 0.5 for half speed +*/ + +CLASS_DECLARATION( Entity, SplinePath, "info_splinepath" ) +{ + { &EV_SplinePath_Create, &SplinePath::CreatePath }, + { &EV_SplinePath_Loop, &SplinePath::SetLoop }, + { &EV_SplinePath_Speed, &SplinePath::SetSpeed }, + { &EV_SplinePath_SetTriggerTarget, &SplinePath::SetTriggerTarget }, + { &EV_SplinePath_SetThread, &SplinePath::SetThread }, + { &EV_SplinePath_SetWatch, &SplinePath::SetWatch }, + { &EV_SplinePath_SetFov, &SplinePath::SetFov }, + { &EV_SplinePath_SetFadeTime, &SplinePath::SetFadeTime }, + { &EV_SplinePath_MoveSpline, &SplinePath::MoveSpline }, + { &EV_SplinePath_OffsetSpline, &SplinePath::OffsetSpline }, + { &EV_SplinePath_TurnSpline, &SplinePath::TurnSpline }, + + { NULL, NULL } +}; + +SplinePath::SplinePath() +{ + owner = this; + next = NULL; + loop = NULL; + speed = 1; + doWatch = false; + watchEnt = ""; + fov = 0; + fadeTime = -1; + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_NOT ); + hideModel(); + + if ( !LoadingSavegame ) + { + PostEvent( EV_SplinePath_Create, FRAMETIME ); + } +} + +SplinePath::~SplinePath() +{ + // disconnect from the chain + if ( owner != this ) + { + owner->SetNext( next ); + } + else if ( next ) + { + next->SetPrev( NULL ); + next = NULL; + } + + assert( owner == this ); + assert( next == NULL ); +} + +void SplinePath::SetLoop( Event *ev ) +{ + loop_name = ev->GetString( 1 ); +} + +void SplinePath::SetSpeed( Event *ev ) +{ + speed = ev->GetFloat( 1 ); +} + +void SplinePath::SetTriggerTarget( Event *ev ) +{ + SetTriggerTarget( ev->GetString( 1 ) ); +} + +void SplinePath::SetThread( Event *ev ) +{ + SetThread( ev->GetString( 1 ) ); +} + +void SplinePath::CreatePath( Event *ev ) +{ + const char *target; + Entity *ent; + + // Make the path from the targetlist. + target = Target(); + if ( target[ 0 ] ) + { + ent = G_FindTarget( NULL, target ); + if ( ent ) + { + assert( next == NULL ); + + next = ( SplinePath * )ent; + next->owner = this; + } + else + { + gi.Error( ERR_DROP, "SplinePath::CreatePath: target %s not found\n", target ); + } + } + if ( loop_name.length() ) + { + ent = G_FindTarget( NULL, loop_name.c_str() ); + if ( ent ) + { + loop = ( SplinePath * )ent; + } + } +} + +SplinePath *SplinePath::GetNext( void ) +{ + return next; +} + +SplinePath *SplinePath::GetPrev( void ) +{ + if ( owner == this ) + { + return NULL; + } + + return owner; +} + +void SplinePath::SetNext( SplinePath *node ) +{ + if ( next ) + { + // remove ourselves from the chain + next->owner = next; + } + + next = node; + if ( next ) + { + // disconnect next from it's previous node + if ( next->owner != next ) + { + next->owner->next = NULL; + } + next->owner = this; + } +} + +void SplinePath::SetPrev( SplinePath *node ) +{ + if ( owner != this ) + { + owner->next = NULL; + } + + if ( node && ( node != this ) ) + { + // safely remove the node from its chain + if ( node->next ) + { + node->next->owner = node->next; + } + node->next = this; + owner = node; + } + else + { + owner = this; + } +} + +SplinePath *SplinePath::GetLoop( void ) +{ + return loop; +} + +void SplinePath::SetWatch( const char *name ) +{ + if ( watchEnt != name ) + { + watchEnt = name; + if ( watchEnt.length() ) + { + doWatch = true; + } + else + { + doWatch = false; + } + } +} + +void SplinePath::SetWatch( Event *ev ) +{ + SetWatch( ev->GetString( 1 ) ); +} + +void SplinePath::NoWatch( void ) +{ + doWatch = true; + watchEnt = "none"; +} + +str SplinePath::GetWatch( void ) +{ + return watchEnt; +} + +void SplinePath::SetFov( float newFov ) +{ + fov = newFov; +} + +void SplinePath::SetFov( Event *ev ) +{ + fov = ev->GetFloat( 1 ); +} + +float SplinePath::GetFov( void ) +{ + return fov; +} + +void SplinePath::SetFadeTime( float newFadeTime ) +{ + fadeTime = newFadeTime; +} + +void SplinePath::SetFadeTime( Event *ev ) +{ + fadeTime = ev->GetFloat( 1 ); +} + +float SplinePath::GetFadeTime( void ) +{ + return fadeTime; +} + +void SplinePath::MoveSpline( Event *ev ) +{ + Vector new_position; + Vector offset; + Event *new_event; + + // Get the new position for this spline node + + new_position = ev->GetVector( 1 ); + + // Get the offset to move the entire spline + + offset = new_position - origin; + + // Move the entire spline + + new_event = new Event( EV_SplinePath_OffsetSpline ); + + new_event->AddVector( offset ); + + ProcessEvent( new_event ); +} + +void SplinePath::OffsetSpline( Event *ev ) +{ + Vector offset; + const char *target; + Entity *ent; + + offset = ev->GetVector( 1 ); + + target = Target(); + + setOrigin( origin + offset ); + + if ( !target || !target[ 0 ] ) + return; + + ent = G_FindTarget( NULL, target ); + + if ( !ent ) + return; + + while ( ent && ( ent != this ) ) + { + ent->setOrigin( ent->origin + offset ); + + target = ent->Target(); + + if ( !target || !target[ 0 ] ) + break; + + ent = G_FindTarget( NULL, target ); + } +} + +void SplinePath::TurnSpline( Event *ev ) +{ + Vector new_angles; + Vector angles_diff; + float axis[3][3]; + const char *target; + Entity *ent; + Vector offset; + Vector transformed_origin; + + new_angles = ev->GetVector( 1 ); + + angles_diff = new_angles - angles; + + AnglesToAxis( angles_diff, axis ); + + setAngles( new_angles ); + + target = Target(); + + if ( !target || !target[ 0 ] ) + return; + + ent = G_FindTarget( NULL, target ); + + if ( !ent ) + return; + + while ( ent && ( ent != this ) ) + { + offset = ent->origin - origin; + + MatrixTransformVector( offset, axis, transformed_origin ); + + transformed_origin += origin; + + ent->setOrigin( transformed_origin ); + + target = ent->Target(); + + if ( !target || !target[ 0 ] ) + break; + + ent = G_FindTarget( NULL, target ); + } +} + diff --git a/dlls/game/bspline.h b/dlls/game/bspline.h new file mode 100644 index 0000000..cdd3543 --- /dev/null +++ b/dlls/game/bspline.h @@ -0,0 +1,586 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/bspline.h $ +// $Revision:: 9 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Uniform non-rational bspline class. +// + +#ifndef __BSPLINE_H__ +#define __BSPLINE_H__ + +#include "g_local.h" +#include "vector.h" + +typedef enum + { + SPLINE_NORMAL, + SPLINE_LOOP, + SPLINE_CLAMP + } splinetype_t; + +class BSplineControlPoint : public Class + { + private: + float roll; + Vector position; + Vector orientation; + float speed; + + public: + BSplineControlPoint(); + BSplineControlPoint( const Vector &pos, Vector orient, float speed ); + BSplineControlPoint( const Vector &pos ); + void Clear( void ); + void Set( const Vector &pos ); + void Set( const Vector &pos, float speed ); + void Set( const Vector &pos, Vector orient, float speed ); + void Get( Vector& pos, Vector& orient, float& speed ); + void Get( Vector& pos ); + Vector *GetPosition( void ); + Vector *GetOrientation( void ); + float *GetRoll( void ); + float *GetSpeed( void ); + void operator=( const BSplineControlPoint &point ); + virtual void Archive( Archiver &arc ); + }; + +inline void BSplineControlPoint::Archive( Archiver &arc ) +{ + arc.ArchiveFloat( &roll ); + arc.ArchiveVector( &position ); + arc.ArchiveVector( &orientation ); + arc.ArchiveFloat( &speed ); +} + +inline void BSplineControlPoint::operator= + ( + const BSplineControlPoint &point + ) + + { + position = point.position; + orientation = point.orientation; + speed = point.speed; + roll = point.roll; + } + +inline BSplineControlPoint::BSplineControlPoint() + { + roll = 0; + speed = 1; + } + +inline BSplineControlPoint::BSplineControlPoint + ( + const Vector &pos + ) + + { + roll = 0; + speed = 1; + position = pos; + } + +inline BSplineControlPoint::BSplineControlPoint + ( + const Vector &pos, + Vector orient, + float init_speed + ) + + { + position = pos; + orient.AngleVectors( &orientation, NULL, NULL ); + roll = orient[ ROLL ]; + if ( roll > 180.0f ) + { + roll -= 360.0f; + } + if ( roll < -180.0f ) + { + roll += 360.0f; + } + speed = init_speed; + } + +inline void BSplineControlPoint::Clear + ( + void + ) + + { + roll = 0; + position = Vector(0, 0, 0); + vec_zero.AngleVectors( &orientation, NULL, NULL ); + speed = 1.0f; + } + +inline void BSplineControlPoint::Set + ( + const Vector &pos + ) + + { + speed = 1; + position = pos; + } + +inline void BSplineControlPoint::Set + ( + const Vector &pos, + float pointspeed + ) + + { + speed = pointspeed; + position = pos; + } + +inline void BSplineControlPoint::Set + ( + const Vector &pos, + Vector orient, + float new_speed + ) + + { + position = pos; + orient.AngleVectors( &orientation, NULL, NULL ); + roll = orient[ ROLL ]; + if ( roll > 180.0f ) + { + roll -= 360.0f; + } + if ( roll < -180.0f ) + { + roll += 360.0f; + } + speed = new_speed; + } + +inline void BSplineControlPoint::Get + ( + Vector& pos + ) + { + pos = position; + } + +inline Vector *BSplineControlPoint::GetPosition + ( + void + ) + + { + return &position; + } + +inline void BSplineControlPoint::Get + ( + Vector& pos, + Vector& orient, + float& spd + ) + + { + pos = position; + orient = orientation; + spd = speed; + } + +inline Vector *BSplineControlPoint::GetOrientation + ( + void + ) + { + return &orientation; + } + +inline float *BSplineControlPoint::GetRoll + ( + void + ) + { + return &roll; + } + +inline float *BSplineControlPoint::GetSpeed + ( + void + ) + { + return &speed; + } + +class BSpline : public Class + { + private: + BSplineControlPoint *control_points; + int num_control_points; + int loop_control_point; + splinetype_t curvetype; + qboolean has_orientation; + + float EvalNormal( float u, Vector &pos, Vector& orient ); + float EvalLoop( float u, Vector &pos, Vector& orient ); + float EvalClamp( float u, Vector &pos, Vector& orient ); + + public: + BSpline(); + ~BSpline(); + BSpline( Vector *control_points_, int num_control_points_, splinetype_t type ); + BSpline( Vector *control_points_, Vector *control_orients_, float *control_speeds_, int num_control_points_, splinetype_t type ); + void operator=( const BSpline &spline ); + void SetType( splinetype_t type ); + int GetType( void ); + void Clear( void ); + void Set( const Vector *control_points_, int num_control_points_, splinetype_t type ); + void Set( const Vector *control_points_, const Vector *control_orients_, const float *control_speeds_, int num_control_points_, splinetype_t type ); + void AppendControlPoint( const Vector& new_control_point ); + void AppendControlPoint( const Vector& new_control_point, const float& speed ); + void AppendControlPoint( const Vector& new_control_point, const Vector& new_control_orient, const float& speed ); + Vector Eval( float u ); + float Eval( float u, Vector& pos, Vector& orient ); + + void DrawControlSegments( void ); + void DrawCurve( int num_subdivisions ); + void DrawCurve( const Vector &offset, int num_subdivisions ); + + void SetLoopPoint( const Vector& pos ); + + float EndPoint( void ); + + // return the index of the control point picked or -1 if none. + int PickControlPoint( const Vector& window_point, float pick_size ); + + Vector *GetControlPoint( int id ); + void GetControlPoint( int id, Vector& pos, Vector& orient, float& speed ); + void SetControlPoint( int id, const Vector& new_control_point ); + void SetControlPoint( int id, const Vector& new_control_point, const Vector& new_control_orient, const float& speed ); + virtual void Archive( Archiver &arc ); + }; + +inline BSpline::BSpline() + { + has_orientation = false; + control_points = NULL; + num_control_points = 0; + loop_control_point = 0; + curvetype = SPLINE_NORMAL; + } + +inline BSpline::~BSpline() + { + if ( control_points ) + { + delete [] control_points; + control_points = NULL; + } + } + +inline BSpline::BSpline + ( + Vector *control_points_, + int num_control_points_, + splinetype_t type + ) + + { + has_orientation = false; + control_points = NULL; + num_control_points = 0; + loop_control_point = 0; + curvetype = SPLINE_NORMAL; + + Set( control_points_, num_control_points_, type ); + } + +inline BSpline::BSpline + ( + Vector *control_points_, + Vector *control_orients_, + float *control_speeds_, + int num_control_points_, + splinetype_t type + ) + + { + has_orientation = false; + control_points = NULL; + num_control_points = 0; + loop_control_point = 0; + curvetype = SPLINE_NORMAL; + + Set( control_points_, control_orients_, control_speeds_, num_control_points_, type ); + } + +inline void BSpline::operator= + ( + const BSpline &spline + ) + + { + int i; + + Clear(); + num_control_points = spline.num_control_points; + loop_control_point = spline.loop_control_point; + curvetype = spline.curvetype; + has_orientation = spline.has_orientation; + + if ( num_control_points ) + { + control_points = new BSplineControlPoint[num_control_points]; + assert( control_points ); + for ( i = 0; i < num_control_points ; i++ ) + control_points[ i ] = spline.control_points[ i ]; + } + else + { + control_points = NULL; + } + } + +inline void BSpline::SetType + ( + splinetype_t type + ) + + { + curvetype = type; + } + +inline int BSpline::GetType + ( + void + ) + + { + return curvetype; + } + +inline float BSpline::EndPoint + ( + void + ) + + { + return num_control_points; + } + +inline Vector *BSpline::GetControlPoint + ( + int id + ) + + { + assert( id >= 0 ); + assert( id < num_control_points ); + if ( ( id < 0 ) && ( id >= num_control_points ) ) + { + // probably wrong, but if we're in release mode we have no recourse + id = 0; + } + + return control_points[ id ].GetPosition(); + } + +inline void BSpline::GetControlPoint + ( + int id, + Vector& pos, + Vector& orient, + float& speed + ) + + { + assert( id >= 0 ); + assert( id < num_control_points ); + + if ( !control_points ) + return; + + if ( ( id >= 0 ) && ( id < num_control_points ) ) + { + control_points[ id ].Get( pos, orient, speed ); + } + } + +inline void BSpline::SetControlPoint + ( + int id, + const Vector& new_control_point + ) + + { + assert( id >= 0 ); + assert( id < num_control_points ); + + if ( !control_points ) + return; + + if ( ( id >= 0 ) && ( id < num_control_points ) ) + { + control_points[ id ].Set( new_control_point ); + } + } + +inline void BSpline::SetControlPoint + ( + int id, + const Vector& new_control_point, + const Vector& new_control_orient, + const float& speed + ) + + { + assert( id >= 0 ); + assert( id < num_control_points ); + + if ( !control_points ) + return; + + if ( ( id >= 0 ) && ( id < num_control_points ) ) + { + control_points[ id ].Set( new_control_point, new_control_orient, speed ); + } + } + +inline void BSpline::Archive + ( + Archiver &arc + ) + { + int i; + + arc.ArchiveInteger( &num_control_points ); + if ( arc.Loading() ) + { + if ( num_control_points ) + control_points = new BSplineControlPoint[ num_control_points ]; + } + + arc.ArchiveInteger( &loop_control_point ); + + i = curvetype; + arc.ArchiveInteger( &i ); + curvetype = ( splinetype_t )i; + + arc.ArchiveBoolean( &has_orientation ); + for( i = 0; i < num_control_points; i++ ) + { + control_points[ i ].Archive( arc ); + } + } + +extern Event EV_SplinePath_Create; +extern Event EV_SplinePath_Loop; +extern Event EV_SplinePath_Speed; + +class SplinePath : public Entity + { + protected: + SplinePath *owner; + SplinePath *next; + SplinePath *loop; + str loop_name; + + void CreatePath( Event *ev ); + void SetLoop( Event *ev ); + void SetSpeed( Event *ev ); + void SetTriggerTarget( Event *ev ); + void SetThread( Event *ev ); + void SetFov( Event *ev ); + void SetWatch( Event *ev ); + void SetFadeTime( Event *ev ); + void MoveSpline( Event *ev ); + void OffsetSpline( Event *ev ); + void TurnSpline( Event *ev ); + + public: + float speed; + float fov; + float fadeTime; + qboolean doWatch; + str watchEnt; + str triggertarget; + str thread; + + CLASS_PROTOTYPE( SplinePath ); + + SplinePath(); + ~SplinePath(); + SplinePath *GetNext( void ); + SplinePath *GetPrev( void ); + SplinePath *GetLoop( void ); + void SetFadeTime( float newFadeTime ); + void SetFov( float theFov ); + void SetWatch( const char *name ); + void SetThread( const char *name ); + void SetTriggerTarget( const char *name ); + void NoWatch( void ); + str GetWatch( void ); + float GetFadeTime( void ); + float GetFov( void ); + void SetNext( SplinePath *node ); + void SetPrev( SplinePath *node ); + virtual void Archive( Archiver &arc ); + }; + +typedef SafePtr SplinePathPtr; + +inline void SplinePath::Archive + ( + Archiver &arc + ) + + { + Entity::Archive( arc ); + + arc.ArchiveObjectPointer( ( Class ** )&owner ); + arc.ArchiveObjectPointer( ( Class ** )&next ); + arc.ArchiveObjectPointer( ( Class ** )&loop ); + arc.ArchiveString( &loop_name ); + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &fov ); + arc.ArchiveFloat( &fadeTime ); + arc.ArchiveBoolean( &doWatch ); + arc.ArchiveString( &watchEnt ); + arc.ArchiveString( &thread ); + arc.ArchiveString( &triggertarget ); + if ( arc.Loading() ) + { + CancelEventsOfType( EV_SplinePath_Create ); + } + } + +inline void SplinePath::SetThread + ( + const char *name + ) + + { + thread = name; + } + +inline void SplinePath::SetTriggerTarget + ( + const char *name + ) + + { + triggertarget = name; + } + +#endif /* __BSPLINE_H__ */ diff --git a/dlls/game/camera.cpp b/dlls/game/camera.cpp new file mode 100644 index 0000000..4566691 --- /dev/null +++ b/dlls/game/camera.cpp @@ -0,0 +1,3852 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/camera.cpp $ +// $Revision:: 31 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:11a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Camera. Duh. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "trigger.h" +#include "camera.h" +#include "bspline.h" +#include "player.h" +#include "camera.h" + +//#define CAMERA_PATHFILE_VERSION 1 + +CameraManager CameraMan; + + +//--------------------------------------------------------------------------- +// CameraMoveState::Initialize +//--------------------------------------------------------------------------- +void CameraMoveState::Initialize( Camera * camera ) +{ + assert( camera ); + if ( !camera ) + return; + + pos = camera->origin; + angles = camera->angles; + movedir = vec_zero; + followEnt = NULL; + orbitEnt = NULL; + + followingpath = false; + cameraTime = 0; + lastTime = 0; + newTime = 0; + cameraPath.Clear(); + splinePath = NULL; + currentNode = NULL; + loopNode = NULL; +} + + +//--------------------------------------------------------------------------- +// CameraWatchState::Initialize +//--------------------------------------------------------------------------- +void CameraWatchState::Initialize( Camera * camera ) +{ + assert( camera ); + if ( !camera ) + return; + + watchAngles = camera->angles; + watchEnt = NULL; + watchNodes = true; + watchPath = false; +} + + +//--------------------------------------------------------------------------- +// CameraState::Initialize +//--------------------------------------------------------------------------- +void CameraState::Initialize( Camera * camera ) +{ + assert( camera ); + if ( !camera ) + return; + + move.Initialize( camera ); + watch.Initialize( camera ); + fov = camera->Fov(); +} + + +//--------------------------------------------------------------------------- +// CameraMoveState::DoNodeEvents +//--------------------------------------------------------------------------- +void CameraMoveState::DoNodeEvents( Camera* camera ) +{ + SplinePath *node; + Entity *ent; + Event *event; + + assert( camera ); + + if ( !camera ) + return; + + node = currentNode; + if ( node ) + { + float fadeTime; + float newFov; + + fadeTime = node->GetFadeTime(); + if ( fadeTime == -1.0f ) + { + fadeTime = camera->fadeTime; + } + + if ( node->doWatch ) + { + camera->Watch( node->GetWatch(), fadeTime ); + } + + newFov = node->GetFov(); + if ( newFov ) + { + camera->SetFOV( newFov, fadeTime ); + } + + if ( node->thread != "" ) + { + if ( !ExecuteThread( node->thread ) ) + { + gi.Error( ERR_DROP, "Camera could not start thread '%s' from info_splinepath '%s'\n", + node->thread.c_str(), node->targetname.c_str() ); + } + } + + if ( node->triggertarget != "" ) + { + ent = NULL; + do + { + ent = G_FindTarget( ent, node->triggertarget.c_str() ); + if ( !ent ) + break; + + event = new Event( EV_Activate ); + event->AddEntity( camera ); + ent->PostEvent( event, 0.0f ); + } + while ( 1 ); + } + } +} + + +//--------------------------------------------------------------------------- +// CameraMoveState::Evaluate +//--------------------------------------------------------------------------- +void CameraMoveState::Evaluate( Camera* camera ) +{ + Vector oldpos; + float speed_multiplier; + + assert( camera ); + if ( !camera ) + return; + + oldpos = pos; + // + // check for node events + // we explicitly skip the first node because we process that + // when we begin the follow path command + // + if ( ( lastTime != newTime ) && currentNode ) + { + if ( newTime > 1 ) + { + DoNodeEvents( camera ); + } + currentNode = currentNode->GetNext(); + if ( !currentNode ) + { + currentNode = loopNode; + } + } + lastTime = newTime; + + // + // evaluate position + // + if ( followingpath ) + { + speed_multiplier = cameraPath.Eval( cameraTime, pos, angles ); + + cameraTime += level.fixedframetime * camera->camera_speed * speed_multiplier; + + if ( orbitEnt ) + { + pos += orbitEnt->origin; + if ( camera->orbit_dotrace ) + { + trace_t trace; + Vector start, back; + + start = orbitEnt->origin; + start[ 2 ] += orbitEnt->maxs[ 2 ]; + + back = start - pos; + back.normalize(); + + trace = G_Trace( start, vec_zero, vec_zero, pos, orbitEnt, camera->follow_mask, false, "Camera::EvaluatePosition" ); + + if ( trace.fraction < 1.0f ) + { + pos = trace.endpos; + // step in a bit towards the followEng + pos += back * 16.0f; + } + } + } + } + else // if !( followingpath ) + { + if ( followEnt ) + { + trace_t trace; + Vector start, end, ang, back; + + start = followEnt->origin; + start[ 2 ] += followEnt->maxs[ 2 ]; + + if ( camera->follow_yaw_fixed ) + { + ang = vec_zero; + } + else + { + if ( followEnt->isSubclassOf( Player ) ) + { + Entity * ent; + ent = followEnt; + ( ( Player * )ent )->GetPlayerView( NULL, &ang ); + } + else + { + ang = followEnt->angles; + } + } + ang.y += camera->follow_yaw; + ang.AngleVectors( &back, NULL, NULL ); + + end = start - ( back * camera->follow_dist ); + end[ 2 ] += 12.0f; + + trace = G_Trace( start, vec_zero, vec_zero, end, followEnt, camera->follow_mask, false, "Camera::EvaluatePosition - Orbit" ); + + pos = trace.endpos; + // step in a bit towards the followEnt + pos += back * 16.0f; + } + } + + // + // update times for node events + // + newTime = (int)(cameraTime + 2.0f); + + if ( newTime < 0.0f ) + { + newTime = 0; + } + // + // set movedir + // + movedir = pos - oldpos; +} + + +//--------------------------------------------------------------------------- +// CameraWatchState::Evaluate +//--------------------------------------------------------------------------- +void CameraWatchState::Evaluate( const Camera* camera, const CameraMoveState* move ) +{ + assert( camera ); + assert( move ); + if ( !camera || !move ) + return; + + // + // evaluate orientation + // + if ( watchEnt ) + { + Vector watchPos; + + watchPos.x = watchEnt->origin.x; + watchPos.y = watchEnt->origin.y; + watchPos.z = watchEnt->absmax.z; + watchPos -= camera->origin; + watchPos.normalize(); + watchAngles = watchPos.toAngles(); + } + else if ( watchNodes ) + { + watchAngles = move->angles; + } + else if ( watchPath ) + { + float length; + Vector delta; + + delta = move->movedir; + length = delta.length(); + if ( length > 0.05f ) + { + delta *= 1.0f / length; + watchAngles = delta.toAngles(); + } + } + watchAngles[ 0 ] = AngleMod( watchAngles[ 0 ] ); + watchAngles[ 1 ] = AngleMod( watchAngles[ 1 ] ); + watchAngles[ 2 ] = AngleMod( watchAngles[ 2 ] ); +} + + +//--------------------------------------------------------------------------- +// CameraState::Evaluate +//--------------------------------------------------------------------------- +void CameraState::Evaluate( Camera* camera ) +{ + move.Evaluate( camera ); + watch.Evaluate( camera, &move ); +} + + +//--------------------------------------------------------------------------- +// Events (EV_XXXXXXXX) +//--------------------------------------------------------------------------- +Event EV_Camera_CameraThink +( + "camera_think", + EV_CODEONLY, + NULL, + NULL, + "Called each frame to allow the camera to adjust its position." +); +Event EV_Camera_StartMoving +( + "start", + EV_DEFAULT, + NULL, + NULL, + "Start camera moving." +); +Event EV_Camera_Pause +( + "pause", + EV_SCRIPTONLY, + NULL, + NULL, + "Pause the camera." +); +Event EV_Camera_Continue +( + "continue", + EV_SCRIPTONLY, + NULL, + NULL, + "Continue the camera movement." +); +Event EV_Camera_StopMoving +( + "stop", + EV_CONSOLE | EV_SCRIPTONLY, + NULL, + NULL, + "Stop the camera movement." +); +Event EV_Camera_SetSpeed +( + "speed", + EV_DEFAULT, + "f", + "speed", + "Sets the camera speed." +); +Event EV_Camera_SetFOV +( + "fov", + EV_CONSOLE, + "fF", + "fov fadeTime", + "Sets the camera's field of view (fov).\n" + "if fadeTime is specified, camera will fade over that time\n" + "if fov is less than 3, than an auto_fov will be assumed\n" + "the value of fov will be the ratio used for keeping a watch\n" + "entity in the view at the right scale" +); + +Event EV_Camera_SetInterpolateFOV +( + "interpolatefov", + EV_DEFAULT, + "ff", + "fov interpolateTime", + "This is just like the camera set fov, but this uses a different name\n" + "To prevent naming conflicts." +); + +Event EV_Camera_LoadKFC +( + "loadKFC", + EV_DEFAULT | EV_CONSOLE, + "s", + "kfcFileName", + "Load KFC file, parse it, create an instance of a CameraPath" +); + +// +// FOLLOW EVENTS +// +Event EV_Camera_Follow +( + "follow", + EV_SCRIPTONLY, + "eE", + "targetEnt targetWatchEnt", + "Makes the camera follow an entity and optionally watch an entity." +); +Event EV_Camera_SetFollowDistance +( + "follow_distance", + EV_SCRIPTONLY, + "f", + "distance", + "Sets the camera follow distance." +); +Event EV_Camera_SetFollowYaw +( + "follow_yaw", + EV_SCRIPTONLY, + "f", + "yaw", + "Sets the yaw offset of the camera following an entity." +); +Event EV_Camera_AbsoluteYaw +( + "follow_yaw_absolute", + EV_SCRIPTONLY, + NULL, + NULL, + "Makes the follow camera yaw absolute." +); +Event EV_Camera_RelativeYaw +( + "follow_yaw_relative", + EV_SCRIPTONLY, + NULL, + NULL, + "Makes the follow camera yaw relative (not absolute)." +); + +// +// ORBIT Events +// +Event EV_Camera_Orbit +( + "orbit", + EV_SCRIPTONLY, + "eE", + "targetEnt targetWatchEnt", + "Makes the camera orbit around an entity and optionally watch an entity." +); +Event EV_Camera_SetOrbitHeight +( + "orbit_height", + EV_SCRIPTONLY, + "f", + "height", + "Sets the orbit camera's height." +); + +// +// Watch Events +// +Event EV_Camera_Watch +( + "watch", + EV_SCRIPTONLY, + "eF", + "watchEnt fadeTime", + "Makes the camera watch an entity.\n" + "if fadeTime is specified, camera will fade over that time" +); +Event EV_Camera_WatchPath +( + "watchpath", + EV_CONSOLE | EV_SCRIPTONLY, + "F", + "fadeTime", + "Makes the camera look along the path of travel.\n" + "if fadeTime is specified, camera will fade over that time" +); +Event EV_Camera_WatchNodes +( + "watchnode", + EV_CONSOLE | EV_SCRIPTONLY, + "F", + "fadeTime", + "Makes the camera watch based on what is stored\n" + "in the camera nodes.\n" + "if fadeTime is specified, camera will fade over that time" +); +Event EV_Camera_NoWatch +( + "nowatch", + EV_CONSOLE | EV_SCRIPTONLY, + "F", + "fadeTime", + "Stop watching an entity or looking along a path.\n" + "Camera is now static as far as orientation.\n" + "if fadeTime is specified, camera will fade over that time" +); + +// +// Camera positioning events +// +Event EV_Camera_LookAt +( + "lookat", + EV_SCRIPTONLY, + "e", + "ent", + "Makes the camera look at an entity." +); +Event EV_Camera_TurnTo +( + "turnto", + EV_SCRIPTONLY, + "v", + "angle", + "Makes the camera look in the specified direction." +); +Event EV_Camera_MoveToEntity +( + "moveto", + EV_SCRIPTONLY, + "e", + "ent", + "Move the camera's position to that of the specified entities." +); +Event EV_Camera_MoveToPos +( + "movetopos", + EV_SCRIPTONLY, + "v", + "position", + "Move the camera's position to the specified position." +); + +// +// Camera Transitioning events +// +Event EV_Camera_FadeTime +( + "fadetime", + EV_SCRIPTONLY, + "f", + "fadetime", + "Sets the fade time for camera transitioning." +); +Event EV_Camera_Cut +( + "cut", + EV_SCRIPTONLY, + NULL, + NULL, + "switch camera states immediately, do not transition" +); +Event EV_Camera_SetNextCamera +( + "nextcamera", + EV_SCRIPTONLY, + "s", + "nextCamera", + "Sets the next camera to use." +); +Event EV_Camera_SetAutoState +( + "auto_state", + EV_SCRIPTONLY, + "sSSSSS", + "state1 state2 state3 state4 state5 state6", + "Sets the states the player needs to be in for this camera to activate." +); +Event EV_Camera_SetAutoRadius +( + "auto_radius", + EV_SCRIPTONLY, + "f", + "newRadius", + "Sets the radius of the automatic camera." +); +Event EV_Camera_SetAutoActive +( + "auto_active", + EV_SCRIPTONLY, + "b", + "newActiveState", + "Whether or not the auto camera is active." +); +Event EV_Camera_SetAutoStartTime +( + "auto_starttime", + EV_SCRIPTONLY, + "f", + "newTime", + "Sets how long it takes for the camera to be switched to." +); +Event EV_Camera_SetAutoStopTime +( + "auto_stoptime", + EV_SCRIPTONLY, + "f", + "newTime", + "Sets how long it takes for the camera switch back to the player." +); +Event EV_Camera_SetMaximumAutoFOV +( + "auto_maxfov", + EV_SCRIPTONLY, + "f", + "maxFOV", + "Sets the maximum FOV that should be used when automatically calculating FOV." +); + +// +// general setup functions +// +Event EV_Camera_SetThread +( + "setthread", + EV_SCRIPTONLY, + "s", + "thread", + "Sets the thread to use." +); +Event EV_Camera_SetThread2 +( + "thread", + EV_SCRIPTONLY, + "s", + "thread", + "Sets the thread to use." +); +Event EV_Camera_SetupCamera +( + "_setupcamera", + EV_CODEONLY, + NULL, + NULL, + "setup the camera, post spawn." +); + +/*****************************************************************************/ +/* QUAKED func_camera (0 0.25 0.5) (-8 -8 0) (8 8 16) ORBIT START_ON AUTOMATIC NO_TRACE NO_WATCH LEVEL_EXIT + +Camera used for cinematic sequences. + +"target" points to the target to orbit or follow. If it points to a path, +the camera will follow the path. +"speed" specifies how fast to move on the path or orbit. (default 1). +"fov" specifies fov of camera, default 90. +if fov is less than 3 than an auto-fov feature is assumed. The fov will then +specify the ratio to be used to keep a watched entity zoomed in and on the screen +"follow_yaw" specifies yaw of the follow camera, default 0. +"follow_distance" the distance to follow or orbit if the target is not a path. (default 128). +"orbit_height" specifies height of camera from origin for orbiting, default 128. +"nextcamera" a link to the next camera in a chain of cameras +"thread" a thread label that will be fired when the camera is looked through +"auto_state" if specified, denotes the state the player must be in for the camera to engage +any number of states can be specified and only the first few letters need be specified as well +a state of 'pipe' would mean that any player state that started with 'pipe' would trigger this +camera +"auto_radius" the radius, in which the camera will automatically turn on (default 512) +"auto_starttime" how long it takes for the camera to be switched to (default 0.2) +"auto_stoptime" how long it takes for the camera to switch back to the player (default 0.2) +"auto_maxfov" Sets the maximum FOV that should be used when automatically calculating FOV. (default 90) + +ORBIT tells the camera to create a circular path around the object it points to. +It the camera points to a path, it will loop when it gets to the end of the path. +START_ON causes the camera to be moving as soon as it is spawned. +AUTOMATIC causes the camera to be switched to automatically when the player is within +a certain radius, if "thread" is specified, that thread will be triggered when the camers is activated +NO_TRACE when the camera is in automatic mode, it will try to trace to the player before +switching automatically, this turns off that feature +NO_WATCH if this is an automatic camera, the camera will automatically watch the player +unless this is set, camera's "score" will be affected by how close to the middle of the camera +the player is. +LEVEL_EXIT if the camera is being used, the level exit state will be set + +******************************************************************************/ + +CLASS_DECLARATION( Entity, Camera, "func_camera" ) +{ + { &EV_Camera_CameraThink, &Camera::CameraThink }, + { &EV_Activate, &Camera::StartMoving }, + { &EV_Camera_StartMoving, &Camera::StartMoving }, + { &EV_Camera_StopMoving, &Camera::StopMoving }, + { &EV_Camera_Pause, &Camera::Pause }, + { &EV_Camera_Continue, &Camera::Continue }, + { &EV_Camera_SetSpeed, &Camera::SetSpeed }, + { &EV_Camera_SetFollowDistance, &Camera::SetFollowDistance }, + { &EV_Camera_SetFollowYaw, &Camera::SetFollowYaw }, + { &EV_Camera_AbsoluteYaw, &Camera::AbsoluteYaw }, + { &EV_Camera_RelativeYaw, &Camera::RelativeYaw }, + { &EV_Camera_SetOrbitHeight, &Camera::SetOrbitHeight }, + { &EV_Camera_SetFOV, &Camera::SetFOV }, + { &EV_Camera_SetInterpolateFOV, &Camera::SetInterpolateFOV }, + { &EV_Camera_LoadKFC, &Camera::LoadKFC }, + { &EV_Camera_Orbit, &Camera::OrbitEvent }, + { &EV_Camera_Follow, &Camera::FollowEvent }, + { &EV_Camera_Watch, &Camera::WatchEvent }, + { &EV_Camera_WatchPath, &Camera::WatchPathEvent }, + { &EV_Camera_WatchNodes, &Camera::WatchNodesEvent }, + { &EV_Camera_NoWatch, &Camera::NoWatchEvent }, + { &EV_Camera_LookAt, &Camera::LookAt }, + { &EV_Camera_TurnTo, &Camera::TurnTo }, + { &EV_Camera_MoveToEntity, &Camera::MoveToEntity }, + { &EV_Camera_MoveToPos, &Camera::MoveToPos }, + { &EV_Camera_Cut, &Camera::Cut }, + { &EV_Camera_FadeTime, &Camera::FadeTime }, + { &EV_Camera_SetNextCamera, &Camera::SetNextCamera }, + { &EV_Camera_SetThread, &Camera::SetThread }, + { &EV_Camera_SetThread2, &Camera::SetThread }, + { &EV_Camera_SetupCamera, &Camera::SetupCamera }, + { &EV_SetAngles, &Camera::SetAnglesEvent }, + { &EV_Camera_SetAutoState, &Camera::SetAutoStateEvent }, + { &EV_Camera_SetAutoRadius, &Camera::SetAutoRadiusEvent }, + { &EV_Camera_SetAutoStartTime, &Camera::SetAutoStartTimeEvent }, + { &EV_Camera_SetAutoStopTime, &Camera::SetAutoStopTimeEvent }, + { &EV_Camera_SetMaximumAutoFOV, &Camera::SetMaximumAutoFOVEvent }, + { &EV_Camera_SetAutoActive, &Camera::SetAutoActiveEvent }, + + { NULL, NULL } +}; + + +//--------------------------------------------------------------------------- +// Camera::Camera() +//--------------------------------------------------------------------------- +Camera::Camera() +{ + Vector ang; + + camera_fov = 90; + camera_speed = 1; + orbit_height = 128; + orbit_dotrace = true; + follow_yaw = 0; + follow_yaw_fixed = false; + follow_dist = 128; + follow_mask = MASK_SOLID; + auto_fov = 0; + automatic_maxFOV = 90; + + watchTime = 0; + followTime = 0; + fovTime = 0; + + fadeTime = 0.2f; + + setSolidType( SOLID_NOT ); + setMoveType( MOVETYPE_NONE ); + + showcamera = sv_showcameras->integer; + if ( showcamera ) + { + setModel( "func_camera.tik" ); + showModel(); + } + else + { + hideModel(); + } + + automatic_active = true; + automatic_radius = 512; + automatic_states.ClearObjectList(); + automatic_startTime = 0.7f; + automatic_stopTime = 0.7f; + + newCameraPath = NULL; + newCameraPathSeconds = 0.0f; + + fovFadeTime = 1.0f; + followFadeTime = 1.0f; + watchFadeTime = 1.0f; + + if ( !LoadingSavegame ) + { + PostEvent( EV_Camera_SetupCamera, EV_POSTSPAWN ); + } +} + + +//--------------------------------------------------------------------------- +// Camera::SetupCamera +//--------------------------------------------------------------------------- +void Camera::SetupCamera( Event* ev ) +{ + currentstate.Initialize( this ); + newstate.Initialize( this ); + newCameraPathSeconds = 0.0f; + + if ( spawnflags & START_ON ) + { + PostEvent( EV_Camera_StartMoving, 0.0f ); + } + if ( spawnflags & AUTOMATIC ) + { + level.AddAutomaticCamera( this ); + } +} + + +//--------------------------------------------------------------------------- +// Camera::IsAutomatic +//--------------------------------------------------------------------------- +qboolean Camera::IsAutomatic( void ) +{ + return ( spawnflags & AUTOMATIC ); +} + + +//--------------------------------------------------------------------------- +// Camera::IsLevelExit +//--------------------------------------------------------------------------- +qboolean Camera::IsLevelExit( void ) +{ + return ( spawnflags & LEVEL_EXIT ); +} + + +//--------------------------------------------------------------------------- +// Camera::CalculateScore +//--------------------------------------------------------------------------- +float Camera::CalculateScore( Entity* player, const str& state ) +{ + int i; + float range; + float score; + qboolean found; + + if ( !automatic_active ) + { + return 10; + } + + range = Vector( player->origin - origin ).length() / automatic_radius; + // bias the range so that we don't immediately jump out of the camera if we are out of range + range -= 0.1f; + + score = range; + + // + // early exit if our score exceeds 1 + // + if ( score > 1.0f ) + return score; + + // find out if we match a state + found = false; + for( i = 1; i <= automatic_states.NumObjects(); i++ ) + { + str *auto_state; + + auto_state = &automatic_states.ObjectAt( i ); + if ( !state.icmpn( state, auto_state->c_str(), auto_state->length() ) ) + { + found = true; + break; + } + } + + // if we are comparing states and we haven't found a valid one... + if ( automatic_states.NumObjects() && !found ) + { + // if we aren't in the right state, push our score out significantly + score += 2.0f; + return score; + } + + // perform a trace to the player if necessary + if ( !( spawnflags & NO_TRACE ) && !( spawnflags & NO_WATCH ) ) + { + trace_t trace; + + trace = G_Trace( origin, vec_zero, vec_zero, player->centroid, player, follow_mask, false, "Camera::CalculateScore" ); + if ( trace.startsolid || trace.allsolid || ( trace.fraction < 1.0f ) ) + { + // if we are blocked, push our score out, but not too much since this may be a temporary thing + if ( trace.startsolid || trace.allsolid ) + { + score += 2.0f; + return score; + } + else + { + score += 1.0f - trace.fraction; + } + } + } + + // perform a dot product test for no watch cameras + if ( spawnflags & NO_WATCH ) + { + trace_t trace; + float limit; + float threshold; + float dot; + Vector dir; + + dir = player->centroid - origin; + dir.normalize(); + dot = dir * orientation[ 0 ]; + + threshold = (float)cos( DEG2RAD( ( camera_fov * 0.25f ) ) ); + if ( dot <= threshold ) + { + limit = (float)cos( DEG2RAD( ( camera_fov * 0.45f ) ) ); + if ( dot <= limit ) + { + // we are outside the viewing cone + score += 2.0f; + return score; + } + else + { + // our score is a scale between the two values + score += ( threshold - dot ) / ( limit ); + } + } + + trace = G_Trace( origin, vec_zero, vec_zero, player->origin, player, follow_mask, false, "Camera::CalculateScore" ); + if ( trace.startsolid || trace.allsolid || ( trace.fraction < 1.0f ) ) + { + // if we are blocked, push our score out, but not too much since this may be a temporary thing + if ( trace.startsolid || trace.allsolid ) + { + score += 2.0f; + return score; + } + else + { + score += 1.0f - trace.fraction; + } + } + } + + return score; +} + + +//--------------------------------------------------------------------------- +// Camera::AutomaticStart +//--------------------------------------------------------------------------- +float Camera::AutomaticStart( Entity* player ) +{ + if ( !( spawnflags & NO_WATCH ) && player ) + { + Watch( player, 0.0f ); + Cut( NULL ); + } + + if ( thread.length() ) + { + ExecuteThread( thread ); + } + + return automatic_startTime; +} + + +//--------------------------------------------------------------------------- +// Camera::AutomaticStop +//--------------------------------------------------------------------------- +float Camera::AutomaticStop( Entity* player ) +{ + Stop(); + return automatic_stopTime; +} + + +//--------------------------------------------------------------------------- +// Camera::UpdateStates +//--------------------------------------------------------------------------- +void Camera::UpdateStates( void ) +{ + if ( followTime && watchTime ) + { + newstate.Evaluate( this ); + } + else if ( watchTime ) + { + newstate.watch.Evaluate( this, ¤tstate.move ); + } + else if ( followTime ) + { + newstate.move.Evaluate( this ); + } + currentstate.Evaluate( this ); +} + + + +//--------------------------------------------------------------------------- +// Camera::CalculatePosition +//--------------------------------------------------------------------------- +Vector Camera::CalculatePosition( void ) +{ + int i; + float t; + Vector pos; + + // + // calcualte position + // + if ( followTime ) + { + t = followTime - level.time; + // + // are we still fading? + // + if ( t <= 0.0f ) + { + // + // if not zero out the fade + // + t = 0.0f; + currentstate.move = newstate.move; + newstate.move.Initialize( this ); + followTime = 0; + pos = currentstate.move.pos; + } + else + { + // + // if we are lerp over followFadeTime + // + t = ( followFadeTime - t ) / followFadeTime; + + for ( i = 0; i < 3; i++ ) + { + pos[ i ] = currentstate.move.pos[ i ] + ( t * ( newstate.move.pos[ i ] - currentstate.move.pos[ i ] ) ); + } + } + } + else + { + pos = currentstate.move.pos; + } + + return pos; +} + + +//--------------------------------------------------------------------------- +// Camera::CalculateOrientation +//--------------------------------------------------------------------------- +Vector Camera::CalculateOrientation( void ) +{ + int i; + float t; + Vector ang; + + // + // calculate orientation + // + if ( watchTime ) + { + t = watchTime - level.time; + // + // are we still fading? + // + if ( t <= 0.0f ) + { + // + // if not zero out the fade + // + t = 0.0f; + currentstate.watch = newstate.watch; + newstate.watch.Initialize( this ); + watchTime = 0; + ang = currentstate.watch.watchAngles; + } + else + { + t = ( watchFadeTime - t ) / watchFadeTime; + + for ( i=0; i<3; i++ ) + { + ang[ i ] = LerpAngleFromCurrent( currentstate.watch.watchAngles[ i ], newstate.watch.watchAngles[ i ], this->angles[ i ], t ); + } + } + } + else + { + ang = currentstate.watch.watchAngles; + } + + return ang; +} + + +//--------------------------------------------------------------------------- +// Camera::CalculateFov +//--------------------------------------------------------------------------- +float Camera::CalculateFov( void ) +{ + float fov; + float t; + + // + // calculate fov + // + // check if we have an auto_fov + if ( auto_fov > 0.0f ) + { + if ( currentstate.watch.watchEnt ) + { + float distance; + float size; + float new_fov; + Entity * ent; + + ent = currentstate.watch.watchEnt; + size = ent->maxs[ 2 ] - ent->mins[ 2 ]; + size = ent->edict->radius / 2.0f; + // cap the size + if ( size < 16.0f ) + size = 16.0f; + + distance = Vector( ent->centroid - origin ).length(); + new_fov = RAD2DEG( 2.0f * atan2( size, distance * auto_fov ) ); + if ( new_fov > automatic_maxFOV ) + new_fov = automatic_maxFOV; + else if ( new_fov < 5.0f ) + new_fov = 5.0f; + + return new_fov; + } + else + { + return 90.0f; + } + } + + // if we get here, we don't have an auto_fov, or we have an invalid watch target + if ( fovTime ) + { + t = fovTime - level.time; + // + // are we still fading? + // + if ( t <= 0.0f ) + { + // + // if not zero out the fade + // + t = 0.0f; + currentstate.fov = newstate.fov; + fovTime = 0; + fov = currentstate.fov; + } + else + { + // + // if we are lerp over fovFadeTime + // + t = ( fovFadeTime - t ) / fovFadeTime; + fov = currentstate.fov + ( t * ( newstate.fov - currentstate.fov ) ); + } + } + else + { + fov = currentstate.fov; + } + + return fov; +} + + +//--------------------------------------------------------------------------- +// Camera::GetPathLengthInSeconds +//--------------------------------------------------------------------------- +float Camera::GetPathLengthInSeconds( void ) +{ + return newCameraPath->GetPathLengthInSeconds(); +} + +//--------------------------------------------------------------------------- +// Camera::GetCameraTime +//--------------------------------------------------------------------------- +float +Camera::GetCameraTime( + void + ) +{ + return currentstate.move.cameraTime; +} + + +//--------------------------------------------------------------------------- +// Camera::EvaluateCameraKeyFramePath +//--------------------------------------------------------------------------- +void Camera::EvaluateCameraKeyFramePath( void ) +{ + /// Get a key frame of the camera path when evaluated at this time + CameraKeyFrame frame = newCameraPath->GetInterpolatedFrameForTime( newCameraPathSeconds ); + + /// Set the camera's current position, orientation, and fov + setOrigin( frame.GetPosition() ); + setAngles( frame.GetEulerAngles() ); + camera_fov = frame.GetFOV(); + + /// Advance time by 1 server frame + newCameraPathSeconds += level.frametime; +} + + +//--------------------------------------------------------------------------- +// Camera::CameraThink +//--------------------------------------------------------------------------- +void Camera::CameraThink( Event* ev ) +{ + /// Check if this camera is using a key-framed camera path + if( newCameraPath ) + { + assert( newCameraPath->IsLoaded() ); + EvaluateCameraKeyFramePath(); + } + else + { + UpdateStates(); + + setOrigin( CalculatePosition() ); + setAngles( CalculateOrientation() ); + camera_fov = CalculateFov(); + } + + // + // debug info + // + if ( sv_showcameras->integer != showcamera ) + { + showcamera = sv_showcameras->integer; + if ( showcamera ) + { + setModel( "func_camera.tik" ); + showModel(); + } + else + { + hideModel(); + } + } + + if ( sv_showcameras->integer != showcamera ) + { + showcamera = sv_showcameras->integer; + if ( showcamera ) + { + setModel( "func_camera.tik" ); + showModel(); + } + else + { + hideModel(); + } + } + if ( showcamera && currentstate.move.followingpath ) + { + G_Color3f( 1.0f, 1.0f, 0.0f ); + if ( currentstate.move.orbitEnt ) + { + currentstate.move.cameraPath.DrawCurve( currentstate.move.orbitEnt->origin, 10 ); + } + else + { + currentstate.move.cameraPath.DrawCurve( 10 ); + } + } + + CancelEventsOfType( EV_Camera_CameraThink ); + PostEvent( EV_Camera_CameraThink, FRAMETIME ); +} + + +//--------------------------------------------------------------------------- +// Camera::LookAt +//--------------------------------------------------------------------------- +void Camera::LookAt( Event* ev ) +{ + Vector pos, delta; + Entity * ent; + + ent = ev->GetEntity( 1 ); + + if ( !ent ) + return; + + pos.x = ent->origin.x; + pos.y = ent->origin.y; + pos.z = ent->absmax.z; + delta = pos - origin; + delta.normalize(); + + currentstate.watch.watchAngles = delta.toAngles(); + setAngles( currentstate.watch.watchAngles ); +} + + +//--------------------------------------------------------------------------- +// Camera::TurnTo +//--------------------------------------------------------------------------- +void Camera::TurnTo( Event* ev ) +{ + currentstate.watch.watchAngles = ev->GetVector( 1 ); + setAngles( currentstate.watch.watchAngles ); +} + + +//--------------------------------------------------------------------------- +// Camera::MoveToEntity +//--------------------------------------------------------------------------- +void Camera::MoveToEntity( Event* ev ) +{ + Entity * ent; + + ent = ev->GetEntity( 1 ); + if ( ent ) + currentstate.move.pos = ent->origin; + + setOrigin( currentstate.move.pos ); +} + + +//--------------------------------------------------------------------------- +// Camera::MoveToPos +//--------------------------------------------------------------------------- +void Camera::MoveToPos( Event* ev ) +{ + currentstate.move.pos = ev->GetVector( 1 ); + setOrigin( currentstate.move.pos ); +} + + +//--------------------------------------------------------------------------- +// Camera::Stop +//--------------------------------------------------------------------------- +void Camera::Stop( void ) +{ + if ( followTime ) + { + currentstate.move = newstate.move; + newstate.move.Initialize( this ); + } + + if ( watchTime ) + { + currentstate.watch = newstate.watch; + newstate.watch.Initialize( this ); + } + + CancelEventsOfType( EV_Camera_CameraThink ); + + watchTime = 0; + followTime = 0; +} + + +//--------------------------------------------------------------------------- +// Camera::CreateOrbit +//--------------------------------------------------------------------------- +void Camera::CreateOrbit( const Vector& pos, float radius, const Vector& forward, const Vector& left ) +{ + newstate.move.cameraPath.Clear(); + newstate.move.cameraPath.SetType( SPLINE_LOOP ); + + newstate.move.cameraPath.AppendControlPoint( pos + ( radius * forward ) ); + newstate.move.cameraPath.AppendControlPoint( pos + ( radius * left ) ); + newstate.move.cameraPath.AppendControlPoint( pos - ( radius * forward ) ); + newstate.move.cameraPath.AppendControlPoint( pos - ( radius * left ) ); +} + + +//--------------------------------------------------------------------------- +// Camera::CreatePath +//--------------------------------------------------------------------------- +void Camera::CreatePath( SplinePath* path, splinetype_t type ) +{ + SplinePath *node; + SplinePath *loop; + + newstate.move.cameraPath.Clear(); + newstate.move.cameraPath.SetType( type ); + + newstate.move.splinePath = path; + newstate.move.currentNode = path; + newstate.move.loopNode = NULL; + + node = path; + while( node != NULL ) + { + newstate.move.cameraPath.AppendControlPoint( node->origin, node->angles, node->speed ); + loop = node->GetLoop(); + if ( loop && ( type == SPLINE_LOOP ) ) + { + newstate.move.loopNode = loop; + newstate.move.cameraPath.SetLoopPoint( loop->origin ); + } + node = node->GetNext(); + + if ( node == path ) + break; + } + + if ( ( type == SPLINE_LOOP ) && ( !newstate.move.loopNode ) ) + { + newstate.move.loopNode = path; + } +} + + +//--------------------------------------------------------------------------- +// Camera::FollowPath +//--------------------------------------------------------------------------- +void Camera::FollowPath( SplinePath* path, qboolean loop, Entity* watch ) +{ + // make sure we process any setup events before continuing + ProcessPendingEvents(); + + Stop(); + if ( loop ) + { + CreatePath( path, SPLINE_LOOP ); + } + else + { + CreatePath( path, SPLINE_CLAMP ); + } + + newstate.move.cameraTime = -2; + newstate.move.lastTime = 0; + newstate.move.newTime = 0; + newstate.move.currentNode = path; + // evaluate the first node events + newstate.move.DoNodeEvents( this ); + + if ( watch ) + { + newstate.watch.watchEnt = watch; + } + else + { + Watch( newstate.move.currentNode->GetWatch(), newstate.move.currentNode->GetFadeTime() ); + } + + followFadeTime = fadeTime; + watchFadeTime = fadeTime; + + newstate.move.followingpath = true; + followTime = level.time + followFadeTime; + watchTime = level.time + watchFadeTime; + + PostEvent( EV_Camera_CameraThink, FRAMETIME ); +} + + +//--------------------------------------------------------------------------- +// Camera::Orbit +//--------------------------------------------------------------------------- +void Camera::Orbit +( + Entity *ent, + float dist, + Entity *watch, + float yaw_offset, + qboolean dotrace +) +{ + Vector ang, forward, left; + + // make sure we process any setup events before continuing + ProcessPendingEvents(); + + Stop(); + + if ( watch ) + { + ang = watch->angles; + ang.y += yaw_offset; + } + else + { + ang = vec_zero; + ang.y += yaw_offset; + } + ang.AngleVectors( &forward, &left, NULL ); + + orbit_dotrace = dotrace; + + CreateOrbit( Vector( 0.0f, 0.0f, orbit_height ), dist, forward, left ); + newstate.move.cameraTime = -2; + newstate.move.lastTime = 0; + newstate.move.newTime = 0; + + newstate.move.orbitEnt = ent; + + followFadeTime = fadeTime; + watchFadeTime = fadeTime; + + newstate.move.followingpath = true; + followTime = level.time + followFadeTime; + watchTime = level.time + watchFadeTime; + newstate.move.currentNode = NULL; + + if ( watch ) + { + newstate.watch.watchEnt = watch; + } + else + { + newstate.watch.watchEnt = ent; + } + + PostEvent( EV_Camera_CameraThink, FRAMETIME ); +} + + +//--------------------------------------------------------------------------- +// Camera::FollowEntity +//--------------------------------------------------------------------------- +void Camera::FollowEntity( Entity* ent, float dist, int mask, Entity* watch ) +{ + // make sure we process any setup events before continuing + ProcessPendingEvents(); + + assert( ent ); + + Stop(); + + if ( ent ) + { + newstate.move.followEnt = ent; + newstate.move.followingpath = false; + + followFadeTime = fadeTime; + watchFadeTime = fadeTime; + + newstate.move.cameraTime = -2; + newstate.move.lastTime = 0; + newstate.move.newTime = 0; + newstate.move.currentNode = NULL; + + followTime = level.time + followFadeTime; + watchTime = level.time + watchFadeTime; + if ( watch ) + { + newstate.watch.watchEnt = watch; + } + else + { + newstate.watch.watchEnt = ent; + } + follow_dist = dist; + follow_mask = mask; + PostEvent( EV_Camera_CameraThink, 0.0f ); + } +} + + +//--------------------------------------------------------------------------- +// Camera::StartMoving +//--------------------------------------------------------------------------- +void Camera::StartMoving( Event* ev ) +{ + Entity *targetEnt; + Entity *targetWatchEnt; + Entity *ent; + SplinePath *path; + + newCameraPathSeconds = 0.0f; + + if ( ev->NumArgs() > 0 ) + { + targetEnt = ev->GetEntity( 1 ); + } + else + { + targetEnt = NULL; + } + + if ( ev->NumArgs() > 1 ) + { + targetWatchEnt = ev->GetEntity( 2 ); + } + else + { + targetWatchEnt = NULL; + } + + if ( ( spawnflags & START_ON ) && ( !Q_stricmp( Target(), "" ) ) ) + { + gi.Error( ERR_DROP, "Camera '%s' with START_ON selected, but no target specified.", TargetName() ); + } + + if ( !targetEnt ) + { + ent = G_FindTarget( NULL, Target() ); + if ( !ent ) + { + gi.Error( ERR_DROP, "Can't find target '%s' for camera\n", Target() ); + return; + } + } + else + { + ent = targetEnt; + } + + if ( ent->isSubclassOf( SplinePath ) ) + { + path = ( SplinePath * )ent; + FollowPath( path, spawnflags & ORBIT, targetWatchEnt ); + } + else + { + if ( spawnflags & ORBIT ) + { + Orbit( ent, follow_dist, targetWatchEnt ); + } + else + { + FollowEntity( ent, follow_dist, follow_mask, targetWatchEnt ); + } + } +} + + +//--------------------------------------------------------------------------- +// Camera::SetAutoStateEvent +//--------------------------------------------------------------------------- +void Camera::SetAutoStateEvent( Event* ev ) +{ + int i; + + for( i = 1; i <= ev->NumArgs(); i++ ) + { + char *buffer; + char com_token[ MAX_QPATH ]; + char com_buffer[MAX_STRING_CHARS]; + + strcpy( com_buffer, ev->GetString( i ) ); + buffer = com_buffer; + // get the rest of the line + while( 1 ) + { + strcpy( com_token, COM_ParseExt( &buffer, false ) ); + if (!com_token[0]) + break; + + automatic_states.AddUniqueObject( str( com_token ) ); + } + } +} + + +//--------------------------------------------------------------------------- +// Camera::SetMaximumAutoFOVEvent +//--------------------------------------------------------------------------- +void Camera::SetMaximumAutoFOVEvent( Event* ev ) +{ + automatic_maxFOV = ev->GetFloat( 1 ); +} + + +//--------------------------------------------------------------------------- +// Camera::SetAutoRadiusEvent +//--------------------------------------------------------------------------- +void Camera::SetAutoRadiusEvent( Event* ev ) +{ + automatic_radius = ev->GetFloat( 1 ); +} + + +//--------------------------------------------------------------------------- +// Camera::SetAutoActiveEvent +//--------------------------------------------------------------------------- +void Camera::SetAutoActiveEvent( Event* ev ) +{ + automatic_active = ev->GetBoolean( 1 ); +} + + +//--------------------------------------------------------------------------- +// Camera::SetAutoStartTimeEvent +//--------------------------------------------------------------------------- +void Camera::SetAutoStartTimeEvent( Event* ev ) +{ + automatic_startTime = ev->GetFloat( 1 ); +} + + +//--------------------------------------------------------------------------- +// Camera::SetAutoStopTimeEvent +//--------------------------------------------------------------------------- +void Camera::SetAutoStopTimeEvent( Event* ev ) +{ + automatic_stopTime = ev->GetFloat( 1 ); +} + + +//--------------------------------------------------------------------------- +// Camera::StopMoving +//--------------------------------------------------------------------------- +void Camera::StopMoving( Event* ev ) +{ + Stop(); +} + + +//--------------------------------------------------------------------------- +// Camera::Pause +//--------------------------------------------------------------------------- +void Camera::Pause( Event* ev ) +{ + CancelEventsOfType( EV_Camera_CameraThink ); +} + + +//--------------------------------------------------------------------------- +// Camera::Continue +//--------------------------------------------------------------------------- +void Camera::Continue( Event* ev ) +{ + CancelEventsOfType( EV_Camera_CameraThink ); + PostEvent( EV_Camera_CameraThink, 0.0f ); +} + + +//--------------------------------------------------------------------------- +// Camera::SetAnglesEvent +//--------------------------------------------------------------------------- +void Camera::SetAnglesEvent( Event* ev ) +{ + Vector ang; + + ang = ev->GetVector( 1 ); + setAngles( ang ); +} + + +//--------------------------------------------------------------------------- +// Camera::SetSpeed +//--------------------------------------------------------------------------- +void Camera::SetSpeed( Event* ev ) +{ + camera_speed = ev->GetFloat( 1 ); +} + + +//--------------------------------------------------------------------------- +// Camera::SetFollowDistance +//--------------------------------------------------------------------------- +void Camera::SetFollowDistance( Event* ev ) +{ + follow_dist = ev->GetFloat( 1 ); +} + + +//--------------------------------------------------------------------------- +// Camera::SetOrbitHeight +//--------------------------------------------------------------------------- +void Camera::SetOrbitHeight( float height ) +{ + orbit_height = height; +} + + +//--------------------------------------------------------------------------- +// Camera::SetOrbitHeight +//--------------------------------------------------------------------------- +void Camera::SetOrbitHeight( Event* ev ) +{ + orbit_height = ev->GetFloat( 1 ); +} + + +//--------------------------------------------------------------------------- +// Camera::SetFollowYaw +//--------------------------------------------------------------------------- +void Camera::SetFollowYaw( Event* ev ) +{ + follow_yaw = ev->GetFloat( 1 ); +} + + +//--------------------------------------------------------------------------- +// Camera::AbsoluteYaw +//--------------------------------------------------------------------------- +void Camera::AbsoluteYaw( Event* ev ) +{ + follow_yaw_fixed = true; +} + + +//--------------------------------------------------------------------------- +// Camera::RelativeYaw +//--------------------------------------------------------------------------- +void Camera::RelativeYaw( Event* ev ) +{ + follow_yaw_fixed = false; +} + + +//--------------------------------------------------------------------------- +// Camera::SetNextCamera +//--------------------------------------------------------------------------- +void Camera::SetNextCamera( Event* ev ) +{ + nextCamera = ev->GetString( 1 ); +} + + +//--------------------------------------------------------------------------- +// Camera::Cut +//--------------------------------------------------------------------------- +void Camera::Cut( Event* ev ) +{ + int j; + + if ( followTime ) + { + currentstate.move = newstate.move; + newstate.move.Initialize( this ); + followTime = 0; + } + + if ( watchTime ) + { + currentstate.watch = newstate.watch; + newstate.watch.Initialize( this ); + watchTime = 0; + } + + if ( fovTime ) + { + currentstate.fov = newstate.fov; + //currentstate.fov = g_entities[0].entity->client->ps.fov; + newstate.fov = camera_fov; + fovTime = 0; + } + + CancelEventsOfType( EV_Camera_CameraThink ); + ProcessEvent( EV_Camera_CameraThink ); + + // + // let any clients know that this camera has just cut + // + for( j = 0; j < game.maxclients; j++ ) + { + gentity_t *other; + Player *client; + + other = &g_entities[ j ]; + if ( other->inuse && other->client ) + { + client = ( Player * )other->entity; + client->CameraCut( this ); + } + } +} + + +//--------------------------------------------------------------------------- +// Camera::FadeTime +//--------------------------------------------------------------------------- +void Camera::FadeTime( Event* ev ) +{ + fadeTime = ev->GetFloat( 1 ); +} + +void Camera::OrbitEvent( Event* ev ) +{ + Entity *ent; + + spawnflags |= ORBIT; + ent = ev->GetEntity( 1 ); + if ( ent ) + { + Event * event; + + event = new Event( EV_Camera_StartMoving ); + event->AddEntity( ent ); + if ( ev->NumArgs() > 1 ) + { + event->AddEntity( ev->GetEntity( 2 ) ); + } + + Stop(); + ProcessEvent( event ); + } +} + + +//--------------------------------------------------------------------------- +// Camera::FollowEvent +//--------------------------------------------------------------------------- +void Camera::FollowEvent( Event* ev ) +{ + Entity *ent; + + spawnflags &= ~ORBIT; + ent = ev->GetEntity( 1 ); + if ( ent ) + { + Event * event; + + event = new Event( EV_Camera_StartMoving ); + event->AddEntity( ent ); + if ( ev->NumArgs() > 1 ) + { + event->AddEntity( ev->GetEntity( 2 ) ); + } + + Stop(); + ProcessEvent( event ); + } + else + { + gi.WDPrintf( "FollowEvent :: Entity does not exist." ); + } +} + + +//--------------------------------------------------------------------------- +// Camera::SetFOV +//--------------------------------------------------------------------------- +void Camera::SetFOV( Event* ev ) +{ + float time; + + if ( ev->NumArgs() > 1 ) + { + time = ev->GetFloat( 2 ); + } + else + { + time = fadeTime; + } + + SetFOV( ev->GetFloat( 1 ), time ); +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Camera::SetInterpolateFOV( Event* ev ) +{ + SetFOV( ev->GetFloat(1), ev->GetFloat(2)); +} + +//--------------------------------------------------------------------------- +// Camera::LoadKFC +//--------------------------------------------------------------------------- +void Camera::LoadKFC( Event* ev ) +{ + str kfcFileName = ev->GetString( 1 ); + delete newCameraPath; // destroy any prior CameraPath instance that may be lingering + newCameraPath = new CameraPath( kfcFileName ); + newCameraPathSeconds = 0.0f; +} + + +//--------------------------------------------------------------------------- +// Camera::WatchEvent +//--------------------------------------------------------------------------- +void Camera::WatchEvent( Event* ev ) +{ + float time; + + if ( ev->NumArgs() > 1 ) + { + time = ev->GetFloat( 2 ); + } + else + { + time = fadeTime; + } + + Watch( ev->GetString( 1 ), time ); +} + + +//--------------------------------------------------------------------------- +// GetWatchEntity +//--------------------------------------------------------------------------- +Entity * GetWatchEntity( const str& watch ) +{ + const char *name; + Entity * ent; + + // + // if empty just return + // + if ( watch == "" ) + return NULL; + + // + // ignore all the reserved words + // + if (( watch == "path" ) || + ( watch == "none" ) || + ( watch == "node" ) ) + { + return NULL; + } + + name = watch.c_str(); + + if ( name[ 0 ] == '*' ) + { + if ( !IsNumeric( &name[ 1 ] ) ) + { + gi.WDPrintf( "GetWatchEntity :: Expecting a numeric value but found '%s'.", &name[ 1 ] ); + return NULL; + } + else + { + ent = G_GetEntity( atoi( &name[ 1 ] ) ); + if ( !ent ) + { + gi.WDPrintf( "GetWatchEntity :: Entity with targetname of '%s' not found", &name[ 1 ] ); + return NULL; + } + } + } + else if ( name[ 0 ] == '$' ) + { + ent = G_FindTarget( NULL, &name[ 1 ] ); + if ( !ent ) + { + gi.WDPrintf( "GetWatchEntity :: Entity with targetname of '%s' not found", &name[ 1 ] ); + return NULL; + } + } + else + { + gi.WDPrintf( "GetWatchEntity :: Entity with targetname of '%s' not found", name ); + return NULL; + } + + return ent; +} + + +//--------------------------------------------------------------------------- +// Camera::Watch +//--------------------------------------------------------------------------- +void Camera::Watch( const str& watch, float time ) +{ + // make sure we process any setup events before continuing + ProcessPendingEvents(); + + // + // if empty just return + // + if ( watch == "" ) + return; + + // + // clear out the watch variables + // + watchFadeTime = time; + newstate.watch.watchPath = false; + newstate.watch.watchNodes = false; + newstate.watch.watchEnt = NULL; + watchTime = level.time + watchFadeTime; + + // + // really a watchpath command + // + if ( watch == "path" ) + { + newstate.watch.watchPath = true; + } + + // + // really a watchnodes command + // + else if ( watch == "node" ) + { + newstate.watch.watchNodes = true; + } + + // + // really a watchnodes command + // + else if ( watch == "none" ) + { + // intentionally blank + } + + // + // just a normal watch command + // + else + { + Entity * ent = GetWatchEntity( watch ); + newstate.watch.watchEnt = ent; + } +} + + +//--------------------------------------------------------------------------- +// Camera::Watch +//--------------------------------------------------------------------------- +void Camera::Watch( Entity* ent, float time ) +{ + // + // clear out the watch variables + // + watchFadeTime = time; + newstate.watch.watchPath = false; + newstate.watch.watchNodes = false; + watchTime = level.time + watchFadeTime; + newstate.watch.watchEnt = ent; +} + + +//--------------------------------------------------------------------------- +// Camera::SetFOV +//--------------------------------------------------------------------------- +void Camera::SetFOV( float fov, float time ) +{ + // if it is less than 3, then we are setting an auto_fov state + if ( fov < 3.0f ) + { + auto_fov = fov; + } + else + { + // if we are explicitly setting the fov, turn the auto_fov off + auto_fov = 0; + + fovFadeTime = time; + fovTime = level.time + fovFadeTime; + //currentstate.fov = newstate.fov; + currentstate.fov = g_entities[0].entity->client->ps.fov; + newstate.fov = fov; + } +} + + +//--------------------------------------------------------------------------- +// Camera::WatchPathEvent +//--------------------------------------------------------------------------- +void Camera::WatchPathEvent( Event* ev ) +{ + if ( ev->NumArgs() > 1 ) + { + watchFadeTime = ev->GetFloat( 2 ); + } + else + { + watchFadeTime = fadeTime; + } + + watchTime = level.time + watchFadeTime; + newstate.watch.watchEnt = NULL; + newstate.watch.watchNodes = false; + newstate.watch.watchPath = true; +} + + +//--------------------------------------------------------------------------- +// Camera::WatchNodesEvent +//--------------------------------------------------------------------------- +void Camera::WatchNodesEvent( Event* ev ) +{ + if ( ev->NumArgs() > 1 ) + { + watchFadeTime = ev->GetFloat( 2 ); + } + else + { + watchFadeTime = fadeTime; + } + + watchTime = level.time + watchFadeTime; + newstate.watch.watchEnt = NULL; + newstate.watch.watchPath = false; + newstate.watch.watchNodes = true; +} + + +//--------------------------------------------------------------------------- +// Camera::NoWatchEvent +//--------------------------------------------------------------------------- +void Camera::NoWatchEvent( Event* ev ) +{ + if ( ev->NumArgs() > 1 ) + { + watchFadeTime = ev->GetFloat( 2 ); + } + else + { + watchFadeTime = fadeTime; + } + + watchTime = level.time + watchFadeTime; + newstate.watch.watchEnt = NULL; + newstate.watch.watchPath = false; + newstate.watch.watchNodes = false; +} + + +//--------------------------------------------------------------------------- +// SetCamera +//--------------------------------------------------------------------------- +void SetCamera( Entity* ent, float switchTime ) +{ + int j; + gentity_t *other; + Camera *cam; + Player *client; + + if ( ent && !ent->isSubclassOf( Camera ) ) + return; + + cam = ( Camera * )ent; + for( j = 0; j < game.maxclients; j++ ) + { + other = &g_entities[ j ]; + if ( other->inuse && other->client ) + { + client = ( Player * )other->entity; + client->SetCamera( cam, switchTime ); + } + } +} + + +//--------------------------------------------------------------------------- +// Camera::NextCamera +//--------------------------------------------------------------------------- +str& Camera::NextCamera( void ) +{ + return nextCamera; +} + + +//--------------------------------------------------------------------------- +// Camera::SetThread +//--------------------------------------------------------------------------- +void Camera::SetThread( Event* ev ) +{ + thread = ev->GetString( 1 ); +} + + +//--------------------------------------------------------------------------- +// Camera::Thread +//--------------------------------------------------------------------------- +str& Camera::Thread( void ) +{ + return thread; +} + + +//----------------------------------------------------------------------------------------------- +// Name: SetPlaybackOffsets +// Class: Camera +// +// Description: Sets the playback offsets for this camera instance. All positions and +// orientations included in the camera's CameraPath will be rotated (about +// the Z axis by ) and translated (by ). +// +// Parameters: const float yawOffsetDegrees - rotation for camera's position & orientation +// const Vector& originOffset - translation for camera's position (applied second) +// +// Returns: void +// +//----------------------------------------------------------------------------------------------- +void Camera::SetPlaybackOffsets( const float yawOffsetDegrees, const Vector& originOffset ) +{ + /// Check if a key-framed camera path is being used + if( newCameraPath ) + { + /// Apply the offsets to the CameraPath object + newCameraPath->SetPlaybackOffsets( yawOffsetDegrees, originOffset ); + } +} + + +//--------------------------------------------------------------------------- +// Camera::Fov +//--------------------------------------------------------------------------- +float Camera::Fov( void ) +{ + return camera_fov; +} + + +//--------------------------------------------------------------------------- +// Camera::Reset +//--------------------------------------------------------------------------- +void Camera::Reset( const Vector& org, const Vector& ang ) +{ + setOrigin( org ); + setAngles( ang ); + currentstate.Initialize( this ); + newstate.Initialize( this ); +} + + +//--------------------------------------------------------------------------- +// Camera::bind +//--------------------------------------------------------------------------- +void Camera::bind( Entity* master, qboolean use_my_angles ) +{ + Entity::bind( master, use_my_angles ); + currentstate.move.pos = GetLocalOrigin(); +} + + +//--------------------------------------------------------------------------- +// Camera::unbind +//--------------------------------------------------------------------------- +void Camera::unbind( void ) +{ + Entity::unbind(); + currentstate.move.pos = origin; +} + + +/****************************************************************************** + + Camera Manager + +******************************************************************************/ + +Event EV_CameraManager_NewPath +( + "new", + EV_CONSOLE, + NULL, + NULL, + "Starts a new path." +); +Event EV_CameraManager_SetPath +( + "setpath", + EV_CONSOLE, + "e", + "path", + "Sets the new path." +); +Event EV_CameraManager_SetTargetName +( + "settargetname", + EV_CONSOLE, + "s", + "targetname", + "Set the targetname." +); +Event EV_CameraManager_SetTarget +( + "settarget", + EV_CONSOLE, + "s", + "target", + "Set the trigger target." +); +Event EV_CameraManager_AddPoint +( + "add", + EV_CONSOLE, + NULL, + NULL, + "Add a new point to the camera path where the player is standing." +); +Event EV_CameraManager_DeletePoint +( + "delete", + EV_CONSOLE, + NULL, + NULL, + "Delete the current path node." +); +Event EV_CameraManager_MovePlayer +( + "moveplayer", + EV_CONSOLE, + NULL, + NULL, + "Move the player to the current path node position." +); +Event EV_CameraManager_ReplacePoint +( + "replace", + EV_CONSOLE, + NULL, + NULL, + "Replace the current path node position/angle with the player's." +); +Event EV_CameraManager_NextPoint +( + "next", + EV_CONSOLE, + NULL, + NULL, + "Go to the next path node." +); +Event EV_CameraManager_PreviousPoint +( + "prev", + EV_CONSOLE, + NULL, + NULL, + "Go to the previous path node." +); +Event EV_CameraManager_ShowPath +( + "showpath", + EV_CONSOLE, + "E", + "path", + "Shows the specified path." +); +Event EV_CameraManager_ShowingPath +( + "_showing_path", + EV_CONSOLE, + NULL, + NULL, + "Internal event for showing the path." +); +Event EV_CameraManager_HidePath +( + "hidepath", + EV_CONSOLE, + NULL, + NULL, + "Hides the paths." +); +Event EV_CameraManager_PlayPath +( + "play", + EV_CONSOLE, + "E", + "path", + "Play the current path or the specified one once." +); +Event EV_CameraManager_PlayKFCPath +( + "playKFCpath", + EV_CONSOLE, + "sFV", + "kfc_filename yaw_offset origin_offset", + "Loads a key-framed camera path (.KFC) file. Yaw_offset and origin_offset are optional." +); +Event EV_CameraManager_DestroyKFCPath +( + "destroyKFCpath", + EV_CODEONLY, + "E", + "path", + "Delete the specified .kfc file." +); +Event EV_CameraManager_LoopPath +( + "loopPath", + EV_CONSOLE, + "E", + "path", + "Loop the current path or the specified one." +); +Event EV_CameraManager_StopPlayback +( + "stop", + EV_CONSOLE | EV_SCRIPTONLY, + NULL, + NULL, + "Stop the camera playing path." +); +Event EV_CameraManager_Watch +( + "watch", + EV_SCRIPTONLY, + "s", + "watch", + "Set the current path node to watch something." +); +Event EV_CameraManager_NoWatch +( + "nowatch", + EV_CONSOLE | EV_SCRIPTONLY, + NULL, + NULL, + "Set the current path node to watch nothing." +); +Event EV_CameraManager_Fov +( + "setfov", + EV_CONSOLE, + "s", + "newFOV", + "Set the fov at the current path node." +); +Event EV_CameraManager_FadeTime +( + "setfadetime", + EV_CONSOLE, + "f", + "newFadeTime", + "Set the fadetime of the current path node." +); +Event EV_CameraManager_Speed +( + "setspeed", + EV_CONSOLE, + "f", + "speed", + "Set the speed of the camera at the current path node." +); +Event EV_CameraManager_Save +( + "save", + EV_CONSOLE, + "s", + "filename", + "Saves the camera path." +); +Event EV_CameraManager_Load +( + "load", + EV_CONSOLE, + "s", + "filename", + "Loads a camera path." +); +Event EV_CameraManager_SaveMap +( + "savemap", + EV_CONSOLE, + "s", + "filename", + "Saves the camera path to a map file." +); +Event EV_CameraManager_SetThread +( + "setthread", + EV_SCRIPTONLY, + "s", + "thread", + "Sets the thread for the current path node." +); +Event EV_CameraManager_UpdateInput +( + "updateinput", + EV_CONSOLE, + NULL, + NULL, + "Updates the current node with user interface values." +); +Event EV_CameraManager_NextPath +( + "nextpath", + EV_CONSOLE, + NULL, + NULL, + "Go to the next path." +); +Event EV_CameraManager_PreviousPath +( + "prevpath", + EV_CONSOLE, + NULL, + NULL, + "Go to the previous path." +); +Event EV_CameraManager_RenamePath +( + "renamepath", + EV_CONSOLE, + "s", + "newName", + "Rename the path to the new name." +); + +CLASS_DECLARATION( Listener, CameraManager, NULL ) +{ + { &EV_CameraManager_NewPath, &CameraManager::NewPath }, + { &EV_CameraManager_SetPath, &CameraManager::SetPath }, + { &EV_CameraManager_SetTargetName, &CameraManager::SetTargetName }, + { &EV_CameraManager_SetTarget, &CameraManager::SetTarget }, + { &EV_CameraManager_SetThread, &CameraManager::SetThread }, + { &EV_CameraManager_SetPath, &CameraManager::SetPath }, + { &EV_CameraManager_AddPoint, &CameraManager::AddPoint }, + { &EV_CameraManager_ReplacePoint, &CameraManager::ReplacePoint }, + { &EV_CameraManager_DeletePoint, &CameraManager::DeletePoint }, + { &EV_CameraManager_MovePlayer, &CameraManager::MovePlayer }, + { &EV_CameraManager_NextPoint, &CameraManager::NextPoint }, + { &EV_CameraManager_PreviousPoint, &CameraManager::PreviousPoint }, + { &EV_CameraManager_ShowPath, &CameraManager::ShowPath }, + { &EV_CameraManager_ShowingPath, &CameraManager::ShowingPath }, + { &EV_CameraManager_HidePath, &CameraManager::HidePath }, + { &EV_CameraManager_PlayPath, &CameraManager::PlayPath }, + { &EV_CameraManager_PlayKFCPath, &CameraManager::PlayKFCPath }, + { &EV_CameraManager_DestroyKFCPath, &CameraManager::DestroyKFCPath }, + { &EV_CameraManager_LoopPath, &CameraManager::LoopPath }, + { &EV_CameraManager_StopPlayback, &CameraManager::StopPlayback }, + { &EV_CameraManager_Watch, &CameraManager::Watch }, + { &EV_CameraManager_NoWatch, &CameraManager::NoWatch }, + { &EV_CameraManager_Fov, &CameraManager::Fov }, + { &EV_CameraManager_FadeTime, &CameraManager::FadeTime }, + { &EV_CameraManager_Speed, &CameraManager::Speed }, + { &EV_CameraManager_Save, &CameraManager::Save }, + { &EV_CameraManager_Load, &CameraManager::Load }, + { &EV_CameraManager_SaveMap, &CameraManager::SaveMap }, + { &EV_CameraManager_UpdateInput, &CameraManager::UpdateEvent }, + { &EV_CameraManager_NextPath, &CameraManager::NextPath }, + { &EV_CameraManager_PreviousPath, &CameraManager::PreviousPath }, + { &EV_CameraManager_RenamePath, &CameraManager::RenamePath }, + + { NULL, NULL } +}; + + +//--------------------------------------------------------------------------- +// CameraManager_GetPlayer +//--------------------------------------------------------------------------- +Player *CameraManager_GetPlayer( void ) +{ + assert( g_entities[ 0 ].entity && g_entities[ 0 ].entity->isSubclassOf( Player ) ); + if ( !g_entities[ 0 ].entity || !( g_entities[ 0 ].entity->isSubclassOf( Player ) ) ) + { + gi.WPrintf( "No player found.\n" ); + return NULL; + } + + return ( Player * )g_entities[ 0 ].entity; +} + + +//--------------------------------------------------------------------------- +// CameraManager::CameraManager() +//--------------------------------------------------------------------------- +CameraManager::CameraManager() +{ + pathList.ClearObjectList(); + path = NULL; + current = NULL; + cameraPath_dirty = true; + speed = 1; + watch = 0; + cam = NULL; + isPreviewPlaybackRunning = false; +} + + +//--------------------------------------------------------------------------- +// CameraManager::UpdateUI +//--------------------------------------------------------------------------- +void CameraManager::UpdateUI( void ) +{ + int num; + SplinePath *next; + float temp; + + // + // set path name + // + gi.cvar_set( "cam_filename", pathName ); + if ( current ) + { + gi.cvar_set( "cam_origin", va( "%.2f %.2f %.2f", current->origin[ 0 ], current->origin[ 1 ], current->origin[ 2 ] ) ); + gi.cvar_set( "cam_angles_yaw", va( "%.1f", current->angles[ YAW ] ) ); + gi.cvar_set( "cam_angles_pitch", va( "%.1f", current->angles[ PITCH ] ) ); + gi.cvar_set( "cam_angles_roll", va( "%.1f", current->angles[ ROLL ] ) ); + gi.cvar_set( "cam_thread", current->thread.c_str() ); + gi.cvar_set( "cam_target", current->triggertarget.c_str() ); + gi.cvar_set( "cam_watch", current->watchEnt.c_str() ); + gi.cvar_set( "cam_playbacktime", va( "0" ) ); + temp = current->GetFov(); + if ( temp ) + { + gi.cvar_set( "cam_fov", va( "%.1f", temp ) ); + } + else + { + gi.cvar_set( "cam_fov", "Default" ); + } + + temp = current->GetFadeTime(); + if ( temp != -1 ) + { + gi.cvar_set( "cam_fadetime", va( "%.2f", temp ) ); + } + else + { + gi.cvar_set( "cam_fadetime", "Default" ); + } + + gi.cvar_set( "cam_speed", va( "%.1f", current->speed ) ); + if ( EventPending( EV_CameraManager_ShowingPath ) ) + { + gi.cvar_set( "cam_hiddenstate", "visible" ); + } + else + { + gi.cvar_set( "cam_hiddenstate", "hidden" ); + } + + // + // set node num + // + num = 0; + next = path; + while ( next && ( next != current ) ) + { + next = next->GetNext(); + num++; + } + gi.cvar_set( "cam_nodenum", va( "%d", num ) ); + } +} + + +//--------------------------------------------------------------------------- +// CameraManager::UpdateEvent +//--------------------------------------------------------------------------- +void CameraManager::UpdateEvent( Event* ev ) +{ + Vector tempvec; + cvar_t * cvar; + + if ( !current ) + return; + + // get origin + cvar = gi.cvar( "cam_origin", "", 0 ); + sscanf( cvar->string, "%f %f %f", &tempvec[ 0 ], &tempvec[ 1 ], &tempvec[ 2 ] ); + current->setOrigin( tempvec ); + + // get angles yaw + cvar = gi.cvar( "cam_angles_yaw", "", 0 ); + current->angles[ YAW ] = cvar->value; + + // get angles pitch + cvar = gi.cvar( "cam_angles_pitch", "", 0 ); + current->angles[ PITCH ] = cvar->value; + + // get angles roll + cvar = gi.cvar( "cam_angles_roll", "", 0 ); + current->angles[ ROLL ] = cvar->value; + + current->setAngles( current->angles ); + + // get thread + cvar = gi.cvar( "cam_thread", "", 0 ); + current->SetThread( cvar->string ); + + // get target + cvar = gi.cvar( "cam_target", "", 0 ); + current->SetTriggerTarget( cvar->string ); + + // get watch + cvar = gi.cvar( "cam_watch", "", 0 ); + current->SetWatch( cvar->string ); + + // get fov + cvar = gi.cvar( "cam_fov", "", 0 ); + current->SetFov( cvar->value ); + + // get fadetime + cvar = gi.cvar( "cam_fadetime", "", 0 ); + current->SetFadeTime( cvar->value ); + + // get speed + cvar = gi.cvar( "cam_speed", "", 0 ); + current->speed = cvar->value; + speed = current->speed; +} + + +//--------------------------------------------------------------------------- +// CameraManager::SetPathName +//--------------------------------------------------------------------------- +void CameraManager::SetPathName( const str& name ) +{ + pathName = name; + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::NewPath +//--------------------------------------------------------------------------- +void CameraManager::NewPath( Event* ev ) +{ + if ( path ) + { + cameraPath_dirty = true; + path = NULL; + current = NULL; + } + SetPathName( "untitled" ); + ShowPath(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::RenamePath +//--------------------------------------------------------------------------- +void CameraManager::RenamePath( Event* ev ) +{ + str name; + + if ( !ev->NumArgs() ) + { + cvar_t * cvar; + + // + // get the path name from the cvar + // + cvar = gi.cvar( "cam_filename", "", 0 ); + if ( cvar->string[ 0 ] ) + { + name = cvar->string; + } + else + { + ev->Error( "Usage: cam renamepath [pathname]" ); + return; + } + } + else + { + name = ev->GetString( 1 ); + } + + if ( pathList.ObjectInList( name ) ) + { + // remove the old name + pathList.RemoveObject( name ); + } + SetPathName( name ); + pathList.AddUniqueObject( name ); +} + + +//--------------------------------------------------------------------------- +// CameraManager::SetPath +//--------------------------------------------------------------------------- +void CameraManager::SetPath( const str& pathName ) +{ + Entity * ent; + SplinePath *node; + + ent = G_FindTarget( NULL, pathName ); + + if ( !ent ) + { + warning( "SetPath", "Could not find path named '%s'.", pathName.c_str() ); + return; + } + + if ( !ent->isSubclassOf( SplinePath ) ) + { + warning( "SetPath", "'%s' is not a camera path.", pathName.c_str() ); + return; + } + + node = ( SplinePath * )ent; + + SetPathName( pathName ); + cameraPath_dirty = true; + path = node; + current = node; + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::SetPath +//--------------------------------------------------------------------------- +void CameraManager::SetPath( Event* ev ) +{ + if ( !ev->NumArgs() ) + { + ev->Error( "Usage: cam setpath [pathname]" ); + return; + } + + SetPath( ev->GetString( 1 ) ); +} + + +//--------------------------------------------------------------------------- +// CameraManager::SetTargetName +//--------------------------------------------------------------------------- +void CameraManager::SetTargetName( Event* ev ) +{ + if ( ev->NumArgs() != 1 ) + { + ev->Error( "Usage: cam targetname [name]" ); + return; + } + + if ( !path ) + { + ev->Error( "Camera path not set." ); + return; + } + + path->SetTargetName( ev->GetString( 1 ) ); + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::SetTarget +//--------------------------------------------------------------------------- +void CameraManager::SetTarget( Event* ev ) +{ + if ( ev->NumArgs() != 1 ) + { + ev->Error( "Usage: cam target [name]" ); + return; + } + + if ( !current ) + { + ev->Error( "Camera path not set." ); + return; + } + + current->SetTriggerTarget( ev->GetString( 1 ) ); + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::SetThread +//--------------------------------------------------------------------------- +void CameraManager::SetThread( Event* ev ) +{ + if ( ev->NumArgs() != 1 ) + { + ev->Error( "Usage: cam thread [name]" ); + return; + } + + if ( !current ) + { + ev->Error( "Camera path not set." ); + return; + } + + current->SetThread( ev->GetString( 1 ) ); + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::AddPoint +//--------------------------------------------------------------------------- +void CameraManager::AddPoint( Event* ev ) +{ + Player *player; + SplinePath *prev; + SplinePath *next; + Vector ang; + Vector pos; + + player = CameraManager_GetPlayer(); + if ( player ) + { + prev = current; + if ( current ) + { + next = current->GetNext(); + } + else + { + next = NULL; + } + + player->GetPlayerView( &pos, &ang ); + + current = new SplinePath; + current->setOrigin( pos ); + current->setAngles( ang ); + current->speed = speed; + current->SetPrev( prev ); + current->SetNext( next ); + + if ( !path ) + { + path = current; + } + + ShowPath(); + } + cameraPath_dirty = true; + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::ReplacePoint +//--------------------------------------------------------------------------- +void CameraManager::ReplacePoint( Event* ev ) +{ + Player *player; + Vector ang; + Vector pos; + + player = CameraManager_GetPlayer(); + if ( current && player ) + { + player->GetPlayerView( &pos, &ang ); + + current->setOrigin( pos ); + current->setAngles( ang ); + current->speed = speed; + } + cameraPath_dirty = true; + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::DeletePoint +//--------------------------------------------------------------------------- +void CameraManager::DeletePoint( Event* ev ) +{ + SplinePath *node; + + if ( current ) + { + node = current->GetNext(); + if ( !node ) + { + node = current->GetPrev(); + } + + if ( ( SplinePath * )path == ( SplinePath * )current ) + { + path = current->GetNext(); + } + + delete current; + current = node; + } + cameraPath_dirty = true; + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::MovePlayer +//--------------------------------------------------------------------------- +void CameraManager::MovePlayer( Event* ev ) +{ + Player *player; + Vector pos; + + player = CameraManager_GetPlayer(); + if ( current && player ) + { + player->GetPlayerView( &pos, NULL ); + player->setOrigin( current->origin - pos + player->origin ); + player->SetViewAngles( current->angles ); + player->SetFov( current->fov ); + } +} + + +//--------------------------------------------------------------------------- +// CameraManager::NextPoint +//--------------------------------------------------------------------------- +void CameraManager::NextPoint( Event* ev ) +{ + SplinePath *next; + + if ( current ) + { + next = current->GetNext(); + if ( next ) + { + current = next; + } + } + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::PreviousPoint +//--------------------------------------------------------------------------- +void CameraManager::PreviousPoint( Event* ev ) +{ + SplinePath *prev; + + if ( current ) + { + prev = current->GetPrev(); + if ( prev ) + { + current = prev; + } + } + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::NextPath +//--------------------------------------------------------------------------- +void CameraManager::NextPath( Event* ev ) +{ + int index; + + // + // find current path in container of paths + // + index = pathList.IndexOfObject( pathName ); + if ( index < pathList.NumObjects() ) + index++; + + if ( index > 0 ) + { + SetPath( pathList.ObjectAt( index ) ); + UpdateUI(); + } +} + + +//--------------------------------------------------------------------------- +// CameraManager::PreviousPath +//--------------------------------------------------------------------------- +void CameraManager::PreviousPath( Event* ev ) +{ + int index; + + // + // find current path in container of paths + // + index = pathList.IndexOfObject( pathName ); + if ( index > 1 ) + index--; + + if ( index > 0 ) + { + SetPath( pathList.ObjectAt( index ) ); + UpdateUI(); + } +} + + +//--------------------------------------------------------------------------- +// CameraManager::ShowingPath +//--------------------------------------------------------------------------- +void CameraManager::ShowingPath( Event* ev ) +{ + int count; + SplinePath *node; + SplinePath *prev; + Vector mins( -8.0f, -8.0f, -8.0f ); + Vector maxs( 8.0f, 8.0f, 8.0f ); + + prev = NULL; + for( node = path; node != NULL; node = node->GetNext() ) + { + if ( prev ) + { + G_LineStipple( 4, ( unsigned short )( 0xF0F0F0F0 >> ( 7 - ( level.framenum & 7 ) ) ) ); + G_DebugLine( prev->origin, node->origin, 0.4f, 0.4f, 0.4f, 0.1f ); + G_LineStipple( 1, 0xffff ); + } + + if ( current == node ) + { + G_DrawDebugNumber( node->origin + Vector( 0.0f, 0.0f, 20.0f ), node->speed, 0.5f, 0.0f, 1.0f, 0.0f, 1 ); + if ( current->GetFov() ) + G_DrawDebugNumber( node->origin + Vector( 0.0f, 0.0f, 30.0f ), node->GetFov(), 0.5f, 0.0f, 0.0f, 1.0f, 0 ); + + G_DebugBBox( node->origin, mins, maxs, 1.0f, 1.0f, 0.0f, 1.0f ); + } + else + { + G_DebugBBox( node->origin, mins, maxs, 1.0f, 0.0f, 0.0f, 1.0f ); + } + + // + // draw watch + // + if ( node->doWatch ) + { + Entity *watchEnt; + Vector ang; + Vector delta; + Vector left; + Vector up; + Vector endpoint; + + watchEnt = GetWatchEntity( node->GetWatch() ); + if ( watchEnt ) + { + delta.x = watchEnt->origin.x; + delta.y = watchEnt->origin.y; + delta.z = watchEnt->absmax.z; + delta -= node->origin; + delta.normalize(); + ang = delta.toAngles(); + ang.AngleVectors( NULL, &left, &up ); + + G_LineWidth( 1.0f ); + endpoint = node->origin + ( delta * 48.0f ); + G_DebugLine( node->origin, endpoint, 0.5, 1, 1, 1 ); + G_DebugLine( endpoint, endpoint + (left * 8) - (delta * 8), 0.5f, 1.0f, 1.0f, 1.0f ); + G_DebugLine( endpoint, endpoint - (left * 8) - (delta * 8), 0.5f, 1.0f, 1.0f, 1.0f ); + G_DebugLine( endpoint, endpoint - (up * 8) - (delta * 8), 0.5f, 1.0f, 1.0f, 1.0f ); + G_DebugLine( endpoint, endpoint + (up * 8) - (delta * 8), 0.5f, 1.0f, 1.0f, 1.0f ); + } + } + + G_LineWidth( 3.0f ); + G_DebugLine( node->origin, node->origin + ( Vector( node->orientation[ 0 ] ) * 16 ), 1.0f, 0.0f, 0.0f, 1.0f ); + G_DebugLine( node->origin, node->origin + ( Vector( node->orientation[ 1 ] ) * 16 ), 0.0f, 1.0f, 0.0f, 1.0f ); + G_DebugLine( node->origin, node->origin + ( Vector( node->orientation[ 2 ] ) * 16 ), 0.0f, 0.0f, 1.0f, 1.0f ); + G_LineWidth( 1.0f ); + + prev = node; + } // for + + if ( cameraPath_dirty ) + { + cameraPath_dirty = false; + cameraPath.Clear(); + cameraPath.SetType( SPLINE_CLAMP ); + + node = path; + while( node != NULL ) + { + cameraPath.AppendControlPoint( node->origin, node->angles, node->speed ); + node = node->GetNext(); + + if ( node == path ) + break; + } + } + + // draw the curve itself + G_Color3f( 1.0f, 1.0f, 0.0f ); + cameraPath.DrawCurve( 10 ); + + // draw all the nodes + for( node = path, count = -1; node != NULL; node = node->GetNext(), count++ ) + { + Vector dir, angles; + + dir = cameraPath.Eval( ( float )count - 0.9f ) - cameraPath.Eval( count - 1 ); + angles = dir.toAngles(); + if ( node->doWatch || node->GetFov() || ( node->thread != "" ) || ( node->triggertarget != "" ) ) + { + G_DebugOrientedCircle( cameraPath.Eval( count - 1 ), 5.0f, 0.0f, 1.0f, 1.0f, 1.0f, angles ); + } + else + { + G_DebugOrientedCircle( cameraPath.Eval( count - 1 ), 2.0f, 0.0f, 0.0f, 1.0f, 0.2f, angles ); + } + + // if we are the first node, we need to skip the count so that we properly go to the next node + if ( count == -1 ) + { + count = 0; + } + } + + // update time console variable + if( isPreviewPlaybackRunning ) + gi.cvar_set( "cam_playbacktime", va( "%.2f", level.time - playbackStartTime ) ); + + PostEvent( EV_CameraManager_ShowingPath, FRAMETIME ); +} + + +//--------------------------------------------------------------------------- +// CameraManager::ShowPath +//--------------------------------------------------------------------------- +void CameraManager::ShowPath( void ) +{ + CancelEventsOfType( EV_CameraManager_ShowingPath ); + PostEvent( EV_CameraManager_ShowingPath, FRAMETIME ); + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::ShowPath +//--------------------------------------------------------------------------- +void CameraManager::ShowPath( Event* ev ) +{ + if ( ev->NumArgs() ) + { + SetPath( ev->GetString( 1 ) ); + } + + ShowPath(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::HidePath +//--------------------------------------------------------------------------- +void CameraManager::HidePath( Event* ev ) +{ + CancelEventsOfType( EV_CameraManager_ShowingPath ); + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::StopPlayback +//--------------------------------------------------------------------------- +void CameraManager::StopPlayback( Event* ev ) +{ + isPreviewPlaybackRunning = false; + + if ( cam ) + { + cam->Stop(); + SetCamera( NULL, 0.0f ); + } +} + + +//--------------------------------------------------------------------------- +// CameraManager::PlayPath +//--------------------------------------------------------------------------- +void CameraManager::PlayPath( Event* ev ) +{ + if ( cam ) + { + SetCamera( NULL, 0.0f ); + } + + if ( ev->NumArgs() ) + { + SetPath( ev->GetString( 1 ) ); + } + + if ( path ) + { + if ( !cam ) + { + cam = new Camera; + cam->SetTargetName( "_loadedcamera" ); + cam->ProcessPendingEvents(); + } + + isPreviewPlaybackRunning = true; + playbackStartTime = level.time; + + cam->Reset( path->origin, path->angles ); + cam->FollowPath( path, false, NULL ); + cam->Cut( NULL ); + SetCamera( cam, 0.0f ); + } +} + + +//--------------------------------------------------------------------------- +// CameraManager::PlayKFCPath +//--------------------------------------------------------------------------- +void CameraManager::PlayKFCPath( Event* ev ) +{ + cam = new Camera; + cam->SetTargetName( "_loadedcamera" ); + cam->ProcessPendingEvents(); + + str kfc_filename = ev->GetString( 1 ); + Event* camEv = new Event( EV_Camera_LoadKFC ); + camEv->AddString( kfc_filename ); + cam->ProcessEvent( camEv ); + + /// Check if optional playback offset parameters were included + if( ev->NumArgs() >= 3 ) + { + float yawOffsetDegrees = ev->GetFloat( 2 ); + Vector originOffset = ev->GetVector( 3 ); + cam->SetPlaybackOffsets( yawOffsetDegrees, originOffset ); + } + + cam->Cut( NULL ); + SetCamera( cam, 0.0f ); + + /// Post a future event to the Camera Manager to destroy this camera + Event* camKillEv = new Event( EV_CameraManager_DestroyKFCPath ); + camKillEv->AddEntity( (Entity*)cam ); + float timeDelaySeconds = cam->GetPathLengthInSeconds(); + PostEvent( camKillEv, timeDelaySeconds ); +} + + +//--------------------------------------------------------------------------- +// CameraManager::DestroyKFCPath +//--------------------------------------------------------------------------- +void CameraManager::DestroyKFCPath( Event* ev ) +{ + Camera* cam; + cam = (Camera*) ev->GetEntity( 1 ); + delete cam; + SetCamera( NULL, 0.0f ); +} + + +//--------------------------------------------------------------------------- +// CameraManager::LoopPath +//--------------------------------------------------------------------------- +void CameraManager::LoopPath( Event* ev ) +{ + if ( cam ) + { + SetCamera( NULL, 0.0f ); + } + + if ( ev->NumArgs() ) + { + SetPath( ev->GetString( 1 ) ); + } + + if ( path ) + { + if ( !cam ) + { + cam = new Camera; + cam->SetTargetName( "_loadedcamera" ); + cam->ProcessPendingEvents(); + } + + cam->Reset( path->origin, path->angles ); + cam->FollowPath( path, true, NULL ); + cam->Cut( NULL ); + SetCamera( cam, 0.0f ); + } +} + + +//--------------------------------------------------------------------------- +// CameraManager::Watch +//--------------------------------------------------------------------------- +void CameraManager::Watch( Event* ev ) +{ + if ( current ) + { + current->SetWatch( ev->GetString( 1 ) ); + } + + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::NoWatch +//--------------------------------------------------------------------------- +void CameraManager::NoWatch( Event* ev ) +{ + if ( current ) + { + current->NoWatch(); + } + + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::Fov +//--------------------------------------------------------------------------- +void CameraManager::Fov( Event* ev ) +{ + if ( current ) + { + current->SetFov( ev->GetFloat( 1 ) ); + } + + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::FadeTime +//--------------------------------------------------------------------------- +void CameraManager::FadeTime( Event* ev ) +{ + if ( current ) + { + current->SetFadeTime( ev->GetFloat( 1 ) ); + } + + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::Speed +//--------------------------------------------------------------------------- +void CameraManager::Speed( Event* ev ) +{ + speed = ev->GetFloat( 1 ); + if ( current ) + { + current->speed = speed; + } + + cameraPath_dirty = true; + UpdateUI(); +} + + +//--------------------------------------------------------------------------- +// CameraManager::SavePath +//--------------------------------------------------------------------------- +void CameraManager::SavePath( const str& pathName ) +{ + SplinePath *node; + str buf; + str filename; + int num; + int index; + + num = 0; + for( node = path; node != NULL; node = node->GetNext() ) + { + num++; + } + + if ( num == 0 ) + { + warning( "CameraManager::SavePath", "Can't save. No points in path." ); + return; + } + + filename = "cams/"; + filename += pathName; + filename += ".cam"; + + path->SetTargetName( pathName ); + + gi.Printf( "Saving camera path to '%s'...\n", filename.c_str() ); + + buf = ""; + buf += va( "//\n" ); + buf += va( "// Camera Path \"%s\", %d Nodes.\n", pathName.c_str(), num ); + buf += va( "//\n" ); + + index = 0; + for( node = path; node != NULL; node = node->GetNext() ) + { + // + // start off the spawn command + // + buf += "spawn SplinePath"; + + // + // set the targetname + // + if ( !index ) + { + buf += va( " targetname %s", pathName.c_str() ); + } + else + { + buf += va( " targetname camnode_%s_%d", pathName.c_str(), index ); + } + + // + // set the target + // + if ( index < ( num - 1 ) ) + { + buf += va( " target camnode_%s_%d", pathName.c_str(), index + 1 ); + } + + // + // set the trigger target + // + if ( node->triggertarget != "" ) + { + buf += va( " triggertarget %s", node->triggertarget.c_str() ); + } + + // + // set the thread + // + if ( node->thread != "" ) + { + buf += va( " thread %s", node->thread.c_str() ); + } + + // + // set the origin + // + buf += va( " origin \"%.2f %.2f %.2f\"", node->origin.x, node->origin.y, node->origin.z ); + + // + // set the angles + // + buf += va( " angles \"%.1f %.1f %.1f\"", AngleMod( node->angles.x ), AngleMod( node->angles.y ), AngleMod( node->angles.z ) ); + + // + // set the speed + // + buf += va( " speed %.1f", node->speed ); + + // + // set the watch + // + if ( node->doWatch && ( node->watchEnt != "" ) ) + { + buf += va( " watch %s", node->watchEnt.c_str() ); + } + + // + // set the fov + // + if ( node->GetFov() ) + { + buf += va( " fov %.1f", node->GetFov() ); + } + + // + // set the fadetime + // + if ( node->GetFadeTime() ) + { + buf += va( " fadetime %.1f", node->GetFadeTime() ); + } + + buf += "\n"; + index++; + } + buf += "end\n"; + + gi.FS_WriteFile( filename.c_str(), buf.c_str(), buf.length() + 1 ); + gi.Printf( "done.\n" ); +} + + +//--------------------------------------------------------------------------- +// CameraManager::Save +//--------------------------------------------------------------------------- +void CameraManager::Save( Event* ev ) +{ + str filename; + str name; + + if ( ev->NumArgs() != 1 ) + { + cvar_t * cvar; + + // + // get the path name from the cvar + // + cvar = gi.cvar( "cam_filename", "", 0 ); + if ( cvar->string[ 0 ] ) + { + name = cvar->string; + } + else + { + ev->Error( "Usage: cam save [filename]" ); + return; + } + } + else + { + name = ev->GetString( 1 ); + } + SavePath( name ); + pathList.AddUniqueObject( name ); +} + + +//--------------------------------------------------------------------------- +// CameraManager::Load +//--------------------------------------------------------------------------- +void CameraManager::Load( Event* ev ) +{ + qboolean show; + str filename; + str name; + + if ( ev->NumArgs() != 1 ) + { + cvar_t * cvar; + + // + // get the path name from the cvar + // + cvar = gi.cvar( "cam_filename", "", 0 ); + if ( cvar->string[ 0 ] ) + { + show = true; + name = cvar->string; + } + else + { + ev->Error( "Usage: cam load [filename]" ); + return; + } + } + else + { + show = false; + name = ev->GetString( 1 ); + } + + if ( pathList.ObjectInList( name ) && show ) + { + gi.Printf( "Camera path '%s' already loaded...\n", name.c_str() ); + return; + } + + filename = "cams/"; + filename += name; + filename += ".cam"; + + gi.Printf( "Loading camera path from '%s'...\n", filename.c_str() ); + + level.consoleThread->Parse( filename.c_str() ); + + pathList.AddUniqueObject( name ); + if ( show ) + { + Event * ev; + + ev = new Event( EV_CameraManager_SetPath ); + ev->AddString( name ); + PostEvent( ev, 0.0f ); + ShowPath(); + } +} + + +//--------------------------------------------------------------------------- +// CameraManager::SaveMap +//--------------------------------------------------------------------------- +void CameraManager::SaveMap( Event* ev ) +{ + SplinePath *node; + str buf; + str filename; + int num; + int index; + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "Usage: cam savemap [filename]" ); + return; + } + + num = 0; + for( node = path; node != NULL; node = node->GetNext() ) + { + num++; + } + + if ( num == 0 ) + { + ev->Error( "Can't save. No points in path." ); + return; + } + + filename = "cams/"; + filename += ev->GetString( 1 ); + filename += ".map"; + + if ( !path->targetname.length() ) + { + path->SetTargetName( ev->GetString( 1 ) ); + gi.Printf( "Targetname set to '%s'\n", path->targetname.c_str() ); + } + + gi.Printf( "Saving camera path to map '%s'...\n", filename.c_str() ); + + buf = ""; + index = 0; + for( node = path; node != NULL; node = node->GetNext() ) + { + buf += va( "// pathnode %d\n", index ); + buf += "{\n"; + buf += va( "\"classname\" \"info_splinepath\"\n" ); + if ( index < ( num - 1 ) ) + { + buf += va( "\"target\" \"camnode_%s_%d\"\n", ev->GetString( 1 ), index + 1 ); + } + + if ( !index ) + { + buf += va( "\"targetname\" \"%s\"\n", ev->GetString( 1 ) ); + } + else + { + buf += va( "\"targetname\" \"camnode_%s_%d\"\n", ev->GetString( 1 ), index ); + } + + if ( node->triggertarget != "" ) + { + buf += va( "\"triggertarget\" \"%s\"\n", node->triggertarget.c_str() ); + } + + if ( node->thread != "" ) + { + buf += va( "\"thread\" \"%s\"\n", node->thread.c_str() ); + } + + buf += va( "\"origin\" \"%d %d %d\"\n", ( int )node->origin.x, ( int )node->origin.y, ( int )node->origin.z ); + buf += va( "\"angles\" \"%d %d %d\"\n", ( int )AngleMod( node->angles.x ), ( int )AngleMod( node->angles.y ), ( int )AngleMod( node->angles.z ) ); + buf += va( "\"speed\" \"%.3f\"\n", node->speed ); + buf += "}\n"; + index++; + } + + gi.FS_WriteFile( filename.c_str(), buf.c_str(), buf.length() + 1 ); + gi.Printf( "done.\n" ); +} + diff --git a/dlls/game/camera.h b/dlls/game/camera.h new file mode 100644 index 0000000..6565378 --- /dev/null +++ b/dlls/game/camera.h @@ -0,0 +1,535 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/camera.h $ +// $Revision:: 19 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Camera. Duh. +// + +#ifndef __CAMERA_H__ +#define __CAMERA_H__ + +#include "g_local.h" +#include "entity.h" +#include "bspline.h" +#include "container.h" +#include "CameraPath.h" + +#define CAMERA_SWITCHTIME 0.5f + +#define ORBIT ( 1 << 0 ) +#define START_ON ( 1 << 1 ) +#define AUTOMATIC ( 1 << 2 ) +#define NO_TRACE ( 1 << 3 ) +#define NO_WATCH ( 1 << 4 ) +#define LEVEL_EXIT ( 1 << 5 ) + + +// CameraManager Events +extern Event EV_CameraManager_SetPath ; +extern Event EV_CameraManager_PlayPath ; +extern Event EV_CameraManager_Load ; + +// Camera Events +extern Event EV_Camera_CameraThink; +extern Event EV_Camera_StartMoving; +extern Event EV_Camera_Pause; +extern Event EV_Camera_Continue; +extern Event EV_Camera_StopMoving; +extern Event EV_Camera_SetSpeed; +extern Event EV_Camera_SetFollowDistance; +extern Event EV_Camera_SetOrbitHeight; +extern Event EV_Camera_SetYaw; +extern Event EV_Camera_AbsoluteYaw; +extern Event EV_Camera_RelativeYaw; +extern Event EV_Camera_SetFOV; +extern Event SetInterpolateFOV; +extern Event EV_Camera_Orbit; +extern Event EV_Camera_Follow; +extern Event EV_Camera_Watch; +extern Event EV_Camera_WatchPath; +extern Event EV_Camera_LookAt; +extern Event EV_Camera_TurnTo; +extern Event EV_Camera_MoveToEntity; +extern Event EV_Camera_MoveToPos; +extern Event EV_Camera_NoWatch; +extern Event EV_Camera_FadeTime; +extern Event EV_Camera_Cut; +extern Event EV_Camera_LoadKFC; + +class Camera; + + +//--------------------------------------------------------------------------- +// class CameraMoveState +//--------------------------------------------------------------------------- +class CameraMoveState : public Class +{ +public: + Vector pos; + Vector movedir; // direction of travel + Vector angles; // angles from spline camera + + BSpline cameraPath; + SplinePathPtr splinePath; + SplinePathPtr currentNode; + SplinePathPtr loopNode; + + float cameraTime; + int lastTime; + int newTime; + + qboolean followingpath; + EntityPtr followEnt; + EntityPtr orbitEnt; + + void operator=( const CameraMoveState& newstate ); + void Evaluate( Camera* camera ); + void Initialize( Camera* camera ); + void DoNodeEvents( Camera* camera ); + virtual void Archive( Archiver& arc ); +}; + + +//--------------------------------------------------------------------------- +// CameraMoveState::operator= +//--------------------------------------------------------------------------- +inline void CameraMoveState::operator=( const CameraMoveState& newstate ) +{ + movedir = newstate.movedir; + pos = newstate.pos; + angles = newstate.angles; + + cameraPath = newstate.cameraPath; + splinePath = newstate.splinePath; + currentNode = newstate.currentNode; + loopNode = newstate.loopNode; + + cameraTime = newstate.cameraTime; + lastTime = newstate.lastTime; + newTime = newstate.newTime; + + followEnt = newstate.followEnt; + orbitEnt = newstate.orbitEnt; + + followingpath = newstate.followingpath; +} + + +//--------------------------------------------------------------------------- +// CameraMoveState::Archive +//--------------------------------------------------------------------------- +inline void CameraMoveState::Archive( Archiver& arc ) +{ + Class::Archive( arc ); + + arc.ArchiveVector( &pos ); + arc.ArchiveVector( &movedir ); + arc.ArchiveVector( &angles ); + + cameraPath.Archive( arc ); + + arc.ArchiveSafePointer( &splinePath ); + arc.ArchiveSafePointer( ¤tNode ); + arc.ArchiveSafePointer( &loopNode ); + + arc.ArchiveFloat( &cameraTime ); + arc.ArchiveInteger( &lastTime ); + arc.ArchiveInteger( &newTime ); + + arc.ArchiveBoolean( &followingpath ); + + arc.ArchiveSafePointer( &followEnt ); + arc.ArchiveSafePointer( &orbitEnt ); +} + + +//--------------------------------------------------------------------------- +// class CameraWatchState +//--------------------------------------------------------------------------- +class CameraWatchState : public Class +{ +public: + Vector watchAngles; + + EntityPtr watchEnt; + qboolean watchNodes; + qboolean watchPath; + + void Evaluate( const Camera* camera, const CameraMoveState* move ); + void Initialize( Camera* camera ); + virtual void Archive( Archiver& arc ); +}; + + + +//--------------------------------------------------------------------------- +// CameraWatchState::Archive +//--------------------------------------------------------------------------- +inline void CameraWatchState::Archive( Archiver& arc ) +{ + Class::Archive( arc ); + + arc.ArchiveVector( &watchAngles ); + arc.ArchiveSafePointer( &watchEnt ); + arc.ArchiveBoolean( &watchNodes ); + arc.ArchiveBoolean( &watchPath ); +} + + +//--------------------------------------------------------------------------- +// class CameraState +//--------------------------------------------------------------------------- +class CameraState : public Class +{ +public: + CameraMoveState move; + CameraWatchState watch; + float fov; + + void Evaluate( Camera * camera ); + void Initialize( Camera * camera ); + virtual void Archive( Archiver &arc ); +}; + + +//--------------------------------------------------------------------------- +// CameraState::Archive +//--------------------------------------------------------------------------- +inline void CameraState::Archive( Archiver& arc ) +{ + Class::Archive( arc ); + + move.Archive( arc ); + watch.Archive( arc ); + arc.ArchiveFloat( &fov ); +} + + +//--------------------------------------------------------------------------- +// class Camera +//--------------------------------------------------------------------------- +class Camera : public Entity +{ +private: + friend class CameraState; + friend class CameraWatchState; + friend class CameraMoveState; + // + // follow parameters + // + float follow_yaw; + qboolean follow_yaw_fixed; + float follow_dist; + int follow_mask; + + // camera speed + float camera_speed; + // current camera fov + float camera_fov; + // orbit height + float orbit_height; + // orbit_dotrace + qboolean orbit_dotrace; + // whether or not auto calculate fov, a non-zero value means yes + float auto_fov; + + // automatic variables + float automatic_startTime; + float automatic_stopTime; + float automatic_radius; + float automatic_maxFOV; + qboolean automatic_active; + Container automatic_states; + + // members supporting new key-framed camera system + CameraPath* newCameraPath; // NULL if not currently in use (fall back to old system) + float newCameraPathSeconds; // the number of seconds the camera is into its key-framed path + +protected: + CameraState currentstate; + CameraState newstate; + + float watchTime; // if non-zero, camera view is transitioning + float followTime; // if non-zero, camera position is tranisitioning + float fovTime; // if non-zero, fov is being lerped + + float fadeTime; // time to transition over + float fovFadeTime; // time for fov transition + float followFadeTime; // time for fov transition + float watchFadeTime; // time for fov transition + + str nextCamera; + str thread; + qboolean showcamera; + + void SetupCamera( Event* ev ); + void CameraThink( Event* ev ); + void EvaluateCameraKeyFramePath( void ); + void CreateOrbit( const Vector& pos, float radius, const Vector& forward, const Vector& left ); + void CreatePath( SplinePath* path, splinetype_t type ); + void UpdateStates( void ); + Vector CalculatePosition( void ); + Vector CalculateOrientation( void ); + float CalculateFov( void ); + virtual void bind( Entity* master, qboolean use_my_angles = false ); + virtual void unbind( void ); + +public: + CLASS_PROTOTYPE( Camera ); + + Camera(); + void Stop( void ); + void FollowPath( SplinePath* path, qboolean loop, Entity* watch ); + void Orbit( Entity* ent, float dist, Entity* watch, float yaw_offset = 0, qboolean dotrace = true ); + void FollowEntity( Entity* ent, float dist, int mask, Entity* watch = NULL ); + void Watch( const str& watch, float time ); + void Watch( Entity* ent, float time ); + void SetFOV( float fov, float time ); + void StartMoving( Event* ev ); + void StopMoving( Event* ev ); + void Pause( Event* ev ); + void Continue( Event* ev ); + void SetAnglesEvent( Event* ev ); + void SetSpeed( Event* ev ); + void SetFollowDistance( Event* ev ); + void SetOrbitHeight( float height ); + void SetOrbitHeight( Event* ev ); + void SetFollowYaw( Event* ev ); + void AbsoluteYaw( Event* ev ); + void RelativeYaw( Event* ev ); + void SetFOV( Event* ev ); + void SetInterpolateFOV( Event* ev ); + void LoadKFC( Event* ev ); + void OrbitEvent( Event* ev ); + void FollowEvent( Event* ev ); + void WatchEvent( Event* ev ); + void WatchPathEvent( Event* ev ); + void WatchNodesEvent( Event* ev ); + void NoWatchEvent( Event* ev ); + void LookAt( Event* ev ); + void MoveToEntity( Event* ev ); + void MoveToPos( Event* ev ); + void Cut( Event* ev ); + void FadeTime( Event* ev ); + void TurnTo( Event* ev ); + void SetNextCamera( Event* ev ); + void SetThread( Event* ev ); + float CalculateScore( Entity* player, const str& state ); + float AutomaticStart( Entity* player ); + float AutomaticStop( Entity* player ); + qboolean IsAutomatic( void ); + qboolean IsLevelExit( void ); + void SetAutoStateEvent( Event* ev ); + void SetAutoRadiusEvent( Event* ev ); + void SetAutoStartTimeEvent( Event* ev ); + void SetAutoStopTimeEvent( Event* ev ); + void SetMaximumAutoFOVEvent( Event* ev ); + void SetAutoActiveEvent( Event* ev ); + str& NextCamera( void ); + str& Thread( void ); + float Fov( void ); + void SetPlaybackOffsets( const float yawOffsetDegrees, const Vector& originOffset ); + float GetPathLengthInSeconds( void ); + float GetCameraTime( void ); + void Reset( const Vector& org, const Vector& ang ); + virtual void Archive( Archiver& arc ); +}; + + +//--------------------------------------------------------------------------- +// Camera::Archive +//--------------------------------------------------------------------------- +inline void Camera::Archive( Archiver& arc ) +{ + Entity::Archive( arc ); + + arc.ArchiveFloat( &follow_yaw ); + arc.ArchiveBoolean( &follow_yaw_fixed ); + arc.ArchiveFloat( &follow_dist ); + arc.ArchiveInteger( &follow_mask ); + + arc.ArchiveFloat( &camera_speed ); + arc.ArchiveFloat( &camera_fov ); + arc.ArchiveFloat( &orbit_height ); + arc.ArchiveBoolean( &orbit_dotrace ); + arc.ArchiveFloat( &auto_fov ); + + arc.ArchiveFloat( &automatic_startTime ); + arc.ArchiveFloat( &automatic_stopTime ); + arc.ArchiveFloat( &automatic_radius ); + arc.ArchiveFloat( &automatic_maxFOV ); + arc.ArchiveBoolean( &automatic_active ); + + automatic_states.Archive( arc ); + + if ( arc.Saving() ) + { + bool cameraValid; + + if ( newCameraPath ) + cameraValid = true; + else + cameraValid = false; + + arc.ArchiveBool( &cameraValid ); + + if ( cameraValid ) + arc.ArchiveObject( newCameraPath ); + } + else + { + bool cameraValid; + + arc.ArchiveBool( &cameraValid ); + + if ( cameraValid ) + { + newCameraPath = new CameraPath(); + + arc.ArchiveObject( newCameraPath ); + } + else + { + newCameraPath = NULL; + } + } + + arc.ArchiveFloat( &newCameraPathSeconds ); + + // currentstate + currentstate.Archive( arc ); + // newstate + newstate.Archive( arc ); + + arc.ArchiveFloat( &watchTime ); + arc.ArchiveFloat( &followTime ); + arc.ArchiveFloat( &fovTime ); + arc.ArchiveFloat( &fadeTime ); + arc.ArchiveFloat( &fovFadeTime ); + arc.ArchiveFloat( &followFadeTime ); + arc.ArchiveFloat( &watchFadeTime ); + + arc.ArchiveString( &nextCamera ); + arc.ArchiveString( &thread ); + + arc.ArchiveBoolean( &showcamera ); + + if ( arc.Loading() ) + { + if ( spawnflags & AUTOMATIC ) + { + level.AddAutomaticCamera( this ); + } + } +} + + +void SetCamera( Entity *ent, float switchTime ); +Entity * GetWatchEntity( const str &watch ); + +typedef SafePtr CameraPtr; + + +//--------------------------------------------------------------------------- +// class CameraManager +//--------------------------------------------------------------------------- +class CameraManager : public Listener +{ +protected: + Container pathList; + BSpline cameraPath; + SplinePathPtr path; + SplinePathPtr current; + float speed; + int watch; + str pathName; + CameraPtr cam; + qboolean cameraPath_dirty; + float playbackStartTime; + qboolean isPreviewPlaybackRunning; + + void NewPath( Event* ev ); + void SetPath( Event* ev ); + void SetTargetName( Event* ev ); + void SetTarget( Event* ev ); + void SetThread( Event* ev ); + void AddPoint( Event* ev ); + void ReplacePoint( Event* ev ); + void DeletePoint( Event* ev ); + void MovePlayer( Event* ev ); + void NextPoint( Event* ev ); + void PreviousPoint( Event* ev ); + void ShowingPath( Event* ev ); + void ShowPath( Event* ev ); + void HidePath( Event* ev ); + void StopPlayback( Event* ev ); + void PlayPath( Event* ev ); + void PlayKFCPath( Event* ev ); + void DestroyKFCPath( Event* ev ); + void LoopPath( Event* ev ); + void Watch( Event* ev ); + void NoWatch( Event* ev ); + void Fov( Event* ev ); + void FadeTime( Event* ev ); + void Speed( Event* ev ); + void Save( Event* ev ); + void Load( Event* ev ); + void SaveMap( Event* ev ); + void UpdateEvent( Event* ev ); + void NextPath( Event* ev ); + void PreviousPath( Event* ev ); + void RenamePath( Event* ev ); + void ShowPath( void ); + void UpdateUI( void ); + void SetPathName( const str& name ); + void SavePath( const str& pathName ); + +public: + CLASS_PROTOTYPE( CameraManager ); + + CameraManager(); + void SetPath( const str& pathName ); + virtual void Archive( Archiver& arc ); +}; + + + +//--------------------------------------------------------------------------- +// CameraManager::Archive +//--------------------------------------------------------------------------- +inline void CameraManager::Archive( Archiver& arc ) +{ + Listener::Archive( arc ); + + pathList.Archive( arc ); + + // Don't need to archive cameraPath because it gets rebuilt ayways + //BSpline cameraPath; + + // no need to archive the cameraPath + arc.ArchiveSafePointer( &path ); + arc.ArchiveSafePointer( ¤t ); + arc.ArchiveFloat( &speed ); + arc.ArchiveInteger( &watch ); + arc.ArchiveString( &pathName ); + arc.ArchiveSafePointer( &cam ); + + // make sure the cameraPath gets rebuilt + cameraPath_dirty = true; +} + + +extern CameraManager CameraMan; + +#endif /* camera.h */ diff --git a/dlls/game/changePosture.cpp b/dlls/game/changePosture.cpp new file mode 100644 index 0000000..24a2e2e --- /dev/null +++ b/dlls/game/changePosture.cpp @@ -0,0 +1,367 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/changePosture.cpp $ +// $Revision:: 3 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// changePosture Implementation +// +// PARAMETERS: +// +// +// ANIMATIONS: +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "changePosture.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, ChangePosture, NULL ) + { + { &EV_Behavior_Args, &ChangePosture::SetArgs}, + { &EV_PostureChanged_Completed, &ChangePosture::PostureDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: ChangePosture() +// Class: ChangePosture +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +ChangePosture::ChangePosture() +{ + _posture = ""; +} + +//-------------------------------------------------------------- +// Name: ~ChangePosture() +// Class: ChangePosture +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +ChangePosture::~ChangePosture() +{ +} + + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: ChangePosture +// +// Description: +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void ChangePosture::SetArgs( Event *ev ) +{ + _posture = ev->GetString( 1 ); +} + +void ChangePosture::PostureDone( Event *ev ) +{ + _postureDone = true; +} + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: ChangePosture +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void ChangePosture::Begin( Actor &self ) +{ + init(); +} + + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: ChangePosture +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t ChangePosture::Evaluate( Actor &self ) +{ + + BehaviorReturnCode_t stateResult; + + + think(); + + switch ( _state ) + { + + //--------------------------------------------------------------------- + case CHANGE_POSTURE_SETUP: + //--------------------------------------------------------------------- + stateResult = evaluateStateSetup(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CHANGE_POSTURE_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CHANGE_POSTURE_CHANGE ); + break; + + //--------------------------------------------------------------------- + case CHANGE_POSTURE_CHANGE: + //--------------------------------------------------------------------- + stateResult = evaluateStateChange(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CHANGE_POSTURE_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CHANGE_POSTURE_SUCCESS ); + break; + + //--------------------------------------------------------------------- + case CHANGE_POSTURE_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + break; + + //--------------------------------------------------------------------- + case CHANGE_POSTURE_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: ChangePosture +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void ChangePosture::End(Actor &self) +{ +} + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: ChangePosture +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void ChangePosture::transitionToState( changePostureStates_t state ) +{ + switch( state ) + { + case CHANGE_POSTURE_SETUP: + setupStateSetup(); + setInternalState( state , "CHANGE_POSTURE_SETUP" ); + break; + + case CHANGE_POSTURE_CHANGE: + setupStateChange(); + setInternalState( state , "CHANGE_POSTURE_CHANGE" ); + break; + + case CHANGE_POSTURE_SUCCESS: + setInternalState( state , "CHANGE_POSTURE_SUCCESS" ); + break; + + case CHANGE_POSTURE_FAILED: + setInternalState( state , "CHANGE_POSTURE_FAILED" ); + break; + } +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: ChangePosture +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void ChangePosture::setInternalState( changePostureStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: ChangePosture +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void ChangePosture::init() +{ + transitionToState(CHANGE_POSTURE_SETUP); + _canChange = true; + _postureDone = false; +} + +//-------------------------------------------------------------- +// Name: think() +// Class: ChangePosture +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void ChangePosture::think() +{ +} + + +//-------------------------------------------------------------- +// Name: setupStateSetup() +// Class: ChangePosture +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void ChangePosture::setupStateSetup() +{ + _canChange = GetSelf()->postureController->requestPosture( _posture , this ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateSetup() +// Class: ChangePosture +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t ChangePosture::evaluateStateSetup() +{ + if ( !_canChange ) + return BEHAVIOR_FAILED; + + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateSetup() +// Class: ChangePosture +// +// Description: Failure Handler for State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void ChangePosture::failureStateSetup( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateChange() +// Class: ChangePosture +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void ChangePosture::setupStateChange() +{ +} + +//-------------------------------------------------------------- +// Name: evaluateStateChange() +// Class: ChangePosture +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t ChangePosture::evaluateStateChange() +{ + if ( _postureDone ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateChange() +// Class: ChangePosture +// +// Description: Failure Handler for State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void ChangePosture::failureStateChange( const str& failureReason ) +{ +} diff --git a/dlls/game/changePosture.hpp b/dlls/game/changePosture.hpp new file mode 100644 index 0000000..87a3279 --- /dev/null +++ b/dlls/game/changePosture.hpp @@ -0,0 +1,127 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/changePosture.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CloseInOnEnemy Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class ChangePosture; + +#ifndef __CHANGE_POSTURE_HPP__ +#define __CHANGE_POSTURE_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: ChangePosture +// Base Class: Behavior +// +// Description: Makes the actor change posture +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class ChangePosture : public Behavior +{ + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + CHANGE_POSTURE_SETUP, + CHANGE_POSTURE_CHANGE, + CHANGE_POSTURE_SUCCESS, + CHANGE_POSTURE_FAILED + } changePostureStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: // Parameters + str _posture; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( changePostureStates_t state ); + void setInternalState ( changePostureStates_t state , const str &stateName ); + void init (); + void think (); + + + void setupStateSetup (); + BehaviorReturnCode_t evaluateStateSetup (); + void failureStateSetup ( const str& failureReason ); + + void setupStateChange (); + BehaviorReturnCode_t evaluateStateChange (); + void failureStateChange ( const str& failureReason ); + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( ChangePosture ); + + ChangePosture(); + ~ChangePosture(); + + void SetArgs ( Event *ev ); + void PostureDone ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + void setPosture ( const str &postureName ) { _posture = postureName; } + + //------------------------------------- + // Components + //------------------------------------- + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + changePostureStates_t _state; + bool _postureDone; + bool _canChange; + +}; + + +inline void ChangePosture::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_posture ); + + // Archive Components + + // Archive Member Variables + ArchiveEnum ( _state, changePostureStates_t); + arc.ArchiveBool ( &_postureDone ); + arc.ArchiveBool ( &_canChange ); +} + +#endif /* __CHANGE_POSTURE_HPP__ */ diff --git a/dlls/game/characterstate.cpp b/dlls/game/characterstate.cpp new file mode 100644 index 0000000..ebdd520 --- /dev/null +++ b/dlls/game/characterstate.cpp @@ -0,0 +1,1876 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/characterstate.cpp $ +// $Revision:: 24 $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "characterstate.h" +#include "animate.h" +#include "scriptmaster.h" + +static const char *MoveControl_Names[] = +{ + "user", // MOVECONTROL_USER + "legs", // MOVECONTROL_LEGS + "anim", // MOVECONTROL_ANIM + "absolute", // MOVECONTROL_ABSOLUTE + "hanging", // MOVECONTROL_HANGING + "wallhug", // MOVECONTROL_WALLHUG + "monkeybars", // MOVECONTROL_MONKEYBARS + "pipecrawl", // MOVECONTROL_PIPECRAWL + "pipehang", // MOVECONTROL_PIPEHANG + "stepup", // MOVECONTROL_STEPUP + "rope_grab", // MOVECONTROL_ROPE_GRAB + "rope_release", // MOVECONTROL_ROPE_RELEASE + "rope_move", // MOVECONTROL_ROPE_MOVE + "pickupenemy", // MOVECONTROL_PICKUPENEMY + "push", // MOVECONTROL_PUSH + "climbwall", // MOVECONTROL_CLIMBWALL + "useanim", // MOVECONTROL_USEANIM + "crouch", // MOVECONTROL_CROUCH + "loopuseanim", // MOVECONTROL_LOOPUSEANIM + "useobject", // MOVECONTROL_USEOBJECT + "coolobject", // MOVECONTROL_COOLOBJECT + "fakeplayer", // MOVECONTROL_FAKEPLAYER + NULL +}; + +static const char *Camera_Names[] = +{ + "topdown", // CAMERA_TOPDOWN + "behind", // CAMERA_BEHIND + "front", // CAMERA_FRONT + "side", // CAMERA_SIDE + "behind_fixed", // CAMERA_BEHIND_FIXED + "side_left", // CAMERA_SIDE_LEFT + "side_right", // CAMERA_SIDE_RIGHT + "behind_nopitch", // CAMERA_BEHIND_NOPITCH + NULL +}; + +Conditional::Conditional( const Condition &cond ) : + condition( cond ) +{ + result = false; + previous_result = false; + checked = false; +} + +Conditional::Conditional( void ) +{ + gi.Error( ERR_DROP, "Conditional created with wrong constructor\n" ); +} + +Expression::Expression() +{ +} + +Expression::Expression( const Expression &exp ) +{ + int i; + + value = exp.value; + + for( i = 1; i <= exp.conditions.NumObjects(); i++ ) + { + conditions.AddObject( exp.conditions.ObjectAt( i ) ); + } +} + +Expression::Expression( Script &script, State &state ) +{ + str token; + condition_t condition; + int start; + + value = script.GetToken( true ); + + if ( !script.TokenAvailable( false ) || Q_stricmp( script.GetToken( false ), ":" ) ) + { + gi.Error( ERR_DROP, "%s: Expecting ':' on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + while( script.TokenAvailable( false ) ) + { + token = script.GetToken( true ); + + switch( token[ 0 ] ) + { + case '!' : + condition.test = TC_ISFALSE; + start = 1; + break; + + case '+' : + condition.test = TC_EDGETRUE; + start = 1; + break; + + case '-' : + condition.test = TC_EDGEFALSE; + start = 1; + break; + + default : + condition.test = TC_ISTRUE; + start = 0; + } + + if ( token.length() <= start ) + { + gi.Error( ERR_DROP, "%s: Illegal syntax '%s' on line %d.\n", script.Filename(), &token, script.GetLineNumber() ); + condition.condition_index = 0; + continue; + } + + condition.condition_index = state.addCondition( &token[ start ], script ); + if ( !condition.condition_index ) + { + gi.Error( ERR_DROP, "%s: Unknown condition '%s' on line %d.\n", script.Filename(), &token[ start ], script.GetLineNumber() ); + } + + conditions.AddObject( condition ); + } +} + +Expression::Expression( Script &script, FuzzyVar &fuzzyVar ) +{ + str token; + condition_t condition; + int start; + + value = script.GetToken( true ); + points = (float)atof(value.c_str()); + + if ( !script.TokenAvailable( false ) || Q_stricmp( script.GetToken( false ), ":" ) ) + { + gi.Error( ERR_DROP, "%s: Expecting ':' on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + while( script.TokenAvailable( false ) ) + { + token = script.GetToken( true ); + + switch( token[ 0 ] ) + { + case '!' : + condition.test = TC_ISFALSE; + start = 1; + break; + + case '+' : + condition.test = TC_EDGETRUE; + start = 1; + break; + + case '-' : + condition.test = TC_EDGEFALSE; + start = 1; + break; + + default : + condition.test = TC_ISTRUE; + start = 0; + } + + if ( token.length() <= start ) + { + gi.Error( ERR_DROP, "%s: Illegal syntax '%s' on line %d.\n", script.Filename(), &token, script.GetLineNumber() ); + condition.condition_index = 0; + continue; + } + + condition.condition_index = fuzzyVar.addCondition( &token[ start ], script ); + if ( !condition.condition_index ) + { + gi.Error( ERR_DROP, "%s: Unknown condition '%s' on line %d.\n", script.Filename(), &token[ start ], script.GetLineNumber() ); + } + + conditions.AddObject( condition ); + } +} + + +bool Expression::getResult( const State &state, Entity &ent, const Container *sent_conditionals ) +{ + int i; + condition_t *cond; + Conditional *conditional; + + for( i = 1; i <= conditions.NumObjects(); i++ ) + { + cond = &conditions.ObjectAt( i ); + conditional = sent_conditionals->ObjectAt( cond->condition_index ); + + if ( !conditional || !conditional->getResult( cond->test, ent ) ) + { + return false; + } + } + + return true; +} + +bool Expression::getResult( const FuzzyVar &fVar, Entity &ent, const Container *sent_conditionals ) +{ + int i; + condition_t *cond; + Conditional *conditional; + + for( i = 1; i <= conditions.NumObjects(); i++ ) + { + cond = &conditions.ObjectAt( i ); + conditional = sent_conditionals->ObjectAt( cond->condition_index ); + + if ( !conditional || !conditional->getResult( cond->test, ent ) ) + { + return false; + } + } + + return true; +} + +void State::readNextState( Script &script ) +{ + nextState = script.GetToken( false ); +} + +void State::readMoveType( Script &script ) +{ + str token; + const char **name; + int i; + + token = script.GetToken( false ); + + for( i = 0, name = MoveControl_Names; *name != NULL; name++, i++ ) + { + if ( !token.icmp( *name ) ) + { + break; + } + } + + if ( *name == NULL ) + { + gi.Error( ERR_DROP, "%s: Unknown movetype '%s' on line %d.\n", script.Filename(), token.c_str(), script.GetLineNumber() ); + } + else + { + movetype = ( movecontrol_t )i; + } +} + +qboolean State::setCameraType( const str &ctype ) +{ + const char **name; + int i; + + for( i = 0, name = Camera_Names; *name != NULL; name++, i++ ) + { + if ( !ctype.icmp( *name ) ) + { + cameratype = ( cameratype_t )i; + return true; + } + } + return false; +} + +void State::readCamera( Script &script ) +{ + str token; + + token = script.GetToken( false ); + + if ( !setCameraType( token ) ) + { + gi.Error( ERR_DROP, "%s: Unknown camera type '%s' on line %d.\n", script.Filename(), token.c_str(), script.GetLineNumber() ); + } +} + +void State::readLegs( Script &script ) +{ + str token; + + if ( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) + { + gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + while( script.TokenAvailable( true ) ) + { + token = script.GetToken( true ); + if ( !Q_stricmp( token.c_str(), "}" ) ) + { + break; + } + + script.UnGetToken(); + legAnims.AddObject( Expression( script, *this ) ); + } +} + +void State::readTorso( Script &script ) +{ + str token; + + if ( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) + { + gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + while( script.TokenAvailable( true ) ) + { + token = script.GetToken( true ); + if ( !Q_stricmp( token.c_str(), "}" ) ) + { + break; + } + + script.UnGetToken(); + torsoAnims.AddObject( Expression( script, *this ) ); + } +} + +void State::readBehavior( Script &script ) +{ + str token; + + if ( !script.TokenAvailable( true ) ) + { + gi.Error( ERR_DROP, "%s: Expecting behavior name on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + behaviorName = script.GetToken( true ); + if ( !getClass( behaviorName ) ) + { + gi.Error( ERR_DROP, "%s: Unknown behavior '%s' on line %d.\n", script.Filename(), behaviorName.c_str(), script.GetLineNumber() ); + } + + // Read in the behavior arguments if there are any + + // New Format - Parameters use ( x , y , z ) format + while ( script.TokenAvailable( false ) && script.AtOpenParen( false ) ) + { + // Need to burn off first "(" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "(" ) ) + { + script.UnGetToken(); + } + + while ( !script.AtCloseParen( false ) ) + { + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "," ) ) + { + addBehaviorParm( token ); + } + } + + // Need to burn off last ")" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), ")" ) ) + { + script.UnGetToken(); + } + } + + // Old Format - For Legacy Data + while ( script.TokenAvailable( false ) && script.AtString( false ) ) + { + token = script.GetToken( false ); + addBehaviorParm( token ); + } +} + +void State::readHeadBehavior( Script &script ) +{ + str token; + + if ( !script.TokenAvailable( true ) ) + { + gi.Error( ERR_DROP, "%s: Expecting behavior name on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + headBehaviorName = script.GetToken( true ); + if ( !getClass( headBehaviorName ) ) + { + gi.Error( ERR_DROP, "%s: Unknown behavior '%s' on line %d.\n", script.Filename(), headBehaviorName.c_str(), script.GetLineNumber() ); + } + + // Read in the behavior arguments if there are any + + // New Format - Parameters use ( x , y , z ) format + while ( script.TokenAvailable( false ) && script.AtOpenParen( false ) ) + { + // Need to burn off first "(" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "(" ) ) + { + script.UnGetToken(); + } + + while ( !script.AtCloseParen( false ) ) + { + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "," ) ) + { + addHeadBehaviorParm( token ); + } + } + + // Need to burn off last ")" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), ")" ) ) + { + script.UnGetToken(); + } + } + + // Old Format - For Legacy Data + while ( script.TokenAvailable( false ) && script.AtString( false ) ) + { + token = script.GetToken( false ); + addHeadBehaviorParm( token ); + } +} + +void State::readEyeBehavior( Script &script ) +{ + str token; + + if ( !script.TokenAvailable( true ) ) + { + gi.Error( ERR_DROP, "%s: Expecting behavior name on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + eyeBehaviorName = script.GetToken( true ); + if ( !getClass( eyeBehaviorName ) ) + { + gi.Error( ERR_DROP, "%s: Unknown behavior '%s' on line %d.\n", script.Filename(), headBehaviorName.c_str(), script.GetLineNumber() ); + } + + // Read in the behavior arguments if there are any + + // New Format - Parameters use ( x , y , z ) format + while ( script.TokenAvailable( false ) && script.AtOpenParen( false ) ) + { + // Need to burn off first "(" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "(" ) ) + { + script.UnGetToken(); + } + + while ( !script.AtCloseParen( false ) ) + { + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "," ) ) + { + addEyeBehaviorParm( token ); + } + } + + // Need to burn off last ")" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), ")" ) ) + { + script.UnGetToken(); + } + } + + // Old Format - For Legacy Data + while ( script.TokenAvailable( false ) && script.AtString( false ) ) + { + token = script.GetToken( false ); + addEyeBehaviorParm( token ); + } +} + +void State::readTorsoBehavior( Script &script ) +{ + str token; + + if ( !script.TokenAvailable( true ) ) + { + gi.Error( ERR_DROP, "%s: Expecting behavior name on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + torsoBehaviorName = script.GetToken( true ); + if ( !getClass( torsoBehaviorName ) ) + { + gi.Error( ERR_DROP, "%s: Unknown behavior '%s' on line %d.\n", script.Filename(), headBehaviorName.c_str(), script.GetLineNumber() ); + } + + // Read in the behavior arguments if there are any + + // New Format - Parameters use ( x , y , z ) format + while ( script.TokenAvailable( false ) && script.AtOpenParen( false ) ) + { + // Need to burn off first "(" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "(" ) ) + { + script.UnGetToken(); + } + + while ( !script.AtCloseParen( false ) ) + { + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "," ) ) + { + addTorsoBehaviorParm( token ); + } + } + + // Need to burn off last ")" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), ")" ) ) + { + script.UnGetToken(); + } + } + + // Old Format - For Legacy Data + while ( script.TokenAvailable( false ) && script.AtString( false ) ) + { + token = script.GetToken( false ); + addTorsoBehaviorParm( token ); + } +} + +void State::readTime( Script &script ) +{ + str token; + + // Old Format - For Legacy Data + if ( script.TokenAvailable( false ) && script.AtString( false ) ) + { + token = script.GetToken( false ); + minTime = (float)atof( token.c_str() ); + } + + if ( script.TokenAvailable( false ) && script.AtString( false ) ) + { + token = script.GetToken( false ); + maxTime = (float)atof( token.c_str() ); + } + else + { + maxTime = minTime; + } + + // New Format - Parameters use ( x , y , z ) format + if ( script.TokenAvailable( false ) && script.AtOpenParen( false ) ) + { + // Need to burn off first "(" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "(" ) ) + { + script.UnGetToken(); + } + + // Grab Min Time + token = script.GetToken( false ); + minTime = (float)atof( token.c_str() ); + + // Set a default value for maxTime + maxTime = minTime; + + token = script.GetToken( false ); + + if ( !Q_stricmp( token.c_str(), "," )) + { + token = script.GetToken( false ); + maxTime = (float)atof( token.c_str() ); + + // Grab Closing Paren + token = script.GetToken( false ); + } + + // Check that we have the closing paren, if not - complain + if ( Q_stricmp( token.c_str(), ")") ) + { + gi.Error( ERR_DROP, "%s: Missing Closing Paren ) on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + } +} + +void State::readStates( Script &script ) +{ + str token; + + if ( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) + { + gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + while( script.TokenAvailable( true ) ) + { + token = script.GetToken( true ); + if ( !Q_stricmp( token.c_str(), "}" ) ) + { + break; + } + + script.UnGetToken(); + states.AddObject( Expression( script, *this ) ); + } +} + +void State::ParseAndProcessCommand( const str &command, Entity *target ) +{ + int argc; + const char *argv[ MAX_COMMANDS ]; + char args[ MAX_COMMANDS ][ MAXTOKEN ]; + Script script; + Event *event; + + script.Parse( command, command.length(), "CommandString" ); + + argc = 0; + while( script.TokenAvailable( false ) ) + { + if ( argc >= MAX_COMMANDS ) + { + gi.WDPrintf( "State:ParseAndProcessCommand : Line exceeds %d command limit", MAX_COMMANDS ); + script.SkipToEOL(); + break; + } + strcpy( args[ argc ], script.GetToken( false ) ); + argv[ argc ] = args[ argc ]; + argc++; + } + + assert( argc > 0 ); + + if ( argc <= 0 ) + return; + + event = new Event( args[0] ); + event->AddTokens( argc - 1, &argv[ 1 ] ); + target->ProcessEvent( event ); +} + +void State::ProcessEntryCommands( Entity *target ) +{ + int i,count; + str command; + + assert( target ); + if ( !target ) + { + return; + } + + count = entryCommands.NumObjects(); + for( i = 1; i <= count; i++ ) + { + ParseAndProcessCommand( entryCommands.ObjectAt( i ), target ); + } +} + +void State::ProcessExitCommands( Entity *target ) +{ + int i,count; + str command; + + assert( target ); + if ( !target ) + { + return; + } + + count = exitCommands.NumObjects(); + for( i = 1; i <= count; i++ ) + { + ParseAndProcessCommand( exitCommands.ObjectAt( i ), target ); + } +} + +void State::readCommands( Script &script, Container &container ) +{ + str token; + str command; + qboolean finish_line; + + if ( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) + { + gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + while( script.TokenAvailable( true ) ) + { + finish_line = false; + + while( script.TokenAvailable( false ) ) + { + token = script.GetToken( false ); + + //Skip Parens "()" or Commas "," + while( !Q_stricmp( token.c_str(), "(" ) || !Q_stricmp( token.c_str(), ")" ) || !Q_stricmp( token.c_str(), "," )) + { + if ( script.TokenAvailable( false ) ) + token = script.GetToken( false ); + else + { + finish_line = true; + break; + } + } + + if ( finish_line ) + break; + + if ( !Q_stricmp( token.c_str(), "}" ) ) + return; + + if ( token.length() ) + { + if ( strstr( token.c_str(), " " ) == NULL ) + { + command.append( token ); + } + else + { + command.append( "\"" ); + command.append( token ); + command.append( "\"" ); + } + } + else + { + command.append( "\"\"" ); + } + + command.append( " " ); + } + + container.AddObject( command ); + command = ""; + } + + return; +} + +State *State::Evaluate( Entity &ent, Container *sent_conditionals ) +{ + int i; + Expression *exp; + State *state; + int index; + + for( i = 1; i <= condition_indexes.NumObjects(); i++ ) + { + index = condition_indexes.ObjectAt( i ); + sent_conditionals->ObjectAt( index )->clearCheck(); + //conditions.ObjectAt( i )->clearCheck(); + } + + for( i = 1; i <= states.NumObjects(); i++ ) + { + exp = &states.ObjectAt( i ); + if ( exp->getResult( *this, ent, sent_conditionals ) ) + { + state = statemap.FindState( exp->getValue() ); + return state; + } + } + + return this; +} + +const char *State::getLegAnim( Entity &ent, Container *sent_conditionals ) +{ + int i; + Expression *exp; + int index; + + for( i = 1; i <= condition_indexes.NumObjects(); i++ ) + { + index = condition_indexes.ObjectAt( i ); + sent_conditionals->ObjectAt( index )->clearCheck(); + //conditions.ObjectAt( i )->clearCheck(); + } + + for( i = 1; i <= legAnims.NumObjects(); i++ ) + { + exp = &legAnims.ObjectAt( i ); + if ( exp->getResult( *this, ent, sent_conditionals ) ) + { + return exp->getValue(); + } + } + + return ""; +} + +const char *State::getTorsoAnim( Entity &ent, Container *sent_conditionals ) +{ + int i; + Expression *exp; + int index; + + for( i = 1; i <= condition_indexes.NumObjects(); i++ ) + { + index = condition_indexes.ObjectAt( i ); + sent_conditionals->ObjectAt( index )->clearCheck(); + } + + for( i = 1; i <= torsoAnims.NumObjects(); i++ ) + { + exp = &torsoAnims.ObjectAt( i ); + if ( exp->getResult( *this, ent, sent_conditionals ) ) + { + return exp->getValue(); + } + } + + return ""; +} + +const char *State::getBehaviorName( void ) +{ + return behaviorName.c_str(); +} + +const char *State::getHeadBehaviorName( void ) +{ + return headBehaviorName.c_str(); +} + +const char *State::getEyeBehaviorName( void ) +{ + return eyeBehaviorName.c_str(); +} + +const char *State::getTorsoBehaviorName( void ) +{ + return torsoBehaviorName.c_str(); +} + +float State::getMinTime( void ) +{ + return minTime; +} + +float State::getMaxTime( void ) +{ + return maxTime; +} + +int State::addCondition( const char *name, Script &script ) +{ + Conditional *condition; + Condition *cond; + int index; + + str token; + + condition = NULL; + cond = statemap.getCondition( name ); + if ( !cond ) + { + return 0; + } + + condition = new Conditional( *cond ); + + // Get the paramaters + // Legacy Format - Parameters use "" + while ( script.TokenAvailable( false ) && script.AtString( false ) ) + { + token = script.GetToken( false ); + condition->addParm( token ); + } + + // New Format - Parameters use ( x , y , z ) format + while ( script.TokenAvailable( false ) && script.AtOpenParen( false ) ) + { + // Need to burn off first "(" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "(" ) ) + { + script.UnGetToken(); + } + + while ( !script.AtCloseParen( false ) ) + { + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "," ) ) + { + condition->addParm( token ); + } + } + + // Need to burn off last ")" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), ")" ) ) + { + script.UnGetToken(); + } + } + + // only add a new conditional if a similar one doesn't exist + index = statemap.findConditional( condition ); + + if ( index ) + { + // delete the one we just made + delete condition; + } + else + { + index = statemap.addConditional( condition ); + } + + condition_indexes.AddUniqueObject( index ); + + return index; +} + +void State::CheckStates( void ) +{ + const char *value; + int i; + + if ( !statemap.FindState( nextState.c_str() ) ) + { + gi.Error( ERR_DROP, "Unknown next state '%s' referenced in state '%s'.\n", nextState.c_str(), getName() ); + } + + for( i = 1; i <= states.NumObjects(); i++ ) + { + value = states.ObjectAt( i ).getValue(); + if ( !statemap.FindState( value ) ) + { + gi.WDPrintf( + "============\n" + "ERROR in %s:\n" + " Possibly Unknown state '%s' referenced in state '%s'. -- Check your includes \n" + "============", + statemap.Filename(), value, getName() ); + } + } +} + +void State::GetLegAnims( Container *c ) +{ + int i,j; + qboolean addobj = true; + + for ( i=1; i<=legAnims.NumObjects(); i++ ) + { + const char *value = legAnims.ObjectAt( i ).getValue(); + addobj = true; + + // Check to see if it's already in there + for ( j=1; j<=c->NumObjects(); j++ ) + { + if ( !stricmp( c->ObjectAt( j ), value ) ) + { + addobj = false; + break; + } + } + if ( addobj ) + c->AddObject( value ); + } +} + +void State::GetTorsoAnims( Container *c ) +{ + int i,j; + qboolean addobj = true; + + for ( i=1; i<=torsoAnims.NumObjects(); i++ ) + { + const char *value = torsoAnims.ObjectAt( i ).getValue(); + addobj = true; + + // Check to see if it's already in there + for ( j=1; j<=c->NumObjects(); j++ ) + { + if ( !stricmp( c->ObjectAt( j ), value ) ) + { + addobj = false; + break; + } + } + if ( addobj ) + c->AddObject( value ); + } +} + +qboolean State::IgnoreGlobalStates() +{ + return ignoreGlobalStates; +} + +State::State( const char *statename, Script &script, StateMap &map ) : + statemap( map ) + +{ + str cmd; + + name = statename; + nextState = statename; + movetype = DEFAULT_MOVETYPE; + cameratype = DEFAULT_CAMERA; + //behaviorName = "idle"; + behaviorName = ""; + headBehaviorName = ""; + eyeBehaviorName = ""; + torsoBehaviorName = ""; + + minTime = -1.0f; + maxTime = -1.0f; + + ignoreGlobalStates = false; + + if ( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) + { + gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + while( script.TokenAvailable( true ) ) + { + cmd = script.GetToken( true ); + if ( !cmd.icmp( "nextstate" ) ) + { + readNextState( script ); + } + else if ( !cmd.icmp( "movetype" ) ) + { + readMoveType( script ); + } + else if ( !cmd.icmp( "camera" ) ) + { + readCamera( script ); + } + else if ( !cmd.icmp( "legs" ) ) + { + readLegs( script ); + } + else if ( !cmd.icmp( "torso" ) ) + { + readTorso( script ); + } + else if ( !cmd.icmp( "behavior" ) ) + { + readBehavior( script ); + } + else if ( !cmd.icmp( "headbehavior" ) ) + { + readHeadBehavior( script ); + } + else if ( !cmd.icmp( "eyebehavior" ) ) + { + readEyeBehavior( script ); + } + else if ( !cmd.icmp( "torsobehavior" ) ) + { + readTorsoBehavior( script ); + } + else if ( !cmd.icmp( "time" ) ) + { + readTime( script ); + } + else if ( !cmd.icmp( "states" ) ) + { + readStates( script ); + } + else if ( !cmd.icmp( "ignoreglobalstate" ) ) + { + ignoreGlobalStates = true; + } + else if ( !cmd.icmp( "entrycommands" ) ) + { + readCommands( script, entryCommands ); + } + else if ( !cmd.icmp( "exitcommands" ) ) + { + readCommands( script, exitCommands ); + } + else if ( !cmd.icmp( "}" ) ) + { + break; + } + else + { + gi.Error( ERR_DROP, "%s: Unknown command '%s' on line %d.\n", script.Filename(), cmd.c_str(), script.GetLineNumber() ); + } + } +} + +StateMap::StateMap( void ) +{ + gi.Error( ERR_DROP, "StateMap created with wrong constructor\n" ); +} + +StateMap::StateMap( const char *file_name, Condition *conditions, Container *conditionals ) +{ + + assert( file_name ); + + filename = file_name; + + this->current_conditions = conditions; + + this->current_conditionals = conditionals; + + Script *script; + script = new Script(file_name); + if ( script ) + { + ReadStates(script); + delete script; + script = 0; + } + + // Have all the states check themselves to see if they reference any non-existant states. + int i; + for( i = 1; i <= stateList.NumObjects(); i++ ) + { + stateList.ObjectAt( i )->CheckStates(); + } +} + +StateMap::~StateMap() +{ + int i,num; + + num = stateList.NumObjects(); + for( i=num; i>0; i-- ) + { + delete stateList.ObjectAt( i ); + } + stateList.FreeObjectList(); + + num = globalStateList.NumObjects(); + for( i=num; i>0; i-- ) + { + delete globalStateList.ObjectAt( i ); + } + + globalStateList.FreeObjectList(); +} + +Condition *StateMap::getCondition( const char *name ) +{ + Condition *c; + + if ( current_conditions ) + { + for( c = current_conditions; c->name; c++ ) + { + if ( !strcmp( c->name, name ) ) + { + return c; + } + } + } + + return NULL; +} + +void StateMap::ReadStates( Script *script ) +{ + str cmd; + str statename; + State *state; + const char *includedFile; + + while( script->TokenAvailable( true ) ) + { + cmd = script->GetToken( true ); + //if ( cmd == "" ) + // break; + if ( !cmd.icmp( "state" ) ) + { + statename = script->GetToken( false ); + state = FindState( statename.c_str() ); + if ( state ) + { + stateList.RemoveObject( state ); + delete state; + state = NULL; + } + + // parse the state even if we already have it defined + state = new State( statename.c_str(), *script, *this ); + stateList.AddObject( state ); + } + else if ( !cmd.icmp( "globalstate" ) ) + { + statename = script->GetToken( false ); + state = FindGlobalState( statename.c_str() ); + if ( state ) + { + globalStateList.RemoveObject( state ); + delete state; + state = NULL; + } + + state = new State( statename.c_str(), *script, *this ); + globalStateList.AddObject( state ); + } + else if ( !cmd.icmp( "$include" ) ) + { + includedFile = script->GetToken( false ); + Script *newScript = new Script(includedFile); + if ( !newScript ) + break; + + ReadStates(newScript); + + // Copy over defines to the parent script's list + Container *macrolist = newScript->GetMacroList(); + Container *mainlist = script->GetMacroList(); + macro *theMacro = 0; + for( int i = 1; i <= macrolist->NumObjects(); i++) + { + theMacro = macrolist->ObjectAt( i ); + macro *addMacro = new macro; + addMacro->macroName = theMacro->macroName; + addMacro->macroText = theMacro->macroText; + mainlist->AddObject( addMacro ); + } + + // delete our temporary script object + delete newScript; + newScript = 0; + } + else + { + gi.Error( ERR_DROP, "%s: Unknown command '%s' on line %d.\n", script->Filename(), cmd.c_str(), script->GetLineNumber() ); + } + } +} + +int StateMap::findConditional( Conditional *condition ) +{ + int i; + int j; + Conditional *c; + bool found; + + + // Check for the one special case where we don't want to merge the conditionals + + if ( strcmp( condition->getName(), "CHANCE" ) == 0 ) + return 0; + + for( i = 1; i <= current_conditionals->NumObjects(); i++ ) + { + c = current_conditionals->ObjectAt( i ); + if ( ( c->getName() == condition->getName() ) && ( c->numParms() == condition->numParms() ) ) + { + found = true; + for( j = 1; j <= c->numParms(); j++ ) + { + if ( strcmp( c->getParm( j ), condition->getParm( j ) ) ) + { + found = false; + break; + } + } + + if ( found ) + { + return i; + } + } + } + + return 0; +} + +int StateMap::addConditional( Conditional *condition ) +{ + int index; + index = current_conditionals->AddObject( condition ); + + return index; +} + +Conditional *StateMap::getConditional( const char *name ) +{ + int i; + Conditional *c; + Condition *condition; + + for( i = 1; i <= current_conditionals->NumObjects(); i++ ) + { + c = current_conditionals->ObjectAt( i ); + if ( !strcmp( c->getName(), name ) ) + { + return c; + } + } + + condition = getCondition( name ); + + c = new Conditional( *condition ); + current_conditionals->AddObject( c ); + + return c; +} + +State *StateMap::FindState( const char *name ) +{ + int i; + + for( i = 1; i <= stateList.NumObjects(); i++ ) + { + if ( !strcmp( stateList.ObjectAt( i )->getName(), name ) ) + { + return stateList.ObjectAt( i ); + } + } + + return NULL; +} + +State *StateMap::FindGlobalState( const char *name ) +{ + for ( int i = 1; i <= globalStateList.NumObjects(); i++ ) + { + if ( !strcmp( globalStateList.ObjectAt( i )->getName(), name ) ) + { + return globalStateList.ObjectAt( i ); + } + } + + return NULL; +} + +// Caching statemaps + +struct cached_statemap_t +{ + StateMap *statemap; + Container *conditionals; +}; + +struct cached_fuzzyengine_t +{ + FuzzyEngine *fengine; + Container *conditionals; +}; + +Container cached_statemaps; +Container cached_fuzzyengines; + +StateMap *GetStatemap( const str &filename, Condition *conditions, Container *conditionals, + qboolean reload, qboolean cache_only ) +{ + int i; + int j; + cached_statemap_t *cache = NULL; + cached_statemap_t new_cache; + qboolean found = false; + Conditional *new_conditional; + Conditional *old_conditional; + Condition *cond; + + for( i = 1 ; i <= cached_statemaps.NumObjects() ; i++ ) + { + cache = &cached_statemaps.ObjectAt( i ); + + if ( strcmp( cache->statemap->Filename(), filename.c_str() ) == 0 ) + { + found = true; + break; + } + } + + if ( found && reload ) + { + delete cache->statemap; + delete cache->conditionals; + + cache->conditionals = new Container; + cache->statemap = new StateMap( filename, conditions, cache->conditionals ); + } + + if ( !found ) + { + new_cache.conditionals = new Container; + new_cache.statemap = new StateMap( filename, conditions, new_cache.conditionals ); + + cached_statemaps.AddObject( new_cache ); + + cache = &new_cache; + } + + // Copy conditionals over + + if ( !cache_only ) + { + for( i = 1 ; i <= cache->conditionals->NumObjects() ; i++ ) + { + old_conditional = cache->conditionals->ObjectAt( i ); + + cond = cache->statemap->getCondition( old_conditional->condition.name ); + + new_conditional = new Conditional( *cond ); + + for( j = 1 ; j <= old_conditional->parmList.NumObjects() ; j++ ) + { + new_conditional->parmList.AddObject( old_conditional->parmList.ObjectAt( j ) ); + } + + conditionals->AddObject( new_conditional ); + } + } + + return cache->statemap; +} + + +FuzzyEngine *GetFuzzyEngine( const str &filename, Condition *conditions, Container *conditionals, + qboolean reload, qboolean cache_only ) +{ + int i; + int j; + cached_fuzzyengine_t *cache = NULL; + cached_fuzzyengine_t new_cache; + qboolean found = false; + Conditional *new_conditional; + Conditional *old_conditional; + Condition *cond; + + for( i = 1 ; i <= cached_fuzzyengines.NumObjects() ; i++ ) + { + cache = &cached_fuzzyengines.ObjectAt( i ); + + if ( strcmp( cache->fengine->Filename(), filename.c_str() ) == 0 ) + { + found = true; + break; + } + } + + if ( found && reload ) + { + delete cache->fengine; + delete cache->conditionals; + + cache->conditionals = new Container; + cache->fengine = new FuzzyEngine( filename, conditions, cache->conditionals ); + } + + if ( !found ) + { + new_cache.conditionals = new Container; + new_cache.fengine = new FuzzyEngine( filename, conditions, new_cache.conditionals ); + + cached_fuzzyengines.AddObject( new_cache ); + + cache = &new_cache; + } + + // Copy conditionals over + + if ( !cache_only ) + { + for( i = 1 ; i <= cache->conditionals->NumObjects() ; i++ ) + { + old_conditional = cache->conditionals->ObjectAt( i ); + + cond = cache->fengine->getCondition( old_conditional->condition.name ); + + new_conditional = new Conditional( *cond ); + + for( j = 1 ; j <= old_conditional->parmList.NumObjects() ; j++ ) + { + new_conditional->parmList.AddObject( old_conditional->parmList.ObjectAt( j ) ); + } + + conditionals->AddObject( new_conditional ); + } + } + + return cache->fengine; +} + +void CacheStatemap( const str &filename, Condition *conditions ) +{ + GetStatemap( filename, conditions, NULL, false, true ); +} + +void CacheFuzzyEngine( const str &filename, Condition *conditions ) +{ + GetFuzzyEngine( filename , conditions, NULL, false, true ); +} + +void StateMap::GetAllAnims( Container *c ) +{ + int i; + + for( i = 1; i <= stateList.NumObjects(); i++ ) + { + stateList.ObjectAt( i )->GetLegAnims( c ); + stateList.ObjectAt( i )->GetTorsoAnims( c ); + } + + for( i = 1; i <= globalStateList.NumObjects(); i++ ) + { + globalStateList.ObjectAt( i )->GetLegAnims( c ); + globalStateList.ObjectAt( i )->GetTorsoAnims( c ); + } +} + +void ClearCachedStatemaps( void ) +{ + int i,j,num2; + cached_statemap_t *cache; + + num2 = cached_statemaps.NumObjects(); + + for( i=num2 ; i>0; i-- ) + { + cache = &cached_statemaps.ObjectAt( i ); + + delete cache->statemap; + + int num = cache->conditionals->NumObjects(); + for ( j=num; j>0; j-- ) + { + Conditional *cond = cache->conditionals->ObjectAt( j ); + delete cond; + } + delete cache->conditionals; + } + + cached_statemaps.FreeObjectList(); +} + +void ClearCachedFuzzyEngines( void ) +{ + int i,j,num2; + cached_fuzzyengine_t *cache; + + num2 = cached_fuzzyengines.NumObjects(); + + for( i=num2 ; i>0; i-- ) + { + cache = &cached_fuzzyengines.ObjectAt( i ); + + delete cache->fengine; + + int num = cache->conditionals->NumObjects(); + for ( j=num; j>0; j-- ) + { + Conditional *cond = cache->conditionals->ObjectAt( j ); + delete cond; + } + delete cache->conditionals; + } + + cached_fuzzyengines.FreeObjectList(); +} + +//====================================== +// Fuzzy Engine Implementation +//====================================== +FuzzyEngine::FuzzyEngine( const char *file_name, Condition *conditions, Container *conditionals ) +{ + assert( file_name ); + _filename = file_name; + + this->_current_conditions = conditions; + this->_current_conditionals = conditionals; + + ReadFuzzyVars(_filename); +} + +void FuzzyEngine::ReadFuzzyVars( const char *filename ) +{ + str cmd; + str fuzzyVarName; + Script script; + FuzzyVar *fuzzyVar; + + script.LoadFile( filename ); + + while( script.TokenAvailable( true ) ) + { + cmd = script.GetToken( true ); + if ( !cmd.icmp( "fuzzyvar" ) ) + { + fuzzyVarName = script.GetToken( false ); + fuzzyVar = FindFuzzyVar( fuzzyVarName.c_str() ); + if ( fuzzyVar ) + { + _varList.RemoveObject( fuzzyVar ); + gi.Error( ERR_DROP, "%s: Duplicate definition of FuzzyVar '%s' on line %d.\n", filename, fuzzyVarName.c_str(), script.GetLineNumber() ); + } + // parse the state even if we already have it defined + fuzzyVar = new FuzzyVar( fuzzyVarName.c_str(), script, *this ); + _varList.AddObject( fuzzyVar ); + } + else + { + gi.Error( ERR_DROP, "%s: Unknown command '%s' on line %d.\n", script.Filename(), cmd.c_str(), script.GetLineNumber() ); + } + } +} + +FuzzyVar *FuzzyEngine::FindFuzzyVar( const char *name ) +{ + int i; + + for( i = 1; i <= _varList.NumObjects(); i++ ) + { + if ( !strcmp( _varList.ObjectAt( i )->getName(), name ) ) + { + return _varList.ObjectAt( i ); + } + } + + return NULL; +} + +int FuzzyEngine::addConditional( Conditional *condition ) +{ + int index; + index = _current_conditionals->AddObject( condition ); + + return index; +} + +int FuzzyEngine::findConditional( Conditional *condition ) +{ + int i; + int j; + Conditional *c; + bool found; + + + // Check for the one special case where we don't want to merge the conditionals + + if ( strcmp( condition->getName(), "CHANCE" ) == 0 ) + return 0; + + for( i = 1; i <= _current_conditionals->NumObjects(); i++ ) + { + c = _current_conditionals->ObjectAt( i ); + if ( ( c->getName() == condition->getName() ) && ( c->numParms() == condition->numParms() ) ) + { + found = true; + for( j = 1; j <= c->numParms(); j++ ) + { + if ( strcmp( c->getParm( j ), condition->getParm( j ) ) ) + { + found = false; + break; + } + } + + if ( found ) + { + return i; + } + } + } + + return 0; +} + +const char *FuzzyEngine::Filename() +{ + return _filename.c_str(); +} + +Condition *FuzzyEngine::getCondition( const char *name ) +{ + Condition *c; + + if ( _current_conditions ) + { + for( c = _current_conditions; c->name; c++ ) + { + if ( !strcmp( c->name, name ) ) + { + return c; + } + } + } + + return NULL; +} + + +//====================================== +// Fuzzy Var Implementation +//====================================== +FuzzyVar::FuzzyVar( const char *varname, Script &script, FuzzyEngine &engine ) : + _fuzzyEngine( engine ) + +{ + str cmd; + + _name = varname; + + if ( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) + { + gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + while( script.TokenAvailable( true ) ) + { + cmd = script.GetToken( true ); + if ( !cmd.icmp( "evaluations" ) ) + { + readEvaluations( script ); + } + else if ( !cmd.icmp( "}" ) ) + { + break; + } + else + { + gi.Error( ERR_DROP, "%s: Unknown command '%s' on line %d.\n", script.Filename(), cmd.c_str(), script.GetLineNumber() ); + } + } +} + +void FuzzyVar::readEvaluations( Script &script ) +{ + str token; + + if ( !script.TokenAvailable( true ) || Q_stricmp( script.GetToken( true ), "{" ) ) + { + gi.Error( ERR_DROP, "%s: Expecting '{' on line %d.\n", script.Filename(), script.GetLineNumber() ); + } + + while( script.TokenAvailable( true ) ) + { + token = script.GetToken( true ); + if ( !Q_stricmp( token.c_str(), "}" ) ) + { + break; + } + + script.UnGetToken(); + _evaluations.AddObject( Expression( script, *this ) ); + } +} + +float FuzzyVar::Evaluate( Entity &ent, Container *sent_conditionals ) +{ + int i; + Expression *exp; + float points; + int index; + + points = 0.0f; + + for( i = 1; i <= _condition_indexes.NumObjects(); i++ ) + { + index = _condition_indexes.ObjectAt( i ); + sent_conditionals->ObjectAt( index )->clearCheck(); + } + + for( i = 1; i <= _evaluations.NumObjects(); i++ ) + { + exp = &_evaluations.ObjectAt( i ); + if ( exp->getResult( *this, ent, sent_conditionals ) ) + { + points += exp->getPoints(); + } + } + + return points; +} + +int FuzzyVar::addCondition( const char *name, Script &script ) +{ + Conditional *condition; + Condition *cond; + int index; + + str token; + + condition = NULL; + cond = _fuzzyEngine.getCondition( name ); + + if ( !cond ) + return 0; + + + condition = new Conditional( *cond ); + + // Get the paramaters + // Legacy Format - Parameters use "" + while ( script.TokenAvailable( false ) && script.AtString( false ) ) + { + token = script.GetToken( false ); + condition->addParm( token ); + } + + // New Format - Parameters use ( x , y , z ) format + while ( script.TokenAvailable( false ) && script.AtOpenParen( false ) ) + { + // Need to burn off first "(" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "(" ) ) + { + script.UnGetToken(); + } + + while ( !script.AtCloseParen( false ) ) + { + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), "," ) ) + { + condition->addParm( token ); + } + } + + // Need to burn off last ")" - but check it to be sure + token = script.GetToken( false ); + if ( Q_stricmp( token.c_str(), ")" ) ) + { + script.UnGetToken(); + } + } + + // only add a new conditional if a similar one doesn't exist + index = _fuzzyEngine.findConditional( condition ); + + if ( index ) + { + // delete the one we just made + delete condition; + } + else + { + index = _fuzzyEngine.addConditional( condition ); + } + + _condition_indexes.AddUniqueObject( index ); + + return index; +} + +const char *FuzzyVar::getName() +{ + return _name.c_str(); +} diff --git a/dlls/game/characterstate.h b/dlls/game/characterstate.h new file mode 100644 index 0000000..0342fa7 --- /dev/null +++ b/dlls/game/characterstate.h @@ -0,0 +1,600 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/characterstate.h $ +// $Revision:: 15 $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +//=========================== +// Forward Declarations +//=========================== + +#ifndef __CHARACTERSTATE_H__ +#define __CHARACTERSTATE_H__ + +#include "g_local.h" +#include "script.h" + +enum testcondition_t + { + TC_ISTRUE, // no prefix + TC_ISFALSE, // ! + TC_EDGETRUE, // + + TC_EDGEFALSE // - + }; + +enum movecontrol_t + { + MOVECONTROL_USER, // Quake style + MOVECONTROL_LEGS, // Quake style, legs state system active + MOVECONTROL_ANIM, // move based on animation, with full collision testing + MOVECONTROL_ABSOLUTE, // move based on animation, with full collision testing but no turning + MOVECONTROL_HANGING, // move based on animation, with full collision testing, hanging + MOVECONTROL_WALLHUG, // move based on animation, with full collision testing, hanging + MOVECONTROL_MONKEYBARS, // move based on animation, with full collision testing, monkey bars + MOVECONTROL_PIPECRAWL, // move based on animation, with full collision testing, crawling on pipe + MOVECONTROL_PIPEHANG, // move based on animation, with full collision testing, hanging from pipe + MOVECONTROL_STEPUP, + MOVECONTROL_ROPE_GRAB, + MOVECONTROL_ROPE_RELEASE, + MOVECONTROL_ROPE_MOVE, + MOVECONTROL_PICKUPENEMY, + MOVECONTROL_PUSH, + MOVECONTROL_CLIMBWALL, + MOVECONTROL_USEANIM, + MOVECONTROL_CROUCH, + MOVECONTROL_LOOPUSEANIM, + MOVECONTROL_USEOBJECT, + MOVECONTROL_COOLOBJECT, + MOVECONTROL_FAKEPLAYER + }; + +enum cameratype_t + { + CAMERA_TOPDOWN, + CAMERA_BEHIND, + CAMERA_FRONT, + CAMERA_SIDE, + CAMERA_BEHIND_FIXED, + CAMERA_SIDE_LEFT, + CAMERA_SIDE_RIGHT, + CAMERA_BEHIND_NOPITCH + }; + +#define DEFAULT_MOVETYPE MOVECONTROL_LEGS +#define DEFAULT_CAMERA CAMERA_BEHIND + +class Conditional; + +template< class Type > +struct Condition + { + const char *name; + qboolean ( Type::*func )( Conditional &condition ); + }; + +class Conditional : public Class + { + private : + qboolean result; + qboolean previous_result; + bool checked; + + public : + Condition condition; + Container parmList; + + bool getResult( testcondition_t test, Entity &ent ); + const char *getName( void ); + + Conditional( void ); + Conditional( const Condition &condition ); + + const char *getParm( int number ); + void addParm( const str &parm ); + int numParms( void ); + void clearCheck( void ); + }; + +inline void Conditional::addParm + ( + const str &parm + ) + + { + parmList.AddObject( parm ); + } + +inline const char *Conditional::getParm + ( + int number + ) + + { + if ( ( number < 1 ) || ( number > parmList.NumObjects() ) ) + { + gi.Error( ERR_DROP, "Parm #%d out of range on %s condition\n", number, condition.name ); + } + return parmList.ObjectAt( number ).c_str(); + } + +inline int Conditional::numParms + ( + void + ) + + { + return parmList.NumObjects(); + } + +inline void Conditional::clearCheck + ( + void + ) + + { + checked = false; + } + +inline const char *Conditional::getName + ( + void + ) + + { + return condition.name; + } + +inline bool Conditional::getResult + ( + testcondition_t test, + Entity &ent + ) + + { + + if ( condition.func && !checked ) + { + checked = true; + previous_result = result; + + result = ( ent.*condition.func )( (Conditional&)*this ); + } + + switch( test ) + { + case TC_ISFALSE : + return !result; + break; + + case TC_EDGETRUE : + return result && !previous_result; + break; + + case TC_EDGEFALSE : + return !result && previous_result; + break; + + case TC_ISTRUE : + default : + return result != false; + } + } + +class State; +class StateMap; +class FuzzyVar; + +class Expression : public Class + { + private : + struct condition_t + { + testcondition_t test; + int condition_index; + }; + + str value; + Container conditions; + + // For Fuzzy Vars + float points; + + public : + Expression(); + Expression( const Expression &exp ); + Expression( Script &script, State &state ); + Expression( Script &script, FuzzyVar &fuzzyVar ); + + void operator=( const Expression &exp ); + + bool getResult( const State &state, Entity &ent, const Container *sent_conditionals ); + bool getResult( const FuzzyVar &fVar, Entity &ent, const Container *sent_conditionals ); + const char *getValue( void ); + + // For Fuzzy Vars + float getPoints(); + }; + +inline void Expression::operator= + ( + const Expression &exp + ) + + { + int i; + Expression *temp_exp = (Expression *)&exp; // This is here to get around some const issues + + value = exp.value; + points = exp.points; + + conditions.FreeObjectList(); + for( i = 1; i <= temp_exp->conditions.NumObjects(); i++ ) + { + conditions.AddObject( temp_exp->conditions.ObjectAt( i ) ); + } + } + +inline const char *Expression::getValue + ( + void + ) + + { + return value.c_str(); + } + +inline float Expression::getPoints() + { + return points; + } + +class State : public Class + { + private : + Container condition_indexes; + + StateMap &statemap; + + str name; + + str nextState; + movecontrol_t movetype; + cameratype_t cameratype; + + str behaviorName; + Container behaviorParmList; + + str headBehaviorName; + Container headBehaviorParmList; + + str eyeBehaviorName; + Container eyeBehaviorParmList; + + str torsoBehaviorName; + Container torsoBehaviorParmList; + + float minTime; + float maxTime; + + Container legAnims; + Container torsoAnims; + + Container states; + Container entryCommands; + Container exitCommands; + + qboolean ignoreGlobalStates; + + void readNextState( Script &script ); + void readMoveType( Script &script ); + void readCamera( Script &script ); + void readLegs( Script &script ); + void readTorso( Script &script ); + void readBehavior( Script &script ); + void readHeadBehavior( Script &script ); + void readEyeBehavior( Script &script ); + void readTorsoBehavior( Script &script ); + void readTime( Script &script ); + void readStates( Script &script ); + void readCommands( Script &script, Container &container ); + + void ParseAndProcessCommand( const str &command, Entity *target ); + + public : + State( const char *name, Script &script, StateMap &map ); + + State *Evaluate( Entity &ent, Container *ent_conditionals ); + int addCondition( const char *name, Script &script ); + void CheckStates( void ); + + const char *getName( void ); + + const char *getLegAnim( Entity &ent, Container *sent_conditionals ); + const char *getTorsoAnim( Entity &ent, Container *sent_conditionals ); + const char *getBehaviorName( void ); + const char *getHeadBehaviorName( void ); + const char *getEyeBehaviorName( void ); + const char *getTorsoBehaviorName( void ); + State *getNextState( void ); + movecontrol_t getMoveType( void ); + cameratype_t getCameraType( void ); + qboolean setCameraType( const str &ctype ); + + const char *getBehaviorParm( int number=1 ); + const char *getHeadBehaviorParm( int number=1 ); + const char *getEyeBehaviorParm( int number=1 ); + const char *getTorsoBehaviorParm( int number=1 ); + void addBehaviorParm( const str &parm ); + void addHeadBehaviorParm( const str &parm ); + void addEyeBehaviorParm( const str &parm ); + void addTorsoBehaviorParm( const str &parm ); + int numBehaviorParms( void ); + int numHeadBehaviorParms( void ); + int numEyeBehaviorParms( void ); + int numTorsoBehaviorParms( void ); + + float getMinTime( void ); + float getMaxTime( void ); + void ProcessEntryCommands( Entity *target ); + void ProcessExitCommands( Entity *target ); + void GetLegAnims( Container *c ); + void GetTorsoAnims( Container *c ); + qboolean IgnoreGlobalStates(); + }; + +inline void State::addBehaviorParm + ( + const str &parm + ) + + { + behaviorParmList.AddObject( parm ); + } + +inline void State::addHeadBehaviorParm + ( + const str &parm + ) + + { + headBehaviorParmList.AddObject( parm ); + } + +inline void State::addEyeBehaviorParm + ( + const str &parm + ) + + { + eyeBehaviorParmList.AddObject( parm ); + } + +inline void State::addTorsoBehaviorParm + ( + const str &parm + ) + + { + torsoBehaviorParmList.AddObject( parm ); + } + +inline const char *State::getBehaviorParm + ( + int number + ) + + { + return behaviorParmList.ObjectAt( number ).c_str(); + } + +inline const char *State::getHeadBehaviorParm + ( + int number + ) + + { + return headBehaviorParmList.ObjectAt( number ).c_str(); + } + +inline const char *State::getEyeBehaviorParm + ( + int number + ) + + { + return eyeBehaviorParmList.ObjectAt( number ).c_str(); + } + +inline const char *State::getTorsoBehaviorParm + ( + int number + ) + + { + return torsoBehaviorParmList.ObjectAt( number ).c_str(); + } + +inline int State::numBehaviorParms + ( + void + ) + + { + return behaviorParmList.NumObjects(); + } + +inline int State::numHeadBehaviorParms + ( + void + ) + + { + return headBehaviorParmList.NumObjects(); + } + +inline int State::numEyeBehaviorParms + ( + void + ) + + { + return eyeBehaviorParmList.NumObjects(); + } + +inline int State::numTorsoBehaviorParms + ( + void + ) + + { + return torsoBehaviorParmList.NumObjects(); + } + +class StateMap : public Class + { + private : + Container stateList; + Container globalStateList; + Condition *current_conditions; + Container *current_conditionals; + str filename; + + public : + StateMap(); + StateMap( const char *filename, Condition *conditions, Container *conditionals ); + ~StateMap(); + + Condition *getCondition( const char *name ); + int findConditional( Conditional *condition ); + int addConditional( Conditional *condition ); + Conditional *getConditional( const char *name ); + void GetAllAnims( Container *c ); + State *FindState( const char *name ); + State *FindGlobalState( const char *name ); + const char *Filename(); + void ReadStates( Script *script ); + State *GotoGlobalState( State *CurrentState, State *newState ); + }; + +inline const char *StateMap::Filename + ( + void + ) + + { + return filename.c_str(); + } + +inline const char *State::getName + ( + void + ) + + { + return name.c_str(); + } + +inline State *State::getNextState + ( + void + ) + + { + return statemap.FindState( nextState.c_str() ); + } + +inline movecontrol_t State::getMoveType + ( + void + ) + + { + return movetype; + } + +inline cameratype_t State::getCameraType + ( + void + ) + + { + return cameratype; + } + + + + +//================================================ +// Class Name: Fuzzy Engine +// +// Description: +// +//================================================ +class FuzzyEngine : public Class + { + public : + FuzzyEngine( const char *filename, Condition *conditions, Container *conditionals ); + + Condition *getCondition( const char *name ); + int findConditional( Conditional *condition ); + int addConditional( Conditional *condition ); + Conditional *getConditional( const char *name ); + FuzzyVar *FindFuzzyVar( const char *name ); + const char *Filename(); + void ReadFuzzyVars( const char *filename ); + + + private : + Container _varList; + Condition *_current_conditions; + Container *_current_conditionals; + str _filename; + + }; + +//================================================ +// Class Name: Fuzzy Engine +// +// Description: +// +//================================================ +class FuzzyVar : public Class + { + public: + FuzzyVar( const char *name, Script &script, FuzzyEngine &f_engine ); + + float Evaluate( Entity &ent, Container *ent_conditionals ); + int addCondition( const char *name, Script &script ); + void readEvaluations( Script &script ); + + const char *getName( void ); + + private : + Container _condition_indexes; + Container _evaluations; + + FuzzyEngine &_fuzzyEngine; + + str _name; + Container _points; + }; + +void ClearCachedStatemaps( void ); +StateMap *GetStatemap( const str &filename, Condition *conditions, Container *conditionals, qboolean reload, qboolean cache_only = false ); +void CacheStatemap( const str &filename, Condition *conditions ); + +void ClearCachedFuzzyEngines( void ); +FuzzyEngine *GetFuzzyEngine( const str &filename, Condition *conditions, Container *conditionals, qboolean reload, qboolean cache_only = false ); +void CacheFuzzyEngine( const str &filename, Condition *conditions ); + +#endif /* !__CHARACTERSTATE_H__ */ diff --git a/dlls/game/chars.h b/dlls/game/chars.h new file mode 100644 index 0000000..98d046e --- /dev/null +++ b/dlls/game/chars.h @@ -0,0 +1,124 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +//=========================================================================== +// +// Name: chars.h +// Function: bot characteristics +// Programmer: Mr Elusive (MrElusive@idsoftware.com) +// Last update: 1999-09-08 +// Tab Size: 4 (real tabs) +//=========================================================================== + + +//======================================================== +//======================================================== +//name +#define CHARACTERISTIC_NAME 0 //string +//gender of the bot +#define CHARACTERISTIC_GENDER 1 //string ("male", "female", "it") +//attack skill +// > 0.0 && < 0.2 = don't move +// > 0.3 && < 1.0 = aim at enemy during retreat +// > 0.0 && < 0.4 = only move forward/backward +// >= 0.4 && < 1.0 = circle strafing +// > 0.7 && < 1.0 = random strafe direction change +#define CHARACTERISTIC_ATTACK_SKILL 2 //float [0, 1] +//weapon weight file +#define CHARACTERISTIC_WEAPONWEIGHTS 3 //string +//view angle difference to angle change factor +#define CHARACTERISTIC_VIEW_FACTOR 4 //float <0, 1] +//maximum view angle change +#define CHARACTERISTIC_VIEW_MAXCHANGE 5 //float [1, 360] +//reaction time in seconds +#define CHARACTERISTIC_REACTIONTIME 6 //float [0, 5] +//accuracy when aiming +#define CHARACTERISTIC_AIM_ACCURACY 7 //float [0, 1] +//weapon specific aim accuracy +#define CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN 8 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_SHOTGUN 9 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER 10 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER 11 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_LIGHTNING 12 +#define CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN 13 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_RAILGUN 14 +#define CHARACTERISTIC_AIM_ACCURACY_BFG10K 15 //float [0, 1] +//skill when aiming +// > 0.0 && < 0.9 = aim is affected by enemy movement +// > 0.4 && <= 0.8 = enemy linear leading +// > 0.8 && <= 1.0 = enemy exact movement leading +// > 0.5 && <= 1.0 = prediction shots when enemy is not visible +// > 0.6 && <= 1.0 = splash damage by shooting nearby geometry +#define CHARACTERISTIC_AIM_SKILL 16 //float [0, 1] +//weapon specific aim skill +#define CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER 17 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER 18 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_PLASMAGUN 19 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_BFG10K 20 //float [0, 1] +//======================================================== +//chat +//======================================================== +//file with chats +#define CHARACTERISTIC_CHAT_FILE 21 //string +//name of the chat character +#define CHARACTERISTIC_CHAT_NAME 22 //string +//characters per minute type speed +#define CHARACTERISTIC_CHAT_CPM 23 //integer [1, 4000] +//tendency to insult/praise +#define CHARACTERISTIC_CHAT_INSULT 24 //float [0, 1] +//tendency to chat misc +#define CHARACTERISTIC_CHAT_MISC 25 //float [0, 1] +//tendency to chat at start or end of level +#define CHARACTERISTIC_CHAT_STARTENDLEVEL 26 //float [0, 1] +//tendency to chat entering or exiting the game +#define CHARACTERISTIC_CHAT_ENTEREXITGAME 27 //float [0, 1] +//tendency to chat when killed someone +#define CHARACTERISTIC_CHAT_KILL 28 //float [0, 1] +//tendency to chat when died +#define CHARACTERISTIC_CHAT_DEATH 29 //float [0, 1] +//tendency to chat when enemy suicides +#define CHARACTERISTIC_CHAT_ENEMYSUICIDE 30 //float [0, 1] +//tendency to chat when hit while talking +#define CHARACTERISTIC_CHAT_HITTALKING 31 //float [0, 1] +//tendency to chat when bot was hit but didn't dye +#define CHARACTERISTIC_CHAT_HITNODEATH 32 //float [0, 1] +//tendency to chat when bot hit the enemy but enemy didn't dye +#define CHARACTERISTIC_CHAT_HITNOKILL 33 //float [0, 1] +//tendency to randomly chat +#define CHARACTERISTIC_CHAT_RANDOM 34 //float [0, 1] +//tendency to reply +#define CHARACTERISTIC_CHAT_REPLY 35 //float [0, 1] +//======================================================== +//movement +//======================================================== +//tendency to crouch +#define CHARACTERISTIC_CROUCHER 36 //float [0, 1] +//tendency to jump +#define CHARACTERISTIC_JUMPER 37 //float [0, 1] +//tendency to walk +#define CHARACTERISTIC_WALKER 48 //float [0, 1] +//tendency to jump using a weapon +#define CHARACTERISTIC_WEAPONJUMPING 38 //float [0, 1] +//tendency to use the grapple hook when available +#define CHARACTERISTIC_GRAPPLE_USER 39 //float [0, 1] //use this!! +//======================================================== +//goal +//======================================================== +//item weight file +#define CHARACTERISTIC_ITEMWEIGHTS 40 //string +//the aggression of the bot +#define CHARACTERISTIC_AGGRESSION 41 //float [0, 1] +//the self preservation of the bot (rockets near walls etc.) +#define CHARACTERISTIC_SELFPRESERVATION 42 //float [0, 1] +//how likely the bot is to take revenge +#define CHARACTERISTIC_VENGEFULNESS 43 //float [0, 1] //use this!! +//tendency to camp +#define CHARACTERISTIC_CAMPER 44 //float [0, 1] +//======================================================== +//======================================================== +//tendency to get easy frags +#define CHARACTERISTIC_EASY_FRAGGER 45 //float [0, 1] +//how alert the bot is (view distance) +#define CHARACTERISTIC_ALERTNESS 46 //float [0, 1] +//how much the bot fires it's weapon +#define CHARACTERISTIC_FIRETHROTTLE 47 //float [0, 1] + diff --git a/dlls/game/class.cpp b/dlls/game/class.cpp new file mode 100644 index 0000000..b6056fb --- /dev/null +++ b/dlls/game/class.cpp @@ -0,0 +1,890 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/class.cpp $ +// $Revision:: 17 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:43a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class that all classes that are used in conjunction with the game should +// be based off of. Class gives run-time type information about any class +// derived from it. This is really handy when you have a pointer to an object +// that you need to know if it supports certain behaviour. +// +// WARNING: This file is shared between game, cgame and possibly the user interface. +// It is instanced in each one of these directories because of the way that SourceSafe works. +// + +#include +#include +#include + + +#if defined( GAME_DLL ) + +#include "g_local.h" + +#elif defined ( CGAME_DLL ) + +#include "cg_local.h" +#include "listener.h" +#include + +#else + +#include "listener.h" +#include + +#endif + +#include "class.h" +#include "Linklist.h" +#include + +int totalmemallocated = 0; +int numclassesallocated = 0; + +static ClassDef *classlist = NULL; + + +ClassDef::ClassDef() +{ + this->classname = NULL; + this->classID = NULL; + this->superclass = NULL; + this->responses = NULL; + this->numEvents = 0; + this->responseLookup = NULL; + this->newInstance = NULL; + this->classSize = 0; + this->super = NULL; + this->prev = this; + this->next = this; +} + +ClassDef::ClassDef( const char *classname, const char *classID, const char *superclass, ResponseDef *responses, + void *( *newInstance )( void ), int classSize ) +{ + ClassDef *node; + + if ( classlist == NULL ) + { + classlist = new ClassDef; + } + + this->classname = classname; + this->classID = classID; + this->superclass = superclass; + this->responses = responses; + this->numEvents = 0; + this->responseLookup = NULL; + this->newInstance = newInstance; + this->classSize = classSize; + this->super = getClass( superclass ); + + // It's not uncommon for classes to not have a class id, so just set it + // to an empty string so that we're not checking for it all the time. + if ( !classID ) + { + this->classID = ""; + } + + // Check if any subclasses were initialized before their superclass + for( node = classlist->next; node != classlist; node = node->next ) + { + if ( ( node->super == NULL ) && ( !Q_stricmp( node->superclass, this->classname ) ) && + ( Q_stricmp( node->classname, "Class" ) ) ) + { + node->super = this; + } + } + + // Add to front of list + LL_Add( classlist, this, prev, next ); +} + +void ClassDef::Shutdown( void ) +{ + if ( responseLookup ) + { + delete[] responseLookup; + responseLookup = NULL; + } +} + +ClassDef::~ClassDef() +{ + ClassDef *node; + + if ( classlist != this ) + { + LL_Remove( this, prev, next ); + + // Check if any subclasses were initialized before their superclass + for( node = classlist->next; node != classlist; node = node->next ) + { + if ( node->super == this ) + { + node->super = NULL; + } + } + } + else + { + // If the head of the list is deleted before the list is cleared, then we may have problems + assert( this->next == this->prev ); + } + + if ( responseLookup ) + { + delete[] responseLookup; + responseLookup = NULL; + } +} + +void ClassDef::BuildResponseList( void ) +{ + ClassDef *c; + ResponseDef *r; + int ev; + int i; + qboolean *set; + int num; + + if ( responseLookup ) + { + delete[] responseLookup; + responseLookup = NULL; + } + + num = Event::NumEventCommands(); + responseLookup = ( Response ** )new char[ sizeof( Response * ) * num ]; + memset( responseLookup, 0, sizeof( Response * ) * num ); + + set = new qboolean[ num ]; + memset( set, 0, sizeof( qboolean ) * num ); + + this->numEvents = num; + + for( c = this; c != NULL; c = c->super ) + { + r = c->responses; + if ( r ) + { + for( i = 0; r[ i ].event != NULL; i++ ) + { + ev = ( int )*r[ i ].event; + if ( !set[ ev ] ) + { + set[ ev ] = true; + if ( r[ i ].response ) + { + responseLookup[ ev ] = &r[ i ].response; + } + else + { + responseLookup[ ev ] = NULL; + } + } + } + } + } + + delete[] set; +} + +void BuildEventResponses( void ) +{ + ClassDef *c; + int amount; + int numclasses; + + amount = 0; + numclasses = 0; + for( c = classlist->next; c != classlist; c = c->next ) + { + c->BuildResponseList(); + + amount += c->numEvents * sizeof( Response * ); + numclasses++; + } + + CLASS_DPrintf( "\n------------------\nEvent system initialized: " + "%d classes %d events %d total memory in response list\n\n", numclasses, Event::NumEventCommands(), amount ); +} + +ClassDef *getClassForID( const char *name ) +{ + ClassDef *c; + + for( c = classlist->next; c != classlist; c = c->next ) + { + if ( c->classID && !Q_stricmp( c->classID, name ) ) + { + return c; + } + } + + return NULL; +} + +ClassDef *getClass( const char *name ) +{ + ClassDef *c; + + for( c = classlist->next; c != classlist; c = c->next ) + { + if ( !Q_stricmp( c->classname, name ) ) + { + return c; + } + } + + return NULL; +} + +ClassDef *getClassList( void ) +{ + return classlist; +} + +void listAllClasses( void ) +{ + ClassDef *c; + + for( c = classlist->next; c != classlist; c = c->next ) + { + CLASS_DPrintf( "%s\n", c->classname ); + } +} + +void listInheritanceOrder( const char *classname ) +{ + ClassDef *cls; + ClassDef *c; + + cls = getClass( classname ); + if ( !cls ) + { + CLASS_WDPrintf( "Unknown class: %s\n", classname ); + return; + } + for( c = cls; c != NULL; c = c->super ) + { + CLASS_DPrintf( "%s\n", c->classname ); + } +} + +qboolean checkInheritance( const ClassDef *superclass, const ClassDef *subclass ) +{ + const ClassDef *c; + + for( c = subclass; c != NULL; c = c->super ) + { + if ( c == superclass ) + { + return true; + } + } + return false; +} + +qboolean checkInheritance( ClassDef *superclass, const char *subclass ) +{ + ClassDef *c; + + c = getClass( subclass ); + if ( c == NULL ) + { + CLASS_WDPrintf( "Unknown class: %s\n", subclass ); + return false; + } + return checkInheritance( superclass, c ); +} + +qboolean checkInheritance( const char *superclass, const char *subclass ) +{ + ClassDef *c1; + ClassDef *c2; + + c1 = getClass( superclass ); + c2 = getClass( subclass ); + if ( c1 == NULL ) + { + CLASS_WDPrintf( "Unknown class: %s\n", superclass ); + return false; + } + if ( c2 == NULL ) + { + CLASS_WDPrintf( "Unknown class: %s\n", subclass ); + return false; + } + return checkInheritance( c1, c2 ); +} + +void CLASS_Print( FILE *class_file, const char *fmt, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + if ( class_file ) + fprintf( class_file, text ); + else + CLASS_DPrintf( text ); +} + +CLASS_DECLARATION( NULL, Class, NULL ) +{ + { NULL, NULL } +}; + +#ifdef NDEBUG + +void * Class::operator new( size_t s ) +{ + int *p; + + if ( s == 0 ) + return 0; + + s += sizeof( int ); +#if defined( GAME_DLL ) + p = ( int * )gi.Malloc( s ); +#elif defined( CGAME_DLL ) + p = ( int * )cgi.Malloc( s ); +#else + p = ( int * )Z_TagMalloc( s, TAG_CLIENT ); +#endif + *p = s; + totalmemallocated += s; + numclassesallocated++; + return p + 1; +} + +void Class::operator delete( void *ptr ) +{ + int *p; + + p = ( ( int * )ptr ) - 1; + totalmemallocated -= *p; + numclassesallocated--; +#if defined( GAME_DLL ) + gi.Free( p ); +#elif defined( CGAME_DLL ) + cgi.Free( p ); +#else + Z_Free( p ); +#endif +} + +#else + +#ifdef MEMORY_LEAK_TEST +int classindex = 0; +#endif + +void * Class::operator new( size_t s ) +{ + int *p; + + s += sizeof( int ) * 3; +#if defined( GAME_DLL ) + p = ( int * )gi.Malloc( s ); +#elif defined( CGAME_DLL ) + p = ( int * )cgi.Malloc( s ); +#else + p = ( int * )Z_TagMalloc( s, TAG_CLIENT ); +#endif + // set memory to a known wrong number + memset( p, 0xaa, s ); +#ifdef MEMORY_LEAK_TEST + p[ 0 ] = classindex++; +#else + p[ 0 ] = 0x12348765; +#endif + *( int * )( ((byte *)p) + s - sizeof( int ) ) = 0x56784321; + p[ 1 ] = s; + totalmemallocated += s; + numclassesallocated++; + return p + 2; +} + +void Class::operator delete( void *ptr ) +{ + int *p; + + p = ( ( int * )ptr ) - 2; +#ifndef MEMORY_LEAK_TEST + assert( p[ 0 ] == 0x12348765 ); +#endif + assert( *( int * )( ((byte *)p) + p[ 1 ] - sizeof( int ) ) == 0x56784321 ); + + totalmemallocated -= p[ 1 ]; + numclassesallocated--; +#if defined( GAME_DLL ) + gi.Free( p ); +#elif defined( CGAME_DLL ) + cgi.Free( p ); +#else + Z_Free( p ); +#endif +} + +#endif + +void DisplayMemoryUsage( void ) +{ + CLASS_Printf( "Classes %-5d Class memory used: %d\n", numclassesallocated, totalmemallocated ); +} + +Class::Class() +{ + SafePtrList = NULL; +} + +Class::~Class() +{ + ClearSafePointers(); +} + +#ifdef GAME_DLL + +void Class::Archive( Archiver &arc ) +{ +} + +#endif + +void Class::ClearSafePointers( void ) +{ + while( SafePtrList != NULL ) + { + SafePtrList->Clear(); + } +} + + +void Class::warning( const char *function, const char *fmt, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + if ( getClassID() ) + { + CLASS_WDPrintf( "%s::%s : %s\n", getClassID(), function, text ); + } + else + { + CLASS_WDPrintf( "%s::%s : %s\n", getClassname(), function, text ); + } +} + +void Class::error( const char *function, const char *fmt, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + if ( getClassID() ) + { + CLASS_Error( ERR_DROP, "%s::%s : %s\n", getClassID(), function, text ); + } + else + { + CLASS_Error( ERR_DROP, "%s::%s : %s\n", getClassname(), function, text ); + } +} + +qboolean Class::inheritsFrom( const char *name ) const +{ + ClassDef *c; + + c = getClass( name ); + if ( c == NULL ) + { + CLASS_WDPrintf( "Unknown class: %s\n", name ); + return false; + } + return checkInheritance( c, classinfo() ); +} + +qboolean Class::isInheritedBy( const char *name ) const +{ + ClassDef *c; + + c = getClass( name ); + if ( c == NULL ) + { + CLASS_WDPrintf( "Unknown class: %s\n", name ); + return false; + } + return checkInheritance( classinfo(), c ); +} + +const char *Class::getClassname( void ) +{ + ClassDef *cls; + + cls = classinfo(); + return cls->classname; +} + +const char *Class::getClassID( void ) +{ + ClassDef *cls; + + cls = classinfo(); + return cls->classID; +} + +const char *Class::getSuperclass( void ) +{ + ClassDef *cls; + + cls = classinfo(); + return cls->superclass; +} + +void *Class::newInstance( void ) +{ + ClassDef *cls; + + cls = classinfo(); + return cls->newInstance(); +} + +#define MAX_INHERITANCE 64 +void ClassEvents( const char *classname, qboolean print_to_disk ) +{ + ClassDef *c; + ResponseDef *r; + int ev; + int i, j; + qboolean *set; + int num, orderNum; + Event **events; + byte *order; + FILE *class_file; + str classNames[ MAX_INHERITANCE ]; + str class_filename; + + c = getClass( classname ); + if ( !c ) + { + CLASS_WDPrintf( "Unknown class: %s\n", classname ); + return; + } + + class_file = NULL; + + if ( print_to_disk ) + { + class_filename = str ( classname ) + ".txt"; + class_file = fopen( class_filename.c_str(), "w" ); + if ( class_file == NULL ) + return; + } + + num = Event::NumEventCommands(); + + set = new qboolean[ num ]; + memset( set, 0, sizeof( qboolean ) * num ); + + events = new Event *[ num ]; + memset( events, 0, sizeof( Event * ) * num ); + + order = new byte[ num ]; + memset( order, 0, sizeof( byte ) * num ); + + orderNum = 0; + for( ; c != NULL; c = c->super ) + { + if ( orderNum < MAX_INHERITANCE ) + { + classNames[ orderNum ] = c->classname; + } + r = c->responses; + if ( r ) + { + for( i = 0; r[ i ].event != NULL; i++ ) + { + ev = ( int )*r[ i ].event; + if ( !set[ ev ] ) + { + set[ ev ] = true; + + if ( r[ i ].response ) + { + events[ ev ] = r[ i ].event; + order[ ev ] = orderNum; + } + } + } + } + orderNum++; + } + + CLASS_Print( class_file, "********************************************************\n" ); + CLASS_Print( class_file, "********************************************************\n" ); + CLASS_Print( class_file, "* All Events For Class: %s\n", classname ); + CLASS_Print( class_file, "********************************************************\n" ); + CLASS_Print( class_file, "********************************************************\n\n" ); + + for( j = orderNum - 1; j >= 0; j-- ) + { + CLASS_Print( class_file, "\n********************************************************\n" ); + CLASS_Print( class_file, "* Class: %s\n", classNames[ j ].c_str() ); + CLASS_Print( class_file, "********************************************************\n\n" ); + for( i = 1; i < num; i++ ) + { + int index; + + index = Event::MapSortedEventIndex( i ); + if ( events[ index ] && ( order[ index ] == j ) ) + { + Event::PrintEventDocumentation( events[ index ], class_file ); + } + } + } + + if ( class_file != NULL ) + { + CLASS_DPrintf( "Printed class info to file %s\n", class_filename.c_str() ); + fclose( class_file ); + } + + delete[] events; + delete[] order; + delete[] set; +} + +static int dump_numclasses; +static int dump_numevents; +void DumpClass( FILE * class_file, const char * className, int typeFlag ) +{ + ClassDef *c; + ResponseDef *r; + int ev; + int i; + int num; + Event **events; + + c = getClass( className ); + if ( !c ) + { + return; + } + + num = Event::NumEventCommands(); + + events = new Event *[ num ]; + memset( events, 0, sizeof( Event * ) * num ); + + // gather event responses for this class + r = c->responses; + if ( r ) + { + for( i = 0; r[ i ].event != NULL; i++ ) + { + ev = ( int )*r[ i ].event; + if ( r[ i ].response ) + { + events[ ev ] = r[ i ].event; + } + } + } + + CLASS_Print( class_file, "\n"); + if ( c->classID[ 0 ] ) + { + CLASS_Print( class_file, "

%s (%s)", c->classname, c->classname, c->classID ); + } + else + { + CLASS_Print( class_file, "

%s", c->classname, c->classname ); + } + + // print out lineage + for( c = c->super; c != NULL; c = c->super ) + { + CLASS_Print( class_file, " -> %s", c->classname, c->classname ); + } + CLASS_Print( class_file, "

\n"); + + dump_numclasses++; + + CLASS_Print( class_file, "
\n"); + for( i = 1; i < num; i++ ) + { + int index; + + index = Event::MapSortedEventIndex( i ); + if ( events[ index ] ) + { + Event::PrintEventDocumentation( events[ index ], class_file, true, typeFlag ); + dump_numevents++; + } + } + CLASS_Print( class_file, "
\n"); + delete[] events; +} + +void DumpAllClasses( int typeFlag, int outputType, const char *filename ) +{ + str defaultname; +#if defined( GAME_DLL ) + defaultname = "g_allclasses"; +#elif defined( CGAME_DLL ) + defaultname = "cg_allclasses"; +#else + defaultname = "cl_allclasses"; +#endif + + if ( outputType == OUTPUT_HTML || !outputType ) + { + HTMLDocFileOutput *hdout = new HTMLDocFileOutput(); + if ( filename ) + hdout->Write(filename, classlist, typeFlag); + else + hdout->Write(defaultname, classlist, typeFlag); + delete hdout; + } + + if ( outputType == OUTPUT_CMD || !outputType ) + { + ToolDocFileOutput *dout = new ToolDocFileOutput(); + if ( filename ) + dout->Write(filename, classlist, typeFlag); + else + dout->Write(defaultname, classlist, typeFlag); + delete dout; + } + +/* + int i, j, num, smallest; + ClassDef *c; + FILE * class_file; + str class_filename; + str class_title; + str classes[ MAX_CLASSES ]; + +#if defined( GAME_DLL ) + class_filename = "g_allclasses.html"; + class_title = "Game Module"; +#elif defined( CGAME_DLL ) + class_filename = "cg_allclasses.html"; + class_title = "Client Game Module"; +#else + class_filename = "cl_allclasses.html"; + class_title = "Client Module"; +#endif + + class_file = fopen( class_filename.c_str(), "w" ); + if ( class_file == NULL ) + return; + + // construct the HTML header for the document + CLASS_Print( class_file, "\n"); + CLASS_Print( class_file, "\n"); + CLASS_Print( class_file, "%s Classes\n", class_title.c_str() ); + CLASS_Print( class_file, "\n"); + CLASS_Print( class_file, "\n"); + CLASS_Print( class_file, "

\n"); + CLASS_Print( class_file, "
%s Classes
\n", class_title.c_str() ); + CLASS_Print( class_file, "

\n"); +#if defined( GAME_DLL ) + // + // print out some commonly used classnames + // + CLASS_Print( class_file, "

" ); + CLASS_Print( class_file, "Actor, " ); + CLASS_Print( class_file, "Animate, " ); + CLASS_Print( class_file, "Entity, " ); + CLASS_Print( class_file, "ScriptSlave, " ); + CLASS_Print( class_file, "ScriptThread, " ); + CLASS_Print( class_file, "Sentient, " ); + CLASS_Print( class_file, "Trigger, " ); + CLASS_Print( class_file, "World" ); + CLASS_Print( class_file, "

" ); +#endif + + dump_numclasses = 0; + dump_numevents = 0; + + // get all the classes + num = 0; + for( c = classlist->next; c != classlist; c = c->next ) + { + if ( num < MAX_CLASSES ) + { + classes[ num++ ] = c->classname; + } + } + + // go through and process each class from smallest to greatest + for( i = 0; i < num; i++ ) + { + smallest = -1; + for( j = 0; j < num; j++ ) + { + if ( classes[ j ].length() > 1 ) + { + if ( smallest >= 0 ) + { + if ( classes[ j ].icmp( classes[ smallest ] ) < 0 ) + { + smallest = j; + } + } + else + { + smallest = j; + } + } + } + DumpClass( class_file, classes[ smallest ], typeFlag ); + // delete the name from the list + classes[ smallest ] = ""; + } + + if ( class_file != NULL ) + { + CLASS_Print( class_file, "

\n"); + CLASS_Print( class_file, "%d %s Classes.
%d %s Events.\n", dump_numclasses, class_title.c_str(), dump_numevents, class_title.c_str() ); + CLASS_Print( class_file, "

\n"); + CLASS_Print( class_file, "\n"); + CLASS_Print( class_file, "\n"); + CLASS_DPrintf( "Dumped all classes to file %s\n", class_filename.c_str() ); + fclose( class_file ); + } +*/ +} + + +void ShutdownClasses( void ) +{ + ClassDef *c; + + for( c = classlist->next; c != classlist; c = c->next ) + { + c->Shutdown(); + } +} diff --git a/dlls/game/class.h b/dlls/game/class.h new file mode 100644 index 0000000..20bd366 --- /dev/null +++ b/dlls/game/class.h @@ -0,0 +1,515 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/class.h $ +// $Revision:: 18 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:43a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class that all classes that are used in conjunction with the game should +// be based off of. Class gives run-time type information about any class +// derived from it. This is really handy when you have a pointer to an object +// that you need to know if it supports certain behaviour. +// +// WARNING: This file is shared between game, cgame and possibly the user interface. +// It is instanced in each one of these directories because of the way that SourceSafe works. +// +class Class; +class Event; + +#ifndef __CLASS_H__ +#define __CLASS_H__ + +#if defined( GAME_DLL ) +// +// game dll specific defines +// +#include "g_local.h" +#include "Linklist.h" + +#define CLASS_DPrintf gi.DPrintf +#define CLASS_Printf gi.Printf +#define CLASS_WDPrintf gi.WDPrintf +#define CLASS_WPrintf gi.WPrintf +#define CLASS_Error gi.Error + +class Archiver; + +#elif defined ( CGAME_DLL ) +// +// cgame dll specific defines +// +#include "../../DLLs/game/q_shared.h" +#include "Linklist.h" + +#define CLASS_DPrintf cgi.DPrintf +#define CLASS_Printf cgi.Printf +#define CLASS_WDPrintf cgi.WDPrintf +#define CLASS_WPrintf cgi.WPrintf +#define CLASS_Error cgi.Error + +#else + +// +// client specific defines +// +#include "../../DLLs/game/q_shared.h" +#include "Linklist.h" + +#define CLASS_DPrintf Com_DPrintf +#define CLASS_Printf Com_Printf +#define CLASS_WDPrintf Com_WDPrintf +#define CLASS_WPrintf Com_WPrintf +#define CLASS_Error Com_Error +#endif + +// Flags used for the Output class +#define EVENT_TIKI_ONLY 1 +#define EVENT_SCRIPT_ONLY 2 +#define EVENT_ALL 3 + +#define OUTPUT_HTML 1 +#define OUTPUT_CMD 2 +#define OUTPUT_ALL 3 + +#define MAX_CLASSES 1024 + +typedef void ( Class::*Response )( Event *event ); + +template< class Type > +struct ResponseDef + { + Event *event; + void ( Type::*response )( Event *event ); + }; + +/*********************************************************************** + + ClassDef + +***********************************************************************/ + +class ClassDef + { + public: + const char *classname; + const char *classID; + const char *superclass; + void *( *newInstance )( void ); + int classSize; + ResponseDef *responses; + int numEvents; + Response **responseLookup; + ClassDef *super; + ClassDef *next; + ClassDef *prev; + + ClassDef(); + ~ClassDef(); + ClassDef( const char *classname, const char *classID, const char *superclass, + ResponseDef *responses, void *( *newInstance )( void ), int classSize ); + void BuildResponseList( void ); + void Shutdown( void ); + }; + +/*********************************************************************** + + SafePtr + +***********************************************************************/ + +class SafePtrBase; + +class Class; + +class SafePtrBase + { + private: + void AddReference( Class *ptr ); + void RemoveReference( Class *ptr ); + + protected: + SafePtrBase *prevSafePtr; + SafePtrBase *nextSafePtr; + Class *ptr; + + public: + SafePtrBase(); + virtual ~SafePtrBase(); + void InitSafePtr( Class *newptr ); + Class *Pointer( void ); + void Clear( void ); + }; + +/*********************************************************************** + + Class + +***********************************************************************/ + +#define CLASS_DECLARATION( nameofsuperclass, nameofclass, classid ) \ + ClassDef nameofclass::ClassInfo \ + ( \ + #nameofclass, classid, #nameofsuperclass, \ + ( ResponseDef * )nameofclass::Responses, \ + nameofclass::_newInstance, sizeof( nameofclass ) \ + ); \ + void *nameofclass::_newInstance( void ) \ + { \ + return new nameofclass; \ + } \ + ClassDef *nameofclass::classinfo( void ) const \ + { \ + return &( nameofclass::ClassInfo ); \ + } \ + ResponseDef nameofclass::Responses[] = + +#define CLASS_PROTOTYPE( nameofclass ) \ + public: \ + static ClassDef ClassInfo; \ + static void *_newInstance( void ); \ + virtual ClassDef *classinfo( void ) const; \ + static ResponseDef Responses[] + +class Class + { + private: + SafePtrBase *SafePtrList; + friend class SafePtrBase; + + protected: + void ClearSafePointers( void ); + + public: + CLASS_PROTOTYPE( Class ); + void * operator new( size_t ); + void operator delete( void * ); + + Class(); + virtual ~Class(); + void warning( const char *function, const char *fmt, ... ); + void error( const char *function, const char *fmt, ... ); + qboolean inheritsFrom( ClassDef *c ) const; + qboolean inheritsFrom( const char *name ) const; + qboolean isInheritedBy( const char *name ) const; + qboolean isInheritedBy( ClassDef *c ) const; + const char *getClassname( void ); + const char *getClassID( void ); + const char *getSuperclass( void ); + void *newInstance( void ); + +#ifdef GAME_DLL + virtual void Archive( Archiver &arc ); +#endif + }; + +void BuildEventResponses( void ); +ClassDef *getClassForID( const char *name ); +ClassDef *getClass( const char *name ); +ClassDef *getClassList( void ); +void listAllClasses( void ); +void listInheritanceOrder( const char *classname ); +qboolean checkInheritance( const ClassDef * const superclass, const ClassDef * const subclass ); +qboolean checkInheritance( ClassDef * const superclass, const char * const subclass ); +qboolean checkInheritance( const char * const superclass, const char * const subclass ); +void DisplayMemoryUsage( void ); +void ClassEvents( const char *classname, qboolean dump = false ); +void DumpAllClasses( int typeFlag = EVENT_ALL, int outputType = OUTPUT_ALL, const char *filename = NULL); +void DumpClass( FILE * class_file, const char * className, int typeFlag = 0); + +inline qboolean Class::inheritsFrom + ( + ClassDef *c + ) const + + { + return checkInheritance( c, classinfo() ); + } + +inline qboolean Class::isInheritedBy + ( + ClassDef *c + ) const + + { + return checkInheritance( classinfo(), c ); + } + +// The lack of a space between the ")" and "inheritsFrom" is intentional. +// It allows the macro to compile like a function call. However, this +// may cause problems in some compilers (like gcc), so we may have to +// change this to work like a normal macro with the object passed in +// as a parameter to the macro. +#define isSubclassOf( classname )inheritsFrom( &classname::ClassInfo ) +#define isSuperclassOf( classname )isInheritedBy( &classname::ClassInfo ) + +/*********************************************************************** + + SafePtr + +***********************************************************************/ + +inline void SafePtrBase::AddReference + ( + Class *ptr + ) + + { + if ( !ptr->SafePtrList ) + { + ptr->SafePtrList = this; + LL_Reset( this, nextSafePtr, prevSafePtr ); + } + else + { + LL_Add( ptr->SafePtrList, this, nextSafePtr, prevSafePtr ); + } + } + +inline void SafePtrBase::RemoveReference + ( + Class *ptr + ) + + { + if ( ptr->SafePtrList == this ) + { + if ( ptr->SafePtrList->nextSafePtr == this ) + { + ptr->SafePtrList = NULL; + } + else + { + ptr->SafePtrList = nextSafePtr; + LL_Remove( this, nextSafePtr, prevSafePtr ); + } + } + else + { + LL_Remove( this, nextSafePtr, prevSafePtr ); + } + } + +inline void SafePtrBase::Clear + ( + void + ) + + { + if ( ptr ) + { + RemoveReference( ptr ); + ptr = NULL; + } + } + +inline SafePtrBase::SafePtrBase() + { + prevSafePtr = NULL; + nextSafePtr = NULL; + ptr = NULL; + } + +inline SafePtrBase::~SafePtrBase() + { + Clear(); + } + +inline Class * SafePtrBase::Pointer + ( + void + ) + + { + return ptr; + } + +inline void SafePtrBase::InitSafePtr + ( + Class *newptr + ) + + { + if ( ptr != newptr ) + { + if ( ptr ) + { + RemoveReference( ptr ); + } + + ptr = newptr; + if ( ptr == NULL ) + { + return; + } + + AddReference( ptr ); + } + } + +template +class SafePtr : public SafePtrBase + { + public: + SafePtr( T* objptr = 0 ); + SafePtr( const SafePtr& obj ); + + SafePtr& operator=( const SafePtr& obj ); + SafePtr& operator=( T * const obj ); + +#ifdef LINUX + friend int operator==<>( SafePtr a, T *b ); + friend int operator!=<>( SafePtr a, T *b ); + friend int operator==<>( T *a, SafePtr b ); + friend int operator!=<>( T *a, SafePtr b ); + friend int operator==<>( SafePtr a, SafePtr b ); + friend int operator!=<>( SafePtr a, SafePtr b ); +#else + friend int operator==( SafePtr a, T *b ); + friend int operator!=( SafePtr a, T *b ); + friend int operator==( T *a, SafePtr b ); + friend int operator!=( T *a, SafePtr b ); + friend int operator==( SafePtr a, SafePtr b ); + friend int operator!=( SafePtr a, SafePtr b ); +#endif + + operator T*() const; + T* operator->() const; + T& operator*() const; + }; + +template +inline SafePtr::SafePtr( T* objptr ) + { + InitSafePtr( objptr ); + } + +template +inline SafePtr::SafePtr( const SafePtr& obj ) + { + InitSafePtr( obj.ptr ); + } + +template +inline SafePtr& SafePtr::operator=( const SafePtr& obj ) + { + InitSafePtr( obj.ptr ); + return *this; + } + +template +inline SafePtr& SafePtr::operator=( T * const obj ) + { + InitSafePtr( obj ); + return *this; + } + +template +inline int operator== + ( + SafePtr a, + T* b + ) + + { + return a.ptr == b; + } + +template +inline int operator!= + ( + SafePtr a, + T* b + ) + + { + return a.ptr != b; + } + +template +inline int operator== + ( + T* a, + SafePtr b + ) + + { + return a == b.ptr; + } + +template +inline int operator!= + ( + T* a, + SafePtr b + ) + + { + return a != b.ptr; + } + +template +inline int operator== + ( + SafePtr a, + SafePtr b + ) + + { + return a.ptr == b.ptr; + } + +template +inline int operator!= + ( + SafePtr a, + SafePtr b + ) + + { + return a.ptr != b.ptr; + } + +template +inline SafePtr::operator T*() const + { + return ( T * )ptr; + } + +template +inline T* SafePtr::operator->() const + { + return ( T * )ptr; + } + +template +inline T& SafePtr::operator*() const + { + return *( T * )ptr; + } + +typedef SafePtr ClassPtr; + +#ifdef GAME_DLL +#include "archive.h" +#endif + +// used by listener for event allocation +extern int totalmemallocated; + +#ifndef GAME_DLL +extern "C" + { + // interface functions + void ShutdownClasses( void ); + } +#endif + +#endif /* class.h */ diff --git a/dlls/game/closeInOnEnemy.cpp b/dlls/game/closeInOnEnemy.cpp new file mode 100644 index 0000000..b4d6fda --- /dev/null +++ b/dlls/game/closeInOnEnemy.cpp @@ -0,0 +1,365 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/closeInOnEnemy.cpp $ +// $Revision:: 7 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CloseInOnEnemy Implementation +// +// PARAMETERS: +// +// +// ANIMATIONS: +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "closeInOnEnemy.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, CloseInOnEnemy, NULL ) + { + { &EV_Behavior_Args, &CloseInOnEnemy::SetArgs }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: CloseInOnEnemy() +// Class: CloseInOnEnemy +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +CloseInOnEnemy::CloseInOnEnemy() +{ + _anim = ""; + _torsoAnim = ""; + _dist = 64.0f; +} + +//-------------------------------------------------------------- +// Name: ~CloseInOnEnemy() +// Class: CloseInOnEnemy +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +CloseInOnEnemy::~CloseInOnEnemy() +{ +} + + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: CloseInOnEnemy +// +// Description: +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void CloseInOnEnemy::SetArgs( Event *ev ) +{ + _anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + _dist = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + _torsoAnim = ev->GetString( 3 ); +} + + + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: CloseInOnEnemy +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void CloseInOnEnemy::Begin( Actor &self ) +{ + init( self ); +} + + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: CloseInOnEnemy +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t CloseInOnEnemy::Evaluate( Actor &self ) +{ + + BehaviorReturnCode_t stateResult; + + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case CLOSE_IN_ON_ENEMY_APPROACH: + //--------------------------------------------------------------------- + stateResult = evaluateStateApproach(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CLOSE_IN_ON_ENEMY_FAILED ); + + if ( stateResult == BEHAVIOR_FAILED_STEERING_NO_PATH ) + transitionToState( CLOSE_IN_ON_ENEMY_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CLOSE_IN_ON_ENEMY_SUCCESS ); + break; + + //--------------------------------------------------------------------- + case CLOSE_IN_ON_ENEMY_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + break; + + //--------------------------------------------------------------------- + case CLOSE_IN_ON_ENEMY_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: CloseInOnEnemy +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void CloseInOnEnemy::End(Actor &self) +{ + _chaseEnemy.End( self ); + self.SetAnim( "idle" ); +} + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: CloseInOnEnemy +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemy::transitionToState( closeInOnEnemyStates_t state ) +{ + switch( state ) + { + case CLOSE_IN_ON_ENEMY_APPROACH: + setupStateApproach(); + setInternalState( state , "CLOSE_IN_ON_ENEMY_APPROACH" ); + break; + + case CLOSE_IN_ON_ENEMY_SUCCESS: + setInternalState( state , "CLOSE_IN_ON_ENEMY_SUCCESS" ); + break; + + case CLOSE_IN_ON_ENEMY_FAILED: + setInternalState( state , "CLOSE_IN_ON_ENEMY_FAILED" ); + break; + } +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: CloseInOnEnemy +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemy::setInternalState( closeInOnEnemyStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: CloseInOnEnemy +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemy::init( Actor &self ) +{ + _self = &self; + updateEnemy(); + transitionToState(CLOSE_IN_ON_ENEMY_APPROACH); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: CloseInOnEnemy +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemy::think() +{ + if ( !_currentEnemy ) + { + SetFailureReason( "CloseInOnEnemy::updateEnemy -- No Enemy" ); + transitionToState( CLOSE_IN_ON_ENEMY_FAILED ); + } +} + + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: CloseInOnEnemy +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemy::updateEnemy() +{ + Entity *currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + SetFailureReason( "CloseInOnEnemy::updateEnemy -- No Enemy" ); + transitionToState( CLOSE_IN_ON_ENEMY_FAILED ); + } + + } + + _currentEnemy = currentEnemy; +} + +//-------------------------------------------------------------- +// Name: setTorsoAnim() +// Class: CloseInOnEnemy +// +// Description: Sets our Torso Animation +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemy::setTorsoAnim() +{ + _self->SetAnim( _torsoAnim , NULL , torso ); +} + +//-------------------------------------------------------------- +// Name: setupStateApproach() +// Class: CloseInOnEnemy +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemy::setupStateApproach() +{ + _chaseEnemy.SetAnim( _anim ); + _chaseEnemy.SetDistance( _dist ); + _chaseEnemy.SetEntity( *_self, _currentEnemy ); + + if ( _torsoAnim.length() > 0 ) + setTorsoAnim(); + + _chaseEnemy.Begin( *_self ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateApproach() +// Class: CloseInOnEnemy +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t CloseInOnEnemy::evaluateStateApproach() +{ + return _chaseEnemy.Evaluate( *_self ); +} + +//-------------------------------------------------------------- +// Name: failureStateApproach() +// Class: CloseInOnEnemy +// +// Description: Failure Handler for State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemy::failureStateApproach( const str& failureReason ) +{ +} diff --git a/dlls/game/closeInOnEnemy.hpp b/dlls/game/closeInOnEnemy.hpp new file mode 100644 index 0000000..fa03e44 --- /dev/null +++ b/dlls/game/closeInOnEnemy.hpp @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/closeInOnEnemy.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CloseInOnEnemy Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class CloseInOnEnemy; + +#ifndef __CLOSE_IN_ON_ENEMY_HPP__ +#define __CLOSE_IN_ON_ENEMY_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: CloseInOnEnemy +// Base Class: Behavior +// +// Description: Makes the actor move closer to its current enemy +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class CloseInOnEnemy : public Behavior +{ + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + CLOSE_IN_ON_ENEMY_APPROACH, + CLOSE_IN_ON_ENEMY_SUCCESS, + CLOSE_IN_ON_ENEMY_FAILED + } closeInOnEnemyStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: // Parameters + str _anim; + str _torsoAnim; + float _dist; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( closeInOnEnemyStates_t state ); + void setInternalState ( closeInOnEnemyStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + void setTorsoAnim (); + + void setupStateApproach (); + BehaviorReturnCode_t evaluateStateApproach (); + void failureStateApproach ( const str& failureReason ); + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( CloseInOnEnemy ); + + CloseInOnEnemy(); + ~CloseInOnEnemy(); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + void setAnim ( const str &animName ); + void setTorsoAnim( const str &animName ); + void setDist ( float distance ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoEntity _chaseEnemy; + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + closeInOnEnemyStates_t _state; + EntityPtr _currentEnemy; + Actor *_self; + +}; + +inline void CloseInOnEnemy::setAnim( const str &animName ) +{ + _anim = animName; +} + +inline void CloseInOnEnemy::setTorsoAnim( const str &animName ) +{ + _torsoAnim = animName; +} + +inline void CloseInOnEnemy::setDist( float distance ) +{ + _dist = distance; +} + +inline void CloseInOnEnemy::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_anim ); + arc.ArchiveString ( &_torsoAnim ); + arc.ArchiveFloat ( &_dist ); + + // Archive Components + arc.ArchiveObject ( &_chaseEnemy ); + + // Archive Member Variables + ArchiveEnum ( _state, closeInOnEnemyStates_t); + arc.ArchiveSafePointer ( &_currentEnemy ); + arc.ArchiveObjectPointer ( ( Class ** )&_self ); +} + +#endif /* CLOSE_IN_ON_ENEMY_HPP */ diff --git a/dlls/game/closeInOnEnemyWhileFiringWeapon.cpp b/dlls/game/closeInOnEnemyWhileFiringWeapon.cpp new file mode 100644 index 0000000..a22078e --- /dev/null +++ b/dlls/game/closeInOnEnemyWhileFiringWeapon.cpp @@ -0,0 +1,520 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/closeInOnEnemyWhileFiringWeapon.cpp $ +// $Revision:: 5 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CloseInOnEnemyWhileFiringWeapon Implementation +// +// PARAMETERS: +// +// +// ANIMATIONS: +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "closeInOnEnemyWhileFiringWeapon.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, CloseInOnEnemyWhileFiringWeapon, NULL ) + { + { &EV_Behavior_Args, &CloseInOnEnemyWhileFiringWeapon::SetArgs }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: CloseInOnEnemyWhileFiringWeapon() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +CloseInOnEnemyWhileFiringWeapon::CloseInOnEnemyWhileFiringWeapon() +{ + _approachAnim = ""; + _aimAnim = ""; + _fireAnim = ""; + _fireTimeMin = 0.0f; + _fireTimeMax = 0.0f; + _pauseTimeMin = 0.0f; + _pauseTimeMax = 0.0f; + _dist = 0.0f; + _nextFireTime = 0.0f; + _nextPauseTime = 0.0f; +} + +//-------------------------------------------------------------- +// Name: ~CloseInOnEnemyWhileFiringWeapon() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +CloseInOnEnemyWhileFiringWeapon::~CloseInOnEnemyWhileFiringWeapon() +{ +} + + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::SetArgs( Event *ev ) +{ + _approachAnim = ev->GetString( 1 ); + _aimAnim = ev->GetString( 2 ); + _fireAnim = ev->GetString( 3 ); + _fireTimeMin = ev->GetFloat( 4 ); + _fireTimeMax = ev->GetFloat( 5 ); + _pauseTimeMin = ev->GetFloat( 6 ); + _pauseTimeMax = ev->GetFloat( 7 ); + _dist = ev->GetFloat( 8 ); +} + + + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::Begin( Actor &self ) +{ + init( self ); +} + + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t CloseInOnEnemyWhileFiringWeapon::Evaluate( Actor &self ) +{ + + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case CIWF_APPROACH_SETUP_APPROACH: + //--------------------------------------------------------------------- + stateResult = evaluateStateSetupApproach(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CIWF_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CIWF_APPROACH_FIRE ); + break; + + //--------------------------------------------------------------------- + case CIWF_APPROACH_FIRE: + //--------------------------------------------------------------------- + stateResult = evaluateStateApproachFire(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CIWF_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CIWF_APPROACH_FIRE_PAUSE ); + break; + + //--------------------------------------------------------------------- + case CIWF_APPROACH_FIRE_PAUSE: + //--------------------------------------------------------------------- + stateResult = evaluateStateApproachFirePause(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CIWF_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CIWF_APPROACH_FIRE ); + break; + + //--------------------------------------------------------------------- + case CIWF_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + break; + + //--------------------------------------------------------------------- + case CIWF_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::End(Actor &self) +{ + _chaseEnemy.End( *_self ); + _fireWeapon.End( *_self ); +} + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::transitionToState( CIWFStates_t state ) +{ + switch( state ) + { + case CIWF_APPROACH_SETUP_APPROACH: + setupStateSetupApproach(); + setInternalState( state , "CIWF_APPROACH_SETUP_APPROACH" ); + break; + + case CIWF_APPROACH_FIRE: + setupStateApproachFire(); + setInternalState( state , "CIWF_APPROACH_FIRE" ); + break; + + case CIWF_APPROACH_FIRE_PAUSE: + setupStateApproachFirePause(); + setInternalState( state , "CIWF_APPROACH_FIRE_PAUSE" ); + break; + + case CIWF_SUCCESS: + setInternalState( state , "CIWF_SUCCESS" ); + break; + + case CIWF_FAILED: + setInternalState( state , "CIWF_FAILED" ); + break; + } +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::setInternalState( CIWFStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::init( Actor &self ) +{ + _self = &self; + updateEnemy(); + transitionToState(CIWF_APPROACH_SETUP_APPROACH); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::think() +{ +} + + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::updateEnemy() +{ + Entity *currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + SetFailureReason( "CloseInOnEnemyWhileFiringWeapon::updateEnemy -- No Enemy" ); + transitionToState( CIWF_FAILED ); + } + + } + + _currentEnemy = currentEnemy; +} + +//-------------------------------------------------------------- +// Name: setTorsoAnim() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Sets our Torso Animation +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::setTorsoAnim() +{ + _self->SetAnim( _aimAnim , NULL , torso ); +} + +//-------------------------------------------------------------- +// Name: setupStateSetupApproach() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::setupStateSetupApproach() +{ + _chaseEnemy.SetAnim( _approachAnim ); + _chaseEnemy.SetDistance( _dist ); + _chaseEnemy.SetEntity( *_self, _currentEnemy ); + + setTorsoAnim(); + _chaseEnemy.Begin( *_self ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateApproach() +// Class: evaluateStateSetupApproach +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t CloseInOnEnemyWhileFiringWeapon::evaluateStateSetupApproach() +{ + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateSetupApproach() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Failure Handler for State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::failureStateSetupApproach( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateApproachFire() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Sets up Stand Firing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::setupStateApproachFire() +{ + _nextFireTime = level.time + G_Random( _fireTimeMax ) + _fireTimeMin; + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateApproachFire() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Evaluates Stand Firing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CloseInOnEnemyWhileFiringWeapon::evaluateStateApproachFire() +{ + BehaviorReturnCode_t result; + + //Evaluate our chase first + result = _chaseEnemy.Evaluate( *_self ); + + if ( result != BEHAVIOR_SUCCESS && + result != BEHAVIOR_EVALUATING + ) + { + return BEHAVIOR_FAILED; + } + + _fireWeapon.Evaluate( *_self ); + _self->combatSubsystem->AimWeaponTag(_currentEnemy); + + if ( level.time > _nextFireTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateStandFiring() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Failure Handler for Stand Firing State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::failureStateApproachFire( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateApproachFirePause() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Sets up Stand Firing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::setupStateApproachFirePause() +{ + _nextPauseTime = level.time + G_Random( _pauseTimeMax ) + _pauseTimeMin; + _fireWeapon.End( *_self ); + setTorsoAnim(); +} + +//-------------------------------------------------------------- +// Name: evaluateStateApproachFirePause() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Evaluates Stand Firing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CloseInOnEnemyWhileFiringWeapon::evaluateStateApproachFirePause() +{ + BehaviorReturnCode_t result; + + //Evaluate our chase first + result = _chaseEnemy.Evaluate( *_self ); + + if ( result != BEHAVIOR_SUCCESS && + result != BEHAVIOR_EVALUATING + ) + { + return BEHAVIOR_FAILED; + } + + + if ( level.time > _nextPauseTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateApproachFirePause() +// Class: CloseInOnEnemyWhileFiringWeapon +// +// Description: Failure Handler for Stand Firing State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnEnemyWhileFiringWeapon::failureStateApproachFirePause( const str& failureReason ) +{ +} diff --git a/dlls/game/closeInOnEnemyWhileFiringWeapon.hpp b/dlls/game/closeInOnEnemyWhileFiringWeapon.hpp new file mode 100644 index 0000000..c8bec48 --- /dev/null +++ b/dlls/game/closeInOnEnemyWhileFiringWeapon.hpp @@ -0,0 +1,156 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/closeInOnEnemyWhileFiringWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CloseInOnEnemyWhileFiringWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class CloseInOnEnemyWhileFiringWeapon; + +#ifndef __CLOSE_IN_ON_ENEMY_WHILE_FIRING_WEAPON_HPP__ +#define __CLOSE_IN_ON_ENEMY_WHILE_FIRING_WEAPON_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: CloseInOnEnemyWhileFiringWeapon +// Base Class: Behavior +// +// Description: Makes the actor move closer to its current enemy +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class CloseInOnEnemyWhileFiringWeapon : public Behavior +{ + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + CIWF_APPROACH_SETUP_APPROACH, + CIWF_APPROACH_FIRE, + CIWF_APPROACH_FIRE_PAUSE, + CIWF_SUCCESS, + CIWF_FAILED + } CIWFStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _approachAnim; + str _aimAnim; + str _fireAnim; + float _fireTimeMin; + float _fireTimeMax; + float _pauseTimeMin; + float _pauseTimeMax; + float _dist; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( CIWFStates_t state ); + void setInternalState ( CIWFStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + void setTorsoAnim (); + + void setupStateSetupApproach (); + BehaviorReturnCode_t evaluateStateSetupApproach (); + void failureStateSetupApproach ( const str& failureReason ); + + void setupStateApproachFire (); + BehaviorReturnCode_t evaluateStateApproachFire (); + void failureStateApproachFire ( const str& failureReason ); + + void setupStateApproachFirePause (); + BehaviorReturnCode_t evaluateStateApproachFirePause (); + void failureStateApproachFirePause ( const str& failureReason ); + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( CloseInOnEnemyWhileFiringWeapon ); + + CloseInOnEnemyWhileFiringWeapon(); + ~CloseInOnEnemyWhileFiringWeapon(); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + void setAnim ( const str &animName ); + void setTorsoAnim( const str &animName ); + void setDist ( float distance ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoEntity _chaseEnemy; + FireWeapon _fireWeapon; + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + CIWFStates_t _state; + EntityPtr _currentEnemy; + float _nextFireTime; + float _nextPauseTime; + Actor *_self; + +}; + +inline void CloseInOnEnemyWhileFiringWeapon::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_approachAnim ); + arc.ArchiveString ( &_aimAnim ); + arc.ArchiveString ( &_fireAnim ); + arc.ArchiveFloat ( &_fireTimeMin ); + arc.ArchiveFloat ( &_fireTimeMax ); + arc.ArchiveFloat ( &_pauseTimeMin ); + arc.ArchiveFloat ( &_pauseTimeMax ); + arc.ArchiveFloat ( &_dist ); + + // Archive Components + arc.ArchiveObject ( &_chaseEnemy ); + arc.ArchiveObject ( &_fireWeapon ); + + // Archive Member Variables + ArchiveEnum ( _state, CIWFStates_t); + arc.ArchiveSafePointer ( &_currentEnemy ); + arc.ArchiveFloat ( &_nextFireTime ); + arc.ArchiveFloat ( &_nextPauseTime ); + arc.ArchiveObjectPointer ( ( Class ** )&_self ); +} + +#endif /* __CLOSE_IN_ON_ENEMY_WHILE_FIRING_WEAPON_HPP__ */ diff --git a/dlls/game/closeInOnPlayer.cpp b/dlls/game/closeInOnPlayer.cpp new file mode 100644 index 0000000..535a025 --- /dev/null +++ b/dlls/game/closeInOnPlayer.cpp @@ -0,0 +1,356 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/closeInOnPlayer.cpp $ +// $Revision:: 4 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CloseInOnPlayer Implementation +// +// PARAMETERS: +// +// +// ANIMATIONS: +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "closeInOnPlayer.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, CloseInOnPlayer, NULL ) + { + { &EV_Behavior_Args, &CloseInOnPlayer::SetArgs }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: CloseInOnPlayer() +// Class: CloseInOnPlayer +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +CloseInOnPlayer::CloseInOnPlayer() +{ + _anim = ""; + _torsoAnim = ""; + _dist = 64.0f; +} + +//-------------------------------------------------------------- +// Name: ~CloseInOnPlayer() +// Class: CloseInOnPlayer +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +CloseInOnPlayer::~CloseInOnPlayer() +{ +} + + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: CloseInOnPlayer +// +// Description: +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void CloseInOnPlayer::SetArgs( Event *ev ) +{ + _anim = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + _dist = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + _torsoAnim = ev->GetString( 3 ); +} + + + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: CloseInOnPlayer +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void CloseInOnPlayer::Begin( Actor &self ) +{ + init( self ); +} + + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: CloseInOnPlayer +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t CloseInOnPlayer::Evaluate( Actor &self ) +{ + + BehaviorReturnCode_t stateResult; + + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case CLOSE_IN_ON_PLAYER_APPROACH: + //--------------------------------------------------------------------- + stateResult = evaluateStateApproach(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CLOSE_IN_ON_PLAYER_FAILED ); + + if ( stateResult == BEHAVIOR_FAILED_STEERING_NO_PATH ) + transitionToState( CLOSE_IN_ON_PLAYER_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CLOSE_IN_ON_PLAYER_SUCCESS ); + break; + + //--------------------------------------------------------------------- + case CLOSE_IN_ON_PLAYER_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + break; + + //--------------------------------------------------------------------- + case CLOSE_IN_ON_PLAYER_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: CloseInOnPlayer +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void CloseInOnPlayer::End(Actor &self) +{ + _chase.End( self ); +} + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: CloseInOnPlayer +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnPlayer::transitionToState( closeInOnPlayerStates_t state ) +{ + switch( state ) + { + case CLOSE_IN_ON_PLAYER_APPROACH: + setupStateApproach(); + setInternalState( state , "CLOSE_IN_ON_PLAYER_APPROACH" ); + break; + + case CLOSE_IN_ON_PLAYER_SUCCESS: + setInternalState( state , "CLOSE_IN_ON_PLAYER_SUCCESS" ); + break; + + case CLOSE_IN_ON_PLAYER_FAILED: + setInternalState( state , "CLOSE_IN_ON_PLAYER_FAILED" ); + break; + } +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: CloseInOnPlayer +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnPlayer::setInternalState( closeInOnPlayerStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: CloseInOnPlayer +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnPlayer::init( Actor &self ) +{ + _self = &self; + findPlayer(); + transitionToState(CLOSE_IN_ON_PLAYER_APPROACH); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: CloseInOnPlayer +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnPlayer::think() +{ +} + + +//-------------------------------------------------------------- +// Name: findPlayer() +// Class: CloseInOnPlayer +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnPlayer::findPlayer() +{ + Entity *player = (Entity*)GetPlayer(0); + + if ( !player || player->flags & FL_NOTARGET ) + { + SetFailureReason( "CloseInOnPlayer::findPlayer -- No Player" ); + transitionToState( CLOSE_IN_ON_PLAYER_FAILED ); + return; + } + + _player = player; + +} + +//-------------------------------------------------------------- +// Name: setTorsoAnim() +// Class: CloseInOnPlayer +// +// Description: Sets our Torso Animation +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnPlayer::setTorsoAnim() +{ + _self->SetAnim( _torsoAnim , NULL , torso ); +} + +//-------------------------------------------------------------- +// Name: setupStateApproach() +// Class: CloseInOnPlayer +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnPlayer::setupStateApproach() +{ + _chase.SetAnim( _anim ); + _chase.SetDistance( _dist ); + _chase.SetEntity( *_self, _player ); + + if ( _torsoAnim.length() > 0 ) + setTorsoAnim(); + + _chase.Begin( *_self ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateApproach() +// Class: CloseInOnPlayer +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t CloseInOnPlayer::evaluateStateApproach() +{ + return _chase.Evaluate( *_self ); +} + +//-------------------------------------------------------------- +// Name: failureStateApproach() +// Class: CloseInOnPlayer +// +// Description: Failure Handler for State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CloseInOnPlayer::failureStateApproach( const str& failureReason ) +{ +} diff --git a/dlls/game/closeInOnPlayer.hpp b/dlls/game/closeInOnPlayer.hpp new file mode 100644 index 0000000..d8bdcc8 --- /dev/null +++ b/dlls/game/closeInOnPlayer.hpp @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/closeInOnEnemy.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CloseInOnEnemy Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class CloseInOnPlayer; + +#ifndef __CLOSE_IN_ON_PLAYER_HPP__ +#define __CLOSE_IN_ON_PLAYER_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: CloseInOnPlayer +// Base Class: Behavior +// +// Description: Makes the actor move closer to the player enemy +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class CloseInOnPlayer : public Behavior +{ + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + CLOSE_IN_ON_PLAYER_APPROACH, + CLOSE_IN_ON_PLAYER_SUCCESS, + CLOSE_IN_ON_PLAYER_FAILED + } closeInOnPlayerStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: // Parameters + str _anim; + str _torsoAnim; + float _dist; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( closeInOnPlayerStates_t state ); + void setInternalState ( closeInOnPlayerStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void findPlayer (); + void setTorsoAnim (); + + void setupStateApproach (); + BehaviorReturnCode_t evaluateStateApproach (); + void failureStateApproach ( const str& failureReason ); + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( CloseInOnPlayer ); + + CloseInOnPlayer(); + ~CloseInOnPlayer(); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + void setAnim ( const str &animName ); + void setTorsoAnim( const str &animName ); + void setDist ( float distance ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoEntity _chase; + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + closeInOnPlayerStates_t _state; + EntityPtr _player; + Actor *_self; + +}; + +inline void CloseInOnPlayer::setAnim( const str &animName ) +{ + _anim = animName; +} + +inline void CloseInOnPlayer::setTorsoAnim( const str &animName ) +{ + _torsoAnim = animName; +} + +inline void CloseInOnPlayer::setDist( float distance ) +{ + _dist = distance; +} + +inline void CloseInOnPlayer::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_anim ); + arc.ArchiveString ( &_torsoAnim ); + arc.ArchiveFloat ( &_dist ); + + // Archive Components + arc.ArchiveObject ( &_chase ); + + // Archive Member Variables + ArchiveEnum ( _state, closeInOnPlayerStates_t); + arc.ArchiveSafePointer ( &_player ); + arc.ArchiveObjectPointer ( ( Class ** )&_self ); +} + +#endif /* CLOSE_IN_ON_PLAYER_HPP */ diff --git a/dlls/game/compiler.cpp b/dlls/game/compiler.cpp new file mode 100644 index 0000000..6164a00 --- /dev/null +++ b/dlls/game/compiler.cpp @@ -0,0 +1,1308 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/compiler.cpp $ +// $Revision:: 10 $ +// $Date:: 10/10/02 3:05p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "compiler.h" + +opcode_t pr_opcodes[] = +{ + { "", "DONE", -1, false, &def_entity, &def_entity, &def_void }, + + { "++", "UINC_F", 1, true, &def_float, &def_void, &def_void }, + { "--", "UDEC_F", 1, true, &def_float, &def_void, &def_void }, + + { "*", "MUL_F", 3, false, &def_float, &def_float, &def_float }, + { "*", "MUL_V", 3, false, &def_vector, &def_vector, &def_float }, + { "*", "MUL_FV", 3, false, &def_float, &def_vector, &def_vector }, + { "*", "MUL_VF", 3, false, &def_vector, &def_float, &def_vector }, + + { "/", "DIV", 3, false, &def_float, &def_float, &def_float }, + + { "+", "ADD_F", 4, false, &def_float, &def_float, &def_float }, + { "+", "ADD_V", 4, false, &def_vector, &def_vector, &def_vector }, + { "+", "ADD_S", 4, false, &def_string, &def_string, &def_string }, + { "+", "ADD_FS", 4, false, &def_float, &def_string, &def_string }, + { "+", "ADD_SF", 4, false, &def_string, &def_float, &def_string }, + { "+", "ADD_VS", 4, false, &def_vector, &def_string, &def_string }, + { "+", "ADD_SV", 4, false, &def_string, &def_vector, &def_string }, + + { "-", "SUB_F", 4, false, &def_float, &def_float, &def_float }, + { "-", "SUB_V", 4, false, &def_vector, &def_vector, &def_vector }, + + { "==", "EQ_F", 5, false, &def_float, &def_float, &def_float }, + { "==", "EQ_V", 5, false, &def_vector, &def_vector, &def_float }, + { "==", "EQ_S", 5, false, &def_string, &def_string, &def_float }, + { "==", "EQ_E", 5, false, &def_entity, &def_entity, &def_float }, + { "==", "EQ_FNC", 5, false, &def_function, &def_function, &def_float }, + + { "!=", "NE_F", 5, false, &def_float, &def_float, &def_float }, + { "!=", "NE_V", 5, false, &def_vector, &def_vector, &def_float }, + { "!=", "NE_S", 5, false, &def_string, &def_string, &def_float }, + { "!=", "NE_E", 5, false, &def_entity, &def_entity, &def_float }, + { "!=", "NE_FNC", 5, false, &def_function, &def_function, &def_float }, + + { "<=", "LE", 5, false, &def_float, &def_float, &def_float }, + { ">=", "GE", 5, false, &def_float, &def_float, &def_float }, + { "<", "LT", 5, false, &def_float, &def_float, &def_float }, + { ">", "GT", 5, false, &def_float, &def_float, &def_float }, + + { "=", "STORE_F", 6, true, &def_float, &def_float, &def_float }, + { "=", "STORE_V", 6, true, &def_vector, &def_vector, &def_vector }, + { "=", "STORE_S", 6, true, &def_string, &def_string, &def_string }, + { "=", "STORE_ENT", 6, true, &def_entity, &def_entity, &def_entity }, + { "=", "STORE_FNC", 6, true, &def_function, &def_function, &def_function }, + + { "=", "STORE_FTOS", 6, true, &def_string, &def_float, &def_string }, + + { "*=", "UMUL_F", 6, true, &def_float, &def_float, &def_void }, + { "/=", "UDIV_F", 6, true, &def_float, &def_float, &def_void }, + { "+=", "UADD_F", 6, true, &def_float, &def_float, &def_void }, + { "-=", "USUB_F", 6, true, &def_float, &def_float, &def_void }, + { "&=", "UAND_F", 6, true, &def_float, &def_float, &def_void }, + { "|=", "UOR_F", 6, true, &def_float, &def_float, &def_void }, + + { "", "RETURN", -1, false, &def_void, &def_void, &def_void }, + + { "!", "NOT_F", -1, false, &def_float, &def_void, &def_float }, + { "!", "NOT_V", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_S", -1, false, &def_vector, &def_void, &def_float }, + { "!", "NOT_ENT", -1, false, &def_entity, &def_void, &def_float }, + { "!", "NOT_FNC", -1, false, &def_function, &def_void, &def_float }, + + { "", "IF", -1, false, &def_float, &def_float, &def_void }, + { "", "IFNOT", -1, false, &def_float, &def_float, &def_void }, + + // calls returns REG_RETURN + { "", "CALL", -1, false, &def_function, &def_void, &def_void }, + { "", "OCALL", -1, false, &def_function, &def_entity, &def_void }, + { "", "THREAD", -1, false, &def_function, &def_void, &def_void }, + + { "", "PUSH_F", -1, false, &def_float, &def_void, &def_void }, + { "", "PUSH_V", -1, false, &def_vector, &def_void, &def_void }, + { "", "PUSH_S", -1, false, &def_string, &def_void, &def_void }, + { "", "PUSH_ENT", -1, false, &def_entity, &def_void, &def_void }, + { "", "PUSH_FNC", -1, false, &def_function, &def_void, &def_void }, + { "", "PUSH_FTOS", -1, false, &def_string, &def_void, &def_void }, + + { "", "GOTO", -1, false, &def_float, &def_void, &def_void }, + + { "&&", "AND", 7, false, &def_float, &def_float, &def_float }, + { "||", "OR", 7, false, &def_float, &def_float, &def_float }, + + { "&", "BITAND", 3, false, &def_float, &def_float, &def_float }, + { "|", "BITOR", 3, false, &def_float, &def_float, &def_float }, + + { NULL } +}; + +def_t def_ret; +def_t junkdef; + +#define FUNCTION_PRIORITY 2 +#define TOP_PRIORITY 7 +#define NOT_PRIORITY 5 + +Compiler::Compiler( Program &prg ) : + program( prg ), lex( prg ) + +{ + pr_scope = NULL; + callthread = false; + filenumber = 0; +} + +/* +============ +Statement + +Emits a primitive statement, returning the var it places it's value in +============ +*/ + +def_t *Compiler::Statement( const opcode_t *op, def_t *var_a, const def_t *var_b ) +{ + dstatement_t *statement; + def_t *var_c; + + if ( program.numstatements >= MAX_STATEMENTS ) + { + lex.ParseError( "Exceeded max statements." ); + } + + statement = &program.statements[ program.numstatements ]; + statement->linenumber = lex.SourceLine(); + statement->file = filenumber; + program.numstatements++; + + statement->op = op - pr_opcodes; + statement->a = var_a ? var_a->localofs : 0; + statement->b = var_b ? var_b->localofs : 0; + if ( ( op->type_c == &def_void ) || op->right_associative ) + { + // ifs, gotos, and assignments don't need vars allocated + var_c = NULL; + statement->c = 0; + } + else + { + // allocate result space + var_c = new def_t; + + var_c->ofs = program.numpr_globals; + var_c->localofs = var_c->ofs; + var_c->type = op->type_c->type; + var_c->name = resultstring; + + program.def_tail->next = var_c; + program.def_tail = var_c; + + statement->c = program.numpr_globals; + program.pr_global_defs[ program.numpr_globals ] = var_c; + + if ( var_c->type == &type_string ) + { + eval_t temp; + + VectorClear( temp.vector ); + temp.string = program.CopyString( NULL ); + memcpy( program.pr_globals + var_c->ofs, &temp, 4 * type_size[ var_c->type->type ] ); + } + + program.numpr_globals += type_size[ op->type_c->type->type ]; + } + + if ( op->right_associative ) + { + return var_a; + } + + return var_c; +} + +/* +============ +ParseImmediate + +Looks for a preexisting constant +============ +*/ +def_t *Compiler::ParseImmediate( void ) +{ + def_t *cn; + + // check for a constant with the same value + for( cn = program.def_head.next; cn; cn = cn->next ) + { + if ( cn->initialized != 2 ) + { + continue; + } + + if ( cn->type != lex.pr_immediate_type ) + { + continue; + } + + if ( lex.pr_immediate_type == &type_entity ) + { + if ( program.getInteger( cn->ofs ) == lex.pr_immediate.entity ) + { + lex.Lex(); + return cn; + } + } + else if ( lex.pr_immediate_type == &type_string ) + { + if ( !strcmp( program.getString( cn->ofs ), lex.pr_immediate_string ) ) + { + lex.Lex(); + return cn; + } + } + else if ( lex.pr_immediate_type == &type_float ) + { + if ( program.getFloat( cn->ofs ) == lex.pr_immediate._float ) + { + lex.Lex(); + + return cn; + } + } + else if ( lex.pr_immediate_type == &type_vector ) + { + if ( ( program.getFloat( cn->ofs ) == lex.pr_immediate.vector[ 0 ] ) && + ( program.getFloat( cn->ofs + 1 ) == lex.pr_immediate.vector[ 1 ] ) && + ( program.getFloat( cn->ofs + 2 ) == lex.pr_immediate.vector[ 2 ] ) ) + { + lex.Lex(); + + return cn; + } + } + else + { + lex.ParseError( "weird immediate type" ); + } + } + + // allocate a new one + cn = new def_t; + cn->next = NULL; + program.def_tail->next = cn; + program.def_tail = cn; + cn->type = lex.pr_immediate_type; + cn->name = immediatestring; + cn->initialized = 2; + cn->scope = NULL; // always share immediates + + // copy the immediate to the global area + cn->ofs = program.numpr_globals; + cn->localofs = cn->ofs; + program.pr_global_defs[ cn->ofs ] = cn; + program.numpr_globals += type_size[ lex.pr_immediate_type->type ]; + if ( lex.pr_immediate_type == &type_string ) + { + lex.pr_immediate.string = program.CopyString( lex.pr_immediate_string ); + } + + memcpy( program.pr_globals + cn->ofs, &lex.pr_immediate, 4 * type_size[ lex.pr_immediate_type->type ] ); + + lex.Lex(); + + return cn; +} + +/* +============ +ParseFunctionCall +============ +*/ +def_t *Compiler::ParseFunctionCall( def_t *func ) +{ + def_t *e; + int arg; + type_t *t; + opcode_t *op; + dstatement_t *patch; + int size; + bool callThreadForThisFunction; + + callThreadForThisFunction = callthread; + + callthread = false; + + t = func->type; + + if ( t->type != ev_function ) + { + lex.ParseError( "not a function" ); + } + + // copy the arguments to the global parameter variables + arg = 0; + size = 0; + if ( !lex.Check( ")" ) ) + { + do + { + if ( ( t->num_parms != -1 ) && ( arg >= t->num_parms ) ) + { + lex.ParseError( "too many parameters" ); + } + + e = Expression( TOP_PRIORITY ); + + if ( ( t->num_parms != -1 ) && ( e->type != t->parm_types[ arg ] ) ) + { + // special case for when we're storing a float in a string + if ( ( t->parm_types[ arg ]->type != ev_string ) || + ( e->type->type != ev_float ) ) + { + lex.ParseError( "type mismatch on parm %i", arg + 1 ); + } + } + + switch( e->type->type ) + { + case ev_string : + op = &pr_opcodes[ OP_PUSH_S ]; + + break; + + case ev_float : + if ( t->parm_types[ arg ]->type == ev_string ) + { + op = &pr_opcodes[ OP_PUSH_FTOS ]; + } + else + { + op = &pr_opcodes[ OP_PUSH_F ]; + } + break; + + case ev_vector : + op = &pr_opcodes[ OP_PUSH_V ]; + break; + + case ev_entity : + op = &pr_opcodes[ OP_PUSH_ENT ]; + break; + + case ev_function : + op = &pr_opcodes[ OP_PUSH_FNC ]; + break; + + default: + op = NULL; // shut up compiler + lex.ParseError( "No appropriate operator for storing type '%s'", e->type->def->name.c_str() ); + break; + } + + size += type_size[ e->type->type ]; + + Statement( op, e, 0 ); + + arg++; + } + while( lex.Check( "," ) ); + + if ( ( t->num_parms != -1 ) && ( arg < t->min_parms ) ) + { + lex.ParseError( "too few parameters" ); + } + + lex.Expect( ")" ); + } + + if ( ( t->num_parms != -1 ) && ( arg < t->min_parms ) ) + { + lex.ParseError( "too few parameters" ); + } + + if ( arg > MAX_PARMS ) + { + lex.ParseError( "More than %d parameters", MAX_PARMS ); + } + + patch = &program.statements[ program.numstatements ]; + if ( callThreadForThisFunction ) + { + if ( func->initialized && program.functions[ program.getFunction( func->ofs ) ].eventnum ) + { + lex.ParseError( "Built-in functions cannot be called as threads" ); + } + Statement( &pr_opcodes[ OP_THREAD ], func, 0 ); + callthread = false; + + // threads return the thread number + def_ret.type = &type_float; + } + else + { + Statement( &pr_opcodes[ OP_CALL ], func, 0 ); + def_ret.type = t->aux_type; + } + patch->b = size; + + return &def_ret; +} + +/* +============ +ParseObjectCall +============ +*/ +def_t *Compiler::ParseObjectCall( def_t *func, def_t *object ) +{ + def_t *e; + int arg; + type_t *t; + opcode_t *op; + dstatement_t *patch; + int size; + + t = func->type; + + if ( t->type != ev_function ) + { + lex.ParseError( "not a function" ); + } + + lex.Check( "(" ); + + // copy the arguments to the global parameter variables + arg = 0; + size = 0; + if ( !lex.Check( ")" ) ) + { + do + { + if ( ( t->num_parms != -1 ) && ( arg >= t->num_parms ) ) + { + lex.ParseError( "too many parameters" ); + } + + e = Expression( TOP_PRIORITY ); + + if ( ( t->num_parms != -1 ) && ( e->type != t->parm_types[ arg ] ) ) + { + // special case for when we're storing a float in a string + if ( ( t->parm_types[ arg ]->type != ev_string ) || + ( e->type->type != ev_float ) ) + { + lex.ParseError( "type mismatch on parm %i", arg + 1 ); + } + } + + switch( e->type->type ) + { + case ev_string : + op = &pr_opcodes[ OP_PUSH_S ]; + + break; + + case ev_float : + if ( t->parm_types[ arg ]->type == ev_string ) + { + op = &pr_opcodes[ OP_PUSH_FTOS ]; + } + else + { + op = &pr_opcodes[ OP_PUSH_F ]; + } + break; + + case ev_vector : + op = &pr_opcodes[ OP_PUSH_V ]; + break; + + case ev_entity : + op = &pr_opcodes[ OP_PUSH_ENT ]; + break; + + case ev_function : + op = &pr_opcodes[ OP_PUSH_FNC ]; + break; + + default: + op = NULL; // shut up compiler + lex.ParseError( "No appropriate operator for storing type '%s'", e->type->def->name.c_str() ); + break; + } + + size += type_size[ e->type->type ]; + + Statement( op, e, 0 ); + + arg++; + } + while( lex.Check( "," ) ); + + if ( ( t->num_parms != -1 ) && ( arg < t->min_parms ) ) + { + lex.ParseError( "too few parameters" ); + } + + lex.Expect( ")" ); + } + + if ( ( t->num_parms != -1 ) && ( arg < t->min_parms ) ) + { + lex.ParseError( "too few parameters" ); + } + + if ( arg > MAX_PARMS ) + { + lex.ParseError( "More than %d parameters", MAX_PARMS ); + } + + patch = &program.statements[ program.numstatements ]; + Statement( &pr_opcodes[ OP_OCALL ], func, object ); + patch->c = size; + + def_ret.type = t->aux_type; + + return &def_ret; +} + +/* +============ +ParseValue + +Returns the global ofs for the current token +============ +*/ +def_t *Compiler::ParseValue( void ) +{ + def_t *d; + char *name; + + // if the token is an immediate, allocate a constant for it + if ( lex.pr_token_type == tt_immediate ) + { + return ParseImmediate(); + } + + if ( lex.Check( "thread" ) ) + { + callthread = true; + } + + name = lex.ParseName(); + + // look through the defs + d = program.GetDef( NULL, name, pr_scope, false, &lex ); + if ( !d ) + { + lex.ParseError( "Unknown value \"%s\"", name ); + } + + return d; +} + +/* +============ +Term +============ +*/ +def_t *Compiler::Term( void ) +{ + def_t *e; + int op; + etype_t t; + + if ( lex.Check( "!" ) ) + { + e = Expression( NOT_PRIORITY ); + t = e->type->type; + switch( t ) + { + case ev_float : + op = OP_NOT_F; + break; + + case ev_string : + op = OP_NOT_S; + break; + + case ev_entity : + op = OP_NOT_ENT; + break; + + case ev_vector : + op = OP_NOT_V; + break; + + case ev_function : + op = OP_NOT_FNC; + break; + + default : + lex.ParseError( "type mismatch for !" ); + + // shut up compiler + op = OP_NOT_FNC; + break; + } + + return Statement( &pr_opcodes[ op ], e, 0 ); + } + + if ( lex.Check( "(" ) ) + { + e = Expression( TOP_PRIORITY ); + lex.Expect( ")" ); + + return e; + } + + return ParseValue(); +} + +/* +============== +Expression +============== +*/ +def_t *Compiler::Expression( int priority ) +{ + opcode_t *op; + opcode_t *oldop; + def_t *e; + def_t *e2; + etype_t type_a; + etype_t type_b; + char *name; + + if ( priority == 0 ) + { + return Term(); + } + + e = Expression( priority - 1 ); + + while( 1 ) + { + if ( priority == FUNCTION_PRIORITY ) + { + if ( lex.Check( "(" ) ) + { + return ParseFunctionCall( e ); + } + else if ( ( ( e->type->type == ev_entity ) || + ( ( e->type->type == ev_void ) && ( e->ofs < OFS_END ) ) ) && + ( lex.Check( "." ) ) ) + { + if ( lex.pr_token_type != tt_name ) + { + lex.ParseError( "Expecting function call" ); + } + + name = lex.ParseName(); + + // look through the defs + e2 = program.GetDef( NULL, name, pr_scope, false, &lex ); + if ( !e2 ) + { + lex.ParseError( "Unknown value \"%s\"", name ); + } + else if ( e2->type->type != ev_function ) + { + lex.ParseError( "\"%s\" is not a valid function name", name ); + } + + return ParseObjectCall( e2, e ); + } + + //else if ( lex.Check( ";" ) ) + //{ + // lex.ParseError( "Invalid command - %s", e->name.c_str() ); + //} + } + + for( op = pr_opcodes; op->name; op++ ) + { + if ( op->priority != priority ) + { + continue; + } + + if ( !lex.Check( op->name ) ) + { + continue; + } + + if ( op->type_b == &def_void ) + { + e = Statement( op, e, 0 ); + return e; + } + + if ( op->right_associative ) + { + e2 = Expression( priority ); + } + else + { + e2 = Expression( priority - 1 ); + } + + // type check + type_a = e->type->type; + type_b = e2->type->type; + + oldop = op; + while( ( type_a != op->type_a->type->type ) || + ( type_b != op->type_b->type->type ) ) + { + op++; + if ( !op->name || strcmp( op->name, oldop->name ) ) + { + lex.ParseError( "type mismatch for %s", oldop->name ); + } + } + + if ( op->right_associative ) + { + e = Statement( op, e2, e ); + } + else + { + e = Statement( op, e, e2 ); + } + break; + } + + if ( !op->name ) + { + // next token isn't at this priority level + break; + } + } + + return e; +} + +/* +============ +ParseStatement + +============ +*/ +void Compiler::ParseStatement( void ) +{ + def_t *e; + dstatement_t *patch1; + dstatement_t *patch2; + dstatement_t *patch3; + dstatement_t *patch4; + etype_t type_a; + etype_t type_b; + opcode_t *op; + + if ( lex.Check( "{" ) ) + { + do + { + ParseStatement(); + } + while( !lex.Check( "}" ) ); + + return; + } + + if ( lex.Check( "return" ) ) + { + if ( lex.Check( ";" ) ) + { + if ( pr_scope->type->aux_type->type != ev_void ) + { + lex.ParseError( "expecting return value" ); + } + + Statement( &pr_opcodes[ OP_RETURN ], 0, 0 ); + return; + } + + e = Expression( TOP_PRIORITY ); + lex.Expect( ";" ); + + type_a = e->type->type; + type_b = pr_scope->type->aux_type->type; + + if ( type_a == type_b ) + { + Statement( &pr_opcodes[ OP_RETURN ], e, 0 ); + + return; + } + + for( op = pr_opcodes; op->name; op++ ) + { + if ( !strcmp( op->name, "=" ) ) + { + break; + } + } + + assert( op->name ); + + while( ( type_a != op->type_a->type->type ) || + ( type_b != op->type_b->type->type ) ) + { + op++; + if ( !op->name || strcmp( op->name, "=" ) ) + { + lex.ParseError( "type mismatch for return value" ); + } + } + + def_ret.type = pr_scope->type->aux_type; + Statement( op, e, &def_ret ); + Statement( &pr_opcodes[ OP_RETURN ], 0, 0 ); + + return; + } + + if ( lex.Check( "while" ) ) + { + lex.Expect( "(" ); + + patch2 = &program.statements[ program.numstatements ]; + e = Expression( TOP_PRIORITY ); + lex.Expect( ")" ); + patch1 = &program.statements[ program.numstatements ]; + + Statement( &pr_opcodes[ OP_IFNOT ], e, 0 ); + ParseStatement(); + + junkdef.ofs = patch2 - &program.statements[ program.numstatements ]; + junkdef.localofs = junkdef.ofs; + Statement( &pr_opcodes[ OP_GOTO ], &junkdef, 0 ); + patch1->b = &program.statements[ program.numstatements ] - patch1; + + return; + } + + if ( lex.Check( "for" ) ) + { + lex.Expect( "(" ); + + // init + if ( !lex.Check( ";" ) ) + { + do + { + Expression( TOP_PRIORITY ); + } + while( lex.Check( "," ) ); + + lex.Expect( ";" ); + } + + // condition + patch2 = &program.statements[ program.numstatements ]; + + e = Expression( TOP_PRIORITY ); + lex.Expect( ";" ); + + patch1 = &program.statements[ program.numstatements ]; + Statement( &pr_opcodes[ OP_IFNOT ], e, 0 ); + + // counter + if ( !lex.Check( ")" ) ) + { + patch3 = &program.statements[ program.numstatements ]; + Statement( &pr_opcodes[ OP_IF ], e, 0 ); + + patch4 = patch2; + patch2 = &program.statements[ program.numstatements ]; + do + { + Expression( TOP_PRIORITY ); + } + while( lex.Check( "," ) ); + lex.Expect( ")" ); + + // goto patch4 + junkdef.ofs = patch4 - &program.statements[ program.numstatements ]; + junkdef.localofs = junkdef.ofs; + Statement( &pr_opcodes[ OP_GOTO ], &junkdef, 0 ); + + // fixup patch3 + patch3->b = &program.statements[ program.numstatements ] - patch3; + } + + ParseStatement(); + + // goto patch2 + junkdef.ofs = patch2 - &program.statements[ program.numstatements ]; + junkdef.localofs = junkdef.ofs; + Statement( &pr_opcodes[ OP_GOTO ], &junkdef, 0 ); + + // fixup patch1 + patch1->b = &program.statements[ program.numstatements ] - patch1; + + return; + } + + if ( lex.Check( "do" ) ) + { + patch1 = &program.statements[ program.numstatements ]; + ParseStatement(); + lex.Expect( "while" ); + lex.Expect( "(" ); + e = Expression( TOP_PRIORITY ); + lex.Expect( ")" ); + lex.Expect( ";" ); + + junkdef.ofs = patch1 - &program.statements[ program.numstatements ]; + junkdef.localofs = junkdef.ofs; + Statement( &pr_opcodes[ OP_IF ], e, &junkdef ); + return; + } + + if ( lex.CheckType() != NULL ) + { + ParseDefs(); + program.locals_end = program.numpr_globals; + + return; + } + + if ( lex.Check( "if" ) ) + { + lex.Expect( "(" ); + e = Expression( TOP_PRIORITY ); + lex.Expect( ")" ); + + patch1 = &program.statements[ program.numstatements ]; + Statement( &pr_opcodes[ OP_IFNOT ], e, 0 ); + + ParseStatement(); + + if ( lex.Check( "else" ) ) + { + patch2 = &program.statements[ program.numstatements ]; + Statement( &pr_opcodes[ OP_GOTO ], 0, 0 ); + patch1->b = &program.statements[ program.numstatements ] - patch1; + ParseStatement(); + patch2->a = &program.statements[ program.numstatements ] - patch2; + } + else + { + patch1->b = &program.statements[ program.numstatements ] - patch1; + } + + return; + } + + Expression( TOP_PRIORITY ); + lex.Expect(";"); +} + +/* +============ +ParseImmediateStatements + +Parse a function body +============ +*/ +function_t Compiler::ParseImmediateStatements( const type_t *type, const char parm_names[ MAX_PARMS ][ MAX_NAME ], + int numparms ) +{ + int i; + function_t f; + def_t *defs[ MAX_PARMS ]; + + // + // define the parms + // + for( i = 0; i < numparms; i++ ) + { + defs[ i ] = program.GetDef( type->parm_types[ i ], parm_names[ i ], pr_scope, true, &lex ); + + f.parm_ofs[ i ] = defs[ i ]->ofs; + if ( ( i > 0 ) && ( f.parm_ofs[ i ] < f.parm_ofs[ i - 1 ] ) ) + { + gi.Error( ERR_DROP, "bad parm order" ); + } + } + + f.code = program.numstatements; + + // + // parse regular statements + // + // we've already passed the { + //lex.Expect( "{" ); + + while( !lex.Check( "}" ) ) + { + ParseStatement(); + } + + // emit an end of statements opcode + Statement( pr_opcodes, 0, 0 ); + + return f; +} + +/* +============ +ParseFunction + +Parses a function type +============ +*/ +type_t *Compiler::ParseFunction( type_t *returntype, char parm_names[ MAX_PARMS ][ MAX_NAME ], int *num_parms ) +{ + type_t newtype; + type_t *type; + char *name; + + // function type + memset( &newtype, 0, sizeof( newtype ) ); + + newtype.type = ev_function; + newtype.aux_type = returntype; // return type + newtype.num_parms = 0; + newtype.min_parms = -1; + + if ( !lex.Check( ")" ) ) + { + if ( lex.Check( "..." ) ) + { + newtype.num_parms = -1; // variable args + } + else + { + do + { + if ( newtype.num_parms >= MAX_PARMS ) + { + lex.ParseError( "Too many arguments for function call." ); + } + + type = lex.ParseType(); + name = lex.ParseName(); + strcpy( parm_names[ newtype.num_parms ], name ); + newtype.parm_types[ newtype.num_parms ] = type; + newtype.num_parms++; + } + while( lex.Check( "," ) ); + } + + lex.Expect( ")" ); + } + + if ( num_parms ) + { + *num_parms = newtype.num_parms; + } + + return program.FindType( &newtype ); +} + +void Compiler::ParseFunctionDef( type_t *type, const char *name ) +{ + def_t *def; + function_t f; + dfunction_t *df; + int i; + char parm_names[ MAX_PARMS ][ MAX_NAME ]; + int numparms; + + if ( pr_scope ) + { + lex.ParseError( "Functions must be global" ); + } + + type = ParseFunction( type, parm_names, &numparms ); + def = program.GetDef( type, name, NULL, true, &lex ); + + // check if this is a prototype or declaration + if ( !lex.Check( "{" ) ) + { + // it's just a prototype, so get the ; and move on + lex.Expect( ";" ); + return; + } + + if ( def->initialized ) + { + lex.ParseError( "%s redeclared", name ); + } + + program.locals_start = program.numpr_globals; + program.locals_end = program.numpr_globals; + + pr_scope = def; + f = ParseImmediateStatements( type, parm_names, numparms ); + pr_scope = NULL; + + program.locals_end = program.numpr_globals; + + def->initialized = 1; + + if ( program.numfunctions >= MAX_FUNCTIONS ) + { + lex.ParseError( "Exceeded max functions." ); + gi.Error( ERR_DROP, "Compile failed." ); + } + + program.setFunction( def->ofs, program.numfunctions ); + + // fill in the dfunction + df = &program.functions[ program.numfunctions ]; + program.numfunctions++; + + df->eventnum = 0; + df->first_statement = f.code; + df->s_name = def->name; + df->s_file = program.s_file; + df->numparms = def->type->num_parms; + df->minparms = def->type->min_parms; + df->locals = program.locals_end - program.locals_start; + df->parm_start = program.locals_start; + df->parm_total = 0; + + for( i = 0; i < df->numparms; i++ ) + { + df->parm_size[ i ] = type_size[ def->type->parm_types[ i ]->type ]; + df->parm_total += df->parm_size[ i ]; + } + + program.locals_start = program.locals_end; +} + +void Compiler::ParseVariableDef( type_t *type, const char *name ) +{ + def_t *def; + + def = program.GetDef( type, name, pr_scope, true, &lex ); + + // check for an initialization + if ( lex.Check( "=" ) ) + { + if ( def->initialized ) + { + lex.ParseError( "%s redeclared", name ); + } + + if ( lex.pr_immediate_type != type ) + { + lex.ParseError( "wrong immediate type for %s", name ); + } + + def->initialized = 1; + + if ( type == &type_string ) + { + lex.pr_immediate.string = program.CopyString( lex.pr_immediate_string ); + + if ( pr_scope ) + { + //Statement( &pr_opcodes[ OP_STORE_S ], immediate, def ); + gi.WDPrintf( "Declaring and initializing a string at the same on the stack doesn't work right now.\n Please change line %d\n", lex.SourceLine() ); + } + } + + memcpy( program.pr_globals + def->ofs, &lex.pr_immediate, 4 * type_size[ lex.pr_immediate_type->type ] ); + + lex.Lex(); + } + else if ( type == &type_string ) + { + eval_t temp; + + VectorClear( temp.vector ); + temp.string = program.CopyString( NULL ); + memcpy( program.pr_globals + def->ofs, &temp, 4 * type_size[ type->type ] ); + } +} + +/* +================ +ParseDefs + +Called at the outer layer and when a local statement is hit +================ +*/ +void Compiler::ParseDefs( void ) +{ + str name; + type_t *type; + + if ( lex.pr_token_type == tt_preprocessor ) + { + if ( lex.Check( "#include" ) ) + { + if ( pr_scope ) + { + lex.ParseError( "#include can only be used globally." ); + } + + if ( ( lex.pr_token_type != tt_immediate ) || ( lex.pr_immediate_type != &type_string ) ) + { + lex.ParseError( "Expected quoted filename" ); + } + + program.Compile( lex.pr_immediate_string ); + lex.Lex(); + + return; + } + else + { + lex.ParseError( "Unknown preprocessor command" ); + } + + return; + } + + type = lex.ParseType(); + name = lex.ParseName(); + + // check for a function prototype or declaraction + if ( lex.Check( "(" ) ) + { + ParseFunctionDef( type, name.c_str() ); + } + else + { + ParseVariableDef( type, name.c_str() ); + while( lex.Check( "," ) ) + { + name = lex.ParseName(); + ParseVariableDef( type, name.c_str() ); + } + lex.Expect( ";" ); + } +} + +/* +============ +CompileFile + +compiles the 0 terminated text, adding defintions to the pr structure +============ +*/ +bool Compiler::CompileFile( const char *text, int filenum ) +{ + filenumber = filenum; + + callthread = false; + + def_ret.ofs = OFS_RETURN; + def_ret.localofs = def_ret.ofs; + + lex.SetSource( text ); + + // read first token + lex.Lex(); + + while( lex.pr_token_type != tt_eof ) + { + try + { + // outside all functions + pr_scope = NULL; + + ParseDefs(); + } + + catch( ... ) + { + if ( ++program.pr_error_count > scr_maxerrors->integer ) + { + break; + } + + if ( pr_scope ) + { + lex.SkipOutOfFunction(); + } + else + { + lex.SkipToSemicolon(); + } + } + } + + return( program.pr_error_count == 0 ); +} diff --git a/dlls/game/compiler.h b/dlls/game/compiler.h new file mode 100644 index 0000000..02a27e4 --- /dev/null +++ b/dlls/game/compiler.h @@ -0,0 +1,199 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/compiler.h $ +// $Revision:: 4 $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// +// +// Form of for statement with a counter: +// +// a = 0; +// start: << patch4 +// if ( !( a < 10 ) ) +// { +// goto end; << patch1 +// } +// else +// { +// goto process; << patch3 +// } +// +// increment: << patch2 +// a = a + 1; +// goto start; << goto patch4 +// +// process: +// statements; +// goto increment; << goto patch2 +// +// end: +// +// Form of for statement without a counter: +// +// a = 0; +// start: << patch2 +// if ( !( a < 10 ) ) +// { +// goto end; << patch1 +// } +// +// process: +// statements; +// goto increment; << goto patch2 +// +// end: +// + +#ifndef __COMPILER_H__ +#define __COMPILER_H__ + +#include "program.h" +#include "lexer.h" + +#define MAX_ERRORS 10 + +typedef struct + { + int code; // first statement + char *file; // source file with definition + int file_line; + int parm_ofs[ MAX_PARMS ]; // allways contiguous, right? + } function_t; + +typedef struct + { + const char *name; + const char *opname; + float priority; + bool right_associative; + def_t *type_a; + def_t *type_b; + def_t *type_c; + } opcode_t; + +extern opcode_t pr_opcodes[ 99 ]; // sized by initialization + +enum + { + OP_DONE, + + OP_UINC_F, + OP_UDEC_F, + + OP_MUL_F, + OP_MUL_V, + OP_MUL_FV, + OP_MUL_VF, + OP_DIV_F, + OP_ADD_F, + OP_ADD_V, + OP_ADD_S, + OP_ADD_FS, + OP_ADD_SF, + OP_ADD_VS, + OP_ADD_SV, + OP_SUB_F, + OP_SUB_V, + + OP_EQ_F, + OP_EQ_V, + OP_EQ_S, + OP_EQ_E, + OP_EQ_FNC, + + OP_NE_F, + OP_NE_V, + OP_NE_S, + OP_NE_E, + OP_NE_FNC, + + OP_LE, + OP_GE, + OP_LT, + OP_GT, + + OP_STORE_F, + OP_STORE_V, + OP_STORE_S, + OP_STORE_ENT, + OP_STORE_FNC, + + OP_STORE_FTOS, + + OP_UMUL_F, + OP_UDIV_F, + OP_UADD_F, + OP_USUB_F, + OP_UAND_F, + OP_UOR_F, + + OP_RETURN, + OP_NOT_F, + OP_NOT_V, + OP_NOT_S, + OP_NOT_ENT, + OP_NOT_FNC, + OP_IF, + OP_IFNOT, + + OP_CALL, + OP_OCALL, + OP_THREAD, + + OP_PUSH_F, + OP_PUSH_V, + OP_PUSH_S, + OP_PUSH_ENT, + OP_PUSH_FNC, + OP_PUSH_FTOS, + + OP_GOTO, + OP_AND, + OP_OR, + + OP_BITAND, + OP_BITOR + }; + +class Compiler + { + public : + def_t *pr_scope; // the function being parsed, or NULL + + Program &program; + Lexer lex; + + bool callthread; + + int filenumber; + + Compiler( Program &prg ); + + def_t *Statement( const opcode_t *op, def_t *var_a, const def_t *var_b ); + def_t *ParseImmediate( void ); + def_t *ParseFunctionCall( def_t *func ); + def_t *ParseObjectCall( def_t *func, def_t *object ); + def_t *ParseValue( void ); + def_t *Term( void ); + def_t *Expression( int priority ); + void ParseStatement( void ); + function_t ParseImmediateStatements( const type_t *type, const char parm_names[ MAX_PARMS ][ MAX_NAME ], int numparms ); + type_t *ParseFunction( type_t *returntype, char parm_names[ MAX_PARMS ][ MAX_NAME ], int *numparms ); + void ParseFunctionDef( type_t *type, const char *name ); + void ParseVariableDef( type_t *type, const char *name ); + void ParseDefs( void ); + //bool CompileFile( const char *text, const char *filename, int filenum ); + bool CompileFile( const char *text, int filenum ); + }; + +#endif diff --git a/dlls/game/container.h b/dlls/game/container.h new file mode 100644 index 0000000..6d263ed --- /dev/null +++ b/dlls/game/container.h @@ -0,0 +1,620 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/container.h $ +// $Revision:: 12 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class for a dynamic array. Allows adding, removing, index of, +// and finding of entries with specified value. NOTE: indices in container +// are 1 based, not 0. This means that loops must check from 1 up to and including +// NumObjects() (ei. for( i = 1; i <= list.NumObjects(); i++ ) ). +// +// FIXME: Someday make this 0 based and update all code to match. +// +// WARNING: This file is shared between game, cgame and possibly the user interface. +// It is instanced in each one of these directories because of the way that SourceSafe works. +// + +#ifndef __CONTAINER_H__ +#define __CONTAINER_H__ + +#include + +#if defined( GAME_DLL ) +// +// game dll specific defines +// +#include "g_local.h" + +#define CONTAINER_Error gi.Error +#define CONTAINER_DPrintf gi.DPrintf +#define CONTAINER_WDPrintf gi.WDPrintf + +#elif defined ( CGAME_DLL ) +// +// cgame dll specific defines +// +#define CONTAINER_Error cgi.Error +#define CONTAINER_DPrintf cgi.DPrintf +#define CONTAINER_WDPrintf cgi.WDPrintf + +#else + +// +// client specific defines +// +#define CONTAINER_Error Com_Error +#define CONTAINER_DPrintf Com_DPrintf +#define CONTAINER_WDPrintf Com_WDPrintf +#endif + +#include + +class Archiver; +template< class Type > +class Container + { + private: + Type *objlist; + int numobjects; + int maxobjects; + + public: + Container(); + ~Container(); + + void FreeObjectList( void ); + void ClearObjectList( void ); + int NumObjects( void ) const; + void Resize( int maxelements ); + void SetObjectAt( int index, const Type& obj ); + int AddObject( const Type& obj ); + int AddUniqueObject( const Type& obj ); + void AddObjectAt( int index, const Type& obj ); + int IndexOfObject( const Type& obj ); + int IndexOfObject( const Type& obj ) const; + qboolean ObjectInList( const Type& obj ); + Type& ObjectAt( const int index ) const; + Type *AddressOfObjectAt( int index ); + void InsertObjectAt( int index, const Type& obj ); + void RemoveObjectAt( int index ); + void RemoveObject( const Type& obj ); + void Sort( int ( __cdecl *compare )( const void *elem1, const void *elem2 ) ); + Type& operator[]( const int index ) const; +#if defined( GAME_DLL ) + void Archive( Archiver &arc ); +#endif + }; + +template< class Type > +Container::Container() + { + objlist = NULL; + FreeObjectList(); + } + +template< class Type > +Container::~Container() + { + FreeObjectList(); + } + +template< class Type > +void Container::FreeObjectList + ( + void + ) + + { + if ( objlist ) + { + delete[] objlist; + } + objlist = NULL; + numobjects = 0; + maxobjects = 0; + } + +template< class Type > +void Container::ClearObjectList + ( + void + ) + + { + // only delete the list if we have objects in it + if ( objlist && numobjects ) + { + delete[] objlist; + + if ( maxobjects == 0 ) + { + objlist = NULL; + return; + } + + objlist = new Type[ maxobjects ]; + numobjects = 0; + } + } + +template< class Type > +int Container::NumObjects + ( + void + ) const + + { + return numobjects; + } + +template< class Type > +void Container::Resize + ( + int maxelements + ) + + { + Type *temp; + int i; + + assert( maxelements >= 0 ); + + if ( maxelements <= 0 ) + { + FreeObjectList(); + return; + } + + if ( !objlist ) + { + maxobjects = maxelements; + objlist = new Type[ maxobjects ]; + } + else + { + temp = objlist; + maxobjects = maxelements; + if ( maxobjects < numobjects ) + { + maxobjects = numobjects; + } + + objlist = new Type[ maxobjects ]; + for( i = 0; i < numobjects; i++ ) + { + objlist[ i ] = temp[ i ]; + } + delete[] temp; + } + } + +template< class Type > +void Container::SetObjectAt + ( + int index, + const Type& obj + ) + + { + if ( !objlist ) + return; + + if ( ( index <= 0 ) || ( index > numobjects ) ) + { + CONTAINER_Error( ERR_DROP, "Container::SetObjectAt : index out of range" ); + } + + objlist[ index - 1 ] = obj; + } + +template< class Type > +int Container::AddObject + ( + const Type& obj + ) + + { + if ( !objlist ) + { + Resize( 10 ); + } + + if ( numobjects == maxobjects ) + { + Resize( maxobjects * 2 ); + } + + objlist[ numobjects ] = obj; + numobjects++; + + return numobjects; + } + +template< class Type > +int Container::AddUniqueObject + ( + const Type& obj + ) + + { + int index; + + index = IndexOfObject( obj ); + if ( !index ) + index = AddObject( obj ); + return index; + } + +template< class Type > +void Container::AddObjectAt + ( + int index, + const Type& obj + ) + + { + // + // this should only be used when reconstructing a list that has to be identical to the original + // + if ( index > maxobjects ) + { + Resize( index ); + } + if ( index > numobjects ) + { + numobjects = index; + } + SetObjectAt( index, obj ); + } + +template< class Type > +int Container::IndexOfObject + ( + const Type& obj + ) + + { + int i; + + if ( !objlist ) + return 0; + + for( i = 0; i < numobjects; i++ ) + { + if ( objlist[ i ] == obj ) + { + return i + 1; + } + } + + return 0; + } + +template< class Type > +int Container::IndexOfObject + ( + const Type& obj + ) const + + { + int i; + + if ( !objlist ) + return 0; + + for( i = 0; i < numobjects; i++ ) + { + if ( objlist[ i ] == obj ) + { + return i + 1; + } + } + + return 0; + } + +template< class Type > +qboolean Container::ObjectInList + ( + const Type& obj + ) + + { + if ( !IndexOfObject( obj ) ) + { + return false; + } + + return true; + } + +template< class Type > +Type& Container::ObjectAt + ( + int index + ) const + + { + if ( ( index <= 0 ) || ( index > numobjects ) ) + { + CONTAINER_Error( ERR_DROP, "Container::ObjectAt : index out of range" ); + } + + return objlist[ index - 1 ]; + } + +template< class Type > +Type * Container::AddressOfObjectAt + ( + int index + ) + + { + // + // this should only be used when reconstructing a list that has to be identical to the original + // + if ( index > maxobjects ) + { + CONTAINER_Error( ERR_DROP, "Container::AddressOfObjectAt : index is greater than maxobjects" ); + } + if ( index > numobjects ) + { + numobjects = index; + } + return &objlist[ index - 1 ]; + } + +template< class Type > +void Container::InsertObjectAt + ( + int index, + const Type& obj + ) + + { + if ( ( index <= 0 ) || ( index > numobjects + 1 ) ) + { + CONTAINER_Error( ERR_DROP, "Container::RemoveObjectAt : index out of range" ); + return; + } + + numobjects++; + int arrayIndex=index-1; + + if (numobjects > maxobjects) + { + maxobjects = numobjects; + if ( !objlist ) + { + objlist = new Type[ maxobjects ]; + assert(arrayIndex < maxobjects); + assert(arrayIndex >= 0); + objlist[ arrayIndex ] = obj; + return; + } + else + { + Type *temp = objlist; + if ( maxobjects < numobjects ) + { + maxobjects = numobjects; + } + + objlist = new Type[ maxobjects ]; + int i; + for( i = arrayIndex - 1; i >= 0 ; i-- ) + { + assert(i < maxobjects); + assert(i >= 0); + objlist[ i ] = temp[ i ]; + } + + assert(arrayIndex < maxobjects); + assert(arrayIndex >= 0); + objlist[ arrayIndex ] = obj; + for( i = numobjects - 1; i > arrayIndex; i-- ) + { + assert(i < maxobjects); + assert(i >= 0); + objlist[ i ] = temp[ i-1 ]; + } + delete[] temp; + } + } + else + { + for( int i = numobjects - 1; i > arrayIndex; i-- ) + { + assert(i < maxobjects); + assert(i >= 0); + objlist[ i ] = objlist[ i - 1 ]; + } + objlist[ arrayIndex ] = obj; + } + } + +template< class Type > +void Container::RemoveObjectAt + ( + int index + ) + + { + int i; + + if ( !objlist ) + { + CONTAINER_WDPrintf( "Container::RemoveObjectAt : Empty list\n" ); + return; + } + + if ( ( index <= 0 ) || ( index > numobjects ) ) + { + CONTAINER_Error( ERR_DROP, "Container::RemoveObjectAt : index out of range" ); + return; + } + + i = index - 1; + numobjects--; + for( i = index - 1; i < numobjects; i++ ) + { + objlist[ i ] = objlist[ i + 1 ]; + } + } + +template< class Type > +void Container::RemoveObject + ( + const Type& obj + ) + + { + int index; + + index = IndexOfObject( obj ); + if ( !index ) + { + CONTAINER_WDPrintf( "Container::RemoveObject : Object not in list\n" ); + return; + } + + RemoveObjectAt( index ); + } + +template< class Type > +void Container::Sort + ( + int ( __cdecl *compare )( const void *elem1, const void *elem2 ) + ) + + { + if ( !objlist ) + { + CONTAINER_WDPrintf( "Container::Sort : Empty list\n" ); + return; + } + + qsort( ( void * )objlist, ( size_t )numobjects, sizeof( Type ), compare ); + } + +template< class Type > +Type& Container::operator[]( const int index ) const +{ + return ObjectAt( index + 1 ); +} + +#if 0 +#if defined( GAME_DLL ) + +#include "str.h" +void Container::Archive + ( + Archiver &arc + ) + { + int i, num; + + if ( arc.Loading() ) + { + ClearObjectList(); + arc.ArchiveInteger( &num ); + Resize( num ); + } + else + { + num = numobjects; + arc.ArchiveInteger( &num ); + } + for( i = 1; i <= num; i++ ) + { + arc.ArchiveString( AddressOfObjectAt( i ) ); + } + } + +#include "vector.h" +void Container::Archive + ( + Archiver &arc + ) + { + int i, num; + + if ( arc.Loading() ) + { + ClearObjectList(); + arc.ArchiveInteger( &num ); + Resize( num ); + } + else + { + num = numobjects; + arc.ArchiveInteger( &num ); + } + for( i = 1; i <= num; i++ ) + { + arc.ArchiveVector( AddressOfObjectAt( i ) ); + } + } + +void Container::Archive + ( + Archiver &arc + ) + { + int i, num; + + if ( arc.Loading() ) + { + ClearObjectList(); + arc.ArchiveInteger( &num ); + Resize( num ); + } + else + { + num = numobjects; + arc.ArchiveInteger( &num ); + } + for( i = 1; i <= num; i++ ) + { + arc.ArchiveInteger( AddressOfObjectAt( i ) ); + } + } + +void Container::Archive + ( + Archiver &arc + ) + { + int i, num; + + if ( arc.Loading() ) + { + ClearObjectList(); + arc.ArchiveInteger( &num ); + Resize( num ); + } + else + { + num = numobjects; + arc.ArchiveInteger( &num ); + } + for( i = 1; i <= num; i++ ) + { + arc.ArchiveFloat( AddressOfObjectAt( i ) ); + } + } + +#endif +#endif + +#endif /* container.h */ diff --git a/dlls/game/corridorCombatWithRangedWeapon.cpp b/dlls/game/corridorCombatWithRangedWeapon.cpp new file mode 100644 index 0000000..c9bae15 --- /dev/null +++ b/dlls/game/corridorCombatWithRangedWeapon.cpp @@ -0,0 +1,1449 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/corridorCombatWithRangedWeapon.cpp $ +// $Revision:: 8 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CorridorCombatWithRangedWeapon Implementation +// +// PARAMETERS: +// str _movementAnim -- The animation to play while moving to the cover node +// +// ANIMATIONS: +// _movementAnim : PARAMETER +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "corridorCombatWithRangedWeapon.hpp" + +extern Event EV_PostureChanged_Completed; + +//-------------------------------------------------------------- +// +// Init Static Vars +// +//-------------------------------------------------------------- +const float CorridorCombatWithRangedWeapon::NODE_RADIUS = 32.0f; + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, CorridorCombatWithRangedWeapon, NULL ) + { + { &EV_Behavior_Args, &CorridorCombatWithRangedWeapon::SetArgs }, + { &EV_Behavior_AnimDone, &CorridorCombatWithRangedWeapon::AnimDone }, + { &EV_PostureChanged_Completed, &CorridorCombatWithRangedWeapon::PostureDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: CorridorCombatWithRangedWeapon() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +CorridorCombatWithRangedWeapon::CorridorCombatWithRangedWeapon() +{ + _self = NULL; + _movementAnim = "idle"; + _torsoAnim = "idle"; + _fireAnim = "idle"; + _preFireAnim = "idle"; + _postFireAnim = "idle"; + _postureChangeChance = 0.0f; + _maxDistance = 0.0f; + _retreatDistance = 0.0f; + _threatDistance = 0.0f; + _fireTimeMin = 0.0f; + _fireTimeMax = 0.0f; + _pauseTimeMin = 0.0f; + _pauseTimeMax = 0.0f; + +} + +//-------------------------------------------------------------- +// Name: CorridorCombatWithRangedWeapon() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +CorridorCombatWithRangedWeapon::~CorridorCombatWithRangedWeapon() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::SetArgs( Event *ev ) +{ + int num = ev->NumArgs(); + if ( num > 0 ) _movementAnim = ev->GetString( 1 ); + if ( num > 1 ) _torsoAnim = ev->GetString( 2 ); + if ( num > 2 ) _fireAnim = ev->GetString( 3 ); + if ( num > 3 ) _preFireAnim = ev->GetString( 4 ); + if ( num > 4 ) _postFireAnim = ev->GetString( 5 ); + if ( num > 5 ) _postureChangeChance = ev->GetFloat ( 6 ); + if ( num > 6 ) _maxDistance = ev->GetFloat ( 7 ); + if ( num > 7 ) _retreatDistance = ev->GetFloat ( 8 ); + if ( num > 8 ) _threatDistance = ev->GetFloat ( 9 ); + if ( num > 9 ) _fireTimeMin = ev->GetFloat ( 10 ); + if ( num > 10 ) _fireTimeMax = ev->GetFloat ( 11 ); + if ( num > 11 ) _pauseTimeMin = ev->GetFloat ( 12 ); + if ( num > 12 ) _pauseTimeMax = ev->GetFloat ( 13 ); + +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::AnimDone( Event *ev ) +{ +} + + +//-------------------------------------------------------------- +// Name: PostureDone() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Handles a Posture Done Event +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::PostureDone( Event *ev ) +{ + _finishedPostureTransition = true; +} + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::Begin( Actor &self ) +{ + init( self ); +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_FINDNODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateFindNode(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CORRIDORCOMBAT_WRW_STAND ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CORRIDORCOMBAT_WRW_MOVETONODE ); + break; + + //--------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_MOVETONODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateMoveToNode(); + + if ( stateResult == BEHAVIOR_FAILED ) + { + _self->SetAnim( "idle" , NULL , legs ); + transitionToState( CORRIDORCOMBAT_WRW_STAND ); + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + _self->SetAnim( "idle" , NULL , legs ); + stateResult = evaluateRotate(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + if ( checkShouldDuck() ) + transitionToState( CORRIDORCOMBAT_WRW_CHANGEPOSTURE_DUCK ); + else + transitionToState( CORRIDORCOMBAT_WRW_STAND ); + } + + } + + break; + + //--------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_BACKPEDAL: + //--------------------------------------------------------------------- + stateResult = evaluateStateBackPedal(); + + if ( stateResult == BEHAVIOR_FAILED ) + { + _self->movementSubsystem->setMovingBackwards( false ); + _self->SetAnim( "idle" , NULL , legs ); + _holdPositionTime = level.time + G_Random(2.0) + 2.0f; + + transitionToState( CORRIDORCOMBAT_WRW_STAND ); + return BEHAVIOR_EVALUATING; + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + _self->movementSubsystem->setMovingBackwards( false ); + _self->SetAnim( "idle" , NULL , legs ); + transitionToState( CORRIDORCOMBAT_WRW_FINDBETTERNODE ); + } + break; + + //--------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_FINDBETTERNODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateFindBetterNode(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CORRIDORCOMBAT_WRW_STAND ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CORRIDORCOMBAT_WRW_MOVETOBETTERNODE ); + + break; + + //--------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_MOVETOBETTERNODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateMoveToBetterNode(); + + if ( stateResult == BEHAVIOR_FAILED ) + { + _self->SetAnim( "idle" , NULL , legs ); + transitionToState( CORRIDORCOMBAT_WRW_FAILED ); + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + _self->SetAnim( "idle" , NULL , legs ); + stateResult = evaluateRotate(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + if ( checkShouldDuck() ) + transitionToState( CORRIDORCOMBAT_WRW_CHANGEPOSTURE_DUCK ); + else + transitionToState( CORRIDORCOMBAT_WRW_STAND ); + } + + } + break; + + //--------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_CHANGEPOSTURE_DUCK: + //--------------------------------------------------------------------- + stateResult = evaluateStateChangePostureDuck(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CORRIDORCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CORRIDORCOMBAT_WRW_DUCKED ); + + break; + + //--------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_CHANGEPOSTURE_STAND: + //--------------------------------------------------------------------- + stateResult = evaluateStateChangePostureStand(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CORRIDORCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CORRIDORCOMBAT_WRW_STAND ); + + break; + + //--------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_DUCKED: + //--------------------------------------------------------------------- + if ( checkShouldStand() ) //Make sure we don't need to stand back up + { + transitionToState( CORRIDORCOMBAT_WRW_CHANGEPOSTURE_STAND ); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateRotate(); + stateResult = evaluateStateDucked(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CORRIDORCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CORRIDORCOMBAT_WRW_DUCKED_FIRING ); + break; + + //---------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_DUCKED_FIRING: + //---------------------------------------------------------------------- + if ( checkShouldStand() ) //Make sure we don't need to stand back up + { + transitionToState( CORRIDORCOMBAT_WRW_CHANGEPOSTURE_STAND ); + _fireWeapon.End(*_self); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateRotate(); + if ( stateResult != BEHAVIOR_SUCCESS ) + return BEHAVIOR_EVALUATING; + + + stateResult = evaluateStateFireDucked(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CORRIDORCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CORRIDORCOMBAT_WRW_DUCKED ); + + break; + + //---------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_STAND: + //---------------------------------------------------------------------- + if ( checkShouldRetreat() ) + { + transitionToState( CORRIDORCOMBAT_WRW_BACKPEDAL ); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateRotate(); + stateResult = evaluateStateStanding(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CORRIDORCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( CORRIDORCOMBAT_WRW_STAND_FIRING ); + break; + + //---------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_STAND_FIRING: + //---------------------------------------------------------------------- + if ( checkShouldRetreat() ) + { + transitionToState( CORRIDORCOMBAT_WRW_BACKPEDAL ); + _fireWeapon.End(*_self); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateRotate(); + if ( stateResult != BEHAVIOR_SUCCESS ) + return BEHAVIOR_EVALUATING; + + stateResult = evaluateStateFireStanding(); + if ( stateResult == BEHAVIOR_FAILED ) + { + _fireWeapon.End(*_self); + transitionToState( CORRIDORCOMBAT_WRW_FAILED ); + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + _fireWeapon.End(*_self); + transitionToState( CORRIDORCOMBAT_WRW_STAND ); + } + + break; + + //--------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_HOLD_POSITION: + //--------------------------------------------------------------------- + stateResult = evaluateStateHoldPosition(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( CORRIDORCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState(CORRIDORCOMBAT_WRW_FINDBETTERNODE); + break; + + //---------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_SUCCESS: + //---------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + + break; + + + //---------------------------------------------------------------------- + case CORRIDORCOMBAT_WRW_FAILED: + //---------------------------------------------------------------------- + return BEHAVIOR_FAILED; + + break; + } + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::End(Actor &self) +{ + if ( !_self ) + return; + + _self->movementSubsystem->setMovingBackwards( false ); + _fireWeapon.End(*_self); +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::transitionToState( corridorCombatStates_t state ) +{ + switch ( state ) + { + case CORRIDORCOMBAT_WRW_FINDNODE: + setupStateFindNode(); + setInternalState( state , "CORRIDORCOMBAT_WRW_FINDNODE" ); + break; + + case CORRIDORCOMBAT_WRW_MOVETONODE: + setupStateMoveToNode(); + setInternalState( state , "CORRIDORCOMBAT_WRW_MOVETONODE" ); + break; + + case CORRIDORCOMBAT_WRW_BACKPEDAL: + setupStateBackPedal(); + setInternalState( state , "CORRIDORCOMBAT_WRW_BACKPEDAL" ); + break; + + case CORRIDORCOMBAT_WRW_FINDBETTERNODE: + setupStateFindBetterNode(); + setInternalState( state , "CORRIDORCOMBAT_WRW_FINDBETTERNODE" ); + break; + + case CORRIDORCOMBAT_WRW_MOVETOBETTERNODE: + setupStateMoveToBetterNode(); + setInternalState( state , "CORRIDORCOMBAT_WRW_MOVETOBETTERNODE" ); + break; + + case CORRIDORCOMBAT_WRW_CHANGEPOSTURE_DUCK: + setupStateChangePostureDuck(); + setInternalState( state , "CORRIDORCOMBAT_WRW_CHANGEPOSTURE_DUCK" ); + break; + + case CORRIDORCOMBAT_WRW_CHANGEPOSTURE_STAND: + setupStateChangePostureStand(); + setInternalState( state , "CORRIDORCOMBAT_WRW_CHANGEPOSTURE_STAND" ); + break; + + case CORRIDORCOMBAT_WRW_DUCKED: + setupStateDucked(); + setInternalState( state , "CORRIDORCOMBAT_WRW_DUCKED" ); + break; + + case CORRIDORCOMBAT_WRW_DUCKED_FIRING: + setupStateFireDucked(); + setInternalState( state , "CORRIDORCOMBAT_WRW_DUCKED_FIRING" ); + break; + + case CORRIDORCOMBAT_WRW_STAND: + setupStateStanding(); + setInternalState( state , "CORRIDORCOMBAT_WRW_STAND" ); + break; + + case CORRIDORCOMBAT_WRW_STAND_FIRING: + setupStateFireStanding(); + setInternalState( state , "CORRIDORCOMBAT_WRW_STAND_FIRING" ); + break; + + case CORRIDORCOMBAT_WRW_HOLD_POSITION: + setupStateHoldPosition(); + setInternalState( state , "CORRIDORCOMBAT_WRW_HOLD_POSITION" ); + break; + + case CORRIDORCOMBAT_WRW_SUCCESS: + setInternalState( state , "CORRIDORCOMBAT_WRW_SUCCESS" ); + break; + + case CORRIDORCOMBAT_WRW_FAILED: + setInternalState( state , "CORRIDORCOMBAT_WRW_FAILED" ); + break; + } +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setInternalState( corridorCombatStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::init( Actor &self ) +{ + _self = &self; + transitionToState(CORRIDORCOMBAT_WRW_FINDNODE); + _finishedPostureTransition = true; + _holdPositionTime = 0.0f; + _enemyUpdateTime = 0.0f; + updateEnemy(); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::think() +{ + updateEnemy(); +} + + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::updateEnemy() +{ + if ( level.time < _enemyUpdateTime ) + return; + + Entity *currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + SetFailureReason( "CorridorCombatWithRangedWeapon::updateEnemy -- No Enemy" ); + transitionToState( CORRIDORCOMBAT_WRW_FAILED ); + } + + } + + _currentEnemy = currentEnemy; + _enemyUpdateTime = level.time + G_Random() + 3.0f; + setupRotate(); +} + +//-------------------------------------------------------------- +// Name: setupStateFindNode() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up the Find Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateFindNode() +{ +} + + +//-------------------------------------------------------------- +// Name: evaluateStateFindNode() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates the Find Cover State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateFindNode() +{ + if ( _node ) + _node->UnreserveNode(); + + _node = HelperNode::FindClosestHelperNode( *_self , NODETYPE_COMBAT , DESCRIPTOR_CORRIDOR , _maxDistance ); + + if ( !_node ) + { + failureStateFindNode( "CorridorCombatWithRangedWeapon::evaluateStateFindCover() -- Cannot Find Suitable Cover Node" ); + return BEHAVIOR_FAILED; + } + + _node->ReserveNode(); + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateFindNode() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Failure Handler for Find Cover State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateFindNode(const str& failureReason) +{ + SetFailureReason( failureReason ); +} + + + +//-------------------------------------------------------------- +// Name: setupStateMoveToNode() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateMoveToNode() +{ + _gotoPoint.SetAnim( _movementAnim ); + _gotoPoint.SetDistance( NODE_RADIUS ); + _gotoPoint.SetPoint( _node->origin ); + _gotoPoint.Begin( *_self ); + + setTorsoAnim(); +} + + +//-------------------------------------------------------------- +// Name: evaluateStateMoveToNode() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateMoveToNode() +{ + BehaviorReturnCode_t result = _gotoPoint.Evaluate( *_self ); + str failureReason = "CorridorCombatWithRangedWeapon::evaluateStateMoveToCover -- _gotoPoint component returned: " + _gotoPoint.GetFailureReason(); + + if ( result == BEHAVIOR_FAILED ) + failureStateMoveToNode( failureReason ); + + return result; + +} + + +//-------------------------------------------------------------- +// Name: failureStateMoveToNode +// Class: CorridorCombatWithRangedWeapon +// +// Description: Failure Handler for State Move To Cover +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateMoveToNode( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: setupStateBackPedal() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateBackPedal() +{ + Vector selfToEnemy; + float dist; + + dist = G_Random ( 64.0f ) + 96.0f; + + selfToEnemy = _currentEnemy->origin - _self->origin; + selfToEnemy = selfToEnemy.toAngles(); + selfToEnemy[PITCH] = 0; + selfToEnemy[ROLL] = 0; + _self->setAngles( selfToEnemy ); + selfToEnemy.AngleVectors( &selfToEnemy ); + _self->movementSubsystem->setMoveDir( selfToEnemy ); + _self->movementSubsystem->setAnimDir( selfToEnemy ); + + _moveRandomDir.SetDistance( dist ); + _moveRandomDir.SetAnim( "backpedal" ); + _moveRandomDir.SetMode(MoveRandomDirection::RANDOM_MOVE_IN_BACK); + _moveRandomDir.Begin(*_self); + _moveRandomDir.SetMinDistance( dist * .75 ); + _self->movementSubsystem->setMovingBackwards( true ); + +} + +//-------------------------------------------------------------- +// Name: evaluateStateBackPedal() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateBackPedal() +{ + BehaviorReturnCode_t moveResult = _moveRandomDir.Evaluate( *_self ); + + if ( moveResult == BEHAVIOR_SUCCESS ) + { + return BEHAVIOR_SUCCESS; + } + + if ( moveResult == BEHAVIOR_FAILED ) + { + failureStateBackPedal( _moveRandomDir.GetFailureReason() ); + return BEHAVIOR_FAILED; + } + + return BEHAVIOR_EVALUATING; + +} + +//-------------------------------------------------------------- +// Name: failureStateBackPedal() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Failure Handler +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateBackPedal( const str& failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: setupStateFindBetterNode() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateFindBetterNode() +{ +} + +//-------------------------------------------------------------- +// Name: evaluateStateFindBetterNode() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateFindBetterNode() +{ + if ( _node ) + _node->UnreserveNode(); + + _node = HelperNode::FindClosestHelperNodeAtDistanceFrom( *_self , _currentEnemy , NODETYPE_COMBAT , DESCRIPTOR_CORRIDOR , _maxDistance , _retreatDistance ); + + if ( !_node ) + { + failureStateFindBetterNode( "CorridorCombatWithRangedWeapon::evaluateStateFindBetterNode() -- Cannot Find Suitable Cover Node" ); + return BEHAVIOR_FAILED; + } + + _node->ReserveNode(); + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateFindBetterNode() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateFindBetterNode( const str& failureReason ) +{ + SetFailureReason( failureReason ); +} + +//-------------------------------------------------------------- +// Name: setupStateMoveToBetterNode() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateMoveToBetterNode() +{ + _gotoPoint.SetAnim( _movementAnim ); + _gotoPoint.SetDistance( NODE_RADIUS ); + _gotoPoint.SetPoint( _node->origin ); + _gotoPoint.Begin( *_self ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateMoveToBetterNode() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateMoveToBetterNode() +{ + BehaviorReturnCode_t result = _gotoPoint.Evaluate( *_self ); + str failureReason = "CorridorCombatWithRangedWeapon::evaluateStateMoveToBetterNode -- _gotoPoint component returned: " + _gotoPoint.GetFailureReason(); + + if ( result == BEHAVIOR_FAILED ) + failureStateMoveToBetterNode( failureReason ); + + return result; +} + +//-------------------------------------------------------------- +// Name: failureStateMoveToBetterNode() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Failure Handler for state +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateMoveToBetterNode( const str& failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: setupStateChangePostureDuck() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up the ChangePostureDuck state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateChangePostureDuck() +{ + bool canChange = _self->postureController->requestPosture( "DUCK" , this ); + + if ( !canChange ) + { + failureStateChangePostureDuck ( "Requested Posture is unavailable" ); + return; + } + + _finishedPostureTransition = false; +} + +//-------------------------------------------------------------- +// Name: evaluateStateChangePostureDuck() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates our ChangePostureDuck state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateChangePostureDuck() +{ + //Basically waiting for our check to come true + if ( _finishedPostureTransition ) + return BEHAVIOR_SUCCESS; + + + return BEHAVIOR_EVALUATING; + +} + +//-------------------------------------------------------------- +// Name: failureStateChangePostureDuck() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Failure Handler for ChangePostureDuck State +// +// Parameters: const str &failureReason -- Why we're failing +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateChangePostureDuck( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + +//-------------------------------------------------------------- +// Name: setupStateChangePostureStand() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up the ChangePostureStand state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateChangePostureStand() +{ + bool canChange = _self->postureController->requestPosture( "STAND" , this ); + + if ( !canChange ) + { + failureStateChangePostureStand ( "Requested Posture is unavailable" ); + return; + } + + _finishedPostureTransition = false; +} + +//-------------------------------------------------------------- +// Name: evaluateStateChangePostureStand() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates our ChangePostureStand state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateChangePostureStand() +{ + //Basically waiting for our check to come true + if ( _finishedPostureTransition ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + +} + +//-------------------------------------------------------------- +// Name: failureStateChangePostureStand() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Failure Handler for Change Posture Stand function +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateChangePostureStand( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + +//-------------------------------------------------------------- +// Name: setupStateDucked() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up our Ducked State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateDucked() +{ + _nextPauseTime = level.time + G_Random( _pauseTimeMax ) + _pauseTimeMin; + setTorsoAnim(); + _self->SetAnim( "duck" , NULL , legs ); + _fireWeapon.End(*_self); +} + +//-------------------------------------------------------------- +// Name: evaluateStateDucked() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates our Ducked State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateDucked() +{ + if ( level.time > _nextPauseTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + +} + +//-------------------------------------------------------------- +// Name: failureStateDucked() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Failure Handler for Ducked State +// +// Parameters: const str &failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateDucked( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + +//-------------------------------------------------------------- +// Name: setupStateFireDucked() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateFireDucked() +{ + _nextFireTime = level.time + G_Random( _fireTimeMax ) + _fireTimeMin; + if ( _self->combatSubsystem->CanAttackEnemy() ) + { + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); + } +} + +//-------------------------------------------------------------- +// Name: evaluateStateFireDucked() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateFireDucked() +{ + _fireWeapon.Evaluate( *_self ); + _self->combatSubsystem->AimWeaponTag(_currentEnemy); + + if ( level.time > _nextFireTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateFireDucked() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Failure Handler For State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateFireDucked( const str& failureReason ) +{ + SetFailureReason( failureReason ); +} + + + +//-------------------------------------------------------------- +// Name: setupStateStanding() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Setup for the Standing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateStanding() +{ + _self->SetAnim( "idle" , NULL , legs); + _nextPauseTime = level.time + G_Random( _pauseTimeMax ) + _pauseTimeMin; + setTorsoAnim(); +} + +//-------------------------------------------------------------- +// Name: evaluateStateStanding() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates the Standing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateStanding() +{ + if ( level.time > _nextPauseTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateStanding() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Failure Handler for the Standing State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateStanding( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: setupStateFireStanding() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up our Fire Standing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateFireStanding() +{ + _self->SetAnim( "idle" , NULL , legs); + _nextFireTime = level.time + G_Random( _fireTimeMax ) + _fireTimeMin; + //if ( _self->combatSubsystem->CanAttackEnemy() ) + if ( _self->combatSubsystem->CanAttackTarget( _currentEnemy ) ) + { + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); + } +} + +//-------------------------------------------------------------- +// Name: evaluateStateFireStanding() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates our Fire Standing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateFireStanding() +{ + _fireWeapon.Evaluate( *_self ); + _self->combatSubsystem->AimWeaponTag(_currentEnemy); + + if ( level.time > _nextFireTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateFireStanding() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Failure Handler for Fire Standing State +// +// Parameters: const str &failureReason -- Why we're failing +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateFireStanding( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + +//-------------------------------------------------------------- +// Name: setupStateHoldPosition() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupStateHoldPosition() +{ + _self->SetAnim( "idle" ); + setTorsoAnim(); + _holdPositionTime = level.time + G_Random() + 1.0f; +} + +//-------------------------------------------------------------- +// Name: evaluateStateHoldPosition() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateStateHoldPosition() +{ + if ( level.time >= _holdPositionTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateHoldPosition() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Failure Handler +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::failureStateHoldPosition( const str& failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: setupRotate +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets up Rotate Component +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setupRotate() +{ + _rotate.SetTurnSpeed( 30.0f ); + _rotate.SetEntity( _currentEnemy ); + _rotate.Begin( *_self ); +} + + +//-------------------------------------------------------------- +// Name: evaluateRotate() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Evaluates our Rotate Component +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CorridorCombatWithRangedWeapon::evaluateRotate() +{ + return _rotate.Evaluate( *_self ); +} + + +//-------------------------------------------------------------- +// Name: setTorsoAnim() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Sets our Torso Animation +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CorridorCombatWithRangedWeapon::setTorsoAnim() +{ + _self->SetAnim( _torsoAnim , NULL , torso ); +} + +//-------------------------------------------------------------- +// Name: checkShouldDuck() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Checks if actor should duck +// +// Parameters: None +// +// Returns: true or false +//-------------------------------------------------------------- +bool CorridorCombatWithRangedWeapon::checkShouldDuck() +{ + // We don't duck if the enemy is too close + if ( _self->WithinDistanceXY( _currentEnemy , _threatDistance ) ) + return false; + + if ( G_Random() <= _postureChangeChance ) + return true; + + return false; +} + +//-------------------------------------------------------------- +// Name: checkShouldStand() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Checks if actor should stand +// +// Parameters: None +// +// Returns: true or false +//-------------------------------------------------------------- +bool CorridorCombatWithRangedWeapon::checkShouldStand() +{ + if ( _self->WithinDistanceXY( _currentEnemy , _threatDistance ) ) + return true; + + return false; +} + +bool CorridorCombatWithRangedWeapon::checkShouldRetreat() +{ + if ( level.time >= _holdPositionTime && _self->WithinDistanceXY( _currentEnemy , _retreatDistance ) ) + return true; + + return false; + +} + +//-------------------------------------------------------------- +// Name: CanExecute() +// Class: CorridorCombatWithRangedWeapon +// +// Description: Checks if the behavior can execute +// +// Parameters: float maxDistance +// Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool CorridorCombatWithRangedWeapon::CanExecute( float maxDistance , Actor &self ) +{ + HelperNodePtr node; + node = HelperNode::FindClosestHelperNode( self , NODETYPE_COMBAT , DESCRIPTOR_CORRIDOR , maxDistance ); + + if ( node ) + return true; + + return false; +} + + diff --git a/dlls/game/corridorCombatWithRangedWeapon.hpp b/dlls/game/corridorCombatWithRangedWeapon.hpp new file mode 100644 index 0000000..fb11c10 --- /dev/null +++ b/dlls/game/corridorCombatWithRangedWeapon.hpp @@ -0,0 +1,294 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/corridorCombatWithRangedWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CooridorCombatWithRangedWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class CorridorCombatWithRangedWeapon; + +#ifndef __CORRIDORCOMBAT_WITH_RANGEDWEAPON_HPP__ +#define __CORRIDORCOMBAT_WITH_RANGEDWEAPON_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: CooridorCombatWithRangedWeapon +// Base Class: Behavior +// +// Description: +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class CorridorCombatWithRangedWeapon : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + CORRIDORCOMBAT_WRW_FINDNODE, + CORRIDORCOMBAT_WRW_MOVETONODE, + CORRIDORCOMBAT_WRW_BACKPEDAL, + CORRIDORCOMBAT_WRW_FINDBETTERNODE, + CORRIDORCOMBAT_WRW_MOVETOBETTERNODE, + CORRIDORCOMBAT_WRW_CHANGEPOSTURE_DUCK, + CORRIDORCOMBAT_WRW_CHANGEPOSTURE_STAND, + CORRIDORCOMBAT_WRW_DUCKED, + CORRIDORCOMBAT_WRW_DUCKED_FIRING, + CORRIDORCOMBAT_WRW_STAND, + CORRIDORCOMBAT_WRW_STAND_FIRING, + CORRIDORCOMBAT_WRW_HOLD_POSITION, + CORRIDORCOMBAT_WRW_SUCCESS, + CORRIDORCOMBAT_WRW_FAILED + } corridorCombatStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _movementAnim; + str _torsoAnim; + str _fireAnim; + str _preFireAnim; + str _postFireAnim; + float _postureChangeChance; + float _maxDistance; + float _retreatDistance; + float _threatDistance; + float _fireTimeMin; + float _fireTimeMax; + float _pauseTimeMin; + float _pauseTimeMax; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( corridorCombatStates_t state ); + void setInternalState ( corridorCombatStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + void setTorsoAnim (); + + bool checkShouldDuck (); + bool checkShouldStand (); + bool checkShouldRetreat (); + + void setupRotate(); + BehaviorReturnCode_t evaluateRotate(); + + void setupStateFindNode (); + BehaviorReturnCode_t evaluateStateFindNode (); + void failureStateFindNode ( const str& failureReason ); + + void setupStateMoveToNode (); + BehaviorReturnCode_t evaluateStateMoveToNode (); + void failureStateMoveToNode ( const str& failureReason ); + + void setupStateBackPedal (); + BehaviorReturnCode_t evaluateStateBackPedal (); + void failureStateBackPedal ( const str& failureReason ); + + void setupStateFindBetterNode (); + BehaviorReturnCode_t evaluateStateFindBetterNode (); + void failureStateFindBetterNode ( const str& failureReason ); + + void setupStateMoveToBetterNode (); + BehaviorReturnCode_t evaluateStateMoveToBetterNode (); + void failureStateMoveToBetterNode ( const str& failureReason ); + + void setupStateChangePostureDuck (); + BehaviorReturnCode_t evaluateStateChangePostureDuck (); + void failureStateChangePostureDuck ( const str& failureReason ); + + void setupStateChangePostureStand (); + BehaviorReturnCode_t evaluateStateChangePostureStand (); + void failureStateChangePostureStand ( const str& failureReason ); + + void setupStateDucked (); + BehaviorReturnCode_t evaluateStateDucked (); + void failureStateDucked ( const str& failureReason ); + + void setupStateFireDucked (); + BehaviorReturnCode_t evaluateStateFireDucked (); + void failureStateFireDucked ( const str& failureReason ); + + void setupStateFirePauseDucked (); + BehaviorReturnCode_t evaluateStateFirePauseDucked (); + void failureStateFirePauseDucked ( const str& failureReason ); + + void setupStateStanding (); + BehaviorReturnCode_t evaluateStateStanding (); + void failureStateStanding ( const str& failureReason ); + + void setupStateFireStanding (); + BehaviorReturnCode_t evaluateStateFireStanding (); + void failureStateFireStanding ( const str& failureReason ); + + void setupStateFirePauseStanding (); + BehaviorReturnCode_t evaluateStateFirePauseStanding (); + void failureStateFirePauseStanding ( const str& failureReason ); + + void setupStateHoldPosition (); + BehaviorReturnCode_t evaluateStateHoldPosition (); + void failureStateHoldPosition ( const str& failureReason ); + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( CorridorCombatWithRangedWeapon ); + + CorridorCombatWithRangedWeapon(); + ~CorridorCombatWithRangedWeapon(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void PostureDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + + void SetMovementAnim( const str& movementAnim ) { _movementAnim = movementAnim; } + const str& GetMovementAnim() { return _movementAnim; } + + void SetTorsoAnim( const str& torsoAnim ) { _torsoAnim = torsoAnim; } + const str& GetTorsoAnim() { return _torsoAnim; } + + void SetFireAnim( const str& fireAnim ) { _fireAnim = fireAnim; } + const str& GetFireAnim() { return _fireAnim; } + + void SetPreFireAnim( const str& preFireAnim ) { _preFireAnim = preFireAnim; } + const str& GetPreFireAnim() { return _preFireAnim; } + + void SetPostFireAnim( const str& postFireAnim ) { _postFireAnim = postFireAnim; } + const str& GetPostFireAnim() { return _postFireAnim; } + + void SetPostureChangeChance( float chance ) { _postureChangeChance = chance; } + float GetPostureChangeChance() { return _postureChangeChance; } + + void SetMaxDistance( float maxDistance ) { _maxDistance = maxDistance; } + float GetMaxDistance() { return _maxDistance; } + + void SetRetreatDistance( float retreatDistance ) { _retreatDistance = retreatDistance; } + float GetRetreatDistance() { return _retreatDistance; } + + void SetThreatDistance( float threatDistance ) { _threatDistance = threatDistance; } + float GetThreatDistance() { return _threatDistance; } + + void SetFireTimeMin( float fireTimeMin ) { _fireTimeMin = fireTimeMin; } + float GetFireTimeMin() { return _fireTimeMin; } + + void SetFireTimeMax( float fireTimeMax ) { _fireTimeMax = fireTimeMax; } + float GetFireTimeMax() { return _fireTimeMax; } + + void SetPauseTimeMin( float pauseTimeMin ) { _pauseTimeMin = pauseTimeMin; } + float GetPauseTimeMin() { return _pauseTimeMin; } + + void SetPauseTimeMax( float pauseTimeMax ) { _pauseTimeMax = pauseTimeMax; } + float GetPauseTimeMax() { return _pauseTimeMax; } + + virtual void Archive ( Archiver &arc ); + + static bool CanExecute( float maxDistance , Actor &self ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoPoint _gotoPoint; + FireWeapon _fireWeapon; + MoveRandomDirection _moveRandomDir; + RotateToEntity _rotate; + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + corridorCombatStates_t _state; + HelperNodePtr _node; + Actor *_self; + EntityPtr _currentEnemy; + bool _finishedPostureTransition; + float _nextFireTime; + float _nextPauseTime; + float _holdPositionTime; + float _enemyUpdateTime; + + static const float NODE_RADIUS; + + + }; + + +inline void CorridorCombatWithRangedWeapon::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveString ( &_movementAnim ); + arc.ArchiveString ( &_torsoAnim ); + arc.ArchiveString ( &_fireAnim ); + arc.ArchiveString ( &_preFireAnim ); + arc.ArchiveString ( &_postFireAnim ); + arc.ArchiveFloat ( &_postureChangeChance ); + arc.ArchiveFloat ( &_maxDistance ); + arc.ArchiveFloat ( &_retreatDistance ); + arc.ArchiveFloat ( &_threatDistance ); + arc.ArchiveFloat ( &_fireTimeMin ); + arc.ArchiveFloat ( &_fireTimeMax ); + arc.ArchiveFloat ( &_pauseTimeMin ); + arc.ArchiveFloat ( &_pauseTimeMax ); + + + // + // Archive Components + // + arc.ArchiveObject ( &_gotoPoint ); + arc.ArchiveObject ( &_fireWeapon ); + arc.ArchiveObject ( &_moveRandomDir ); + arc.ArchiveObject ( &_rotate ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, corridorCombatStates_t); + arc.ArchiveSafePointer ( &_node ); + arc.ArchiveObjectPointer( ( Class ** )&_self ); + arc.ArchiveSafePointer ( &_currentEnemy ); + arc.ArchiveBool ( &_finishedPostureTransition ); + arc.ArchiveFloat ( &_nextFireTime ); + arc.ArchiveFloat ( &_nextPauseTime ); + arc.ArchiveFloat ( &_holdPositionTime ); + arc.ArchiveFloat ( &_enemyUpdateTime ); + +} + + +#endif /* __CORRIDORCOMBAT_WITH_RANGEDWEAPON_HPP__ */ diff --git a/dlls/game/coverCombatWithRangedWeapon.cpp b/dlls/game/coverCombatWithRangedWeapon.cpp new file mode 100644 index 0000000..1b5c4c6 --- /dev/null +++ b/dlls/game/coverCombatWithRangedWeapon.cpp @@ -0,0 +1,1226 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/coverCombatWithRangedWeapon.cpp $ +// $Revision:: 12 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CoverCombatWithRangedWeapon Implementation +// +// PARAMETERS: +// str _movementAnim -- The animation to play while moving to the cover node +// +// ANIMATIONS: +// _movementAnim : PARAMETER +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "coverCombatWithRangedWeapon.hpp" + +extern Event EV_PostureChanged_Completed; + +//-------------------------------------------------------------- +// +// Init Static Vars +// +//-------------------------------------------------------------- +const float CoverCombatWithRangedWeapon::NODE_RADIUS = 32.0f; + + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, CoverCombatWithRangedWeapon, NULL ) + { + { &EV_Behavior_Args, &CoverCombatWithRangedWeapon::SetArgs }, + { &EV_Behavior_AnimDone, &CoverCombatWithRangedWeapon::AnimDone }, + { &EV_PostureChanged_Completed, &CoverCombatWithRangedWeapon::PostureDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: CoverCombatWithRangedWeapon() +// Class: CoverCombatWithRangedWeapon +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +CoverCombatWithRangedWeapon::CoverCombatWithRangedWeapon() +{ + _self = NULL; +} + +//-------------------------------------------------------------- +// Name: CoverCombatWithRangedWeapon() +// Class: CoverCombatWithRangedWeapon +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +CoverCombatWithRangedWeapon::~CoverCombatWithRangedWeapon() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::SetArgs( Event *ev ) +{ + _torsoAnim = ev->GetString ( 1 ); + _fireAnim = ev->GetString ( 2 ); + _movementAnim = ev->GetString ( 3 ); + _maxDistance = ev->GetFloat ( 4 ); + _fireTimeMin = ev->GetFloat ( 5 ); + _fireTimeMax = ev->GetFloat ( 6 ); + _pauseTimeMin = ev->GetFloat ( 7 ); + _pauseTimeMax = ev->GetFloat ( 8 ); + +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: CoverCombatWithRangedWeapon +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::AnimDone( Event *ev ) +{ +} + + +//-------------------------------------------------------------- +// Name: PostureDone() +// Class: CoverCombatWithRangedWeapon +// +// Description: Handles a Posture Done Event +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::PostureDone( Event *ev ) +{ + _finishedPostureTransition = true; +} + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: CoverCombatWithRangedWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::Begin( Actor &self ) +{ + init( self ); +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case COVERCOMBAT_WRW_FIND_COVER: + //--------------------------------------------------------------------- + stateResult = evaluateStateFindCover(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( COVERCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( COVERCOMBAT_WRW_MOVE_TO_COVER ); + + break; + + //--------------------------------------------------------------------- + case COVERCOMBAT_WRW_FIND_BETTER_COVER: + //--------------------------------------------------------------------- + stateResult = evaluateStateFindBetterCover(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( COVERCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( COVERCOMBAT_WRW_MOVE_TO_COVER ); + break; + + //--------------------------------------------------------------------- + case COVERCOMBAT_WRW_MOVE_TO_COVER: + //--------------------------------------------------------------------- + stateResult = evaluateStateMoveToCover(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( COVERCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( COVERCOMBAT_WRW_CHANGE_POSTURE_DUCK ); + + break; + + //--------------------------------------------------------------------- + case COVERCOMBAT_WRW_CHANGE_POSTURE_DUCK: + //--------------------------------------------------------------------- + if ( _spotted ) + { + transitionToState( COVERCOMBAT_WRW_SPOTTED ); + BEHAVIOR_EVALUATING; + } + + stateResult = evaluateStateChangePostureDuck(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( COVERCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( COVERCOMBAT_WRW_DUCKED ); + break; + + //--------------------------------------------------------------------- + case COVERCOMBAT_WRW_CHANGE_POSTURE_STAND: + //--------------------------------------------------------------------- + if ( _spotted ) + { + transitionToState( COVERCOMBAT_WRW_SPOTTED ); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateStateChangePostureStand(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( COVERCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( COVERCOMBAT_WRW_STANDING ); + + break; + + //---------------------------------------------------------------------- + case COVERCOMBAT_WRW_FIRE_STANDING: + //---------------------------------------------------------------------- + if ( _spotted ) + { + _fireWeapon.End( *_self ); + setTorsoAnim(); + transitionToState( COVERCOMBAT_WRW_SPOTTED ); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateRotate(); + stateResult = evaluateStateFireStanding(); + + // Check if we need to stop firing + if ( stateResult == BEHAVIOR_FAILED ) + { + transitionToState( COVERCOMBAT_WRW_FAILED ); + _fireWeapon.End( *_self ); + setTorsoAnim(); + return BEHAVIOR_EVALUATING; + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( COVERCOMBAT_WRW_FIRE_PAUSE_STANDING ); + _fireWeapon.End( *_self ); + setTorsoAnim(); + return BEHAVIOR_EVALUATING; + } + + // See if we just need to duck again + stateResult = evaluateStateStanding(); + + if ( stateResult == BEHAVIOR_FAILED ) + { + transitionToState( COVERCOMBAT_WRW_FAILED ); + _fireWeapon.End( *_self ); + setTorsoAnim(); + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( COVERCOMBAT_WRW_CHANGE_POSTURE_DUCK ); + _fireWeapon.End( *_self ); + setTorsoAnim(); + } + + break; + + //---------------------------------------------------------------------- + case COVERCOMBAT_WRW_FIRE_PAUSE_STANDING: + //---------------------------------------------------------------------- + if ( _spotted ) + { + transitionToState( COVERCOMBAT_WRW_SPOTTED ); + BEHAVIOR_EVALUATING; + } + + stateResult = evaluateRotate(); + stateResult = evaluateStateFirePauseStanding(); + if ( stateResult == BEHAVIOR_FAILED ) + { + transitionToState( COVERCOMBAT_WRW_FAILED ); + _fireWeapon.End( *_self ); + setTorsoAnim(); + return BEHAVIOR_EVALUATING; + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState(COVERCOMBAT_WRW_FIRE_STANDING); + return BEHAVIOR_EVALUATING; + } + + // See if we just need to duck again + stateResult = evaluateStateStanding(); + + if ( stateResult == BEHAVIOR_FAILED ) + { + transitionToState( COVERCOMBAT_WRW_FAILED ); + _fireWeapon.End( *_self ); + setTorsoAnim(); + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( COVERCOMBAT_WRW_CHANGE_POSTURE_DUCK ); + _fireWeapon.End( *_self ); + setTorsoAnim(); + } + + break; + + //---------------------------------------------------------------------- + case COVERCOMBAT_WRW_DUCKED: + //---------------------------------------------------------------------- + if ( _spotted ) + { + transitionToState( COVERCOMBAT_WRW_SPOTTED ); + BEHAVIOR_EVALUATING; + } + + stateResult = evaluateStateDucked(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( COVERCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( COVERCOMBAT_WRW_CHANGE_POSTURE_STAND ); + break; + + //---------------------------------------------------------------------- + case COVERCOMBAT_WRW_STANDING: + //---------------------------------------------------------------------- + if ( _spotted ) + { + transitionToState( COVERCOMBAT_WRW_SPOTTED ); + BEHAVIOR_EVALUATING; + } + + stateResult = evaluateRotate(); + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( COVERCOMBAT_WRW_FIRE_STANDING ); + + + break; + + //---------------------------------------------------------------------- + case COVERCOMBAT_WRW_SPOTTED: + //---------------------------------------------------------------------- + stateResult = evaluateStateSpotted(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( COVERCOMBAT_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( COVERCOMBAT_WRW_FIND_BETTER_COVER ); + break; + + //---------------------------------------------------------------------- + case COVERCOMBAT_WRW_SUCCESS: + //---------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + + break; + + + //---------------------------------------------------------------------- + case COVERCOMBAT_WRW_FAILED: + //---------------------------------------------------------------------- + return BEHAVIOR_FAILED; + + break; + } + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: CoverCombatWithRangedWeapon +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::End(Actor &self) +{ + if ( !_self ) + return; + + _self->movementSubsystem->setMovingBackwards( false ); + _fireWeapon.End(*_self); +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: CoverCombatWithRangedWeapon +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::transitionToState( coverCombatStates_t state ) +{ + switch ( state ) + { + case COVERCOMBAT_WRW_FIND_COVER: + setupStateFindCover(); + setInternalState( state , "COVERCOMBAT_WRW_FIND_COVER" ); + break; + + case COVERCOMBAT_WRW_FIND_BETTER_COVER: + setupStateFindBetterCover(); + setInternalState( state , "COVERCOMBAT_WRW_FIND_BETTER_COVER" ); + break; + + case COVERCOMBAT_WRW_MOVE_TO_COVER: + setupStateMoveToCover(); + setInternalState( state , "COVERCOMBAT_WRW_MOVE_TO_COVER" ); + break; + + case COVERCOMBAT_WRW_CHANGE_POSTURE_DUCK: + setupStateChangePostureDuck(); + setInternalState( state , "COVERCOMBAT_WRW_CHANGE_POSTURE_DUCK" ); + break; + + case COVERCOMBAT_WRW_CHANGE_POSTURE_STAND: + setupStateChangePostureStand(); + setInternalState( state , "COVERCOMBAT_WRW_CHANGE_POSTURE_DUCK" ); + break; + + case COVERCOMBAT_WRW_FIRE_STANDING: + setupStateFireStanding(); + setInternalState( state , "COVERCOMBAT_WRW_FIRE_STANDING" ); + break; + + case COVERCOMBAT_WRW_FIRE_PAUSE_STANDING: + setupStateFirePauseStanding(); + setInternalState( state , "COVERCOMBAT_WRW_FIRE_PAUSE_STANDING" ); + break; + + case COVERCOMBAT_WRW_DUCKED: + setupStateDucked(); + setInternalState( state , "COVERCOMBAT_WRW_DUCKED" ); + break; + + case COVERCOMBAT_WRW_STANDING: + setupStateStanding(); + setInternalState( state , "COVERCOMBAT_WRW_STANDING" ); + break; + + case COVERCOMBAT_WRW_SPOTTED: + setupStateSpotted(); + setInternalState( state , "COVERCOMBAT_WRW_SPOTTED" ); + break; + + case COVERCOMBAT_WRW_SUCCESS: + setInternalState( state , "COVERCOMBAT_WRW_SUCCESS" ); + break; + + case COVERCOMBAT_WRW_FAILED: + setInternalState( state , "COVERCOMBAT_WRW_FAILED" ); + break; + } +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setInternalState( coverCombatStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: CoverCombatWithRangedWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::init( Actor &self ) +{ + _self = &self; + transitionToState(COVERCOMBAT_WRW_FIND_COVER); + _finishedPostureTransition = true; + _nextStandTime = 0.0f; + _nextDuckTime = 0.0f; + _nextSpotCheck = 0.0f; + updateEnemy(); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: CoverCombatWithRangedWeapon +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::think() +{ + trace_t trace; + + if ( level.time < _nextSpotCheck ) + return; + + if ( !_currentEnemy || !_node ) + return; + + updateEnemy(); + trace = G_Trace( _node->origin , vec_zero , vec_zero , _currentEnemy->origin , NULL , MASK_SOLID, false, "CoverCombatWithRangedWeapon::think"); + + if ( trace.fraction >= .95 ) + _spotted = true; + else + _spotted = false; + + _nextSpotCheck = level.time + G_Random(0.5); + +} + + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::updateEnemy() +{ + Entity *currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + SetFailureReason( "CoverCombatWithRangedWeapon::updateEnemy -- No Enemy" ); + transitionToState( COVERCOMBAT_WRW_FAILED ); + } + + } + + _currentEnemy = currentEnemy; + setupRotate(); +} + +//-------------------------------------------------------------- +// Name: setupStateFindCover() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets up the Find Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setupStateFindCover() +{ +} + + +//-------------------------------------------------------------- +// Name: evaluateStateFindCover() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates the Find Cover State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::evaluateStateFindCover() +{ + //This Behavior's not being used anymore and should be removed + //_node = HelperNode::FindClosestHelperNodeThatCannotSeeEntity( *_self , NODETYPE_COVER , _self->edict->clipmask , _maxDistance , 0.0f , _currentEnemy ); + + if ( !_node ) + { + failureStateFindCover( "CoverCombatWithRangedWeapon::evaluateStateFindCover() -- Cannot Find Suitable Cover Node" ); + return BEHAVIOR_FAILED; + } + + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateFindCover() +// Class: CoverCombatWithRangedWeapon +// +// Description: Failure Handler for Find Cover State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::failureStateFindCover(const str& failureReason) +{ + SetFailureReason( failureReason ); +} + + + +//-------------------------------------------------------------- +// Name: setupStateMoveToCover() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets up the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setupStateMoveToCover() +{ + _gotoPoint.SetAnim( _movementAnim ); + _gotoPoint.SetDistance( NODE_RADIUS ); + _gotoPoint.SetPoint( _node->origin ); + _gotoPoint.Begin( *_self ); +} + + +//-------------------------------------------------------------- +// Name: evaluateStateMoveToCover() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::evaluateStateMoveToCover() +{ + BehaviorReturnCode_t result = _gotoPoint.Evaluate( *_self ); + str failureReason = "CoverCombatWithRangedWeapon::evaluateStateMoveToCover -- _gotoPoint component returned: " + _gotoPoint.GetFailureReason(); + + if ( result == BEHAVIOR_FAILED ) + failureStateMoveToCover( failureReason ); + + return result; + +} + + +//-------------------------------------------------------------- +// Name: failureStateMoveToCover +// Class: CoverCombatWithRangedWeapon +// +// Description: Failure Handler for State Move To Cover +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::failureStateMoveToCover( const str &failureReason ) +{ +} + + +//-------------------------------------------------------------- +// Name: setupStateChangePostureDuck() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets up the ChangePostureDuck state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setupStateChangePostureDuck() +{ + bool canChange = _self->postureController->requestPosture( "DUCK" , this ); + + if ( !canChange ) + { + failureStateChangePostureDuck ( "Requested Posture is unavailable" ); + return; + } + + _finishedPostureTransition = false; +} + +//-------------------------------------------------------------- +// Name: evaluateStateChangePostureDuck() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates our ChangePostureDuck state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::evaluateStateChangePostureDuck() +{ + //Basically waiting for our check to come true + if ( _finishedPostureTransition ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + +} + +//-------------------------------------------------------------- +// Name: failureStateChangePostureDuck() +// Class: CoverCombatWithRangedWeapon +// +// Description: Failure Handler for ChangePostureDuck State +// +// Parameters: const str &failureReason -- Why we're failing +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::failureStateChangePostureDuck( const str &failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateChangePostureStand() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets up the ChangePostureStand state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setupStateChangePostureStand() +{ + bool canChange = _self->postureController->requestPosture( "STAND" , this ); + + if ( !canChange ) + { + failureStateChangePostureStand ( "Requested Posture is unavailable" ); + return; + } + + _finishedPostureTransition = false; +} + +//-------------------------------------------------------------- +// Name: evaluateStateChangePostureStand() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates our ChangePostureStand state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::evaluateStateChangePostureStand() +{ + //Basically waiting for our check to come true + if ( _finishedPostureTransition ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + +} + +//-------------------------------------------------------------- +// Name: failureStateChangePostureStand() +// Class: CoverCombatWithRangedWeapon +// +// Description: Failure Handler for Change Posture Stand function +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::failureStateChangePostureStand( const str &failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateSpotted() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets up the Spotted State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setupStateSpotted() +{ + bool canChange = _self->postureController->requestPosture( "STAND" , this ); + + if ( !canChange ) + { + failureStateChangePostureStand ( "Requested Posture is unavailable" ); + return; + } + + _finishedPostureTransition = false; +} + +//-------------------------------------------------------------- +// Name: evaluateStateSpotted() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates our Spotted State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::evaluateStateSpotted() +{ + //Basically waiting for our check to come true + if ( _finishedPostureTransition ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateSpotted() +// Class: CoverCombatWithRangedWeapon +// +// Description: Failure Handler for Spotted State +// +// Parameters: const str &failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::failureStateSpotted( const str &failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateDucked() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets up our Ducked State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setupStateDucked() +{ + _nextStandTime = level.time + G_Random() + 3.0f; +} + +//-------------------------------------------------------------- +// Name: evaluateStateDucked() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates our Ducked State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::evaluateStateDucked() +{ + if ( level.time > _nextStandTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateDucked() +// Class: CoverCombatWithRangedWeapon +// +// Description: Failure Handler for Ducked State +// +// Parameters: const str &failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::failureStateDucked( const str &failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateStanding() +// Class: CoverCombatWithRangedWeapon +// +// Description: Setup for the Standing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setupStateStanding() +{ + _nextDuckTime = level.time + G_Random() + 5.0f; + _nextFireTime = 0.0f; + _nextPauseTime = level.time + G_Random(_pauseTimeMax ) + _pauseTimeMin; +} + +//-------------------------------------------------------------- +// Name: evaluateStateStanding() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates the Standing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::evaluateStateStanding() +{ + if ( level.time > _nextDuckTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateStanding() +// Class: CoverCombatWithRangedWeapon +// +// Description: Failure Handler for the Standing State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::failureStateStanding( const str &failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateFindBetterCover() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets up the Find Better Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setupStateFindBetterCover() +{ +} + + +//-------------------------------------------------------------- +// Name: evaluateStateFindBetterCover() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates the Find Better Cover State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::evaluateStateFindBetterCover() +{ + //Behavior Needs to be removed +// _node = HelperNode::FindClosestHelperNodeThatCannotSeeEntity( *_self , NODETYPE_COVER , _self->edict->clipmask , _maxDistance , 512.0f , _currentEnemy ); + + if ( !_node ) + { + failureStateFindCover( "CoverCombatWithRangedWeapon::evaluateStateFindBetterCover() -- Cannot Find Suitable Cover Node" ); + return BEHAVIOR_FAILED; + } + + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateFindBetterCover() +// Class: CoverCombatWithRangedWeapon +// +// Description: Failure Handler for Find Cover State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::failureStateFindBetterCover(const str& failureReason) +{ + SetFailureReason( failureReason ); +} + +//-------------------------------------------------------------- +// Name: setupRotate +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets up Rotate Component +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setupRotate() +{ + _rotate.SetEntity( _currentEnemy ); + _rotate.Begin( *_self ); +} + + +//-------------------------------------------------------------- +// Name: evaluateRotate() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates our Rotate Component +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::evaluateRotate() +{ + return _rotate.Evaluate( *_self ); +} + +//-------------------------------------------------------------- +// Name: setupStateFireStanding() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets up our Fire Standing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setupStateFireStanding() +{ + _nextFireTime = level.time + G_Random( _fireTimeMax ) + _fireTimeMin; + if ( _self->combatSubsystem->CanAttackEnemy() ) + { + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); + } +} + +//-------------------------------------------------------------- +// Name: evaluateStateFireStanding() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates our Fire Standing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::evaluateStateFireStanding() +{ + _fireWeapon.Evaluate( *_self ); + _self->combatSubsystem->AimWeaponTag(_currentEnemy); + + if ( level.time > _nextFireTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateFireStanding() +// Class: CoverCombatWithRangedWeapon +// +// Description: Failure Handler for Fire Standing State +// +// Parameters: const str &failureReason -- Why we're failing +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::failureStateFireStanding( const str &failureReason ) +{ + +} + +//-------------------------------------------------------------- +// Name: setupStateFirePauseStanding() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets up our Pause Fire State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setupStateFirePauseStanding() +{ + _nextPauseTime = level.time + G_Random( _pauseTimeMax ) + _pauseTimeMin; + setTorsoAnim(); +} + +//-------------------------------------------------------------- +// Name: evaluateStateFirePauseStanding() +// Class: CoverCombatWithRangedWeapon +// +// Description: Evaluates our Pause Fire State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t CoverCombatWithRangedWeapon::evaluateStateFirePauseStanding() +{ + if ( level.time > _nextPauseTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateFirePauseStanding() +// Class: CoverCombatWithRangedWeapon +// +// Description: Failure Handler for our Pause Standing State +// +// Parameters: const str &failureReason -- Why we're failing +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::failureStateFirePauseStanding( const str &failureReason ) +{ +} + + +//-------------------------------------------------------------- +// Name: setTorsoAnim() +// Class: CoverCombatWithRangedWeapon +// +// Description: Sets our Torso Animation +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void CoverCombatWithRangedWeapon::setTorsoAnim() +{ + _self->SetAnim( _torsoAnim , NULL , torso ); +} + +//-------------------------------------------------------------- +// Name: CanExecute() +// Class: CoverCombatWithRangedWeapon +// +// Description: Checks if the Behavior can execute +// +// Parameters: Actor &self, +// float maxDistance +// +// Returns: true or false; +//-------------------------------------------------------------- +bool CoverCombatWithRangedWeapon::CanExecute( Actor &self , float maxDistance ) +{ + //HelperNode* node; + Entity* currentEnemy; + + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + return false; + + //behavior needs to be removed +// node = HelperNode::FindClosestHelperNodeThatCannotSeeEntity( self , NODETYPE_COVER , self.edict->clipmask , maxDistance , 0.0f , currentEnemy ); + + //if ( node ) + // return true; + + return false; +} + + + + + + diff --git a/dlls/game/coverCombatWithRangedWeapon.hpp b/dlls/game/coverCombatWithRangedWeapon.hpp new file mode 100644 index 0000000..15adb2e --- /dev/null +++ b/dlls/game/coverCombatWithRangedWeapon.hpp @@ -0,0 +1,326 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/coverCombatWithRangedWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CoverCombatWithRangedWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class CoverCombatWithRangedWeapon; + +#ifndef __COVERCOMBAT_WITH_RANGEDWEAPON_HPP__ +#define __COVERCOMBAT_WITH_RANGEDWEAPON_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: CoverCombatWithRangedWeapon +// Base Class: Behavior +// +// Description: +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class CoverCombatWithRangedWeapon : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + COVERCOMBAT_WRW_FIND_COVER, + COVERCOMBAT_WRW_FIND_BETTER_COVER, + COVERCOMBAT_WRW_MOVE_TO_COVER, + COVERCOMBAT_WRW_CHANGE_POSTURE_DUCK, + COVERCOMBAT_WRW_CHANGE_POSTURE_STAND, + COVERCOMBAT_WRW_FIRE_STANDING, + COVERCOMBAT_WRW_FIRE_PAUSE_STANDING, + COVERCOMBAT_WRW_SPOTTED, + COVERCOMBAT_WRW_DUCKED, + COVERCOMBAT_WRW_STANDING, + COVERCOMBAT_WRW_SUCCESS, + COVERCOMBAT_WRW_FAILED + } coverCombatStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _movementAnim; + str _torsoAnim; + str _fireAnim; + float _maxDistance; + float _fireTimeMin; + float _fireTimeMax; + float _pauseTimeMin; + float _pauseTimeMax; + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( coverCombatStates_t state ); + void setInternalState ( coverCombatStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + void setTorsoAnim (); + + void setupRotate(); + BehaviorReturnCode_t evaluateRotate(); + + void setupStateFindCover (); + BehaviorReturnCode_t evaluateStateFindCover (); + void failureStateFindCover ( const str& failureReason ); + + void setupStateMoveToCover (); + BehaviorReturnCode_t evaluateStateMoveToCover (); + void failureStateMoveToCover ( const str& failureReason ); + + void setupStateChangePostureDuck (); + BehaviorReturnCode_t evaluateStateChangePostureDuck (); + void failureStateChangePostureDuck ( const str& failureReason ); + + void setupStateChangePostureStand (); + BehaviorReturnCode_t evaluateStateChangePostureStand (); + void failureStateChangePostureStand ( const str& failureReason ); + + void setupStateSpotted (); + BehaviorReturnCode_t evaluateStateSpotted (); + void failureStateSpotted ( const str& failureReason ); + + void setupStateDucked (); + BehaviorReturnCode_t evaluateStateDucked (); + void failureStateDucked ( const str& failureReason ); + + void setupStateStanding (); + BehaviorReturnCode_t evaluateStateStanding (); + void failureStateStanding ( const str& failureReason ); + + void setupStateFindBetterCover (); + BehaviorReturnCode_t evaluateStateFindBetterCover (); + void failureStateFindBetterCover ( const str& failureReason ); + + void setupStateFireStanding (); + BehaviorReturnCode_t evaluateStateFireStanding (); + void failureStateFireStanding ( const str& failureReason ); + + void setupStateFirePauseStanding (); + BehaviorReturnCode_t evaluateStateFirePauseStanding (); + void failureStateFirePauseStanding ( const str& failureReason ); + + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( CoverCombatWithRangedWeapon ); + + CoverCombatWithRangedWeapon(); + ~CoverCombatWithRangedWeapon(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void PostureDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + void SetMovementAnim( const str& movementAnim ); + const str& GetMovementAnim(); + + void SetTorsoAnim( const str& torsoAnim ); + const str& GetTorsoAnim(); + + void SetFireAnim( const str& fireAnim ); + const str& GetFireAnim(); + + void SetMaxDistance( float maxDistance ); + float GetMaxDistance(); + + void SetFireTimeMin( float fireTimeMin ); + float GetFireTimeMin(); + + void SetFireTimeMax( float fireTimeMax ); + float GetFireTimeMax(); + + void SetPauseTimeMin( float pauseTimeMin ); + float GetPauseTimeMin(); + + void SetPauseTimeMax( float pauseTimeMax ); + float GetPauseTimeMax(); + + static bool CanExecute( Actor &self , float maxDistance ); + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoPoint _gotoPoint; + FireWeapon _fireWeapon; + RotateToEntity _rotate; + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + coverCombatStates_t _state; + HelperNodePtr _node; + Actor *_self; + EntityPtr _currentEnemy; + bool _finishedPostureTransition; + bool _spotted; + float _nextSpotCheck; + float _nextStandTime; + float _nextDuckTime; + float _nextFireTime; + float _nextPauseTime; + + static const float NODE_RADIUS; + + + }; + +inline void CoverCombatWithRangedWeapon::SetMovementAnim( const str& movementAnim ) +{ + _movementAnim = movementAnim; +} + +inline const str& CoverCombatWithRangedWeapon::GetMovementAnim() +{ + return _movementAnim; +} + +inline void CoverCombatWithRangedWeapon::SetTorsoAnim( const str& torsoAnim ) +{ + _torsoAnim = torsoAnim; +} + +inline const str& CoverCombatWithRangedWeapon::GetTorsoAnim() +{ + return _torsoAnim; +} + +inline void CoverCombatWithRangedWeapon::SetFireAnim( const str& fireAnim ) +{ + _fireAnim = fireAnim; +} + +inline const str& CoverCombatWithRangedWeapon::GetFireAnim() +{ + return _fireAnim; +} + +inline void CoverCombatWithRangedWeapon::SetMaxDistance( float maxDistance ) +{ + _maxDistance = maxDistance; +} + +inline float CoverCombatWithRangedWeapon::GetMaxDistance() +{ + return _maxDistance; +} + +inline void CoverCombatWithRangedWeapon::SetFireTimeMin( float fireTimeMin ) +{ + _fireTimeMin = fireTimeMin; +} + +inline float CoverCombatWithRangedWeapon::GetFireTimeMin() +{ + return _fireTimeMin; +} + +inline void CoverCombatWithRangedWeapon::SetFireTimeMax( float fireTimeMax ) +{ + _fireTimeMax = fireTimeMax; +} + +inline float CoverCombatWithRangedWeapon::GetFireTimeMax() +{ + return _fireTimeMax; +} + +inline void CoverCombatWithRangedWeapon::SetPauseTimeMin( float pauseTimeMin ) +{ + _pauseTimeMin = pauseTimeMin; +} + +inline float CoverCombatWithRangedWeapon::GetPauseTimeMin() +{ + return _pauseTimeMin; +} + +inline void CoverCombatWithRangedWeapon::SetPauseTimeMax( float pauseTimeMax ) +{ + _pauseTimeMax = pauseTimeMax; +} + +inline float CoverCombatWithRangedWeapon::GetPauseTimeMax() +{ + return _pauseTimeMax; +} + +inline void CoverCombatWithRangedWeapon::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveString ( &_movementAnim ); + arc.ArchiveString ( &_torsoAnim ); + arc.ArchiveString ( &_fireAnim ); + arc.ArchiveFloat ( &_maxDistance ); + arc.ArchiveFloat ( &_fireTimeMin ); + arc.ArchiveFloat ( &_fireTimeMax ); + arc.ArchiveFloat ( &_pauseTimeMin ); + arc.ArchiveFloat ( &_pauseTimeMax ); + + // + // Archive Components + // + arc.ArchiveObject ( &_gotoPoint ); + arc.ArchiveObject ( &_fireWeapon ); + arc.ArchiveObject ( &_rotate ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, coverCombatStates_t ); + arc.ArchiveSafePointer ( &_node ); + arc.ArchiveObjectPointer( ( Class ** )&_self ); + arc.ArchiveSafePointer ( &_currentEnemy ); + arc.ArchiveBool ( &_finishedPostureTransition ); + arc.ArchiveBool ( &_spotted ); + arc.ArchiveFloat ( &_nextSpotCheck ); + arc.ArchiveFloat ( &_nextStandTime ); + arc.ArchiveFloat ( &_nextDuckTime ); + arc.ArchiveFloat ( &_nextFireTime ); + arc.ArchiveFloat ( &_nextPauseTime ); +} + + +#endif /* __COVERCOMBAT_WITH_RANGEDWEAPON_HPP__ */ diff --git a/dlls/game/debuglines.cpp b/dlls/game/debuglines.cpp new file mode 100644 index 0000000..8e36d92 --- /dev/null +++ b/dlls/game/debuglines.cpp @@ -0,0 +1,809 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/debuglines.cpp $ +// $Revision:: 7 $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +//#include "g_local.h" +#include "debuglines.h" + +#define NUM_CIRCLE_SEGMENTS 24 + +debugline_t *DebugLines = NULL; +Vector currentVertex( 0.0f, 0.0f, 0.0f ); +Vector vertColor( 1.0f, 1.0f, 1.0f ); +float vertAlpha = 1.0f; +float vertexIndex = 0.0f; +float linewidth = 1.0f; +unsigned short lineStippleFactor = 1; +unsigned short linePattern = 0xffff; + +void G_InitDebugLines + ( + void + ) + + { + *gi.DebugLines = DebugLines; + *gi.numDebugLines = 0; + + currentVertex = vec_zero; + vertColor = Vector( 1, 1, 1 ); + vertAlpha = 1; + vertexIndex = 0; + + linewidth = 1; + lineStippleFactor = 1; + linePattern = 0xffff; + } + +void G_AllocDebugLines + ( + void + ) + + { + // we do a malloc here so that we don't interfere with the game's memory footprint + DebugLines = ( debugline_t * )malloc( ( int )g_numdebuglines->integer * sizeof( debugline_t ) ); + + G_InitDebugLines(); + } + +void G_DeAllocDebugLines + ( + void + ) + + { + if ( DebugLines ) + { + // we do a free here, because we used malloc above + free( DebugLines ); + DebugLines = NULL; + *gi.DebugLines = DebugLines; + *gi.numDebugLines = 0; + } + } + +void G_DebugLine + ( + const Vector &start, + const Vector &end, + float r, + float g, + float b, + float alpha + ) + + { + debugline_t *line; + + if ( !g_numdebuglines ) + { + return; + } + + if ( *gi.numDebugLines >= g_numdebuglines->integer ) + { + gi.DPrintf( "G_DebugLine: Exceeded MAX_DEBUG_LINES\n" ); + return; + } + + line = &DebugLines[ *gi.numDebugLines ]; + ( *gi.numDebugLines )++; + + VectorCopy( start, line->start ); + VectorCopy( end, line->end ); + VectorSet( line->color, r, g, b ); + line->alpha = alpha; + + line->width = linewidth; + line->factor = lineStippleFactor; + line->pattern = linePattern; + } + +void G_DebugLineC + ( + const vec3_t start, + const vec3_t end, + float r, + float g, + float b, + float alpha + ) + + { + debugline_t *line; + + if ( !g_numdebuglines ) + { + return; + } + + if ( *gi.numDebugLines >= g_numdebuglines->integer ) + { + gi.DPrintf( "G_DebugLine: Exceeded MAX_DEBUG_LINES\n" ); + return; + } + + line = &DebugLines[ *gi.numDebugLines ]; + ( *gi.numDebugLines )++; + + VectorCopy( start, line->start ); + VectorCopy( end, line->end ); + VectorSet( line->color, r, g, b ); + line->alpha = alpha; + + line->width = linewidth; + line->factor = lineStippleFactor; + line->pattern = linePattern; + } + +void G_LineStipple + ( + int factor, + unsigned short pattern + ) + + { + lineStippleFactor = factor; + linePattern = pattern; + } + +void G_LineWidth + ( + float width + ) + + { + linewidth = width; + } + +void G_Color3f + ( + float r, + float g, + float b + ) + + { + vertColor = Vector( r, g, b ); + } + +void G_Color3v + ( + const Vector &color + ) + + { + vertColor = color; + } + +void G_Color4f + ( + float r, + float g, + float b, + float alpha + ) + + { + vertColor = Vector( r, g, b ); + vertAlpha = alpha; + } + +void G_Color3vf + ( + const Vector &color, + float alpha + ) + + { + vertColor = color; + vertAlpha = alpha; + } + +void G_BeginLine + ( + void + ) + + { + currentVertex = vec_zero; + vertexIndex = 0; + } + +void G_Vertex + ( + const Vector &v + ) + + { + vertexIndex++; + if ( vertexIndex > 1.0f ) + { + G_DebugLine( currentVertex, v, vertColor[ 0 ], vertColor[ 1 ], vertColor[ 2 ], vertAlpha ); + } + currentVertex = v; + } + +void G_EndLine + ( + void + ) + + { + currentVertex = vec_zero; + vertexIndex = 0; + } + +void G_DebugBBox + ( + const Vector &org, + const Vector &mins, + const Vector &maxs, + float r, + float g, + float b, + float alpha + ) + { + int i; + Vector points[8]; + + /* + ** compute a full bounding box + */ + for ( i = 0; i < 8; i++ ) + { + Vector tmp; + + if ( i & 1 ) + tmp[0] = org[0] + mins[0]; + else + tmp[0] = org[0] + maxs[0]; + + if ( i & 2 ) + tmp[1] = org[1] + mins[1]; + else + tmp[1] = org[1] + maxs[1]; + + if ( i & 4 ) + tmp[2] = org[2] + mins[2]; + else + tmp[2] = org[2] + maxs[2]; + + points[i] = tmp; + } + + G_Color4f( r, g, b, alpha ); + + G_BeginLine(); + G_Vertex( points[0] ); + G_Vertex( points[1] ); + G_Vertex( points[3] ); + G_Vertex( points[2] ); + G_Vertex( points[0] ); + G_EndLine(); + + G_BeginLine(); + G_Vertex( points[4] ); + G_Vertex( points[5] ); + G_Vertex( points[7] ); + G_Vertex( points[6] ); + G_Vertex( points[4] ); + G_EndLine(); + + for ( i = 0; i < 4; i++ ) + { + G_BeginLine(); + G_Vertex( points[i] ); + G_Vertex( points[4 + i] ); + G_EndLine(); + } + } + +// +// LED style digits +// +// ****1*** +// * * 8 == / +// 6 *4 +// * * * +// ****2*** +// * * * +// 7 *--8 5 9 +// ** * **10 +// ****3*** 12** +// 11 + +static int Numbers[ 12 ][ 8 ] = + { + { 1, 3, 4, 5, 6, 7, 0, 0 }, // 0 + { 4, 5, 0, 0, 0, 0, 0, 0 }, // 1 + { 1, 4, 2, 7, 3, 0, 0, 0 }, // 2 + { 1, 4, 2, 5, 3, 0, 0, 0 }, // 3 + { 6, 4, 2, 5, 0, 0, 0, 0 }, // 4 + { 1, 6, 2, 5, 3, 0, 0, 0 }, // 5 + { 1, 6, 2, 5, 7, 3, 0, 0 }, // 6 + { 1, 8, 0, 0, 0, 0, 0, 0 }, // 7 + { 1, 2, 3, 4, 5, 6, 7, 0 }, // 8 + { 1, 6, 4, 2, 5, 3, 0, 0 }, // 9 + { 9, 10, 11, 12, 0, 0, 0, 0 }, // . + { 2, 0, 0, 0, 0, 0, 0, 0 }, // - + }; + +static float Lines[ 13 ][ 4 ] = + { + { 0, 0, 0, 0 }, // Unused + { -4, 8, 4, 8 }, // 1 + { -4, 4, 4, 4 }, // 2 + { -4, 0, 4, 0 }, // 3 + { 4, 8, 4, 4 }, // 4 + { 4, 4, 4, 0 }, // 5 + { -4, 8, -4, 4 }, // 6 + { -4, 4, -4, 0 }, // 7 + { 4, 8, -4, 0 }, // 8 + + { -1, 2, 1, 2 }, // 9 + { 1, 2, 1, 0 }, // 10 + { -1, 0, 1, 0 }, // 11 + { -1, 0, -1, 2 }, // 12 + }; + +void G_DrawDebugNumber + ( + const Vector &org, + float number, + float scale, + float r, + float g, + float b, + int precision + ) + + { + int i; + int j; + int l; + int num; + Vector up; + Vector left; + Vector pos; + Vector start; + Vector ang; + str text; + Vector delta; + char format[ 20 ]; + + // only draw entity numbers within a certain radius + delta = Vector( g_entities[ 0 ].s.origin ) - org; + if ( ( delta * delta ) > ( 1000.0f * 1000.0f ) ) + { + return; + } + + G_Color4f( r, g, b, 1.0 ); + + ang = game.clients[ 0 ].ps.viewangles; + ang.AngleVectors( NULL, &left, &up ); + + up *= scale; + left *= scale; + + if ( precision > 0 ) + { + sprintf( format, "%%.%df", precision ); + text = va( format, number ); + } + else + { + text = va( "%d", ( int )number ); + } + + start = org + ( (float)( text.length() - 1 ) * 5.0f * left ); + + for( i = 0; i < text.length(); i++ ) + { + if ( text[ i ] == '.' ) + { + num = 10; + } + else if ( text[ i ] == '-' ) + { + num = 11; + } + else + { + num = text[ i ] - '0'; + } + + for( j = 0; j < 8; j++ ) + { + l = Numbers[ num ][ j ]; + if ( l == 0 ) + { + break; + } + + G_BeginLine(); + + pos = start - ( Lines[ l ][ 0 ] * left ) + ( Lines[ l ][ 1 ] * up ); + G_Vertex( pos ); + + pos = start - ( Lines[ l ][ 2 ] * left ) + ( Lines[ l ][ 3 ] * up ); + G_Vertex( pos ); + + G_EndLine(); + } + + start -= 10.0f * left; + } + } + +void G_DebugCircle + ( + const Vector &org, + float radius, + float r, + float g, + float b, + float alpha, + qboolean horizontal + ) + { + int i; + float ang; + Vector angles; + Vector forward; + Vector left; + Vector pos; + Vector delta; + + // only draw circles within a certain radius + delta = Vector( g_entities[ 0 ].s.origin ) - org; + if ( ( delta * delta ) > ( ( 1000.0f + radius ) * ( 1000.0f + radius ) ) ) + { + return; + } + + G_Color4f( r, g, b, alpha ); + + if ( horizontal ) + { + forward = Vector(1, 0, 0); + left = Vector(0, -1, 0); + } + else + { + angles = game.clients[ 0 ].ps.viewangles; + angles.AngleVectors( NULL, &left, &forward ); + } + + G_BeginLine(); + for( i = 0; i <= NUM_CIRCLE_SEGMENTS; i++ ) + { + ang = DEG2RAD( i * 360.0f / NUM_CIRCLE_SEGMENTS ); + pos = org + ( sin( ang ) * radius * forward ) - ( cos( ang ) * radius * left ); + G_Vertex( pos ); + } + G_EndLine(); + } + +void G_DebugOrientedCircle + ( + const Vector &org, + float radius, + float r, + float g, + float b, + float alpha, + Vector angles + ) + { + int i; + float ang; + Vector forward; + Vector left; + Vector pos; + Vector delta; + + // only draw circles within a certain radius + delta = Vector( g_entities[ 0 ].s.origin ) - org; + if ( ( delta * delta ) > ( ( 1000.0f + radius ) * ( 1000.0f + radius ) ) ) + { + return; + } + + G_Color4f( r, g, b, alpha ); + + angles.AngleVectors( NULL, &left, &forward ); + + G_BeginLine(); + for( i = 0; i <= NUM_CIRCLE_SEGMENTS; i++ ) + { + ang = DEG2RAD( i * 360.0f / NUM_CIRCLE_SEGMENTS ); + pos = org + ( sin( ang ) * radius * forward ) - ( cos( ang ) * radius * left ); + G_Vertex( pos ); + } + G_EndLine(); + + // + // Draw the cross sign + // + G_BeginLine(); + ang = DEG2RAD( 45.0f * 360.0f / NUM_CIRCLE_SEGMENTS ); + pos = org + ( sin( ang ) * radius * forward ) - ( cos( ang ) * radius * left ); + G_Vertex( pos ); + ang = DEG2RAD( 225.0f * 360.0f / NUM_CIRCLE_SEGMENTS ); + pos = org + ( sin( ang ) * radius * forward ) - ( cos( ang ) * radius * left ); + G_Vertex( pos ); + + G_BeginLine(); + ang = DEG2RAD( 315.0f * 360.0f / NUM_CIRCLE_SEGMENTS ); + pos = org + ( sin( ang ) * radius * forward ) - ( cos( ang ) * radius * left ); + G_Vertex( pos ); + ang = DEG2RAD( 135.0f * 360.0f / NUM_CIRCLE_SEGMENTS ); + pos = org + ( sin( ang ) * radius * forward ) - ( cos( ang ) * radius * left ); + G_Vertex( pos ); + } + +void G_DebugPyramid + ( + const Vector &org, + float radius, + float r, + float g, + float b, + float alpha + ) + { + Vector delta; + Vector points[ 4 ]; + + // only draw pyramids within a certain radius + delta = Vector( g_entities[ 0 ].s.origin ) - org; + if ( ( delta * delta ) > ( ( 1000.0f + radius ) * ( 1000.0f + radius ) ) ) + { + return; + } + + G_Color4f( r, g, b, alpha ); + + points[ 0 ] = org; + points[ 0 ].z += radius; + + points[ 1 ] = org; + points[ 1 ].z -= radius; + points[ 2 ] = points[ 1 ]; + points[ 3 ] = points[ 1 ]; + + points[ 1 ].x += (float)cos( DEG2RAD( 0.0f ) ) * radius; + points[ 1 ].y += (float)sin( DEG2RAD( 0.0f ) ) * radius; + points[ 2 ].x += (float)cos( DEG2RAD( 120.0f ) ) * radius; + points[ 2 ].y += (float)sin( DEG2RAD( 120.0f ) ) * radius; + points[ 3 ].x += (float)cos( DEG2RAD( 240.0f ) ) * radius; + points[ 3 ].y += (float)sin( DEG2RAD( 240.0f ) ) * radius; + + G_BeginLine(); + G_Vertex( points[ 0 ] ); + G_Vertex( points[ 1 ] ); + G_Vertex( points[ 2 ] ); + G_Vertex( points[ 0 ] ); + G_EndLine(); + + G_BeginLine(); + G_Vertex( points[ 0 ] ); + G_Vertex( points[ 2 ] ); + G_Vertex( points[ 3 ] ); + G_Vertex( points[ 0 ] ); + G_EndLine(); + + G_BeginLine(); + G_Vertex( points[ 0 ] ); + G_Vertex( points[ 3 ] ); + G_Vertex( points[ 1 ] ); + G_Vertex( points[ 0 ] ); + G_EndLine(); + + G_BeginLine(); + G_Vertex( points[ 1 ] ); + G_Vertex( points[ 2 ] ); + G_Vertex( points[ 3 ] ); + G_Vertex( points[ 1 ] ); + G_EndLine(); + } + +void G_DrawCoordSystem + ( + const Vector &pos, + const Vector &forward, + const Vector &right, + const Vector &up, + int length + ) + + { + if ( g_showaxis->integer ) + { + G_DebugLine( pos, pos + ( forward * length ), 1.0f, 0.0f, 0.0f, 1.0f ); + G_DebugLine( pos, pos + ( right * length ), 0.0f, 1.0f, 0.0f, 1.0f ); + G_DebugLine( pos, pos + ( up * length ), 0.0f, 0.0f, 1.0f, 1.0f ); + } + } + +void G_DrawCSystem + ( + void + ) + + { + Vector pos; + Vector ang; + Vector f; + Vector l; + Vector u; + Vector v; + + pos.x = csys_posx->value; + pos.y = csys_posy->value; + pos.z = csys_posz->value; + + ang.x = csys_x->value; + ang.y = csys_y->value; + ang.z = csys_z->value; + + ang.AngleVectors( &f, &l, &u ); + + G_DebugLine( pos, pos + ( f * 48 ), 1.0f, 0.0f, 0.0f, 1.0f ); + G_DebugLine( pos, pos - ( l * 48 ), 0.0f, 1.0f, 0.0f, 1.0f ); + G_DebugLine( pos, pos + ( u * 48 ), 0.0f, 0.0f, 1.0f, 1.0f ); + } + +void G_DebugArrow + ( + const Vector &org, + const Vector &dir, + float length, + float r, + float g, + float b, + float alpha + ) + { + Vector right; + Vector up; + Vector startpoint; + Vector endpoint; + + PerpendicularVector( right, ( Vector )dir ); + up.CrossProduct( right, dir ); + + startpoint = org; + + endpoint = startpoint + ( dir * length ); + length /= 6.0f; + G_DebugLine( startpoint, endpoint, r, g, b, alpha ); + G_DebugLine( endpoint, endpoint - (right * length) - (dir * length), r, g, b, alpha ); + G_DebugLine( endpoint, endpoint + (right * length) - (dir * length), r, g, b, alpha ); + G_DebugLine( endpoint, endpoint - (up * length) - (dir * length), r, g, b, alpha ); + G_DebugLine( endpoint, endpoint + (up * length) - (dir * length), r, g, b, alpha ); + } + +void G_DebugHighlightFacet + ( + const Vector &org, + const Vector &mins, + const Vector &maxs, + facet_t facet, + float r, + float g, + float b, + float alpha + ) + { + int i; + Vector points[8]; + + /* + ** compute a full bounding box + */ + for ( i = 0; i < 8; i++ ) + { + Vector tmp; + + if ( i & 1 ) + tmp[0] = org[0] + mins[0]; + else + tmp[0] = org[0] + maxs[0]; + + if ( i & 2 ) + tmp[1] = org[1] + mins[1]; + else + tmp[1] = org[1] + maxs[1]; + + if ( i & 4 ) + tmp[2] = org[2] + mins[2]; + else + tmp[2] = org[2] + maxs[2]; + + points[i] = tmp; + } + + G_Color4f( r, g, b, alpha ); + + switch( facet ) + { + case facet_north: + G_BeginLine(); + G_Vertex( points[0] ); + G_Vertex( points[5] ); + G_EndLine(); + G_BeginLine(); + G_Vertex( points[1] ); + G_Vertex( points[4] ); + G_EndLine(); + break; + case facet_south: + G_BeginLine(); + G_Vertex( points[2] ); + G_Vertex( points[7] ); + G_EndLine(); + G_BeginLine(); + G_Vertex( points[3] ); + G_Vertex( points[6] ); + G_EndLine(); + break; + case facet_east: + G_BeginLine(); + G_Vertex( points[0] ); + G_Vertex( points[6] ); + G_EndLine(); + G_BeginLine(); + G_Vertex( points[4] ); + G_Vertex( points[2] ); + G_EndLine(); + break; + case facet_west: + G_BeginLine(); + G_Vertex( points[1] ); + G_Vertex( points[7] ); + G_EndLine(); + G_BeginLine(); + G_Vertex( points[5] ); + G_Vertex( points[3] ); + G_EndLine(); + break; + case facet_up: + G_BeginLine(); + G_Vertex( points[0] ); + G_Vertex( points[3] ); + G_EndLine(); + G_BeginLine(); + G_Vertex( points[1] ); + G_Vertex( points[2] ); + G_EndLine(); + break; + case facet_down: + G_BeginLine(); + G_Vertex( points[4] ); + G_Vertex( points[7] ); + G_EndLine(); + G_BeginLine(); + G_Vertex( points[5] ); + G_Vertex( points[6] ); + G_EndLine(); + break; + } + } diff --git a/dlls/game/debuglines.h b/dlls/game/debuglines.h new file mode 100644 index 0000000..f94e906 --- /dev/null +++ b/dlls/game/debuglines.h @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/debuglines.h $ +// $Revision:: 5 $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __DEBUGLINES_H__ +#define __DEBUGLINES_H__ + + +#if defined(__cplusplus) +#include "g_local.h" + +void G_InitDebugLines( void ); +void G_DebugLine( const Vector &start, const Vector &end, float r, float g, float b, float alpha ); +void G_LineStipple( int factor, unsigned short pattern ); +void G_LineWidth( float width ); +void G_Color3f( float r, float g, float b ); +void G_Color3v( const Vector &color ); +void G_Color4f( float r, float g, float b, float alpha ); +void G_Color3vf( const Vector &color, float alpha ); +void G_BeginLine( void ); +void G_Vertex( const Vector &v ); +void G_EndLine( void ); +void G_DebugBBox( const Vector &org, const Vector &mins, const Vector &maxs, float r, float g, float b, float alpha ); +void G_DrawDebugNumber( const Vector &org, float number, float scale, float r, float g, float b, int precision = 0 ); +void G_DebugCircle( const Vector &org, float radius, float r, float g, float b, float alpha, qboolean horizontal = false ); +void G_DebugOrientedCircle( const Vector &org, float radius, float r, float g, float b, float alpha, Vector angles ); +void G_DebugPyramid( const Vector &org, float radius, float r, float g, float b, float alpha ); +void G_DrawCoordSystem( const Vector &pos, const Vector &f, const Vector &r, const Vector &u, int len ); +void G_DebugArrow( const Vector &org, const Vector &dir, float length, float r, float g, float b, float alpha ); +void G_DrawCSystem( void ); + +typedef enum + { + facet_north, + facet_south, + facet_east, + facet_west, + facet_up, + facet_down + } facet_t; + +void G_DebugHighlightFacet( const Vector &org, const Vector &mins, const Vector &maxs, facet_t facet, float r, float g, float b, float alpha ); +#endif // defined(__cplusplus) + +#if defined(__cplusplus) +extern "C" { +#endif + void G_DebugLineC(const vec3_t start,const vec3_t end,float r,float g,float b,float alpha); +#if defined(__cplusplus) +} +#endif + +#endif /* !__DEBUGLINES_H__ */ diff --git a/dlls/game/decals.cpp b/dlls/game/decals.cpp new file mode 100644 index 0000000..95487a2 --- /dev/null +++ b/dlls/game/decals.cpp @@ -0,0 +1,69 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/decals.cpp $ +// $Revision:: 7 $ +// $Author:: Steven $ +// $Date:: 3/19/03 6:10p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Decal entities + +#include "_pch_cpp.h" +#include "decals.h" + + +CLASS_DECLARATION( Entity, Decal, NULL ) +{ + { NULL, NULL } +}; + +Decal::Decal() +{ + edict->s.eType = ET_DECAL; + edict->s.modelindex = 1; // must be non-zero + + if ( !LoadingSavegame ) + { + PostEvent( EV_Remove, FRAMETIME ); + } +} + +void Decal::setDirection( const Vector &dir ) +{ + edict->s.surfaces[0] = DirToByte( ( Vector )dir ); +} + +void Decal::setShader( const str &decal_shader ) +{ + str temp_shader; + + shader = decal_shader; + edict->s.tag_num = gi.imageindex( shader.c_str() ); + + temp_shader = shader + ".spr"; + CacheResource( temp_shader, this ); +} + +void Decal::setOrientation( const str ° ) +{ + Vector ang; + + if ( !deg.icmp( "random" ) ) + ang[2] = random() * 360.0f; + else + ang[2] = (float)atof( deg ); + + setAngles( ang ); +} + +void Decal::setRadius( float rad ) +{ + edict->s.scale = rad; +} diff --git a/dlls/game/decals.h b/dlls/game/decals.h new file mode 100644 index 0000000..e65a58e --- /dev/null +++ b/dlls/game/decals.h @@ -0,0 +1,53 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/decals.h $ +// $Revision:: 5 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Decal entities + +#ifndef __DECAL_H__ +#define __DECAL_H__ + +#include "g_local.h" + +class Decal : public Entity + { + private: + str shader; + + public: + CLASS_PROTOTYPE( Decal ); + + Decal(); + void setDirection( const Vector &dir ); + void setShader( const str &shader ); + void setOrientation( const str ° ); + void setRadius( float rad ); + virtual void Archive( Archiver &arc ); + }; + +inline void Decal::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveString( &shader ); + if ( arc.Loading() ) + { + setShader( shader ); + } + } + +#endif // __DECAL_H__ diff --git a/dlls/game/dispenser.cpp b/dlls/game/dispenser.cpp new file mode 100644 index 0000000..bbf9d90 --- /dev/null +++ b/dlls/game/dispenser.cpp @@ -0,0 +1,623 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/dispenser.cpp $ +// $Revision:: 20 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// This is all the dispensing type objects in the world. They basically work +// by the player using them. As the player uses them they give the player +// something (health/ammo/etc.). +// + +#include "_pch_cpp.h" +#include "dispenser.hpp" +#include "sentient.h" +#include "player.h" + +Event EV_Dispenser_SetMaxAmount +( + "dispenser_maxamount", + EV_DEFAULT, + "f", + "maxAmount", + "Sets the total amount that this dispenser can give out." +); +Event EV_Dispenser_SetDispenseRate +( + "dispenser_rate", + EV_DEFAULT, + "f", + "dispenseRate", + "Sets the dispense rate (how many units per second to give out)." +); +Event EV_Dispenser_SetRegenRate +( + "dispenser_regenrate", + EV_DEFAULT, + "f", + "regenRate", + "Sets the regen rate (how many units per second to regen)." +); +Event EV_Dispenser_SetType +( + "dispenser_type", + EV_DEFAULT, + "s", + "type", + "Sets the type of thing to give out.\n" + "Only health and ammo are valid currently." +); +Event EV_Dispenser_SetSubtype +( + "dispenser_subtype", + EV_DEFAULT, + "s", + "subtype", + "Sets the subtype of thing to give out.\n" + "There is no subtype for health, the subtype for ammo is the ammo type." +); +Event EV_Dispenser_SetOpenDistance +( + "dispenser_openDistance", + EV_DEFAULT, + "f", + "openDistance", + "Sets the distance from the player that the dispenser will open/close\n" +); +Event EV_Dispenser_SetInstant +( + "dispenser_instant", + EV_DEFAULT, + NULL, + NULL, + "Makes the dispenser give out its stuff instantly\n" +); +Event EV_Dispenser_AnimDone +( + "dispenser_animdone", + EV_CODEONLY, + NULL, + NULL, + "Called when the dispenser's animation is done" +); +Event EV_Dispenser_SetSoundName +( + "dispenser_soundName", + EV_DEFAULT, + "s", + "soundName", + "Makes the dispenser use the specified looping sound when its being used\n" +); + +//------------------------- CLASS ------------------------------ +// +// Name: Dispenser +// Base Class: Entity +// +// Description: This is an entity that dispenses stuff (health, ammo, etc) +// to anything that uses it +// +// Method of Use: Just like any entity. +// +//-------------------------------------------------------------- + +CLASS_DECLARATION( Entity, Dispenser, NULL ) +{ + { &EV_Dispenser_SetMaxAmount, &Dispenser::setMaxAmount }, + { &EV_Dispenser_SetDispenseRate, &Dispenser::setDispenseRate }, + { &EV_Dispenser_SetRegenRate, &Dispenser::setRegenRate }, + { &EV_Dispenser_SetType, &Dispenser::setType }, + { &EV_Dispenser_SetSubtype, &Dispenser::setSubtype }, + { &EV_Dispenser_SetOpenDistance, &Dispenser::setOpenDistance }, + { &EV_Dispenser_SetInstant, &Dispenser::setInstant }, + { &EV_Dispenser_AnimDone, &Dispenser::animDone }, + { &EV_Dispenser_SetSoundName, &Dispenser::setSoundName }, + { &EV_Use, &Dispenser::useEvent }, + + { NULL, NULL } +}; + +//---------------------------------------------------------------- +// Name: Dispenser +// Class: Dispenser +// +// Description: constructor +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- + +Dispenser::Dispenser() +{ + animate = new Animate( this ); + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_BBOX ); + setContents( CONTENTS_BODY ); + + _maxAmount = 0; + _amount = 0; + + _dispenseRate = 0; + _regenRate = 0; + + _dispenseType = DISPENSE_TYPE_NONE; + + _lastTimeUsed = 0; + + edict->s.eType = ET_MODELANIM; + + _openDistance = 200.0f; + + if ( !LoadingSavegame ) + { + Event *event = new Event( EV_Anim ); + event->AddString( "closed_idle" ); + PostEvent( event, 0.0f ); + } + + _state = DISPENSER_STATE_CLOSED; + + _instant = false; + + _entityDispensingTo = NULL; + + turnThinkOn(); + + _playingSound = false; + _lastSoundTime = 0.0f; +} + +//---------------------------------------------------------------- +// Name: Think +// Class: Dispenser +// +// Description: Does everything the dispenser needs to do for this frame +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- + +void Dispenser::Think( void ) +{ + bool playerNearby; + Player *player; + Vector dir; + float distance; + + if ( _playingSound && level.time > _lastSoundTime + 0.25f ) + { + StopLoopSound(); + _playingSound = false; + } + + if ( _entityDispensingTo ) + { + bool dispensedStuff; + + dispensedStuff = dispenseStuff( _entityDispensingTo ); + + if ( dispensedStuff ) + return; + else + { + _entityDispensingTo = NULL; + } + } + + // Regenerate if we are suppose to + + if ( _regenRate > 0.0f ) + { + _amount += _regenRate * level.frametime; + + if ( _amount > _maxAmount ) + { + _amount = _maxAmount; + } + + setDispenserAlpha(); + } + + // Do necessary animation stuff + + playerNearby = false; + + if ( _state != DISPENSER_STATE_EMPTY ) + { + // Get the player + + player = (Player *)g_entities[ 0 ].entity; + + // See if the player is nearby + + if ( player ) + { + dir = player->origin - origin; + distance = dir.length(); + + if ( distance < _openDistance ) + { + playerNearby = true; + } + } + } + + if ( _state == DISPENSER_STATE_CLOSED ) + { + if ( playerNearby ) + { + animate->RandomAnimate( "opening", EV_Dispenser_AnimDone ); + _state = DISPENSER_STATE_OPENING; + } + } + else if ( _state == DISPENSER_STATE_OPEN ) + { + if ( !playerNearby ) + { + animate->RandomAnimate( "closing", EV_Dispenser_AnimDone ); + _state = DISPENSER_STATE_CLOSING; + } + } +} + +//---------------------------------------------------------------- +// Name: useEvent +// Class: Dispenser +// +// Description: This is what is called when the dispenser is used by something. +// This function dispenses whatever it needs to here. +// +// Parameters: Event *ev - event (contains using entity) +// +// Returns: None +//---------------------------------------------------------------- + +void Dispenser::useEvent( Event *ev ) +{ + Sentient *sent; + Entity *ent; + + + // Make sure we should bother trying to give something out + + if ( _amount <= 0 ) + return; + + if ( _lastTimeUsed == level.time ) + return; + + //if ( _state != DISPENSER_STATE_OPEN ) + // return; + + // Get the entity that is using us + + ent = ev->GetEntity( 1 ); + + if ( !ent->isSubclassOf( Sentient ) ) + return; + + sent = (Sentient *)ent; + + if ( _instant ) + { + _entityDispensingTo = sent; + } + else + { + dispenseStuff( sent ); + } + + _lastTimeUsed = level.time; +} + +bool Dispenser::dispenseStuff( Sentient *sent ) +{ + DispenseType currentDispenseType = DISPENSE_TYPE_HEALTH; + float maxAmountToGive; + float amountToGive; + float entityMaxAmount = 0.0f; + float entityCurrentAmount = 0.0f; + + if ( !_playingSound ) + { + LoopSound( _useSoundName ); + _playingSound = true; + } + + _lastSoundTime = level.time; + + // Get the max amount/current amount of this type of thing for this entity + + if ( _dispenseType == DISPENSE_TYPE_HEALTH ) + { + entityMaxAmount = sent->getMaxHealth(); + entityCurrentAmount = sent->getHealth(); + + currentDispenseType = DISPENSE_TYPE_HEALTH; + + if ( entityCurrentAmount == entityMaxAmount ) + { + entityMaxAmount = 100.0f; + entityCurrentAmount = sent->GetArmorValue(); + + currentDispenseType = DISPENSE_TYPE_ARMOR; + } + } + else if ( _dispenseType == DISPENSE_TYPE_AMMO ) + { + entityMaxAmount = sent->MaxAmmoCount( _dispenseSubtype ); + entityCurrentAmount = sent->AmmoCount( _dispenseSubtype ); + + currentDispenseType = DISPENSE_TYPE_AMMO; + } + + // Calculate the amount to give + + maxAmountToGive = _dispenseRate * level.frametime; + + if ( _dispenseType == DISPENSE_TYPE_AMMO ) + { + amountToGive = maxAmountToGive; + } + else + { + if ( maxAmountToGive > entityMaxAmount - entityCurrentAmount ) + amountToGive = entityMaxAmount - entityCurrentAmount; + else + amountToGive = maxAmountToGive; + } + + if ( amountToGive > _amount ) + amountToGive = _amount; + + if ( amountToGive < 0.0f ) + amountToGive = 0.0f; + + // Give the calculated amount to the entity + + if ( amountToGive ) + { + if ( currentDispenseType == DISPENSE_TYPE_HEALTH ) + { + sent->setHealth( entityCurrentAmount + amountToGive ); + } + else if ( currentDispenseType == DISPENSE_TYPE_ARMOR ) + { + Event *armorEvent = new Event( EV_Sentient_GiveArmor ); + armorEvent->AddString( "BasicArmor" ); + armorEvent->AddFloat( amountToGive ); + sent->ProcessEvent( armorEvent ); + } + else if ( currentDispenseType == DISPENSE_TYPE_AMMO ) + { + int ammoGiven; + ammoGiven = (int) sent->GiveAmmo( _dispenseSubtype, (int)amountToGive, false ); + amountToGive = ammoGiven; + } + } + + // Use up our amount + + _amount -= amountToGive; + + setDispenserAlpha(); + + // See if we are empty now + + if ( ( _amount <= 0.0f ) && ( _regenRate == 0.0f ) ) + { + animate->RandomAnimate( "emptying", EV_Dispenser_AnimDone ); + _state = DISPENSER_STATE_EMPTYING; + } + + if ( amountToGive > 0 ) + return true; + else + return false; +} + +//---------------------------------------------------------------- +// Name: setMaxAmount +// Class: Dispenser +// +// Description: Sets the maximum amount that we can dispense. It +// also sets the current amount to the maximum amount. +// +// Parameters: Event *ev - event (contains max amount) +// +// Returns: None +//---------------------------------------------------------------- + +void Dispenser::setMaxAmount( Event *ev ) +{ + _maxAmount = ev->GetFloat( 1 ); + + _amount = _maxAmount; +} + +//---------------------------------------------------------------- +// Name: setDispenseRate +// Class: Dispenser +// +// Description: Sets the rate that we will dispense stuff in units per second. +// +// Parameters: Event *ev - event (contains dispense rate) +// +// Returns: None +//---------------------------------------------------------------- + +void Dispenser::setDispenseRate( Event *ev ) +{ + _dispenseRate = ev->GetFloat( 1 ); +} + +//---------------------------------------------------------------- +// Name: setRegenRate +// Class: Dispenser +// +// Description: Sets the regen rate that we will regenerate our +// amount we can give out. Only does this is below its maximum +// amount. This is also in units per second. +// +// Parameters: Event *ev - event (contains regen rate) +// +// Returns: None +//---------------------------------------------------------------- + +void Dispenser::setRegenRate( Event *ev ) +{ + _regenRate = ev->GetFloat( 1 ); +} + +//---------------------------------------------------------------- +// Name: setType +// Class: Dispenser +// +// Description: Sets the type of stuff we are giving out (health, ammo, etc.) +// +// Parameters: Event *ev - event (contains type) +// +// Returns: None +//---------------------------------------------------------------- + +void Dispenser::setType( Event *ev ) +{ + str typeName; + + typeName = ev->GetString( 1 ); + + if ( typeName == "health" ) + { + _dispenseType = DISPENSE_TYPE_HEALTH; + } + else if ( typeName == "ammo" ) + { + _dispenseType = DISPENSE_TYPE_AMMO; + } + else + { + gi.WDPrintf( "Unknown dispense type %s\n", typeName.c_str() ); + _dispenseType = DISPENSE_TYPE_NONE; + } +} + +//---------------------------------------------------------------- +// Name: setSubtype +// Class: Dispenser +// +// Description: Sets the subtype of the thing we are giving out. +// For ammo - this is the ammo type +// +// Parameters: Event *ev - event (contains subtype) +// +// Returns: None +//---------------------------------------------------------------- + +void Dispenser::setSubtype( Event *ev ) +{ + _dispenseSubtype = ev->GetString( 1 ); +} + +//---------------------------------------------------------------- +// Name: setOpenDistance +// Class: Dispenser +// +// Description: Sets the distance from the player that the dispenser will open/close +// +// Parameters: Event *ev - event (contains open distance) +// +// Returns: None +//---------------------------------------------------------------- + +void Dispenser::setOpenDistance( Event *ev ) +{ + _openDistance = ev->GetFloat( 1 ); +} + +//---------------------------------------------------------------- +// Name: animDone +// Class: Dispenser +// +// Description: Called when on of the dispenser's animation is done playing +// +// Parameters: Event * - not used +// +// Returns: None +//---------------------------------------------------------------- + +void Dispenser::animDone( Event * ) +{ + // The animation is done playing so, move the dispenser to the correct state and play the appropriate animation + + if ( _state == DISPENSER_STATE_CLOSING ) + { + animate->RandomAnimate( "closed_idle" ); + _state = DISPENSER_STATE_CLOSED; + } + else if ( _state == DISPENSER_STATE_OPENING ) + { + animate->RandomAnimate( "open_idle" ); + _state = DISPENSER_STATE_OPEN; + } + else if ( _state == DISPENSER_STATE_EMPTYING ) + { + animate->RandomAnimate( "empty_idle" ); + _state = DISPENSER_STATE_EMPTY; + } +} + +//---------------------------------------------------------------- +// Name: setDispenserAlpha +// Class: Dispenser +// +// Description: Sets the alpha of the dispenser based on how much amount is left +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- + +void Dispenser::setDispenserAlpha( void ) +{ + float percentLeft; + + if ( _maxAmount ) + { + percentLeft = _amount / _maxAmount; + + setAlpha( percentLeft ); + G_SetConstantLight( &edict->s.constantLight, &percentLeft, &percentLeft, &percentLeft, 0 ); + } +} + +//---------------------------------------------------------------- +// Name: setInstant +// Class: Dispenser +// +// Description: Sets this dispenser to give out stuff instant (only have to hit use once) +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- + +void Dispenser::setInstant( Event *ev ) +{ + _instant = qtrue; +} + +void Dispenser::setSoundName( Event *ev ) +{ + _useSoundName = ev->GetString( 1 ); +} diff --git a/dlls/game/dispenser.hpp b/dlls/game/dispenser.hpp new file mode 100644 index 0000000..b3bf894 --- /dev/null +++ b/dlls/game/dispenser.hpp @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/misc.cpp $ +// $Revision:: 13 $ +// $Author:: Steven $ +// $Date:: 2/18/02 1:35p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// This is all the dispensing type objects in the world. They basically work +// by the player using them. As the player uses them they give the player +// something (health/ammo/etc.). +// + +#ifndef __DISPENSER_HPP__ +#define __DISPENSER_HPP__ + +//#include "g_local.h" +#include "entity.h" + +//------------------------- CLASS ------------------------------ +// +// Name: Dispenser +// Base Class: Entity +// +// Description: This is an entity that dispenses stuff (health, ammo, etc) +// to anything that uses it +// +// Method of Use: Just like any entity. +// +//-------------------------------------------------------------- + +class Dispenser : public Entity +{ + typedef enum + { + DISPENSE_TYPE_NONE, + DISPENSE_TYPE_HEALTH, + DISPENSE_TYPE_AMMO, + DISPENSE_TYPE_ARMOR + } DispenseType; + + typedef enum + { + DISPENSER_STATE_CLOSED, + DISPENSER_STATE_CLOSING, + DISPENSER_STATE_OPEN, + DISPENSER_STATE_OPENING, + DISPENSER_STATE_EMPTY, + DISPENSER_STATE_EMPTYING + } DispenserState; + +private: + float _maxAmount; + float _amount; + float _dispenseRate; + float _regenRate; + DispenseType _dispenseType; + str _dispenseSubtype; + float _lastTimeUsed; + DispenserState _state; + float _openDistance; + bool _instant; + SentientPtr _entityDispensingTo; + str _useSoundName; + bool _playingSound; + float _lastSoundTime; + +protected: + void setDispenserAlpha( void ); + bool dispenseStuff( Sentient *sent ); + +public: + + CLASS_PROTOTYPE( Dispenser ); + + Dispenser(); + + /* virtual */ void Think( void ); + void useEvent( Event *ev ); + + void setMaxAmount( Event *ev ); + void setDispenseRate( Event *ev ); + void setRegenRate( Event *ev ); + void setType( Event *ev ); + void setSubtype( Event *ev ); + void setOpenDistance( Event *ev ); + void setInstant( Event *ev ); + void setSoundName( Event *ev ); + + void animDone( Event * ); + + virtual void Archive( Archiver &arc ); +}; + +inline void Dispenser::Archive( Archiver &arc ) +{ + Entity::Archive( arc ); + + arc.ArchiveFloat( &_maxAmount ); + arc.ArchiveFloat( &_amount ); + arc.ArchiveFloat( &_dispenseRate ); + arc.ArchiveFloat( &_regenRate ); + + ArchiveEnum( _dispenseType, DispenseType ); + + arc.ArchiveString( &_dispenseSubtype ); + arc.ArchiveFloat( &_lastTimeUsed ); + + ArchiveEnum( _state, DispenserState ); + + arc.ArchiveFloat( &_openDistance ); + + arc.ArchiveBool( &_instant ); + + arc.ArchiveSafePointer( &_entityDispensingTo ); + + arc.ArchiveString( &_useSoundName ); + arc.ArchiveBool( &_playingSound ); + arc.ArchiveFloat( &_lastSoundTime ); +} + +#endif /* Dispenser.h */ diff --git a/dlls/game/doAttack.cpp b/dlls/game/doAttack.cpp new file mode 100644 index 0000000..d52d875 --- /dev/null +++ b/dlls/game/doAttack.cpp @@ -0,0 +1,329 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/doAttack.cpp $ +// $Revision:: 11 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// DoAttack Behavior Implementation +// -- DoAttack will rotate the actor to face his current enemy, after the +// the rotation( with optional animation ) has completed, it will check +// if the actor is able to hit its current enemy with an attack. If that +// check is successful, then it will play its specified attack animation. +// +// Currently, you must put the attack commands inside the animation. +// +// ANIMATIONS: +// Attack Animation : Parameter +// Rotation Animation : Optional +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "doAttack.hpp" +#include + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, DoAttack, NULL ) + { + { &EV_Behavior_Args, &DoAttack::SetArgs }, + { &EV_Behavior_AnimDone, &DoAttack::AnimDone }, + { NULL, NULL } + }; + + + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: DoAttack +// +// Description: +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void DoAttack::SetArgs( Event *ev ) +{ + _anim = ev->GetString( 1 ); + if ( ev->NumArgs() > 1 ) + _turnspeed = ev->GetFloat( 2 ); + else + _turnspeed = 30.0f; + + if ( ev->NumArgs() > 2 ) + _forceAttack = ev->GetBoolean( 3 ); + else + _forceAttack = false; + + if ( ev->NumArgs() > 3 ) + _rotateAnim = ev->GetString( 4 ); + else + _rotateAnim = ""; +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: DoAttack +// +// Description: AnimDone Event Handler +// +// Parameters: Event *ev -- The AnimDone event +// +// Returns: None +//-------------------------------------------------------------- +void DoAttack::AnimDone( Event *ev ) +{ + _state = ATTACK_STATE_COMPLETE; +} + + + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: DoAttack +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void DoAttack::Begin( Actor &self ) +{ + init ( self ); + _setupRotate( self ); + _state = ATTACK_STATE_ROTATE; +} + + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: DoAttack +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t DoAttack::Evaluate( Actor &self ) +{ + if ( _state != ATTACK_STATE_ANIMATING ) + _rotate( self ); + + if ( _state == ATTACK_STATE_START_ANIM ) + { + _playAttackAnim( self ); + _state = ATTACK_STATE_ANIMATING; + } + + if ( _state == ATTACK_STATE_FAILED ) + return BEHAVIOR_FAILED; + + if ( _state == ATTACK_STATE_COMPLETE ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: DoAttack +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void DoAttack::End(Actor &self) +{ + _rotateBehavior.End(self); +} + + + + +//-------------------------------------------------------------- +// Name: _setupRotate() +// Class: DoAttack +// +// Description: Sets up the Rotate Component Behavior +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void DoAttack::_setupRotate( Actor &self ) +{ + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( currentEnemy ) + { + _rotateBehavior.SetEntity( currentEnemy ); + _rotateBehavior.SetTurnSpeed( _turnspeed ); + if ( _rotateAnim.length() ) + _rotateBehavior.SetAnim( _rotateAnim ); + + _rotateBehavior.Begin( self ); + } +} + + + +//-------------------------------------------------------------- +// Name: _rotate() +// Class: DoAttack +// +// Description: Evaluates the Rotate Component Behavior +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void DoAttack::_rotate( Actor &self ) +{ + BehaviorReturnCode_t result; + + result = _rotateBehavior.Evaluate( self ); + + // If we get a failure from rotate, chances are it didn't have + // an entity to rotate to... Let's give it one more shot. + if ( result == BEHAVIOR_FAILED ) + { + _setupRotate( self ); + result = _rotateBehavior.Evaluate( self ); + + // If we have a failure again, then we DO have a serious + // problem + if ( result == BEHAVIOR_FAILED ) + { + str failureReason; + failureReason = "Rotate Component Failed: Returned -- "; + failureReason += _rotateBehavior.GetFailureReason(); + SetFailureReason( failureReason ); + _state = ATTACK_STATE_FAILED; + } + + } + + if ( result == BEHAVIOR_SUCCESS ) + { + if ( _canAttack( self ) ) + { + if ( _state != ATTACK_STATE_ANIMATING && _state != ATTACK_STATE_COMPLETE ) + { + _state = ATTACK_STATE_START_ANIM; + return; + } + } + + else + _state = ATTACK_STATE_FAILED; + + } + +} + + + +//-------------------------------------------------------------- +// Name: _playAttackAnim() +// Class: DoAttack +// +// Description: Plays the specified attack animation +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void DoAttack::_playAttackAnim( Actor &self ) +{ + self.ClearTorsoAnim(); + self.SetAnim( _anim , EV_Actor_NotifyBehavior); + _state = ATTACK_STATE_ANIMATING; +} + + + +//-------------------------------------------------------------- +// Name: _canAttack() +// Class: DoAttack +// +// Description: Checks if the actor can attack +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +bool DoAttack::_canAttack( Actor &self ) + { + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( _forceAttack ) + return true; + + if ( !currentEnemy) + return false; + + if ( self.combatSubsystem->CanAttackTarget( currentEnemy ) ) + return true; + else + return false; + } + +void DoAttack::init( Actor &self ) +{ + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(self.getArchetype()) ) + return; + + if ( !self.combatSubsystem->HaveWeapon() ) + return; + + str objname = self.combatSubsystem->GetActiveWeaponArchetype(); + objname = "Hold" + objname; + + if ( gpm->hasProperty(objname, "Melee") ) + _anim = gpm->getStringValue( objname , "Melee" ); + else + { + str weaponName = self.combatSubsystem->GetActiveWeaponName(); + + if ( !weaponName.length() ) + { + weaponName = "UNSPECIFIED"; + } + + gi.WDPrintf( "\n\n\nDoAttack: TargetName %s ( Entnum %i ) has a weapon, but does not have a MeleeProperty ( Trying Weapon: %s ) in the GPD, Please notify a programmer of this error\n\n\n" , self.TargetName() , self.entnum, weaponName.c_str() ); + //gi.Error( ERR_DROP , "\n\n\nDoAttack: TargetName %s ( Entnum %i ) has a weapon, but does not have a MeleeProperty ( Trying Weapon: %s ) in the GPD, Please notify a programmer of this error\n\n\n" , self.TargetName() , self.entnum, weaponName.c_str() ); + } + +} diff --git a/dlls/game/doAttack.hpp b/dlls/game/doAttack.hpp new file mode 100644 index 0000000..0d5fead --- /dev/null +++ b/dlls/game/doAttack.hpp @@ -0,0 +1,129 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/doAttack.hpp $ +// $Revision:: 169 $ +// $Author:: Bschofield $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// DoAttack Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class RotateToEntity; + +#ifndef __DO_ATTACK_H__ +#define __DO_ATTACK_H__ + +#include "behavior.h" +#include "rotateToEntity.hpp" + +//------------------------- CLASS ------------------------------ +// +// Name: DoAttack +// Base Class: Behavior +// +// Description: Rotates the Actor towards its enemy then +// plays the specified attack animation +// This is good for actors that aren't using +// "real" weapons and are relying on animations +// to attack +// +// Method of Use: State machine or called from another behavior +//-------------------------------------------------------------- +class DoAttack : public Behavior + { + public: // States + typedef enum + { + ATTACK_STATE_SETUP, + ATTACK_STATE_ROTATE, + ATTACK_STATE_START_ANIM, + ATTACK_STATE_ANIMATING, + ATTACK_STATE_COMPLETE, + ATTACK_STATE_FAILED + } attackStates_t; + + private: // Parameters + str _anim; + float _turnspeed; + bool _forceAttack; + str _rotateAnim; + + protected: + void _setupRotate ( Actor &self ); + void _rotate ( Actor &self ); + void _playAttackAnim ( Actor &self ); + + bool _canAttack ( Actor &self ); + void init ( Actor &self ); + + public: + CLASS_PROTOTYPE( DoAttack ); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + void SetAnim ( const str &animName ); + void SetTurnSpeed ( float turnSpeed ); + void SetForceAttack ( bool force ); + void SetRotateAnim ( const str &animName ); + + private: + unsigned int _state; + RotateToEntity _rotateBehavior; + }; + + +inline void DoAttack::SetAnim( const str &animName ) +{ + _anim = animName; +} + +inline void DoAttack::SetTurnSpeed( float turnSpeed ) +{ + _turnspeed = turnSpeed; +} + +inline void DoAttack::SetForceAttack( bool force ) +{ + _forceAttack = force; +} + +inline void DoAttack::SetRotateAnim( const str &animName ) +{ + _rotateAnim = animName; +} + +inline void DoAttack::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_anim ); + arc.ArchiveFloat ( &_turnspeed ); + arc.ArchiveBool ( &_forceAttack ); + arc.ArchiveString ( &_rotateAnim ); + + // Archive Components + + // Archive Member Vars + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveObject ( &_rotateBehavior ); +} + +#endif /* __DO_ATTACK_H__ */ diff --git a/dlls/game/doors.cpp b/dlls/game/doors.cpp new file mode 100644 index 0000000..e947539 --- /dev/null +++ b/dlls/game/doors.cpp @@ -0,0 +1,2058 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/doors.cpp $ +// $Revision:: 28 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Doors are environment objects that rotate open when activated by triggers +// or when used by the player. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "trigger.h" +#include "mover.h" +#include "doors.h" +#include "sentient.h" +#include "scriptmaster.h" +#include "item.h" +#include "actor.h" +#include "player.h" +#include "g_utils.h" +#include + +Event EV_Door_StopSound +( + "sound_stop", + EV_SCRIPTONLY, + "s", + "sound_stop", + "Sets the sound to use when the door stops." +); +Event EV_Door_MoveSound +( + "sound_move", + EV_SCRIPTONLY, + "s", + "sound_move", + "Sets the sound to use when the door moves." +); +Event EV_Door_MessageSound +( + "sound_message", + EV_SCRIPTONLY, + "s", + "sound_message", + "Sets the sound to use when the door displays a message." +); +Event EV_Door_LockedSound +( + "sound_locked", + EV_SCRIPTONLY, + "s", + "sound_locked", + "Sets the sound to use when the door is locked." +); +Event EV_Door_SetWait +( + "wait", + EV_SCRIPTONLY, + "f", + "wait", + "Sets the amount of time to wait before automatically shutting." +); +Event EV_Door_SetDmg +( + "dmg", + EV_SCRIPTONLY, + "i", + "damage", + "Sets the amount of damage the door will do to entities that get stuck in it." +); +Event EV_Door_TriggerFieldTouched +( + "door_triggerfield", + EV_SCRIPTONLY, + "e", + "other", + "Is called when a doors trigger field is touched." +); +Event EV_Door_TryOpen +( + "tryToOpen", + EV_SCRIPTONLY, + "e", + "other", + "Tries to open the door." +); +Event EV_Door_Close +( + "close", + EV_SCRIPTONLY, + NULL, + NULL, + "Closes the door." +); +Event EV_Door_Open +( + "open", + EV_SCRIPTONLY, + "e", + "other", + "Opens the door." +); +Event EV_Door_DoClose +( + "doclose", + EV_SCRIPTONLY, + NULL, + NULL, + "Closes the door (special doors)." +); +Event EV_Door_DoOpen +( + "doopen", + EV_SCRIPTONLY, + "e", + "other", + "Opens the door (special doors)." +); +Event EV_Door_CloseEnd +( + "doorclosed", + EV_CODEONLY, + NULL, + NULL, + "Called when the door finishes closing." +); +Event EV_Door_OpenEnd +( + "dooropened", + EV_CODEONLY, + NULL, + NULL, + "Called when the door finishes opening." +); +Event EV_Door_Fire +( + "toggledoor", + EV_SCRIPTONLY, + "e", + "other", + "Toggles the state of the door (open/close)." +); +Event EV_Door_Link +( + "linkdoor", + EV_SCRIPTONLY, + NULL, + NULL, + "Link doors together." +); +Event EV_Door_SetTime +( + "time", + EV_SCRIPTONLY, + "f", + "traveltime", + "Sets the time it takes for the door to open an close." +); +Event EV_Door_Lock +( + "lock", + EV_SCRIPTONLY, + NULL, + NULL, + "Lock the door." +); +Event EV_Door_Unlock +( + "unlock", + EV_SCRIPTONLY, + NULL, + NULL, + "Unlock the door." +); +Event EV_Door_ExtraTriggerSize +( + "extraTriggerSize", + EV_SCRIPTONLY, + "v", + "extraTriggerSize", + "Sets the extra trigger size to use for a door, defaults to (0 0 0)." +); + +#define DOOR_START_OPEN 1 +#define DOOR_OPEN_DIRECTION 2 +#define DOOR_DONT_LINK 4 +#define DOOR_TOGGLE 32 +#define DOOR_AUTO_OPEN 64 +#define DOOR_TARGETED 128 + +#define STATE_OPEN 1 +#define STATE_OPENING 2 +#define STATE_CLOSING 3 +#define STATE_CLOSED 4 + +/* + +Doors are similar to buttons, but can spawn a fat trigger field around them +to open without a touch, and they link together to form simultanious +double/quad doors. + +Door.master is the master door. If there is only one door, it points to itself. +If multiple doors, all will point to a single one. + +Door.enemy chains from the master door through all doors linked in the chain. + +*/ + +CLASS_DECLARATION( ScriptSlave, Door, "NormalDoor" ) +{ + { &EV_Door_StopSound, &Door::SetStopSound }, + { &EV_Door_MoveSound, &Door::SetMoveSound }, + { &EV_Door_MessageSound, &Door::SetMessageSound }, + { &EV_Door_LockedSound, &Door::SetLockedSound }, + { &EV_Door_SetWait, &Door::SetWait }, + { &EV_Door_SetDmg, &Door::SetDmg }, + { &EV_Door_TriggerFieldTouched, &Door::FieldTouched }, + { &EV_Trigger_Effect, &Door::TryOpen }, + { &EV_Activate, &Door::TryOpen }, + { &EV_Door_TryOpen, &Door::TryOpen }, + { &EV_Door_Close, &Door::Close }, + { &EV_Door_Open, &Door::Open }, + { &EV_Door_CloseEnd, &Door::CloseEnd }, + { &EV_Door_OpenEnd, &Door::OpenEnd }, + { &EV_Door_Fire, &Door::DoorFire }, + { &EV_Door_Link, &Door::LinkDoors }, + { &EV_Door_SetTime, &Door::SetTime }, + { &EV_Use, &Door::DoorUse }, + { &EV_Killed, &Door::DoorFire }, + { &EV_Blocked, &Door::DoorBlocked }, + { &EV_Door_Lock, &Door::LockDoor }, + { &EV_Door_Unlock, &Door::UnlockDoor }, + { &EV_SetAngle, &Door::SetDir }, + { &EV_Touch, NULL }, + { &EV_SetGameplayDamage, &Door::setDamage }, + { &EV_Door_ExtraTriggerSize, &Door::setExtraTriggerSize }, + + { NULL, NULL } +}; + +Door::Door() +{ + float t; + const char *text; + + if ( LoadingSavegame ) + { + return; + } + + nextdoor = 0; + trigger = 0; + locked = false; + master = this; + lastblocktime = 0; + diropened = 0; + + dir = G_GetMovedir( 0.0f ); + t = dir[ 0 ]; + dir[ 0 ] = -dir[ 1 ]; + dir[ 1 ] = t; + + showModel(); + + text = gi.GlobalAlias_FindRandom( "door_stop" ); + if ( text ) + { + SetStopSound( text ); + } + + text = gi.GlobalAlias_FindRandom( "door_moving" ); + if ( text ) + { + SetMoveSound( text ); + } + + text = gi.GlobalAlias_FindRandom( "snd_locked" ); + if ( text ) + { + SetLockedSound( text ); + } + + traveltime = 0.3; + speed = 1.0f / traveltime; + + wait = ( spawnflags & DOOR_TOGGLE ) ? 0 : 3; + dmg = 0; + + setSize( mins, maxs ); + + setOrigin( GetLocalOrigin() ); + + // DOOR_START_OPEN is to allow an entity to be lighted in the closed position + // but spawn in the open position + if ( spawnflags & DOOR_START_OPEN ) + { + state = STATE_OPEN; + PostEvent( EV_Door_Open, EV_POSTSPAWN ); + } + else + { + state = STATE_CLOSED; + } + previous_state = state; + + if ( health ) + { + takedamage = DAMAGE_YES; + } + else + { + takedamage = DAMAGE_NO; + } + + // LinkDoors can't be done until all of the doors have been spawned, so + // the sizes can be detected properly. + nextdoor = 0; + PostEvent( EV_Door_Link, EV_LINKDOORS ); + + // Default to work with monsters and players + respondto = TRIGGER_PLAYERS | TRIGGER_MONSTERS; + if ( spawnflags & 8 ) + { + respondto &= ~TRIGGER_PLAYERS; + } + if ( spawnflags & 16 ) + { + respondto &= ~TRIGGER_MONSTERS; + } + + next_locked_time = 0; +} + +void Door::SetDir( Event *ev ) +{ + float t; + float angle; + + angle = ev->GetFloat( 1 ); + dir = G_GetMovedir( angle ); + t = dir[ 0 ]; + dir[ 0 ] = -dir[ 1 ]; + dir[ 1 ] = t; +} + +void Door::SetStopSound( const str &sound ) +{ + sound_stop = sound; + if ( sound_stop.length() > 1 ) + { + CacheResource( sound_stop.c_str(), this ); + } +} + +void Door::SetMoveSound( const str &sound ) +{ + sound_move = sound; + if ( sound_move.length() > 1 ) + { + CacheResource( sound_move.c_str(), this ); + } +} + +void Door::SetMessageSound( const str &sound ) +{ + sound_message = sound; + if ( sound_message.length() > 1 ) + { + CacheResource( sound_message.c_str(), this ); + } +} + +void Door::SetLockedSound( const str &sound ) +{ + sound_locked = sound; + if ( sound_locked.length() > 1 ) + { + CacheResource( sound_locked.c_str(), this ); + } +} + +void Door::SetStopSound( Event *ev ) +{ + SetStopSound( ev->GetString( 1 ) ); +} + +void Door::SetMoveSound( Event *ev ) +{ + SetMoveSound( ev->GetString( 1 ) ); +} + +void Door::SetMessageSound( Event *ev ) +{ + SetMessageSound( ev->GetString( 1 ) ); +} + +void Door::SetLockedSound( Event *ev ) +{ + SetLockedSound( ev->GetString( 1 ) ); +} + +void Door::SetWait( Event *ev ) +{ + wait = ev->GetFloat( 1 ); +} + +void Door::SetDmg( Event *ev ) +{ + dmg = ev->GetInteger( 1 ); +} + +//-------------------------------------------------------------- +// +// Name: setDamage +// Class: Door +// +// Description: This function acts as a filter to the real function. +// It gets data from the database, and then passes it +// along to the original event. This is here as an attempt +// to sway people into using the database standard instead of +// hardcoded numbers. +// +// Parameters: Event *ev +// str -- The value keyword from the database (low, medium, high, etc). +// +// Returns: None +// +//-------------------------------------------------------------- +void Door::setDamage( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasFormula("OffensiveDamage") ) + return; + + str damagestr = ev->GetString( 1 ); + float damagemod = 1.0f; + if ( gpm->getDefine(damagestr) != "" ) + damagemod = (float)atof(gpm->getDefine(damagestr)); + GameplayFormulaData fd(this, 0, 0, ""); + float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod); + Event *newev = new Event(EV_Door_SetDmg); + newev->AddInteger((int)finaldamage); + ProcessEvent(newev); +} + +qboolean Door::isOpen( void ) +{ + return ( state == STATE_OPEN ); +} + +qboolean Door::isCompletelyClosed( void ) +{ + return ( state == STATE_CLOSED ); +} + +void Door::OpenEnd( Event *ev ) +{ + if ( sound_stop.length() > 1 ) + { + BroadcastSound(); + Sound( sound_stop, CHAN_VOICE ); + } + else + { + StopSound( CHAN_VOICE ); + } + + previous_state = state; + state = STATE_OPEN; + + if ( spawnflags & DOOR_TOGGLE ) + { + // don't close automatically + return; + } + + if ( ( wait > 0.0f ) && ( master == this ) ) + { + PostEvent( EV_Door_Close, wait ); + } +} + +void Door::CloseEnd( Event *ev ) +{ + if ( sound_stop.length() > 1 ) + { + BroadcastSound(); + Sound( sound_stop, CHAN_VOICE ); + } + else + { + StopSound( CHAN_VOICE ); + } + + if ( ( master == this ) && ( state != STATE_CLOSED ) ) + { + gi.AdjustAreaPortalState( this->edict, false ); + } + + previous_state = state; + state = STATE_CLOSED; +} + +void Door::Close( Event *ev ) +{ + Door *door; + + CancelEventsOfType( EV_Door_Close ); + + if ( isCompletelyClosed() ) + return; + + previous_state = state; + state = STATE_CLOSING; + + ProcessEvent( EV_Door_DoClose ); + + if ( sound_move.length() > 1 ) + { + BroadcastSound(); + Sound( sound_move, CHAN_VOICE ); + } + if ( master == this ) + { + if ( max_health ) + { + takedamage = DAMAGE_YES; + health = max_health; + } + + // trigger all paired doors + door = ( Door * )G_GetEntity( nextdoor ); + assert( door->isSubclassOf( Door ) ); + while( door && ( door != this ) ) + { + door->ProcessEvent( EV_Door_Close ); + door = ( Door * )G_GetEntity( door->nextdoor ); + assert( door->isSubclassOf( Door ) ); + } + } +} + +void Door::Open( Event *ev ) +{ + Door *door; + Event *e; + Entity *other; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "No entity specified to open door. Door may open the wrong way." ); + other = world; + } + else + { + other = ev->GetEntity( 1 ); + } + + if ( state == STATE_OPENING ) + { + // already going up + return; + } + + if ( state == STATE_OPEN ) + { + // reset top wait time + if ( wait > 0.0f ) + { + CancelEventsOfType( EV_Door_Close ); + PostEvent( EV_Door_Close, wait ); + } + return; + } + + previous_state = state; + state = STATE_OPENING; + + e = new Event( EV_Door_DoOpen ); + e->AddEntity( other ); + ProcessEvent( e ); + + if ( sound_move.length() > 1 ) + { + BroadcastSound(); + Sound( sound_move, CHAN_VOICE ); + } + if ( master == this ) + { + // trigger all paired doors + door = ( Door * )G_GetEntity( nextdoor ); + assert( door->isSubclassOf( Door ) ); + while( door && ( door != this ) ) + { + e = new Event( EV_Door_Open ); + e->AddEntity( other ); + door->ProcessEvent( e ); + door = ( Door * )G_GetEntity( door->nextdoor ); + assert( door->isSubclassOf( Door ) ); + } + + if ( previous_state == STATE_CLOSED ) + { + gi.AdjustAreaPortalState( this->edict, true ); + } + } +} + +void Door::DoorUse( Event *ev ) +{ + Entity *other; + qboolean respond; + Event *e; + + other = ev->GetEntity( 1 ); + + if ( !other ) + return; + + respond = ( ( ( respondto & TRIGGER_PLAYERS ) && other->isClient() ) || + ( ( respondto & TRIGGER_MONSTERS ) && other->isSubclassOf( Actor ) ) ); + + if ( !respond ) + { + return; + } + + // only allow use when not triggerd by other events + if ( health || ( spawnflags & ( DOOR_AUTO_OPEN | DOOR_TARGETED ) ) ) + { + if ( other->isSubclassOf( Sentient ) && ( state == STATE_CLOSED ) ) + { + if ( health ) + { + gi.SendServerCommand( (int)NULL, "print \"This door is jammed.\"" ); + } + else if ( spawnflags & DOOR_TARGETED ) + { + Sound( "door_triggered", CHAN_VOICE ); + } + } + + if ( spawnflags & DOOR_AUTO_OPEN && locked && other->isSubclassOf( Player ) && sound_locked.length() ) + { + other->Sound( sound_locked, CHAN_VOICE ); + } + + return; + } + + assert( master ); + if ( !master ) + { + // bulletproofing + master = this; + } + + if ( master->state == STATE_CLOSED ) + { + e = new Event( EV_Door_TryOpen ); + e->AddEntity( other ); + master->ProcessEvent( e ); + } + else if ( master->state == STATE_OPEN ) + { + e = new Event( EV_Door_Close ); + e->AddEntity( other ); + master->ProcessEvent( e ); + } +} + +void Door::DoorFire( Event *ev ) +{ + Event *e; + Entity *other; + + other = ev->GetEntity( 1 ); + + assert( master == this ); + if ( master != this ) + { + gi.Error( ERR_DROP, "DoorFire: master != self" ); + } + + // no more messages + SetMessage( NULL ); + + // reset health in case we were damage triggered + health = max_health; + + // will be reset upon return + takedamage = DAMAGE_NO; + + if ( ( spawnflags & ( DOOR_TOGGLE | DOOR_START_OPEN ) ) && ( ( state == STATE_OPENING ) || ( state == STATE_OPEN ) ) ) + { + spawnflags &= ~DOOR_START_OPEN; + ProcessEvent( EV_Door_Close ); + } + else + { + e = new Event( EV_Door_Open ); + e->AddEntity( other ); + ProcessEvent( e ); + } +} + +void Door::DoorBlocked( Event *ev ) +{ + Event *e; + Entity *other; + + assert( master ); + if ( ( master ) && ( master != this ) ) + { + master->ProcessEvent( new Event( ev ) ); + return; + } + + if ( lastblocktime > level.time ) + { + return; + } + + lastblocktime = level.time + 0.3f; + + other = ev->GetEntity( 1 ); + + if ( dmg && other ) + { + other->Damage( this, this, dmg, origin, vec_zero, vec_zero, 0, 0, MOD_CRUSH ); + + // if we killed him, lets keep on going + + if ( other->deadflag ) + { + return; + } + } + + if ( ( state == STATE_OPENING ) || ( state == STATE_OPEN ) ) + { + spawnflags &= ~DOOR_START_OPEN; + ProcessEvent( EV_Door_Close ); + } + else + { + e = new Event( EV_Door_Open ); + e->AddEntity( other ); + ProcessEvent( e ); + } +} + +void Door::FieldTouched( Event *ev ) +{ + Entity *other; + + other = ev->GetEntity( 1 ); + + if ( !other ) + return; + + if ( ( state != STATE_OPEN ) && !( spawnflags & DOOR_AUTO_OPEN ) && !other->isSubclassOf( Actor ) ) + return; + + TryOpen( ev ); +} + +qboolean Door::CanBeOpenedBy( Entity *ent ) +{ + assert( master ); + if ( ( master ) && ( master != this ) ) + { + return master->CanBeOpenedBy( ent ); + } + + if ( !locked && !key.length() ) + { + return true; + } + + if ( ent && ent->isSubclassOf( Sentient ) && ( ( Sentient * )ent )->HasItem( key.c_str() ) ) + { + return true; + } + + return false; +} + +void Door::TryOpen( Event *ev ) +{ + Entity *other; + Event *event; + + //FIXME + // hack so that doors aren't triggered by guys when game starts. + // have to fix delay that guys go through before setting up their threads + if ( level.time < 0.4f ) + { + return; + } + + other = ev->GetEntity( 1 ); + + assert( master ); + if ( master && ( this != master ) ) + { + event = new Event( EV_Door_TryOpen ); + event->AddEntity( other ); + master->ProcessEvent( event ); + return; + } + + if ( !other || other->deadflag ) + { + return; + } + + if ( locked ) + { + if ( next_locked_time <= level.time ) + { + if ( sound_locked.length() > 1 && !other->isSubclassOf( Actor ) ) + { + other->Sound( sound_locked, CHAN_VOICE ); + } + else if ( other->isSubclassOf( Player ) ) + { + other->Sound( sound_locked, CHAN_VOICE ); +// gi.centerprintf ( other->edict, "This door is locked." ); + } + } + + // Always increment next locked time + + next_locked_time = level.time + 0.5f; + + // locked doors don't open for anyone + return; + } + + if ( !CanBeOpenedBy( other ) ) + { + Item *item; + ClassDef *cls; + + if ( other->isClient() ) + { + cls = getClass( key.c_str() ); + if ( !cls ) + { + gi.WDPrintf( "No item named '%s'\n", key.c_str() ); + return; + } + item = ( Item * )cls->newInstance(); + item->CancelEventsOfType( EV_Item_DropToFloor ); + item->CancelEventsOfType( EV_Remove ); + item->ProcessPendingEvents(); + gi.centerprintf ( other->edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$UnlockItem$$%s", item->getName().c_str() ); + delete item; + } + return; + } + + // once we're opened by an item, we no longer need that item to open the door + key = ""; + + if ( Message().length() ) + { + gi.centerprintf( other->edict, CENTERPRINT_IMPORTANCE_NORMAL, Message().c_str() ); + Sound( sound_message, CHAN_VOICE ); + } + + event = new Event( EV_Door_Fire ); + event->AddEntity( other ); + ProcessEvent( event ); +} + +void Door::SpawnTriggerField( const Vector &fmins, const Vector &fmaxs ) +{ + TouchField *trig; + Vector min; + Vector max; + + min = fmins - Vector( 60, 60, 8 ) - _extraTriggerSize; + max = fmaxs + Vector( 60, 60, 8 ) + _extraTriggerSize; + + trig = new TouchField; + trig->Setup( this, EV_Door_TriggerFieldTouched, min, max, respondto ); + + trigger = trig->entnum; +} + +qboolean Door::DoorTouches( const Door *e1 ) +{ + if ( e1->absmin.x > absmax.x ) + { + return false; + } + if ( e1->absmin.y > absmax.y ) + { + return false; + } + if ( e1->absmin.z > absmax.z ) + { + return false; + } + if ( e1->absmax.x < absmin.x ) + { + return false; + } + if ( e1->absmax.y < absmin.y ) + { + return false; + } + if ( e1->absmax.z < absmin.z ) + { + return false; + } + + return true; +} + +void Door::LinkDoors( Event *ev ) +{ + Entity *entptr; + Door *ent; + Door *next; + Vector cmins; + Vector cmaxs; + int i; + + setSolidType( SOLID_BSP ); + setMoveType( MOVETYPE_PUSH ); + + if ( nextdoor ) + { + // already linked by another door + return; + } + + // master doors own themselves + master = this; + + if ( spawnflags & DOOR_DONT_LINK ) + { + // don't want to link this door + nextdoor = entnum; + return; + } + + cmins = absmin; + cmaxs = absmax; + + ent = this; + for( entptr = this; entptr; entptr = G_FindClass( entptr, getClassID() ) ) + { + str targetName = TargetName(); + next = ( Door * )entptr; + if ( !ent->DoorTouches( next ) ) + { + continue; + } + + if ( next->nextdoor ) + { + error( "cross connected doors. Targetname = %s entity %d\n", targetName.c_str(), entnum ); + } + + ent->nextdoor = next->entnum; + ent = next; + + for( i = 0; i < 3; i++ ) + { + if ( ent->absmin[ i ] < cmins[ i ] ) + { + cmins[ i ] = ent->absmin[ i ]; + } + if ( ent->absmax[ i ] > cmaxs[ i ] ) + { + cmaxs[ i ] = ent->absmax[ i ]; + } + } + + // set master door + ent->master = this; + + if ( ent->health ) + { + health = ent->health; + } + + if ( ent->Targeted() ) + { + + str entTargetname = ent->TargetName(); + if ( !Targeted() ) + { + SetTargetName( ent->TargetName() ); + } + else if ( strcmp( TargetName(), ent->TargetName() ) ) + { + // not a critical error, but let them know about it. + gi.WDPrintf( "cross connected doors: %s to %s\n",targetName.c_str(), entTargetname.c_str()); + + ent->SetTargetName( TargetName() ); + } + } + + if ( ent->Message().length() ) + { + if ( Message().length() && !strcmp( Message().c_str(), ent->Message().c_str() ) ) + { + // not a critical error, but let them know about it. + gi.WDPrintf( "Different messages on linked doors. Targetname = %s", TargetName() ); + } + + // only master should have a message + SetMessage( ent->Message().c_str() ); + ent->SetMessage( NULL ); + } + } + + // make the chain a loop + ent->nextdoor = entnum; + + // open up any portals we control + if ( spawnflags & DOOR_START_OPEN ) + { + gi.AdjustAreaPortalState( this->edict, true ); + } + + // shootable or targeted doors don't need a trigger + if ( health || ( spawnflags & DOOR_TARGETED ) ) + { + // Don't let the player trigger the door + return; + } + + // Don't spawn trigger field when set to toggle + if ( !( spawnflags & DOOR_TOGGLE ) ) + { + SpawnTriggerField( cmins, cmaxs ); + } +} + +void Door::SetTime( Event *ev ) +{ + traveltime = ev->GetFloat( 1 ); + if ( traveltime < FRAMETIME ) + { + traveltime = FRAMETIME; + } + + speed = 1.0f / traveltime; +} + +void Door::LockDoor( Event *ev ) +{ + locked = true; +} + +void Door::UnlockDoor( Event *ev ) +{ + locked = false; +} + +/*****************************************************************************/ +/*QUAKED func_rotatingdoor (0 0.25 0.5) ? START_OPEN OPEN_DIRECTION DOOR_DONT_LINK NOT_PLAYERS NOT_MONSTERS TOGGLE AUTO_OPEN TARGETED +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. +DOOR_DONT_LINK is for when you have two doors that are touching but you want to operate independently. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). +OPEN_DIRECTION indicates which direction to open when START_OPEN is set. +AUTO_OPEN causes the door to open when a player is near instead of waiting for the player to use the door. +TARGETED door is only operational from triggers or script + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"openangle" how wide to open the door +"angle" determines the opening direction. point toward the middle of the door (away from the hinge) +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"time" move time (0.3 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (0 default) +"key" The item needed to open this door (default nothing) + +"sound_stop" Specify the sound that plays when the door stops moving (default global door_stop) +"sound_move" Specify the sound that plays when the door opens or closes (default global door_moving) +"sound_message" Specify the sound that plays when the door displays a message +"sound_locked" Specify the sound that plays when the door is locked + +******************************************************************************/ + +Event EV_RotatingDoor_OpenAngle +( + "openangle", + EV_DEFAULT, + "f", + "open_angle", + "Sets the open angle of the door." +); + +CLASS_DECLARATION( Door, RotatingDoor, "func_rotatingdoor" ) +{ + { &EV_Door_DoClose, &RotatingDoor::DoClose }, + { &EV_Door_DoOpen, &RotatingDoor::DoOpen }, + { &EV_RotatingDoor_OpenAngle, &RotatingDoor::OpenAngle }, + { NULL, NULL } +}; + +void RotatingDoor::DoOpen( Event *ev ) +{ + Vector ang; + + if ( previous_state == STATE_CLOSED ) + { + if ( ev->NumArgs() > 0 ) + { + Entity *other; + Vector p; + + other = ev->GetEntity( 1 ); + + if ( other ) + { + p = other->origin - origin; + p.z = 0; + diropened = dir * p; + } + else + { + diropened = 0 - init_door_direction; + } + } + else + { + diropened = 0 - init_door_direction; + } + } + + if ( diropened < 0.0f ) + { + ang = startangle + Vector( 0.0f, angle, 0.0f ); + } + else + { + ang = startangle - Vector( 0.0f, angle, 0.0f ); + } + + mover->MoveTo( origin, ang, fabs( speed*angle ), EV_Door_OpenEnd ); +} + +void RotatingDoor::DoClose( Event *ev ) +{ + mover->MoveTo( origin, startangle, fabs( speed*angle ), EV_Door_CloseEnd ); +} + +void RotatingDoor::OpenAngle( Event *ev ) +{ + angle = ev->GetFloat( 1 ); +} + +RotatingDoor::RotatingDoor() +{ + if ( LoadingSavegame ) + { + return; + } + startangle = angles; + + angle = 90; + + init_door_direction = (spawnflags & DOOR_OPEN_DIRECTION); +} + +/*****************************************************************************/ +/*QUAKED func_door (0 0.25 0.5) ? START_OPEN x DOOR_DONT_LINK NOT_PLAYERS NOT_MONSTERS TOGGLE AUTO_OPEN TARGETED +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. +DOOR_DONT_LINK is for when you have two doors that are touching but you want to operate independently. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). +OPEN_DIRECTION indicates which direction to open when START_OPEN is set. +AUTO_OPEN causes the door to open when a player is near instead of waiting for the player to use the door. +TARGETED door is only operational from triggers or script + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction. point toward the middle of the door (away from the hinge) +"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door. +"health" if set, door must be shot open +"speed" move speed (100 default) +"time" move time (1/speed default, overides speed) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (0 default) +"key" The item needed to open this door (default nothing) +"extraTriggerSize" The extra size for the door trigger (defaults to 0 0 0) + +"sound_stop" Specify the sound that plays when the door stops moving (default global door_stop) +"sound_move" Specify the sound that plays when the door opens or closes (default global door_moving) +"sound_message" Specify the sound that plays when the door displays a message +"sound_locked" Specify the sound that plays when the door is locked + +******************************************************************************/ + +Event EV_SlidingDoor_Setup +( + "setup", + EV_CODEONLY, + NULL, + NULL, + "Sets up the sliding door." +); +Event EV_SlidingDoor_SetLip +( + "lip", + EV_SCRIPTONLY, + "f", + "lip", + "Sets the lip of the sliding door." +); +Event EV_SlidingDoor_SetSpeed +( + "speed", + EV_DEFAULT, + "f", + "speed", + "Sets the speed of the sliding door." +); + +CLASS_DECLARATION( Door, SlidingDoor, "func_door" ) +{ + { &EV_Door_DoClose, &SlidingDoor::DoClose }, + { &EV_Door_DoOpen, &SlidingDoor::DoOpen }, + { &EV_SlidingDoor_Setup, &SlidingDoor::Setup }, + { &EV_SlidingDoor_SetLip, &SlidingDoor::SetLip }, + { &EV_SlidingDoor_SetSpeed, &SlidingDoor::SetSpeed }, + { &EV_SetAngle, &SlidingDoor::SetMoveDir }, + + { NULL, NULL } +}; + +void SlidingDoor::SetMoveDir( Event *ev ) +{ + float t; + float angle; + + angle = ev->GetFloat( 1 ); + movedir = G_GetMovedir( angle ); + dir = movedir; + t = dir[ 0 ]; + dir[ 0 ] = -dir[ 1 ]; + dir[ 1 ] = t; +} + +void SlidingDoor::DoOpen( Event *ev ) +{ + mover->MoveTo( pos2, angles, speed*totalmove, EV_Door_OpenEnd ); +} + +void SlidingDoor::DoClose( Event *ev ) +{ + mover->MoveTo( pos1, angles, speed*totalmove, EV_Door_CloseEnd ); +} + +void SlidingDoor::SetLip( Event *ev ) +{ + lip = ev->GetFloat( 1 ); + CancelEventsOfType( EV_SlidingDoor_Setup ); + PostEvent( EV_SlidingDoor_Setup, EV_POSTSPAWN ); +} + +void SlidingDoor::SetSpeed( Event *ev ) +{ + basespeed = ev->GetFloat( 1 ); + CancelEventsOfType( EV_SlidingDoor_Setup ); + PostEvent( EV_SlidingDoor_Setup, EV_POSTSPAWN ); +} + +void SlidingDoor::Setup( Event *ev ) +{ + totalmove = fabs( movedir[0] * size[0] ) + fabs( movedir[1] * size[1] ) + fabs( movedir[2] * size[2] ) - lip; + pos1 = origin; + pos2 = pos1 + ( movedir * totalmove ); + + if ( basespeed ) + { + speed = basespeed / totalmove; + } + edict->s.eType = ET_MOVER; // added for BOTLIB +} + +SlidingDoor::SlidingDoor() +{ + if ( LoadingSavegame ) + { + return; + } + lip = 8; + basespeed = 0; + movedir = G_GetMovedir( 0.0f ); + + PostEvent( EV_SlidingDoor_Setup, EV_POSTSPAWN ); + + totalmove = 0.0f; +} + + + +/*****************************************************************************/ +/*QUAKED script_door (0 0.5 1) ? X x DOOR_DONT_LINK NOT_PLAYERS NOT_MONSTERS x AUTO_OPEN x +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. +DOOR_DONT_LINK is for when you have two doors that are touching but you want to operate independently. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors). +OPEN_DIRECTION indicates which direction to open when START_OPEN is set. +AUTO_OPEN causes the door to open when a player is near instead of waiting for the player to use the door. +TARGETED door is only operational from triggers or script + +"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet +"angle" determines the opening direction. point toward the middle of the door (away from the hinge) +"targetname" targetname of the door +"health" if set, door must be shot open +"speed" move speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (0 default) +"key" The item needed to open this door (default nothing) +"openpercentage" The percentage amount of the length of the door to open +"targeted" Sets the door to targeted mode -- Meaning it must be triggered from another entity +"toggle" Makes the door stay in its current state until forced to change +"sound_stop" Specify the sound that plays when the door stops moving (default global door_stop) +"sound_move" Specify the sound that plays when the door opens or closes (default global door_moving) +"sound_message" Specify the sound that plays when the door displays a message +"sound_locked" Specify the sound that plays when the door is locked + +******************************************************************************/ + +Event EV_ScriptDoor_SetToggle +( + "toggle", + EV_SCRIPTONLY, + "b", + "flag", + "Sets Toggle Mode ( 1 ) is true, ( 0 ) is false" +); + +Event EV_ScriptDoor_SetTargeted +( + "targeted", + EV_SCRIPTONLY, + "b", + "flag", + "Sets the targeted flag ( 1 ) is true , ( 0 ) is false" +); + +Event EV_ScriptDoor_SetOpenPercentage +( + "openpercentage", + EV_SCRIPTONLY, + "f", + "percentage", + "Sets the percentage for the door to open " +); + +Event EV_ScriptDoor_SetupMovement +( + "scriptdoorsetupmovement", + EV_SCRIPTONLY, + NULL, + NULL, + "Sets up Door Movement" +); + +Event EV_ScriptDoor_Setup +( + "scriptdoorsetup", + EV_SCRIPTONLY, + NULL, + NULL, + "Sets up Door " +); + +Event EV_ScriptDoor_Open +( + "dooropen", + EV_SCRIPTONLY, + NULL, + NULL, + "Opens the script door" +); + +Event EV_ScriptDoor_Close +( + "doorclose", + EV_SCRIPTONLY, + NULL, + NULL, + "Closes the script door" +); + +Event EV_ScriptDoor_ForceOpen +( + "forceopen", + EV_SCRIPTONLY, + NULL, + NULL, + "Closes the script door" +); + +Event EV_ScriptDoor_ForceClose +( + "forceclose", + EV_SCRIPTONLY, + NULL, + NULL, + "Closes the script door" +); + +Event EV_ScriptDoor_GetState +( + "getdoorstate", + EV_SCRIPTONLY, + "@s", + "state", + "Returns the current state" +); + +CLASS_DECLARATION( Door, ScriptDoor, "script_door" ) +{ + { &EV_ScriptDoor_SetToggle , &ScriptDoor::SetToggle }, + { &EV_ScriptDoor_SetTargeted , &ScriptDoor::SetTargeted }, + { &EV_ScriptDoor_SetOpenPercentage , &ScriptDoor::SetOpenPercentage }, + { &EV_SetAngle, &ScriptDoor::SetMoveDir }, + { &EV_Door_DoClose, &ScriptDoor::DoClose }, + { &EV_Door_DoOpen, &ScriptDoor::DoOpen }, + { &EV_ScriptDoor_SetupMovement, &ScriptDoor::SetUpMovement }, + { &EV_ScriptDoor_Setup, &ScriptDoor::SetUpScriptDoor }, + { &EV_Use, &ScriptDoor::ScriptDoorUse }, + { &EV_Door_Close, &ScriptDoor::ScriptDoorClose }, + { &EV_Door_Open, &ScriptDoor::ScriptDoorOpen }, + { &EV_ScriptDoor_ForceOpen, &ScriptDoor::ScriptDoorForceOpen }, + { &EV_ScriptDoor_ForceClose, &ScriptDoor::ScriptDoorForceClose }, + { &EV_Door_TriggerFieldTouched, &ScriptDoor::ScriptDoorFieldTouched }, + { &EV_ScriptDoor_GetState, &ScriptDoor::ScriptDoorGetState }, + + { NULL, NULL } +}; + + +ScriptDoor::ScriptDoor() +{ + PostEvent( EV_ScriptDoor_Setup, EV_POSTSPAWN ); +} + +void ScriptDoor::SetToggle( Event *ev ) +{ + SetToggle( ev->GetBoolean( 1 ) ); +} + +void ScriptDoor::SetToggle( bool toggle ) +{ + _toggle = toggle; +} + +void ScriptDoor::ScriptDoorGetState( Event *ev ) +{ + str currentState; + + currentState = "STATE_UNKNOWN"; + switch ( master->GetState() ) + { + case STATE_OPEN: + currentState = "STATE_OPEN"; + break; + + case STATE_OPENING: + currentState = "STATE_OPENING"; + break; + + case STATE_CLOSING: + currentState = "STATE_CLOSING"; + break; + + case STATE_CLOSED: + currentState = "STATE_CLOSED"; + break; + } + + ev->ReturnString( currentState.c_str() ); +} + +void ScriptDoor::SetTargeted( Event *ev ) +{ + SetTargeted( ev->GetBoolean( 1 ) ); +} + +void ScriptDoor::SetTargeted( bool targeted ) +{ + _targeted = targeted; +} + +void ScriptDoor::SetOpenPercentage( Event *ev ) +{ + SetOpenPercentage( ev->GetFloat( 1 ) ); +} + +void ScriptDoor::SetOpenPercentage( float openPercentage ) +{ + _openPercentage = openPercentage; + + CancelEventsOfType( EV_ScriptDoor_SetupMovement ); + PostEvent( EV_ScriptDoor_SetupMovement, EV_POSTSPAWN ); +} + +void ScriptDoor::SetMoveDir( Event *ev ) +{ + SetMoveDir( ev->GetFloat( 1 ) ); +} + +void ScriptDoor::SetMoveDir( float moveDir ) +{ + float t; + float angle; + + angle = moveDir; + _moveDir = G_GetMovedir( angle ); + dir = _moveDir; + t = dir[ 0 ]; + dir[ 0 ] = -dir[ 1 ]; + dir[ 1 ] = t; +} + +void ScriptDoor::SetUpMovement(Event *ev) +{ + SetUpMovement(); +} + +void ScriptDoor::SetUpMovement() +{ + _totalMove = fabs( _moveDir[0] * size[0] ) + fabs( _moveDir[1] * size[1] ) + fabs( _moveDir[2] * size[2] ); + _currentMove = _totalMove * _openPercentage; + + _currentPos = origin; + _destinationPos = _originalPos + ( _moveDir * _currentMove ); + + _speed = _baseSpeed / _totalMove; + +} + +void ScriptDoor::SetUpScriptDoor( Event *ev ) +{ + SetUpScriptDoor(); +} + +void ScriptDoor::SetUpScriptDoor() +{ + _baseSpeed = 50; + _originalPos = origin; + _currentPercentage = 0.0; + _openPercentage = 1.0; + _targeted = false; + _toggle = false; +} + +void ScriptDoor::DoOpen( Event *ev ) +{ + DoOpen(); +} + +void ScriptDoor::DoOpen() +{ + mover->MoveTo( _destinationPos, angles, _speed*_totalMove, EV_Door_OpenEnd ); + _currentPercentage = _openPercentage; +} + +void ScriptDoor::DoClose( Event *ev ) +{ + DoClose(); +} + +void ScriptDoor::DoClose() +{ + mover->MoveTo( _originalPos, angles, _speed*_totalMove, EV_Door_CloseEnd ); +} + + +void ScriptDoor::ScriptDoorUse( Event *ev ) +{ + Entity *other; + qboolean respond; + Event *e; + + other = ev->GetEntity( 1 ); + + if ( !other ) + return; + + if ( master != this ) + { + e = new Event ( ev ); + master->ProcessEvent( e ); + } + + + if ( _targeted ) + return; + + respond = ( ( ( respondto & TRIGGER_PLAYERS ) && other->isClient() ) || + ( ( respondto & TRIGGER_MONSTERS ) && other->isSubclassOf( Actor ) ) ); + + if ( !respond ) + return; + + // only allow use when not triggerd by other events + if ( health || ( spawnflags & ( DOOR_AUTO_OPEN | DOOR_TARGETED ) ) ) + { + if ( other->isSubclassOf( Sentient ) && ( state == STATE_CLOSED ) ) + { + if ( health ) + gi.SendServerCommand( (int)NULL, "print \"This door is jammed.\"" ); + else if ( spawnflags & DOOR_TARGETED ) + Sound( "door_triggered", CHAN_VOICE ); + } + + if ( spawnflags & DOOR_AUTO_OPEN && locked && other->isSubclassOf( Player ) && sound_locked.length() ) + { + other->Sound( sound_locked, CHAN_VOICE ); + } + + return; + } + + assert( master ); + if ( !master ) + { + // bulletproofing + master = this; + } + + if ( master->GetState() == STATE_CLOSED ) + { + e = new Event( EV_Door_TryOpen ); + e->AddEntity( other ); + master->ProcessEvent( e ); + } + else if ( master->GetState() == STATE_OPEN ) + { + if ( _currentPercentage != _openPercentage ) + { + e = new Event( EV_Door_TryOpen ); + e->AddEntity( other ); + master->ProcessEvent( e ); + } + else + { + e = new Event( EV_Door_Close ); + e->AddEntity( other ); + master->ProcessEvent( e ); + } + } +} + +void ScriptDoor::ScriptDoorOpen( Event *ev ) +{ + Door *door; + Event *e; + Entity *other; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "No entity specified to open door. Door may open the wrong way." ); + other = world; + } + else + { + other = ev->GetEntity( 1 ); + } + + if ( _toggle ) + return; + + if ( state == STATE_OPENING ) + { + // already going up + return; + } + + if ( state == STATE_OPEN ) + { + // reset top wait time + if ( wait > 0.0f ) + { + CancelEventsOfType( EV_Door_Close ); + if ( _currentPercentage == _openPercentage ) + PostEvent( EV_Door_Close, wait ); + } + + if ( _currentPercentage == _openPercentage ) + return; + } + + previous_state = state; + state = STATE_OPENING; + + e = new Event( EV_Door_DoOpen ); + e->AddEntity( other ); + ProcessEvent( e ); + + if ( sound_move.length() > 1 ) + { + BroadcastSound(); + Sound( sound_move, CHAN_VOICE ); + } + if ( master == this ) + { + // trigger all paired doors + door = ( Door * )G_GetEntity( nextdoor ); + assert( door->isSubclassOf( Door ) ); + while( door && ( door != this ) ) + { + e = new Event( EV_Door_Open ); + e->AddEntity( other ); + door->ProcessEvent( e ); + door = ( Door * )G_GetEntity( door->GetNextDoor() ); + assert( door->isSubclassOf( Door ) ); + } + + if ( previous_state == STATE_CLOSED ) + { + gi.AdjustAreaPortalState( this->edict, true ); + } + } +} + +void ScriptDoor::ScriptDoorClose( Event *ev ) +{ + Door *door; + + if ( _toggle ) + return; + + CancelEventsOfType( EV_Door_Close ); + + previous_state = state; + state = STATE_CLOSING; + + ProcessEvent( EV_Door_DoClose ); + + if ( sound_move.length() > 1 ) + { + BroadcastSound(); + Sound( sound_move, CHAN_VOICE ); + } + if ( master == this ) + { + if ( max_health ) + { + takedamage = DAMAGE_YES; + health = max_health; + } + + // trigger all paired doors + door = ( Door * )G_GetEntity( nextdoor ); + assert( door->isSubclassOf( Door ) ); + while( door && ( door != this ) ) + { + door->ProcessEvent( EV_Door_Close ); + door = ( Door * )G_GetEntity( door->GetNextDoor() ); + assert( door->isSubclassOf( Door ) ); + } + } +} + +void ScriptDoor::ScriptDoorFieldTouched( Event *ev ) +{ + Entity *other; + + other = ev->GetEntity( 1 ); + + if ( !other ) + return; + + if ( _targeted ) + return; + + if ( ( state != STATE_OPEN ) && !( spawnflags & DOOR_AUTO_OPEN ) && !other->isSubclassOf( Actor ) ) + return; + + TryOpen( ev ); +} + +void ScriptDoor::ScriptDoorForceOpen( Event *ev ) +{ + DoOpen(); +} + +void ScriptDoor::ScriptDoorForceClose( Event *ev ) +{ + DoClose(); +} + + +/* +Event EV_ScriptDoor_DoInit + ( + "doinit", + EV_DEFAULT, + NULL, + NULL, + "Sets up the script door." + ); +Event EV_ScriptDoor_SetOpenThread + ( + "openthread", + EV_DEFAULT, + "s", + "openthread", + "Set the thread to run when the door is opened (required)." + ); +Event EV_ScriptDoor_SetCloseThread + ( + "closethread", + EV_DEFAULT, + "s", + "closethread", + "Set the thread to run when the door is closed (required)." + ); +Event EV_ScriptDoor_SetInitThread + ( + "initthread", + EV_DEFAULT, + "s", + "initthread", + "Set the thread to run when the door is initialized (optional)." + ); + +CLASS_DECLARATION( Door, ScriptDoor, "script_door" ) + { + { &EV_ScriptDoor_DoInit, DoInit }, + { &EV_Door_DoClose, DoClose }, + { &EV_Door_DoOpen, DoOpen }, + { &EV_ScriptDoor_SetInitThread, SetInitThread }, + { &EV_ScriptDoor_SetOpenThread, SetOpenThread }, + { &EV_ScriptDoor_SetCloseThread, SetCloseThread }, + { &EV_SetAngle, SetMoveDir }, + { NULL, NULL } + }; + +void ScriptDoor::SetMoveDir + ( + Event *ev + ) + + { + float t; + float angle; + + angle = ev->GetFloat( 1 ); + movedir = G_GetMovedir( angle ); + dir = movedir; + t = dir[ 0 ]; + dir[ 0 ] = -dir[ 1 ]; + dir[ 1 ] = t; + } + +void ScriptDoor::SetOpenThread + ( + Event *ev + ) + { + openthreadname = ev->GetString( 1 ); + } + +void ScriptDoor::SetCloseThread + ( + Event *ev + ) + { + closethreadname = ev->GetString( 1 ); + } + +void ScriptDoor::DoInit + ( + Event *ev + ) + { + const char * label = NULL; + GameScript * s; + const char * tname; + + startorigin = origin; + doorsize = fabs( movedir * size ); + + // + // see if we have an openthread + // + if ( !openthreadname.length() ) + { + warning( "ScriptDoor", "No openthread defined for door at %.2f %.2f %.2f", origin[0], origin[1], origin[2] ); + } + + // + // see if we have an closethread + // + if ( !closethreadname.length() ) + { + warning( "ScriptDoor", "No closethread defined for door at %.2f %.2f %.2f", origin[0], origin[1], origin[2] ); + } + + if(!ScriptLib.HasGameScript()) + { + warning( "DoInit", "Null game script" ); + return; + } + + s = ScriptLib.GetScript( ScriptLib.GetGameScript() ); + if ( !s ) + { + warning( "DoInit", "Null game script" ); + return; + } + + if ( initthreadname.length() ) + label = initthreadname.c_str(); + + doorthread = Director.CreateThread( s, label, MODEL_SCRIPT ); + if ( !doorthread ) + { + warning( "DoInit", "Could not allocate thread." ); + return; + } + doorthread->Vars()->SetVariable( "self", this ); + tname = TargetName(); + if ( tname && tname[ 0 ] ) + { + str name; + name = "$" + str( tname ); + doorthread->Vars()->SetVariable( "targetname", name.c_str() ); + } + doorthread->Vars()->SetVariable( "startorigin", startorigin ); + doorthread->Vars()->SetVariable( "startangles", startangle ); + doorthread->Vars()->SetVariable( "movedir", movedir ); + doorthread->Vars()->SetVariable( "doorsize", doorsize ); + if ( initthreadname.length() ) + { + // start right away + doorthread->StartImmediately(); + } + } + +void ScriptDoor::DoOpen + ( + Event *ev + ) + + { + if ( !doorthread ) + { + warning( "DoOpen", "No Thread allocated." ); + return; + } + else + { + if ( !doorthread->Goto( openthreadname.c_str() ) ) + { + warning( "DoOpen", "Could not goto %s", openthreadname.c_str() ); + return; + } + } + + if ( previous_state == STATE_CLOSED ) + { + diropened = 0; + if ( ev->NumArgs() > 0 ) + { + Entity *other; + Vector p; + + other = ev->GetEntity( 1 ); + p = other->origin - origin; + p.z = 0; + diropened = dir * p; + } + } + doorthread->Vars()->SetVariable( "origin", origin ); + doorthread->Vars()->SetVariable( "opendot", diropened ); + doorthread->Start( 0 ); + } + +void ScriptDoor::DoClose + ( + Event *ev + ) + { + if ( !doorthread ) + { + warning( "DoClose", "No Thread allocated." ); + return; + } + else + { + if ( !doorthread->Goto( closethreadname.c_str() ) ) + { + warning( "DoOpen", "Could not goto %s", closethreadname.c_str() ); + } + } + doorthread->Vars()->SetVariable( "origin", origin ); + doorthread->Start( 0 ); + } + +void ScriptDoor::SetInitThread + ( + Event *ev + ) + + { + initthreadname = ev->GetString( 1 ); + } + +ScriptDoor::ScriptDoor() + { + if ( LoadingSavegame ) + { + return; + } + startangle = angles; + + // + // clear out the sounds if necessary + // scripted doors typically have their own sounds + // + sound_stop = ""; + sound_move = ""; + + movedir = G_GetMovedir( 0 ); + + PostEvent( EV_ScriptDoor_DoInit, EV_POSTSPAWN ); + } + +*/ diff --git a/dlls/game/doors.h b/dlls/game/doors.h new file mode 100644 index 0000000..848cb51 --- /dev/null +++ b/dlls/game/doors.h @@ -0,0 +1,290 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/doors.h $ +// $Revision:: 9 $ +// $Author:: Steven $ +// $Date:: 9/12/02 6:26p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Doors are environment objects that slide open when activated by triggers +// or when used by the player. +// + +#ifndef __DOORS_H__ +#define __DOORS_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "scriptslave.h" + +extern Event EV_Door_TryOpen; +extern Event EV_Door_GoDown; +extern Event EV_Door_GoUp; +extern Event EV_Door_HitBottom; +extern Event EV_Door_HitTop; +extern Event EV_Door_Fire; +extern Event EV_Door_Link; +extern Event EV_Door_SetSpeed; +extern Event EV_Door_Lock; +extern Event EV_Door_Unlock; + +class Door; + +typedef SafePtr DoorPtr; + +class Door : public ScriptSlave +{ + protected: + str sound_stop; + str sound_move; + str sound_message; + str sound_locked; + float lastblocktime; + float angle; + Vector dir; + float diropened; + int state; + int previous_state; + int trigger; + int nextdoor; + DoorPtr master; + float next_locked_time; + + Vector _extraTriggerSize; + + void SetDir( Event *ev ); + void OpenEnd( Event *ev ); + void CloseEnd( Event *ev ); + void Close( Event *ev ); + void Open( Event *ev ); + void DoorUse( Event *ev ); + void DoorFire( Event *ev ); + void DoorBlocked( Event *ev ); + void FieldTouched( Event *ev ); + void TryOpen( Event *ev ); + void SpawnTriggerField( const Vector &fmins, const Vector &fmaxs ); + qboolean DoorTouches( const Door *e1 ); + void LinkDoors( Event *ev ); + void SetTime( Event *ev ); + void LockDoor( Event *ev ); + void UnlockDoor( Event *ev ); + void SetStopSound( const str &sound ); + void SetStopSound( Event *ev ); + void SetMoveSound( const str &sound ); + void SetMoveSound( Event *ev ); + void SetMessageSound( const str &sound ); + void SetMessageSound( Event *ev ); + void SetLockedSound( const str &sound ); + void SetLockedSound( Event *ev ); + void SetWait( Event *ev ); + void SetDmg( Event *ev ); + void setDamage( Event *ev ); + + public: + CLASS_PROTOTYPE( Door ); + + qboolean locked; + + Door(); + qboolean isOpen( void ); + qboolean isCompletelyClosed( void ); + qboolean CanBeOpenedBy( Entity *ent ); + int GetState( void ); + int GetNextDoor( void ); + + void setExtraTriggerSize( Event *ev ) { _extraTriggerSize = ev->GetVector( 1 ); } + + virtual void Archive( Archiver &arc ); + }; + +inline int Door::GetState( void ) +{ + return state; +} + +inline int Door::GetNextDoor( void ) +{ + return nextdoor; +} + +inline void Door::Archive( Archiver &arc ) +{ + ScriptSlave::Archive( arc ); + + arc.ArchiveString( &sound_stop ); + arc.ArchiveString( &sound_move ); + arc.ArchiveString( &sound_message ); + arc.ArchiveString( &sound_locked ); + + if ( arc.Loading() ) + { + SetStopSound( sound_stop ); + SetMoveSound( sound_move ); + SetMessageSound( sound_message ); + SetLockedSound( sound_locked ); + } + + arc.ArchiveFloat( &lastblocktime ); + arc.ArchiveFloat( &angle ); + arc.ArchiveVector( &dir ); + arc.ArchiveFloat( &diropened ); + arc.ArchiveInteger( &state ); + arc.ArchiveInteger( &previous_state ); + arc.ArchiveInteger( &trigger ); + arc.ArchiveInteger( &nextdoor ); + arc.ArchiveSafePointer( &master ); + arc.ArchiveFloat( &next_locked_time ); + + arc.ArchiveVector( &_extraTriggerSize ); + + arc.ArchiveBoolean( &locked ); +} + +class RotatingDoor : public Door +{ + protected: + float angle; + Vector startangle; + int init_door_direction; + + public: + CLASS_PROTOTYPE( RotatingDoor ); + + RotatingDoor(); + + void DoOpen( Event *ev ); + void DoClose( Event *ev ); + void OpenAngle( Event *ev ); + virtual void Archive( Archiver &arc ); +}; + +inline void RotatingDoor::Archive( Archiver &arc ) +{ + Door::Archive( arc ); + + arc.ArchiveFloat( &angle ); + arc.ArchiveVector( &startangle ); + arc.ArchiveInteger( &init_door_direction ); +} + +class SlidingDoor : public Door +{ + protected: + float totalmove; + float lip; + Vector pos1; + Vector pos2; + float basespeed; + Vector movedir; + + public: + CLASS_PROTOTYPE( SlidingDoor ); + + SlidingDoor(); + + void SetMoveDir( Event *ev ); + void Setup( Event *ev ); + void SetLip( Event *ev ); + void SetSpeed( Event *ev ); + void DoOpen( Event *ev ); + void DoClose( Event *ev ); + virtual void Archive( Archiver &arc ); +}; + +inline void SlidingDoor::Archive( Archiver &arc ) +{ + Door::Archive( arc ); + + arc.ArchiveFloat( &totalmove ); + arc.ArchiveFloat( &lip ); + arc.ArchiveVector( &pos1 ); + arc.ArchiveVector( &pos2 ); + arc.ArchiveFloat( &basespeed ); + arc.ArchiveVector( &movedir ); +} + +class ScriptDoor : public Door +{ + protected: + bool _toggle; + bool _targeted; + float _openPercentage; + float _currentPercentage; + float _totalMove; + float _currentMove; + float _baseSpeed; + float _speed; + Vector _originalPos; + Vector _currentPos; + Vector _destinationPos; + Vector _moveDir; + + public: + CLASS_PROTOTYPE( ScriptDoor ); + + ScriptDoor(); + + void SetToggle( Event *ev ); + void SetToggle( bool toggle ); + + void SetTargeted( Event *ev ); + void SetTargeted( bool targeted ); + + void SetOpenPercentage( Event *ev ); + void SetOpenPercentage( float openPercentage ); + + void SetMoveDir( Event *ev ); + void SetMoveDir( float moveDir ); + + void DoClose( Event *ev ); + void DoClose(); + + void DoOpen( Event *ev ); + void DoOpen(); + + void ScriptDoorGetState( Event *ev ); + + void SetUpMovement( Event *ev ); + void SetUpMovement(); + + void SetUpScriptDoor( Event *ev ); + void SetUpScriptDoor(); + + void ScriptDoorUse ( Event *ev ); + void ScriptDoorOpen ( Event *ev ); + void ScriptDoorClose ( Event *ev ); + void ScriptDoorFieldTouched ( Event *ev ); + + void ScriptDoorForceOpen ( Event *ev ); + void ScriptDoorForceClose ( Event *ev ); + + virtual void Archive( Archiver &arc ); +}; + +inline void ScriptDoor::Archive( Archiver &arc ) +{ + Door::Archive( arc ); + + arc.ArchiveBool ( &_toggle ); + arc.ArchiveBool ( &_targeted ); + arc.ArchiveFloat ( &_openPercentage ); + arc.ArchiveFloat ( &_currentPercentage ); + arc.ArchiveFloat ( &_totalMove ); + arc.ArchiveFloat ( &_currentMove ); + arc.ArchiveFloat ( &_baseSpeed ); + arc.ArchiveFloat ( &_speed ); + arc.ArchiveVector ( &_originalPos ); + arc.ArchiveVector ( &_currentPos ); + arc.ArchiveVector ( &_destinationPos ); + arc.ArchiveVector ( &_moveDir ); +} + +#endif /* doors.h */ diff --git a/dlls/game/earthquake.cpp b/dlls/game/earthquake.cpp new file mode 100644 index 0000000..630cf0d --- /dev/null +++ b/dlls/game/earthquake.cpp @@ -0,0 +1,278 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/earthquake.cpp $ +// $Revision:: 11 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Earthquake trigger causes a localized earthquake when triggered. +// The earthquake effect is visible to the user as the shaking of his screen. +// +#include "_pch_cpp.h" +#include "earthquake.h" + +#define EARTHQUAKE_NO_RAMPUP ( 1 << 0 ) +#define EARTHQUAKE_NO_RAMPDOWN ( 1 << 1 ) + +/*****************************************************************************/ +/*QUAKED func_earthquake (0 0.25 0.5) (-8 -8 -8) (8 8 8) NO_RAMPUP NO_RAMPDOWN + Causes an earthquake +"duration" is the duration of the earthquake. Default is 0.8 seconds. +"magnitude" severity of the quake. Default 1.0 +******************************************************************************/ + +Event EV_Earthquake_Deactivate +( + "earthquake_deactivate", + EV_SCRIPTONLY, + NULL, + NULL, + "Stops the earthquake." +); +Event EV_Earthquake_SetDuration +( + "duration", + EV_SCRIPTONLY, + "f", + "duration", + "Sets the duration of the earthquake." +); +Event EV_Earthquake_SetMagnitude +( + "magnitude", + EV_SCRIPTONLY, + "f", + "theMagnitude", + "Sets the magnitude of the earthquake." +); +Event EV_Earthquake_SetDistance +( + "distance", + EV_SCRIPTONLY, + "f", + "distance", + "Sets the distance that an earthquake will effect things." +); +Event EV_Earthquake_SetPlaySound +( + "earthquake_playsound", + EV_SCRIPTONLY, + "B", + "bool", + "Sets whether or not the earthquake plays its looping sound or not (defaults to true)." +); + +CLASS_DECLARATION( Trigger, Earthquake, "func_earthquake" ) +{ + { &EV_Touch, NULL }, + { &EV_Trigger_Effect, &Earthquake::Activate }, + { &EV_Earthquake_Deactivate, &Earthquake::Deactivate }, + { &EV_Earthquake_SetDuration, &Earthquake::SetDuration }, + { &EV_Earthquake_SetMagnitude, &Earthquake::SetMagnitude }, + { &EV_Earthquake_SetDistance, &Earthquake::SetDistance }, + { &EV_Earthquake_SetPlaySound, &Earthquake::setPlaySound }, + + { NULL, NULL } +}; + +Earthquake::Earthquake( void ) +{ + // cache in the quake sound + CacheResource( "snd_earthquake", this ); + + if ( LoadingSavegame ) + { + return; + } + + // Setup some default values + + _duration = 0.8f; + _magnitude = 1.0f; + _distance = 0.0f; + _active = false; + _playSound = false; + + // Make sure it gets sent to the client (so the player hears the sound) + + edict->svflags &= ~SVF_NOCLIENT; +} + +Earthquake::~Earthquake() +{ + level.removeEarthquake( this ); + + StopLoopSound(); +} + +void Earthquake::SetDuration( Event *ev ) +{ + _duration = ev->GetFloat( 1 ); +} + +void Earthquake::SetMagnitude( Event *ev ) +{ + _magnitude = ev->GetFloat( 1 ); +} + +void Earthquake::SetDistance( Event *ev ) +{ + _distance = ev->GetFloat( 1 ); +} + +void Earthquake::Activate( Event *ev ) +{ + // The eartquake has been activated + + if ( _duration > 0.0f ) + { + // Add ourselves to the level list of earthquakes + + level.addEarthquake( this ); + + // Setup everything + + _startTime = level.time; + + _currentMagnitude = _magnitude; + + _active = true; + + if ( _playSound ) + { + LoopSound( "snd_earthquake", DEFAULT_VOL, LEVEL_WIDE_MIN_DIST ); + } + + PostEvent( EV_Earthquake_Deactivate, _duration ); + + turnThinkOn(); + } +} + +void Earthquake::Think( void ) +{ + float timeDelta; + + if ( !_active ) + { + return; + } + + timeDelta = level.time - _startTime; + + // we are in the first half of the earthquake + if ( timeDelta < ( _duration * 0.5f ) ) + { + if ( !( spawnflags & EARTHQUAKE_NO_RAMPUP ) ) + { + float rampUpTime; + + rampUpTime = _startTime + ( _duration * 0.33f ); + + if ( level.time < rampUpTime ) + { + float scale; + + scale = ( timeDelta ) / ( _duration * 0.33f ); + _currentMagnitude = _magnitude * scale; + edict->s.loopSoundVolume = scale; + } + else + { + _currentMagnitude = _magnitude; + edict->s.loopSoundVolume = 1.0f; + } + } + } + // we are in the second half of the earthquake + else + { + if ( !( spawnflags & EARTHQUAKE_NO_RAMPDOWN ) ) + { + float rampDownTime; + + rampDownTime = _startTime + ( _duration * 0.66f ); + + if ( level.time > rampDownTime ) + { + float scale; + + scale = 1.0f - ( ( level.time - rampDownTime ) / ( _duration * 0.33f ) ); + _currentMagnitude = _magnitude * scale; + edict->s.loopSoundVolume = scale; + } + else + { + _currentMagnitude = _magnitude; + edict->s.loopSoundVolume = 1.0f; + } + } + } +} + + +void Earthquake::Deactivate( Event *ev ) +{ + // Remove ourselves from the level list of earthquakes + + level.removeEarthquake( this ); + + // Stop everything + + _active = false; + _currentMagnitude = 0.0f; + + StopLoopSound(); + turnThinkOff(); +} + +void Earthquake::setPlaySound( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + _playSound = ev->GetBoolean( 1 ); + else + _playSound = true; +} + +float Earthquake::getMagnitudeAtPosition( const Vector &position ) +{ + float magnitudeAtPosition = 0.0f; + + // Make sure we are active + + if ( _active ) + { + // We default to our full current magnitude + + magnitudeAtPosition = _currentMagnitude; + + // See if we should diminish our magnitude over distance + + if ( _distance ) + { + Vector dir = origin - position; + const float distance = dir.length(); + + // Modify the magnitude over the distance from the position + + if ( distance > _distance ) + { + magnitudeAtPosition = 0.0f; + } + else + { + magnitudeAtPosition *= 1.0f - ( distance / _distance ); + } + } + } + + return magnitudeAtPosition; +} diff --git a/dlls/game/earthquake.h b/dlls/game/earthquake.h new file mode 100644 index 0000000..b4f5d97 --- /dev/null +++ b/dlls/game/earthquake.h @@ -0,0 +1,73 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/earthquake.h $ +// $Revision:: 6 $ +// $Author:: Steven $ +// $Date:: 9/08/02 10:43a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Earthquake trigger causes a localized earthquake when triggered. +// The earthquake effect is visible to the user as the shaking of his screen. +// + +#ifndef __EARTHQUAKE_H__ +#define __EARTHQUAKE_H__ + +#include "g_local.h" +#include "trigger.h" + +#define EARTHQUAKE_STRENGTH 50 + +extern Event EV_Earthquake_SetDuration; +extern Event EV_Earthquake_SetMagnitude; +extern Event EV_Earthquake_SetDistance; + +class Earthquake : public Trigger +{ + protected: + float _startTime; + bool _active; + float _magnitude; + float _duration; + float _distance; + float _currentMagnitude; + bool _playSound; + + public: + CLASS_PROTOTYPE( Earthquake ); + + Earthquake(); + ~Earthquake(); + void Activate( Event *ev ); + void Deactivate( Event *ev ); + void SetDuration( Event *ev ); + void SetMagnitude( Event *ev ); + void SetDistance( Event *ev ); + /* virtual */ void Think( void ); + bool EarthquakeActive() { return _active; }; + /* virtual */ void Archive( Archiver &arc ); + float getMagnitudeAtPosition( const Vector &position ); + void setPlaySound( Event *ev ); + }; + +inline void Earthquake::Archive( Archiver &arc ) +{ + Trigger::Archive( arc ); + + arc.ArchiveFloat( &_startTime ); + arc.ArchiveBool( &_active ); + arc.ArchiveFloat( &_magnitude ); + arc.ArchiveFloat( &_duration ); + arc.ArchiveFloat( &_distance ); + arc.ArchiveFloat( &_currentMagnitude ); + arc.ArchiveBool( &_playSound ); +} + +#endif /* Earthquake.h */ diff --git a/dlls/game/entity.cpp b/dlls/game/entity.cpp new file mode 100644 index 0000000..f73c00e --- /dev/null +++ b/dlls/game/entity.cpp @@ -0,0 +1,10226 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/entity.cpp $ +// $Revision:: 271 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:43a $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class for all enities that are controlled by the game. If you have any +// object that should be called on a periodic basis and it is not an entity, +// then you have to have an dummy entity that calls it. +// +// An entity in the game is any object that is not part of the world. Any non-world +// object that is visible in the game is an entity, although it is not required that +// all entities be visible to the player. Some objects are basically just virtual +// constructs that act as an instigator of certain actions, for example, some +// triggers are invisible and cannot be touched, but when activated by other +// objects can cause things to happen. +// +// All entities are capable of receiving messages from the game or from other entities. +// Messages received by an entity may be ignored, passed on to their superclass, +// or acted upon by the entity itself. The programmer must decide on the proper +// action for the entity to take to any message. There will be many messages +// that are completely irrelevant to an entity and should be ignored. Some messages +// may require certain states to exist and if they are received by an entity when +// it these states don't exist may indicate a logic error on the part of the +// programmer or map designer and should be reported as warnings (if the problem is +// not severe enough for the game to be halted) or as errors (if the problem should +// not be ignored at any cost). +// + +#include "_pch_cpp.h" +//#include "g_local.h" +#include "entity.h" +#include "worldspawn.h" +#include "scriptmaster.h" +#include "sentient.h" +#include "specialfx.h" +#include "misc.h" +#include "object.h" +#include "player.h" +#include "weaputils.h" +#include "soundman.h" +#include "earthquake.h" +#include +#include "mp_manager.hpp" +#include + + +// Player events +Event EV_ClientMove + ( + "client_move", + EV_DEFAULT, + NULL, + NULL, + "The movement packet from the client is processed by this event." + ); +Event EV_ClientEndFrame + ( + "client_endframe", + EV_DEFAULT, + NULL, + NULL, + "Called at the end of each frame for each client." + ); + +// Generic entity events +Event EV_Classname + ( + "classname" , + EV_TIKIONLY, + "s", + "nameOfClass", + "Determines what class to use for this entity,\n" + "this is pre-processed from the BSP at the start\n" + "of the level." + ); +Event EV_SpawnFlags + ( + "spawnflags", + EV_CODEONLY, + "i", + "flags", + "spawnflags from the BSP, these are set inside the editor" + ); +Event EV_SetTeam + ( + "team", + EV_DEFAULT, + "s", + "moveTeam", + "used to make multiple entities move together." + ); +Event EV_Trigger + ( + "trigger", + EV_SCRIPTONLY, + "s", + "name", + "Trigger the specified target or entity." + ); +Event EV_Activate + ( + "doActivate", + EV_DEFAULT, + "e", + "activatingEntity", + "General trigger event for all entities" + ); +Event EV_Use + ( + "doUse", + EV_DEFAULT, + "e", + "activatingEntity", + "sent to entity when it is used by another entity" + ); + +Event EV_FadeNoRemove + ( + "fade", + EV_DEFAULT, + "F[0,]F[0,1]", + "fadetime target_alpha", + "Fade the entity's alpha, reducing it by 0.03\n" + "every FRAMETIME, until it has faded out, does not remove the entity" + ); + +Event EV_FadeOut + ( + "_fadeout", + EV_CODEONLY, + NULL, + NULL, + "Fade the entity's alpha and scale out, reducing it by 0.03\n" + "every FRAMETIME, until it has faded out, removes the entity\n" + "Once the entity has been completely faded, the entity is removed." + ); + +Event EV_Fade + ( + "fadeout", + EV_DEFAULT, + "F[0,]F[0,1]", + "fadetime target_alpha", + "Fade the entity's alpha and scale out, reducing it by 0.03\n" + "every FRAMETIME, until it has faded out. If fadetime or\n" + "target_alpha are defined, they will override the defaults.\n" + "Once the entity has been completely faded, the entity is removed." + ); +Event EV_FadeIn + ( + "fadein", + EV_DEFAULT, + "F[0,]F[0,1]", + "fadetime target_alpha", + "Fade the entity's alpha and scale in, increasing it by 0.03\n" + "every FRAMETIME, until it has faded completely in to 1.0.\n" + "If fadetime or target_alpha are defined, they will override\n" + "the default values." + ); + Event EV_Killed + ( + "killed", + EV_DEFAULT, + "efei", + "attacker damage inflictor meansofdeath", + "event which is sent to an entity once it as been killed" + ); +Event EV_GotKill + ( + "gotkill" , + EV_DEFAULT, + "eieib", + "victim damage inflictor meansofdeath gib", + "event sent to attacker when an entity dies" + ); +Event EV_Pain + ( + "pain", + EV_DEFAULT, + "iei", + "damage attacker meansofdeath", + "used to inflict pain to an entity" + ); +Event EV_Damage + ( + "_damage", + EV_CODEONLY, + "feevvviii", + "damage inflictor attacker position direction normal knockback damageflags meansofdeath", + "general damage event used by all entities" + ); +Event EV_Stun + ( + "_stun", + EV_CODEONLY, + "f", + "time", + "Stun this entity for the specified time" + ); +Event EV_Kill + ( + "kill", + EV_CONSOLE, + NULL, + NULL, + "console based command to kill yourself if stuck." + ); +Event EV_Gib + ( + "gib", + EV_DEFAULT, + "iIFS", + "number power scale gibmodel", + "causes entity to spawn a number of gibs" + ); +Event EV_Hurt + ( + "hurt", + EV_DEFAULT, + "fSV", + "damage means_of_death direction", + "Inflicts damage if the entity is damageable. If the number of damage\n" + "points specified in the command argument is greater or equal than the\n" + "entity's current health, it will be killed or destroyed." + ); + +Event EV_TakeDamage + ( + "takedamage", + EV_DEFAULT, + NULL, + NULL, + "makes entity take damage." + ); +Event EV_NoDamage + ( + "nodamage", + EV_DEFAULT, + NULL, + NULL, + "entity does not take damage." + ); + +Event EV_Stationary + ( + "stationary", + EV_DEFAULT, + NULL, + NULL, + "entity does not move, causes no physics to be run on it." + ); + +// Physics events +Event EV_MoveDone + ( + "movedone", + EV_DEFAULT, + "e", + "finishedEntity", + "Sent to commanding thread when done with move ." + ); +Event EV_Touch + ( + "doTouch", + EV_DEFAULT, + "e", + "touchingEntity", + "sent to entity when touched for the first time." + ); +Event EV_Contact + ( + "inContactWithEntity", + EV_DEFAULT, + "e", + "entityWrapper", + "sent to an entity that is in contact with specified entity." + ); +Event EV_LostContact + ( + "lostContactWithEntity", + EV_DEFAULT, + "e", + "entityNoLongerInContact", + "sent to an entity no longer in contact with specified entity." + ); +Event EV_Blocked + ( + "doBlocked", + EV_DEFAULT, + "e", + "obstacle", + "sent to entity when blocked." + ); +Event EV_UseBoundingBox + ( + "usebbox", + EV_DEFAULT, + NULL, + NULL, + "do not perform perfect collision, use bounding box instead." + ); +Event EV_Gravity + ( + "gravity", + EV_DEFAULT, + "f", + "gravityValue", + "Change the gravity on this entity" + ); +Event EV_Stop + ( + "stopped", + EV_DEFAULT, + NULL, + NULL, + "sent when entity has stopped bouncing for MOVETYPE_TOSS." + ); +Event EV_SetFullTrace + ( + "fulltrace", + EV_DEFAULT, + "b", + "on_or_off", + "Turns fulltrace physics movement on or off for this entity." + ); + +Event EV_ProcessInitCommands + ( + "processinit", + EV_CODEONLY, + "i", + "modelIndex", + "process the init section of this entity, this is an internal command,\n" + "it is not meant to be called from script." + ); +Event EV_Attach + ( + "attach", + EV_DEFAULT, + "esIVV", + "parent tagname use_angles offset angles_offset", + "attach this entity to the parent's legs tag called tagname" + ); +Event EV_AttachModel + ( + "attachmodel", + EV_DEFAULT, + "ssFSBFFFFVV", + "modelname tagname scale targetname detach_at_death removetime fadeintime fadeoutdelay fadetime offset angles_offset", + "attach a entity with modelname to this entity to tag called tagname.\n" + "scale - scale of attached entities\n" + "targetname - targetname for attached entities\n" + "detach_at_death - when entity dies, should this model be detached.\n" + "removetime - when the entity should be removed, if not specified, never.\n" + "fadeintime - time to fade the model in over.\n" + "fadeoutdelay - time to wait until we fade the attached model out\n" + "fadeoutspeed - time the model fades out over\n" + "offset - vector offset for the model from the specified tag\n" + "angles_offset - angles offset for the model from the specified tag" + ); +Event EV_RemoveAttachedModel + ( + "removeattachedmodel", + EV_DEFAULT, + "sFS", + "tagname fadetime modelname", + "Removes the model attached to this entity at the specified tag." + ); +Event EV_RemoveAttachedModelByTargetname +( + "removeAttachedModelByTargetname", + EV_DEFAULT, + "s", + "targetname", + "Removes all models that are attached to this entity with the specified targetname." +); +Event EV_Detach + ( + "detach", + EV_SCRIPTONLY, + NULL, + NULL, + "detach this entity from its parent." + ); + +Event EV_IncreaseShotCount + ( + "increaseshotcount", + EV_DEFAULT, + NULL, + NULL, + "boosts the shotsFired on this entity's parent if it is an actor" + ); + +// script stuff +Event EV_Model + ( + "model", + EV_DEFAULT, + "s", + "modelName", + "set the model to modelName." + ); +Event EV_Hide + ( + "hide", + EV_DEFAULT, + NULL, + NULL, + "hide the entity, opposite of show." + ); +Event EV_Show + ( + "show", + EV_DEFAULT, + NULL, + NULL, + "show the entity, opposite of hide." + ); +Event EV_BecomeSolid + ( + "solid", + EV_DEFAULT, + NULL, + NULL, + "make solid." + ); +Event EV_BecomeNonSolid + ( + "notsolid", + EV_DEFAULT, + NULL, + NULL, + "make non-solid." + ); +Event EV_Ghost + ( + "ghost", + EV_DEFAULT, + NULL, + NULL, + "make non-solid but still send to client regardless of hide status." + ); +Event EV_TouchTriggers + ( + "touchtriggers", + EV_DEFAULT, + "B", + "touch_triggers_bool", + "Sets whether the entity should touch triggers or not" + ); +Event EV_Sound + ( + "playsound", + EV_DEFAULT, + "sIFS", + "soundName channel volume min_distance", + "play a sound coming from this entity.\n" + "default channel, CHAN_BODY." + ); +Event EV_StopSound + ( + "stopsound", + EV_DEFAULT, + "I", + "channel", + "stop the current sound on the specified channel.\n" + "default channel, CHAN_BODY." + ); +Event EV_Bind + ( + "bind", + EV_DEFAULT, + "e", + "parent", + "bind this entity to the specified entity." + ); +Event EV_Unbind + ( + "unbind", + EV_DEFAULT, + NULL, + NULL, + "unbind this entity." + ); +Event EV_JoinTeam + ( + "joinTeam", + EV_DEFAULT, + "e", + "teamMember", + "join a bind team." + ); +Event EV_QuitTeam + ( + "quitTeam", + EV_DEFAULT, + NULL, + NULL, + "quit the current bind team" + ); +Event EV_SetHealth + ( + "health", + EV_CHEAT | EV_TIKIONLY, + "i", + "newHealth", + "set the health of the entity to newHealth" + ); +Event EV_GetHealth + ( + "gethealth", + EV_CHEAT | EV_SCRIPTONLY, + "@f", + "currentHealth", + "Gets the health of the entity" + ); +Event EV_SetMaxHealth + ( + "maxhealth", + EV_CHEAT, + "i", + "newMaxHealth", + "set the max_health of the entity to newMaxHealth" + ); +Event EV_SetScale + ( + "scale", + EV_DEFAULT, + "f", + "newScale", + "set the scale of the entity" + ); +Event EV_SetRandomScale + ( + "randomScale", + EV_DEFAULT, + "fd", + "minScale maxScale", + "Set the scale of the entity randomly between minScale and maxScale" + ); +Event EV_SetSize + ( + "setsize", + EV_DEFAULT, + "vv", + "mins maxs", + "Set the bounding box of the entity to mins and maxs." + ); +Event EV_GetMins + ( + "getmins", + EV_SCRIPTONLY, + "@v", + "returnval", + "Returns the minimum extent of the bounding box" + ); +Event EV_GetMaxs + ( + "getmaxs", + EV_SCRIPTONLY, + "@v", + "returnval", + "Returns the maximum extent of the bounding box" + ); +Event EV_SetMins + ( + "_setmins", + EV_CODEONLY, + "v", + "mins", + "Set the mins of the bounding box of the entity to mins." + ); +Event EV_SetMaxs + ( + "_setmaxs", + EV_CODEONLY, + "v", + "maxs", + "Set the maxs of the bounding box of the entity to maxs." + ); +Event EV_SetAlpha + ( + "alpha", + EV_DEFAULT, + "f", + "newAlpha", + "Set the alpha of the entity to alpha." + ); +Event EV_SetOrigin + ( + "origin", + EV_SCRIPTONLY, + "v", + "newOrigin", + "Set the origin of the entity to newOrigin." + ); +Event EV_SetOriginEveryFrame + ( + "setorigineveryframe", + EV_DEFAULT, + NULL, + NULL, + "Sets the origin every frame ( for bound objects )" + ); + +Event EV_GetOrigin + ( + "getorigin", + EV_CHEAT | EV_SCRIPTONLY, + "@v", + "returnval", + "Gets the origin of the entity." + ); +Event EV_SetTargetName + ( + "targetname", + EV_SCRIPTONLY, + "s", + "targetName", + "set the targetname of the entity to targetName." + ); +Event EV_GetTargetName + ( + "getTargetName", + EV_SCRIPTONLY, + "@s", + "targetName", + "Gets the targetname of the entity with the leading $." + ); +Event EV_GetRawTargetName + ( + "getRawTargetName", + EV_SCRIPTONLY, + "@s", + "rawTargetName", + "Gets the targetname of the entity without the leading $." + ); +Event EV_SetTarget + ( + "target", + EV_DEFAULT, + "s", + "targetname_to_target", + "target another entity with targetname_to_target." + ); +Event EV_GetTarget + ( + "getTarget", + EV_SCRIPTONLY, + "@sB", + "name_of_current_target wantsPrefix", + "Returns the name of the current target." + ); + +Event EV_GetTargetEntity + ( + "gettargetentity", + EV_SCRIPTONLY, + "@e", + "NULL", + "Returns the current target entity" + ); + +Event EV_SetKillTarget + ( + "killtarget", + EV_DEFAULT, + "s", + "targetName", + "when dying kill entities with this targetName." + ); +Event EV_GetModelName + ( + "getmodelname", + EV_SCRIPTONLY, + "@s", + "modelname", + "Retrieves the model name of the entity" + ); +Event EV_SetAngles + ( + "angles", + EV_SCRIPTONLY, + "v[0,360][0,360][0,360]", + "newAngles", + "set the angles of the entity to newAngles." + ); +Event EV_GetAngles + ( + "getangles", + EV_SCRIPTONLY, + "@v", + "angle_vector", + "Retrieves the entity's angles" + ); +Event EV_SetAngle + ( + "angle", + EV_DEFAULT, + "f", + "newAngle", + "set the angles of the entity using just one value.\n" + "Sets the yaw of the entity or an up and down\n" + "direction if newAngle is [0-359] or -1 or -2" + ); +Event EV_RegisterAlias + ( + "alias", + EV_CACHE, + "ssSSSSSS", + "alias realname parameter1 parameter2 parameter3 parameter4 parameter5 paramater6", + "create an alias for the given realname\n" + "Valid parameters are as follows:\n" + "global - all instances act as one\n" + "stop - once used, don't use again\n" + "timeout [seconds] - once used wait [seconds] until using again\n" + "maxuse [times] - can only be used [times] times.\n" + "weight [weight] - random weighting" + ); +Event EV_RegisterAliasAndCache + ( + "aliascache", + EV_CACHE, + "ssSSSSSS", + "alias realname parameter1 parameter2 parameter3 parameter4 parameter5 paramater6", + "create an alias for the given realname and cache the resource\n" + "Valid parameters are as follows:\n" + "global - all instances act as one\n" + "stop - once used, don't use again\n" + "timeout [seconds] - once used wait [seconds] until using again\n" + "maxuse [times] - can only be used [times] times.\n" + "weight [weight] - random weighting" + ); +Event EV_Cache + ( + "cache", + EV_CACHE, + "s", + "resourceName", + "pre-cache the given resource." + ); +Event EV_AutoCache + ( + "autocache", + EV_CACHE | EV_TIKIONLY, + "s", + "resourceName", + "pre-cache the given resource." + ); +Event EV_SetMass + ( + "mass", + EV_TIKIONLY, + "i", + "massAmount", + "set the mass of this entity." + ); + +Event EV_LoopSound + ( + "loopsound", + EV_DEFAULT, + "sFS", + "soundName volume minimum_distance", + "play a looped-sound with a certain volume and minimum_distance\n" + "which is attached to the current entity." + ); +Event EV_StopLoopSound + ( + "stoploopsound", + EV_DEFAULT, + NULL, + NULL, + "Stop the looped-sound on this entity." + ); + +Event EV_SurfaceModelEvent + ( + "surface", + EV_DEFAULT, + "sSSSSSS", + "surfaceName parameter1 parameter2 parameter3 parameter4 parameter5 parameter6", + "change a legs surface parameter for the given surface.\n" + "+ sets the flag, - clears the flag\n" + "Valid surface commands are:\n" + "skin1 - set the skin1 offset bit\n" + "skin2 - set the skin2 offset bit\n" + "nodraw - don't draw this surface" + ); + + +Event EV_SetAnimOnAttachedModel + ( + "attachanim", + EV_DEFAULT, + "ss", + "anim_name tag_name", + "sets the anim on an attached entity at the specified tag" + ); + +Event EV_SetCinematicAnim + ( + "setcinematicanim", + EV_DEFAULT, + "s", + "anim_name", + "sets a cinematic anim on an entity" + ); + +Event EV_CinematicAnimDone + ( + "cinematicanimdone", + EV_CODEONLY, + NULL, + NULL, + "Called when an entity's cinematic animation is done, " + "it is not meant to be called from script." + ); + +Event EV_SetEntityExplosionModel + ( + "setentityexplosionmodel", + EV_DEFAULT, + "s", + "model_name", + "sets this entitys explosion model which is used in selfDetonate" + ); + +// AI sound events +Event EV_BroadcastSound + ( + "soundevent", + EV_DEFAULT, + "FS", + "soundRadius soundtype", + "Let the AI know that this entity made a sound,\n" + "radius determines how far the sound reaches." + ); +Event EV_HeardSound + ( + "heardsound", + EV_DEFAULT, + "evi", + "noisyEntity noisyLocation soundtype", + "sent to entities when another makes a sound, not for script use\n" + ); + +// Conditionals +Event EV_IfSkill + ( + "ifskill", + EV_DEFAULT, + "isSSSSS", + "skillLevel command argument1 argument2 argument3 argument4 argument5", + "if the current skill level is skillLevel than execute command" + ); + +// Lighting +Event EV_SetLight + ( + "light", + EV_DEFAULT, + "ffff", + "color_red color_green color_blue radius", + "Create a dynmaic light on this entity." + ); + +Event EV_LightOn + ( + "lightOn", + EV_DEFAULT, + NULL, + NULL, + "Turn the configured dynmaic light on this entity on." + ); +Event EV_LightOff + ( + "lightOff", + EV_DEFAULT, + NULL, + NULL, + "Turn the configured dynamic light on this entity off." + ); +Event EV_LightStyle + ( + "lightStyle", + EV_DEFAULT, + "i", + "lightStyleIndex", + "What light style to use for this dynamic light on this entity." + ); +Event EV_LightRed + ( + "lightRed", + EV_DEFAULT, + "f", + "red", + "Set the red component of the dynmaic light on this entity." + ); +Event EV_LightGreen + ( + "lightGreen", + EV_DEFAULT, + "f", + "green", + "Set the green component of the dynmaic light on this entity." + ); +Event EV_LightBlue + ( + "lightBlue", + EV_DEFAULT, + "f", + "blue", + "Set the blue component of the dynmaic light on this entity." + ); +Event EV_LightRadius + ( + "lightRadius", + EV_DEFAULT, + "f", + "radius", + "Set the radius of the dynmaic light on this entity." + ); + +// Entity flag specific +Event EV_EntityFlags + ( + "flags", + EV_DEFAULT, + "SSSSSS", + "parameter1 parameter2 parameter3 parameter4 parameter5 parameter6", + "Change the current entity flags.\n" + "Valid flags are as follows:\n" + "+ sets a flag, - clears a flag\n" + "blood - should it bleed\n" + "explode - should it explode when dead\n" + "die_gibs - should it spawn gibs when dead\n" + "god - makes the entity invincible\n" + "notarget - makes the entity notarget\n" + ); +Event EV_EntityRenderEffects + ( + "rendereffects", + EV_DEFAULT, + "SSSSSS", + "parameter1 parameter2 parameter3 parameter4 parameter5 parameter6", + "Change the current render effects flags.\n" + "Valid flags are as follows:\n" + "+ sets a flag, - clears a flag\n" + "dontdraw - send the entity to the client, but don't draw\n" + "betterlighting - do sphere based vertex lighting on the entity\n" + "lensflare - add a lens glow to the entity at its origin\n" + "viewlensflare - add a view dependent lens glow to the entity at its origin\n" + "lightoffset - use the dynamic color values as a light offset to the model\n" + "skyorigin - this entity is the portal sky origin\n" + "minlight - this entity always has some lighting on it\n" + "fullbright - this entity is always fully lit\n" + "additivedynamiclight - the dynamic light should have an additive effect\n" + "lightstyledynamiclight - the dynamic light uses a light style, use the\n" + "depthhack - this entity is drawn with depth hack on\n" + "'lightstyle' command to set the index of the light style to be used" + + ); +Event EV_EntityEffects + ( + "effects", + EV_DEFAULT, + "SSSSSS", + "parameter1 parameter2 parameter3 parameter4 parameter5 parameter6", + "Change the current entity effects flags.\n" + "Valid flags are as follows:\n" + "+ sets a flag, - clears a flag\n" + "everyframe - process commands every time entity is rendered" + ); +Event EV_EntitySVFlags + ( + "svflags", + EV_DEFAULT, + "SSSSSS", + "parameter1 parameter2 parameter3 parameter4 parameter5 parameter6", + "Change the current server flags.\n" + "Valid flags are as follows:\n" + "+ sets a flag, - clears a flag\n" + "broadcast - always send this entity to the client" + ); + +// Special Effects +Event EV_Censor + ( + "censor", + EV_TIKIONLY, + NULL, + NULL, + "used to ban certain contact when in parentmode\n" + ); +Event EV_Explosion + ( + "explosionattack", + EV_DEFAULT, + "sS", + "explosionModel tagName", + "Spawn an explosion optionally from a specific tag" + ); +Event EV_DoRadiusDamage + ( + "doradiusdamage", + EV_DEFAULT, + "fsffBF", + "damage meansofdeath radius knockback constant_damage repeat_time", + "calls RadiusDamage() using the entity information and owner" + ); +Event EV_SelfDetonate + ( + "selfdetonate", + EV_DEFAULT, + NULL, + NULL, + "spawns and explosion and removes this entity" + ); + +Event EV_ShaderEvent + ( + "shader", + EV_DEFAULT, + "sfF", + "shaderCommand argument1 argument2", + "change a specific shader parameter for the entity.\n" + "Valid shader commands are:\n" + "translation [trans_x] [trans_y] - change the texture translation\n" + "offset [offset_x] [offset_y] - change the texture offset\n" + "rotation [rot_speed] - change the texture rotation speed\n" + "frame [frame_num] - change the animated texture frame\n" + "wavebase [base] - change the base parameter of the wave function\n" + "waveamp [amp] - change the amp parameter of the wave function\n" + "wavebase [phase] - change the phase parameter of the wave function\n" + "wavefreq [freq] - change the frequency parameter of the wave function\n" + ); + +Event EV_ScriptShaderEvent + ( + "scriptshader", + EV_DEFAULT, + "sfF", + "shaderCommand argument1 argument2", + "alias for shader command, change a specific shader parameter for the entity.\n" + "Valid shader commands are:\n" + "translation [trans_x] [trans_y] - change the texture translation\n" + "offset [offset_x] [offset_y] - change the texture offset\n" + "rotation [rot_speed] - change the texture rotation speed\n" + "frame [frame_num] - change the animated texture frame\n" + "wavebase [base] - change the base parameter of the wave function\n" + "waveamp [amp] - change the amp parameter of the wave function\n" + "wavebase [phase] - change the phase parameter of the wave function\n" + "wavefreq [freq] - change the frequency parameter of the wave function\n" + ); + +Event EV_KillAttach + ( + "killattach", + EV_DEFAULT, + NULL, + NULL, + "kill all the attached entities." + ); +Event EV_DropToFloor + ( + "droptofloor", + EV_CODEONLY, + "F", + "maxRange", + "drops the entity to the ground, if maxRange is not specified 8192 is used." + ); +Event EV_AddToSoundManager + ( + "_addtosoundmanager", + EV_CODEONLY, + NULL, + NULL, + "adds the current entity to the sound manager." + ); +Event EV_SetControllerAngles + ( + "setcontrollerangles", + EV_CODEONLY, + "iv", + "num angles", + "Sets the control angles for the specified bone." + ); +Event EV_DeathSinkStart + ( + "deathsinkstart", + EV_CODEONLY, + NULL, + NULL, + "Makes the entity sink into the ground and then get removed (this starts it)." + ); +Event EV_DeathSink + ( + "deathsinkeachframe", + EV_CODEONLY, + NULL, + NULL, + "Makes the entity sink into the ground and then get removed (this gets called each frame)." + ); +Event EV_DamageType + ( + "damage_type", + EV_DEFAULT, + "s", + "meansofdeathstring", + "Set the type of damage that this entity can take" + ); +Event EV_LookAtMe + ( + "lookatme", + EV_DEFAULT, + NULL, + NULL, + "Makes the player look at this object if close." + ); +Event EV_ProjectilesCanStickToMe + ( + "projectilecansticktome", + EV_DEFAULT, + "b", + "can_stick", + "Bool that determines whether projectiles stick in this entity." + ); +Event EV_DetachAllChildren + ( + "detachallchildren", + EV_SCRIPTONLY, + NULL, + NULL, + "Detach all the children from the entity." + ); +Event EV_Morph + ( + "morph", + EV_DEFAULT, + "sFFB", + "morph_target_name final_percent morph_time return_to_zero", + "Morphs to the specified morph target" + ); +Event EV_Unmorph + ( + "unmorph", + EV_DEFAULT, + "s", + "morph_target_name", + "Unmorphs the specified morph target" + ); +Event EV_MorphControl + ( + "morphcontrol", + EV_CODEONLY, + NULL, + NULL, + "Does all of the morph work each frame" + ); + +Event EV_SetObjectProgram + ( + "setobjectprogram", + EV_DEFAULT, + "s", + "program", + "sets this objects program" + ); + +Event EV_ExecuteObjectProgram + ( + "executeobjectprogram", + EV_DEFAULT, + "f", + "time", + "executes this objects program at the specified time" + ); + +Event EV_ProjectileAtk + ( + "projectileattack", + EV_DEFAULT, + "sS", + "projectile_name tag_name", + "Launches a projectile" + ); +Event EV_ProjectileAttackPoint + ( + "projectileattackpoint", + EV_DEFAULT, + "svFF", + "projectileName targetPosition trajectoryAngle lifespan", + "Launches a projectile at the named entity" + ); +Event EV_ProjectileAttackEntity + ( + "projectileattackentity", + EV_DEFAULT, + "ssFF", + "projectileName entityName trajectoryAngle lifespan", + "Launches a projectile at the named entity" + ); +Event EV_ProjectileAttackFromTag + ( + "projectileattackfromtag", + EV_DEFAULT, + "ssFF", + "projectileName tagName speed lifespan", + "Launches a projectile from the named tag" + ); +Event EV_ProjectileAttackFromPoint + ( + "projectileattackfrompoint", + EV_DEFAULT, + "svvFF", + "projectileName position direction speed lifespan", + "Launches a projectile from the desired location" + ); +Event EV_TraceAtk + ( + "traceattack", + EV_DEFAULT, + "ffSFS", + "damage range means_of_death knockback tag_name", + "Does a trace attack" + ); + +Event EV_Contents +( + "contents", + EV_DEFAULT, + "sSSSSS", + "ct1 ct2 ct3 ct4 ct5 ct6", + "The content type of the entity" +); +Event EV_Mask +( + "mask", + EV_DEFAULT, + "sSSSSS", + "mask1 mask2 mask3 mask4 mask5 mask6", + "Sets the mask of the entity. Masks can either be real masks or contents. A +before the" + "mask adds it, a - removes it, and nothing sets it" +); + +Event EV_DisplayEffect + ( + "displayeffect", + EV_DEFAULT, + "sS", + "effect_type effect_name", + "Displays the named effect." + ); + +Event EV_ForceAlpha + ( + "forcealpha", + EV_DEFAULT, + "B", + "bool", + "Sets whether or not alpha is forced" + ); + +Event EV_SpawnEffect + ( + "spawneffect", + EV_DEFAULT, + "ssF", + "modelName tagName removeTime", + "Spawns the effect at the specified tag" + ); + +Event EV_CreateEarthquake + ( + "earthquake", + EV_DEFAULT, + "ffF", + "magnitude duration distance", + "Creates an earthquake" + ); + +Event EV_SetFloatVar + ( + "setfloatvar", + EV_DEFAULT, + "sf", + "variable_name float_value", + "Sets a level, game, or entity variable." + ); +Event EV_SetVectorVar + ( + "setvectorvar", + EV_DEFAULT, + "sv", + "variable_name vector_value", + "Sets a level, game, or entity variable." + ); +Event EV_SetStringVar + ( + "setstringvar", + EV_DEFAULT, + "ss", + "variable_name string_value", + "Sets a level, game, or entity variable." + ); +Event EV_DoesVarExist + ( + "doesVarExist", + EV_SCRIPTONLY, + "@fs", + "float_value variable_name", + "Returns whether or not a variable exists." + ); +Event EV_GetFloatVar + ( + "getfloatvar", + EV_SCRIPTONLY, + "@fs", + "float_value variable_name", + "Gets a level, game, or entity variable." + ); +Event EV_RemoveVariable + ( + "removevar", + EV_SCRIPTONLY, + "s", + "variable_name", + "Removes a level, game, or entity variable and frees any memory used by it" + ); +Event EV_GetVectorVar + ( + "getvectorvar", + EV_SCRIPTONLY, + "@vs", + "vector_value variable_name", + "Gets a level, game, or entity variable." + ); +Event EV_GetStringVar + ( + "getstringvar", + EV_SCRIPTONLY, + "@ss", + "string_value variable_name", + "Gets a level, game, or entity variable." + ); + +Event EV_SetUserVar1 + ( + "uservar1", + EV_DEFAULT, + "s", + "string_value", + "Sets an entity variable." + ); +Event EV_SetUserVar2 + ( + "uservar2", + EV_DEFAULT, + "s", + "string_value", + "Sets an entity variable." + ); +Event EV_SetUserVar3 + ( + "uservar3", + EV_DEFAULT, + "s", + "string_value", + "Sets an entity variable." + ); +Event EV_SetUserVar4 + ( + "uservar4", + EV_DEFAULT, + "s", + "string_value", + "Sets an entity variable." + ); + +Event EV_AffectingViewMode + ( + "viewmode", + EV_DEFAULT, + "s", + "viewModeName", + "Specifies that this entity uses the specified view mode." + ); + +// These are events that may come from tiki files +Event EV_TikiNote + ( + "note", + EV_DEFAULT, + "s", + "note", + "This is a comment" + ); +Event EV_TikiTodo + ( + "todo", + EV_DEFAULT, + "s", + "todo", + "This is an item that needs to be done" + ); + +Event EV_SetGroupID + ( + "setgroupid", + EV_DEFAULT, + "i", + "groupID", + "Sets the groupID of this entity" + ); + +Event EV_Multiplayer + ( + "multiplayer", + EV_DEFAULT, + "sSSSSS", + "realEventName parm1 parm2 parm3 parm4 parm5", + "Sends off a real event only if this is a multiplayer game" + ); + +Event EV_DamageModifier + ( + "damagemodifier", + EV_DEFAULT, + "ssfFFF", + "modifiertype value multiplier chance minpain maxpain", + "Add a damage modifier to this entities list\n" + " modifiertypes are:\n" + " tikiname, name, group, actortype, targetname, damagetype" + ); + +Event EV_SetMoveType + ( + "setmovetype", + EV_DEFAULT, + "s", + "movetype", + "Sets the move type of this entity\n" + " Valid types are: \n" + " none , stationary , noclip , push , stop\n" + " walk , step , fly , toss , flymissile\n" + " bounce, slider, rope , gib , vehicle" + ); + +Event EV_HelperNodeCommand + ( + "helpernodecommand", + EV_CODEONLY, + "s", + "commandtype", + "Command from a helper node" + ); + +Event EV_UseDataAnim + ( + "useanim", + EV_DEFAULT, + "s", + "animname", + "Animation for the player to play when this entity is used." + ); + +Event EV_UseDataType + ( + "usetype", + EV_DEFAULT, + "s", + "usetype", + "Use type (widget name) for the use icon" + ); + +Event EV_UseDataThread + ( + "usethread", + EV_DEFAULT, + "s", + "threadname", + "Thread to call when this entity is used." + ); + +Event EV_UseData + ( + "usedata", + EV_DEFAULT, + "sss", + "animname usetype threadname", + "Sets data for this usuable entity." + ); + +Event EV_UseMaxDist + ( + "usemaxdist", + EV_DEFAULT, + "f", + "maxdist", + "Sets maximum distance this entity can be used." + ); + +Event EV_UseCount + ( + "usecount", + EV_DEFAULT, + "i", + "count", + "Sets the number of times this entity can be used." + ); + +Event EV_SetArchetype + ( + "archetype", + EV_DEFAULT, + "s", + "archetype", + "Sets the archetype name for this entity" + ); + +Event EV_SetMissionObjective + ( + "missionobjective", + EV_DEFAULT, + "b", + "missionobjective", + "Sets the mission objective flag" + ); + +Event EV_SetGameplayHealth + ( + "gdb_sethealth", + EV_DEFAULT, + "s", + "healthstr", + "Sets the gameplay version of health with keywords." + ); + +Event EV_SetGameplayDamage + ( + "gdb_setdamage", + EV_DEFAULT, + "s", + "damagestr", + "Sets the gameplay version of damage with keywords." + ); + +Event EV_ProcessGameplayData + ( + "processgameplaydata", + EV_TIKIONLY, + NULL, + NULL, + "Causes any subclass of entity to process any specific gameplay related data." + ); + +Event EV_GetVelocity + ( + "getvelocity", + EV_SCRIPTONLY, + "@v", + "NULL", + "Returns the Velocity" + ); + +Event EV_SetVelocity + ( + "setvelocity", + EV_SCRIPTONLY, + "v", + "velocity", + "Sets the Velocity" + ); +Event EV_WatchOffset + ( + "watchoffset", + EV_DEFAULT, + "v", + "offset", + "Sets the entity's watch offset." + ); + +Event EV_StartStasis +( + "startStasis", + EV_DEFAULT, + NULL, + NULL, + "Makes the entity go into stasis mode" +); +Event EV_StopStasis +( + "stopStasis", + EV_DEFAULT, + NULL, + NULL, + "Makes the entity stop its stasis mode" +); + +Event EV_SetTargetPos +( + "settargetposition", + EV_DEFAULT, + "s", + "targetbone", + "Sets the Target Position Bone" +); + +Event EV_AddHealthOverTime + ( + "addHealthOverTime", + EV_DEFAULT, + "ff", + "healthToAdd timeToAdd", + "Specifies how much and howlong to add health." + ); +Event EV_SimplePlayDialog + ( + "simplePlayDialog", + EV_DEFAULT, + "sFF", + "sound_file volume min_dist", + "Plays a dialog without all the special features the actors' have." + ); +Event EV_Warp +( + "warp", + EV_CHEAT, + "v", + "position", + "Warps the entity to the specified position." +); +Event EV_TraceHitsEntity +( + "traceHitsEntity", + EV_SCRIPTONLY, + "sfe", + "tagName length entityToCheck", + "Does a trace to check to see if it hits this entity\n." + "Use this very rarely or a programmer will kill you!" +); + +Event EV_SetCustomShader +( + "setcustomshader", + EV_DEFAULT, + "s", + "shader_name", + "Sets Custom Shader FX on Entity" +); +Event EV_ClearCustomShader +( + "clearcustomshader", + EV_DEFAULT, + "s", + "shader_name", + "Clears Custom Shader FX on Entity" +); +Event EV_SetCustomEmitter +( + "setCustomEmitter", + EV_DEFAULT, + "s", + "emitterName", + "Sets up a custom emitter for this entity." +); +Event EV_ClearCustomEmitter +( + "clearCustomEmitter", + EV_DEFAULT, + "s", + "emitterName", + "Clears the custom emitter for this Entity" +); + +Event EV_IsWithinDistanceOf +( + "iswithindistanceof", + EV_DEFAULT, + "@fef", + "returnvalue targetEntity distance", + "returns 1.0 if this entity is within the specified distance of the target entity return 0.0 if it is not" +); +Event EV_NetworkDetail +( + "networkDetail", + EV_TIKIONLY, + NULL, + NULL, + "Sets this entity as detail that doesn't get sent across the network of set as low bandwidth by the client" +); + +CLASS_DECLARATION( Listener, Entity, NULL ) + { + { &EV_DamageModifier, &Entity::AddDamageModifier }, + { &EV_TikiNote, &Entity::TikiNote }, + { &EV_TikiTodo, &Entity::TikiTodo }, + { &EV_Damage, &Entity::DamageEvent }, + { &EV_DamageType, &Entity::DamageType }, + { &EV_Kill, &Entity::Kill }, + { &EV_FadeNoRemove, &Entity::FadeNoRemove }, + { &EV_FadeOut, &Entity::FadeOut }, + { &EV_FadeIn, &Entity::FadeIn }, + { &EV_Fade, &Entity::Fade }, + { &EV_Hide, &Entity::EventHideModel }, + { &EV_Show, &Entity::EventShowModel }, + { &EV_BecomeSolid, &Entity::BecomeSolid }, + { &EV_BecomeNonSolid, &Entity::BecomeNonSolid }, + { &EV_Ghost, &Entity::Ghost }, + { &EV_TouchTriggers, &Entity::TouchTriggersEvent }, + { &EV_Sound, &Entity::Sound }, + { &EV_StopSound, &Entity::StopSound }, + { &EV_SetHealth, &Entity::SetHealth }, + { &EV_GetHealth, &Entity::GetHealth }, + { &EV_SetMaxHealth, &Entity::SetMaxHealth }, + { &EV_SetSize, &Entity::SetSize }, + { &EV_SetMins, &Entity::SetMins }, + { &EV_SetMaxs, &Entity::SetMaxs }, + { &EV_GetMins, &Entity::GetMins }, + { &EV_GetMaxs, &Entity::GetMaxs }, + { &EV_SetScale, &Entity::SetScale }, + { &EV_SetRandomScale, &Entity::setRandomScale }, + { &EV_SetAlpha, &Entity::SetAlpha }, + { &EV_SetOrigin, &Entity::SetOrigin }, + { &EV_GetOrigin, &Entity::GetOrigin }, + { &EV_SetTargetName, &Entity::SetTargetName }, + { &EV_GetTargetName, &Entity::GetTargetName }, + { &EV_GetRawTargetName, &Entity::GetRawTargetName }, + { &EV_SetTarget, &Entity::SetTarget }, + { &EV_GetTarget, &Entity::getTarget }, + { &EV_GetTargetEntity, &Entity::GetTargetEntity }, + { &EV_SetKillTarget, &Entity::SetKillTarget }, + { &EV_GetModelName, &Entity::GetModelName }, + { &EV_SetAngles, &Entity::SetAngles }, + {&EV_GetAngles, &Entity::GetAngles }, + { &EV_SetAngle, &Entity::SetAngleEvent }, + { &EV_SetMass, &Entity::SetMassEvent }, + { &EV_SetFullTrace, &Entity::SetFullTraceEvent }, + { &EV_RegisterAlias, &Entity::RegisterAlias }, + { &EV_RegisterAliasAndCache, &Entity::RegisterAliasAndCache }, + { &EV_Cache, &Entity::Cache }, + { &EV_AutoCache, &Entity::Cache }, + { &EV_LoopSound, &Entity::LoopSound }, + { &EV_StopLoopSound, &Entity::StopLoopSound }, + { &EV_Model, &Entity::SetModelEvent }, + { &EV_SetLight, &Entity::SetLight }, + { &EV_LightOn, &Entity::LightOn }, + { &EV_LightOff, &Entity::LightOff }, + { &EV_LightRed, &Entity::LightRed }, + { &EV_LightGreen, &Entity::LightGreen }, + { &EV_LightBlue, &Entity::LightBlue }, + { &EV_LightRadius, &Entity::LightRadius }, + { &EV_LightStyle, &Entity::LightStyle }, + { &EV_EntityFlags, &Entity::Flags }, + { &EV_EntityEffects, &Entity::Effects }, + { &EV_EntitySVFlags, &Entity::SVFlags }, + { &EV_EntityRenderEffects, &Entity::RenderEffects }, + { &EV_BroadcastSound, &Entity::BroadcastSound }, + { &EV_SurfaceModelEvent, &Entity::SurfaceModelEvent }, + { &EV_ProcessInitCommands, &Entity::ProcessInitCommandsEvent }, + { &EV_Attach, &Entity::AttachEvent }, + { &EV_AttachModel, &Entity::AttachModelEvent }, + { &EV_RemoveAttachedModel, &Entity::RemoveAttachedModelEvent }, + { &EV_RemoveAttachedModelByTargetname, &Entity::removeAttachedModelByTargetname }, + { &EV_Detach, &Entity::DetachEvent }, + { &EV_IncreaseShotCount, &Entity::IncreaseShotCount }, + { &EV_TakeDamage, &Entity::TakeDamageEvent }, + { &EV_NoDamage, &Entity::NoDamageEvent }, + { &EV_Gravity, &Entity::Gravity }, + { &EV_UseBoundingBox, &Entity::UseBoundingBoxEvent }, + { &EV_Hurt, &Entity::HurtEvent }, + { &EV_IfSkill, &Entity::IfSkillEvent }, + { &EV_Classname, &Entity::ClassnameEvent }, + { &EV_SpawnFlags, &Entity::SpawnFlagsEvent }, + { &EV_SetTeam, &Entity::SetTeamEvent }, + { &EV_Trigger, &Entity::TriggerEvent }, + { &EV_Censor, &Entity::Censor }, + { &EV_Stationary, &Entity::StationaryEvent }, + { &EV_Explosion, &Entity::Explosion }, + { &EV_ShaderEvent, &Entity::Shader }, + { &EV_ScriptShaderEvent, &Entity::Shader }, + { &EV_KillAttach, &Entity::KillAttach }, + { &EV_DropToFloor, &Entity::DropToFloorEvent }, + { &EV_Bind, &Entity::BindEvent }, + { &EV_Unbind, &Entity::EventUnbind }, + { &EV_JoinTeam, &Entity::JoinTeam }, + { &EV_QuitTeam, &Entity::EventQuitTeam }, + { &EV_AddToSoundManager, &Entity::AddToSoundManager }, + { &EV_SetControllerAngles, &Entity::SetControllerAngles }, + { &EV_DeathSinkStart, &Entity::DeathSinkStart }, + { &EV_DeathSink, &Entity::DeathSink }, + { &EV_LookAtMe, &Entity::LookAtMe }, + { &EV_ProjectilesCanStickToMe, &Entity::ProjectilesCanStickToMe }, + + { &EV_DetachAllChildren, &Entity::DetachAllChildren }, + { &EV_Morph, &Entity::MorphEvent }, + { &EV_Unmorph, &Entity::UnmorphEvent }, + { &EV_MorphControl, &Entity::MorphControl }, + { &EV_SetCinematicAnim, &Entity::SetCinematicAnim }, + { &EV_CinematicAnimDone, &Entity::CinematicAnimDone }, + { &EV_SetAnimOnAttachedModel, &Entity::SetAnimOnAttachedModel }, + { &EV_SetEntityExplosionModel, &Entity::SetEntityExplosionModel }, + { &EV_SetObjectProgram, &Entity::SetObjectProgram }, + { &EV_ExecuteObjectProgram, &Entity::ExecuteProgram }, + { &EV_DoRadiusDamage, &Entity::DoRadiusDamage }, + { &EV_SelfDetonate, &Entity::SelfDetonate }, + + { &EV_Anim, &Entity::PassToAnimate }, + { &EV_SetFrame, &Entity::PassToAnimate }, + { &EV_StopAnimating, &Entity::PassToAnimate }, + { &EV_Torso_StopAnimating, &Entity::PassToAnimate }, + { &EV_NewAnim, &Entity::PassToAnimate }, + + { &EV_ProjectileAtk, &Entity::ProjectileAtk }, + { &EV_ProjectileAttackPoint, &Entity::ProjectileAttackPoint }, + { &EV_ProjectileAttackEntity, &Entity::ProjectileAttackEntity }, + { &EV_ProjectileAttackFromTag, &Entity::ProjectileAttackFromTag }, + { &EV_ProjectileAttackFromPoint, &Entity::ProjectileAttackFromPoint }, + + { &EV_TraceAtk, &Entity::TraceAtk }, + { &EV_Contents, &Entity::Contents }, + { &EV_Mask, &Entity::setMask }, + + { &EV_DisplayEffect, &Entity::DisplayEffect }, + + { &EV_ForceAlpha, &Entity::ForceAlpha }, + { &EV_SpawnEffect, &Entity::SpawnEffect }, + + { &EV_CreateEarthquake, &Entity::CreateEarthquake }, + + { &EV_SetFloatVar, &Entity::SetFloatVar }, + { &EV_SetVectorVar, &Entity::SetVectorVar }, + { &EV_SetStringVar, &Entity::SetStringVar }, + + { &EV_DoesVarExist, &Entity::doesVarExist }, + { &EV_RemoveVariable, &Entity::RemoveVariable }, + + { &EV_GetFloatVar, &Entity::GetFloatVar }, + { &EV_GetVectorVar, &Entity::GetVectorVar }, + { &EV_GetStringVar, &Entity::GetStringVar }, + + { &EV_SetUserVar1, &Entity::SetUserVar1 }, + { &EV_SetUserVar2, &Entity::SetUserVar2 }, + { &EV_SetUserVar3, &Entity::SetUserVar3 }, + { &EV_SetUserVar4, &Entity::SetUserVar4 }, + + { &EV_AffectingViewMode, &Entity::affectingViewMode }, + { &EV_SetGroupID, &Entity::SetGroupID }, + + { &EV_Multiplayer, &Entity::MultiplayerEvent }, + { &EV_SetMoveType, &Entity::setMoveType }, + + { &EV_UseDataAnim, &Entity::useDataAnim }, + { &EV_UseDataType, &Entity::useDataType }, + { &EV_UseDataThread, &Entity::useDataThread }, + { &EV_UseData, &Entity::useDataEvent }, + { &EV_UseMaxDist, &Entity::useDataMaxDist }, + { &EV_UseCount, &Entity::useDataCount }, + + { &EV_SetArchetype, &Entity::setArchetype }, + { &EV_SetMissionObjective, &Entity::setMissionObjective }, + { &EV_SetGameplayHealth, &Entity::setGameplayHealth }, + { &EV_GetVelocity, &Entity::GetVelocity }, + { &EV_SetVelocity, &Entity::SetVelocity }, + { &EV_WatchOffset, &Entity::SetWatchOffset }, + + { &EV_StartStasis, &Entity::startStasis }, + { &EV_StopStasis, &Entity::stopStasis }, + + { &EV_SetTargetPos, &Entity::setTargetPos }, + + { &EV_AddHealthOverTime, &Entity::addHealthOverTime }, + { &EV_SimplePlayDialog, &Entity::simplePlayDialog }, + + { &EV_Warp, &Entity::warp }, + { &EV_TraceHitsEntity, &Entity::traceHitsEntity }, + { &EV_SetOriginEveryFrame, &Entity::setOriginEveryFrame }, + + { &EV_SetCustomShader, &Entity::setCustomShader }, + { &EV_ClearCustomShader, &Entity::clearCustomShader }, + + { &EV_SetCustomEmitter, &Entity::setCustomEmitter }, + { &EV_ClearCustomEmitter, &Entity::clearCustomEmitter }, + { &EV_IsWithinDistanceOf, &Entity::isWithinDistanceOf }, + + { &EV_NetworkDetail, &Entity::setNetworkDetail }, + + { NULL, NULL } + }; + +Entity::Entity() + { + Setup(); + } + +Entity::Entity( int create_flag ) + { + Setup(); + + if ( create_flag & ENTITY_CREATE_FLAG_ANIMATE ) + animate = new Animate( this ); + + if ( create_flag & ENTITY_CREATE_FLAG_MOVER ) + mover = new Mover( this ); + } + +void Entity::Setup() + { + // Pluggable modules + + animate = NULL; + mover = NULL; + bind_info = NULL; + morph_info = NULL; + + edict = level.AllocEdict( this ); + client = edict->client; + entnum = edict->s.number; + edict->s.clientNum = ENTITYNUM_NONE ; + + // spawning variables + spawnflags = level.spawnflags; + level.spawnflags = 0; + + // rendering variables + setAlpha( 1.0f ); + setScale( 1.0f ); + + // physics variables + total_delta = Vector(0, 0, 0); + mass = 0; + gravity = 1.0; + groundentity = NULL; + groundcontents = 0; + velocity = vec_zero; + avelocity = vec_zero; + edict->clipmask = MASK_SOLID; + + // bind variables + edict->s.bindparent = ENTITYNUM_NONE; + + // this is an generic entity + edict->s.eType = ET_GENERAL; + + setContents( 0 ); + + edict->s.parent = ENTITYNUM_NONE; + edict->s.pos.trType = TR_LERP; + edict->ownerNum = ENTITYNUM_NONE; + + setOrigin( vec_zero ); + origin.copyTo( edict->s.origin2 ); + + setAngles( vec_zero ); + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_NOT ); + + // Character state + health = 0; + max_health = 0; + deadflag = DEAD_NO; + flags = 0; + + // underwater variables + watertype = 0; + waterlevel = 0; + + // Pain and damage variables + takedamage = DAMAGE_NO; + damage_type = -1; + + // Surface variables + numsurfaces = 0; + + // Light variables + lightRadius = 0; + + look_at_me = false; + projectilesCanStickToMe = true; + + explosionModel = ""; + + _affectingViewModes = 0; + + edict->s.infoIcon = 0; + + addAffectingViewModes( gi.GetViewModeClassMask( "entity" ) ); + _groupID = 0; + + damageModSystem = 0; + + _fulltrace = false ; + useData = 0; + setTargetPos( "" ); + + ObjectProgram = NULL; + + _missionObjective = false; + + _networkDetail = false; + } + +Entity::~Entity() +{ + Container bindlist; + Entity *ent; + int num; + int i; + + if ( bind_info ) + { + // unbind any entities that are bound to me + // can't unbind within this loop, so make an array + // and unbind them outside of it. + num = 0; + + for( ent = bind_info->teamchain; ent; ent = ent->bind_info->teamchain ) + { + if ( ent->bind_info->bindmaster == this ) + { + bindlist.AddObject( ent ); + } + } + + num = bindlist.NumObjects(); + for( i = 1; i <= num; i++ ) + { + bindlist.ObjectAt( i )->unbind(); + } + + bindlist.FreeObjectList(); + + unbind(); + quitTeam(); + + detach(); + + // + // go through and set our children + // + num = bind_info->numchildren; + for( i = 0; ( i < MAX_MODEL_CHILDREN ) && num; i++ ) + { + if ( bind_info->children[ i ] == ENTITYNUM_NONE ) + { + continue; + } + ent = G_GetEntity( bind_info->children[ i ] ); + if ( ent ) + { + ent->PostEvent( EV_Remove, 0.0f ); + } + num--; + } + } + + if ( targetname.length() && world ) + { + world->RemoveTargetEntity( targetname, this ); + } + + level.FreeEdict( edict ); + + // Pluggable modules + + if ( animate ) + delete animate; + + if ( mover ) + delete mover; + + if ( bind_info ) + delete bind_info; + + if ( morph_info ) + delete morph_info; + + entityVars.ClearList(); + + if ( damageModSystem ) + delete damageModSystem; + + if ( useData ) + { + delete useData; + useData = NULL; + } + + // Note, the ObjectProgram deletion is handled elsewhere + + // Make sure to remove our selves from the extra list if we are a mission objective + + if ( edict->s.missionObjective ) + { + G_RemoveEntityFromExtraList(entnum); + } +} + +Entity * Entity::FindEntityByName( const str &entityName ) +{ + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + if ( g_entities[i].inuse && g_entities[i].entity && ( entityName == g_entities[ i ].entname ) ) + { + return g_entities[i].entity; + } + } + + return NULL; +} + +void Entity::SetEntNum + ( + int num + ) + + { + if ( edict ) + { + level.FreeEdict( edict ); + } + + level.spawn_entnum = num; + level.AllocEdict( this ); + client = edict->client; + entnum = edict->s.number; + } + +void Entity::ClassnameEvent + ( + Event *ev + ) + + { + strncpy( edict->entname, ev->GetString( 1 ), sizeof( edict->entname ) - 1 ); + } + +void Entity::SpawnFlagsEvent + ( + Event *ev + ) + + { + // spawning variables + spawnflags = ev->GetInteger( 1 ); + if ( spawnflags & SPAWNFLAG_DETAIL ) + { + edict->s.renderfx |= RF_DETAIL; + } + } + +void Entity::SetTarget + ( + const char *text + ) + + { + if ( text ) + { + target = text; + } + else + { + target = ""; + } + } + +void Entity::SetTargetName + ( + const char *text + ) + + { + if ( targetname.length() && world ) + { + world->RemoveTargetEntity( targetname, this ); + } + + if ( text ) + { + if ( text[ 0 ] == '$' ) + text++; + targetname = text; + } + else + { + targetname = ""; + } + + if ( targetname.length() && world ) + { + // + // make sure we don't re-targetname the world entity + // + if ( + ( this != world ) || + ( targetname == str( "world" ) ) + ) + { + world->AddTargetEntity( targetname, this ); + } + else + { + error( "SetTargetName", "World was re-targeted with targetname %s\n", targetname.c_str() ); + + // this is bad + //assert( 0 ); + //gi.WDPrintf( "world was re-targeted with targetname %s\n", targetname.c_str() ); + //targetname = "world"; + } + + } + } + +void Entity::SetKillTarget + ( + const char *text + ) + + { + if ( text ) + { + killtarget = text; + } + else + { + killtarget = ""; + } + } + +void Entity::setModel( const char *mdl ) +{ + str temp; + + if ( LoadingSavegame && ( this == world ) ) + { + // don't set model on the world + return; + } + + if ( !mdl ) + { + mdl = ""; + } + + // Prepend 'models/' to make things easier + temp = ""; + if ( ( strlen( mdl ) > 0 ) && !strchr( mdl, '*' ) && strnicmp( "models/", mdl, 7 ) && !strstr( mdl, ".spr" ) ) + { + temp = "models/"; + } + temp += mdl; + + // we use a temp string so that if model was passed into here, we don't + // accidentally free up the string that we're using in the process. + model = temp; + + gi.setmodel( edict, model.c_str() ); + + if ( gi.IsModel( edict->s.modelindex ) ) + { + Event *ev; + + numsurfaces = gi.NumSurfaces( edict->s.modelindex ); + + if ( !LoadingSavegame ) + { + CancelEventsOfType( EV_ProcessInitCommands ); + + ev = new Event( EV_ProcessInitCommands ); + ev->AddInteger( edict->s.modelindex ); + PostEvent( ev, EV_PROCESS_INIT ); + } + else + { + ProcessInitCommands( edict->s.modelindex, true ); + } + } + else if ( strstr( mdl, ".spr" ) ) + { + edict->s.eType = ET_SPRITE; + } + + // Sanity check to see if we're expecting a B-Model + assert( !( ( edict->solid == SOLID_BSP ) && !edict->s.modelindex ) ); + if ( ( edict->solid == SOLID_BSP ) && !edict->s.modelindex ) + { + const char *name; + + name = getClassID(); + if ( !name ) + { + name = getClassname(); + } + gi.WDPrintf( "%s with SOLID_BSP and no model - '%s'(%d)\n", name, targetname.c_str(), entnum ); + + // Make it non-solid so that the collision code doesn't kick us out. + setSolidType( SOLID_NOT ); + } + + mins = edict->mins; + maxs = edict->maxs; + size = maxs - mins; + edict->radius = size.length() * 0.5f; + edict->radius2 = edict->radius * edict->radius; + + // + // see if we have a mins and maxs set for this model + // + + if ( gi.IsModel( edict->s.modelindex ) && !mins.length() && !maxs.length() ) + { + vec3_t tempmins, tempmaxs; + int animNum; + + animNum = gi.Anim_NumForName( edict->s.modelindex, "idle" ); + + if ( animNum >= 0 ) + { + gi.Frame_Bounds( edict->s.modelindex, animNum, 0, edict->s.scale, tempmins, tempmaxs ); + setSize( tempmins, tempmaxs ); + } + + //vec3_t tempmins, tempmaxs; + //gi.CalculateBounds( edict->s.modelindex, edict->s.scale, tempmins, tempmaxs ); + //setSize( tempmins, tempmaxs ); + } + + if ( this->isSubclassOf(Player) ) + { + //If we're a player, we need to reset the statemachine + Player* player = (Player*)this; + + if ( player ) + { + player->SetAnim( "stand_idle", legs, true ); + player->SetAnim( "stand_idle", torso, true ); + player->LoadStateTable(); + } + } +} + +// Added to set the weapon view model +void Entity::setViewModel + ( + const char *mdl + ) + + { + str temp; + + if ( LoadingSavegame && ( this == world ) ) + { + // don't set model on the world + return; + } + + if ( !mdl ) + { + mdl = ""; + } + + // Prepend 'models/' to make things easier + temp = ""; + if ( ( strlen( mdl ) > 0 ) && !strchr( mdl, '*' ) && strnicmp( "models/", mdl, 7 ) && !strstr( mdl, ".spr" ) ) + { + temp = "models/"; + } + temp += mdl; + + // we use a temp string so that if model was passed into here, we don't + // accidentally free up the string that we're using in the process. + model = temp; + + gi.setviewmodel( edict, model.c_str() ); + + if ( gi.IsModel( edict->s.viewmodelindex ) ) + { + Event *ev; + + numsurfaces = gi.NumSurfaces( edict->s.viewmodelindex ); + + if ( !LoadingSavegame ) + { + CancelEventsOfType( EV_ProcessInitCommands ); + + ev = new Event( EV_ProcessInitCommands ); + ev->AddInteger( edict->s.viewmodelindex ); + PostEvent( ev, EV_PROCESS_INIT ); + } + else + { + ProcessInitCommands( edict->s.viewmodelindex, true ); + } + } + else if ( strstr( mdl, ".spr" ) ) + { + edict->s.eType = ET_SPRITE; + } + + // Sanity check to see if we're expecting a B-Model + assert( !( ( edict->solid == SOLID_BSP ) && !edict->s.viewmodelindex ) ); + if ( ( edict->solid == SOLID_BSP ) && !edict->s.viewmodelindex ) + { + const char *name; + + name = getClassID(); + if ( !name ) + { + name = getClassname(); + } + gi.WDPrintf( "%s with SOLID_BSP and no model - '%s'(%d)\n", name, targetname.c_str(), entnum ); + + // Make it non-solid so that the collision code doesn't kick us out. + setSolidType( SOLID_NOT ); + } + + mins = edict->mins; + maxs = edict->maxs; + size = maxs - mins; + edict->radius = size.length() * 0.5f; + edict->radius2 = edict->radius * edict->radius; + + // + // see if we have a mins and maxs set for this model + // + //FIXME + //We only did this on startup, but with the spawnargs as events it would have to + //be here. Do we still need this? It may cause strange effects. + /* if ( gi.IsModel( edict->s.viewmodelindex ) && !mins.length() && !maxs.length() ) + { + vec3_t tempmins, tempmaxs; + gi.CalculateBounds( edict->s.viewmodelindex, edict->s.scale, tempmins, tempmaxs ); + setSize( tempmins, tempmaxs ); + } */ + } + +void Entity::ProcessInitCommands + ( + int index, + qboolean cache + ) + + { + tiki_cmd_t cmds; + + if ( LoadingSavegame && !cache ) + { + // Don't process init commands when loading a savegame since + // it will cause items to be added to inventories unnecessarily. + // All variables affected by the init commands will be set + // by the unarchive functions. + // + // we do want to process the cache commands though regardless + return; + } + + if ( gi.InitCommands( index, &cmds ) ) + { + int i, j, savedindex; + Event *event; + + // because the model has not necessarily been spawned yet, we need to set + // this entity to have this index so that precaches go where they are supposed + // to, this should have no bad effects, since we are only doing it in the + // cache phase of spawning + if ( index != edict->s.modelindex ) + { + savedindex = edict->s.modelindex; + edict->s.modelindex = index; + } + else + { + savedindex = -1; + } + for( i = 0; i < cmds.num_cmds; i++ ) + { + event = new Event( cmds.cmds[ i ].args[ 0 ] ); + if ( !cache || ( event->GetFlags() & EV_CACHE ) ) + { + for( j = 1; j < cmds.cmds[ i ].num_args; j++ ) + { + event->AddToken( cmds.cmds[ i ].args[ j ] ); + } + ProcessEvent( event ); + } + else + { + delete event; + } + } + // restore the modelindex, see above + if ( savedindex != -1 ) + { + edict->s.modelindex = savedindex; + } + } + } + +void Entity::ProcessInitCommandsEvent + ( + Event *ev + ) + + { + int index; + + index = ev->GetInteger( 1 ); + ProcessInitCommands( index, false ); + } + +void Entity::EventHideModel + ( + Event *ev + ) + + { + hideModel(); + } + +void Entity::EventShowModel + ( + Event *ev + ) + + { + showModel(); + } + +void Entity::SetTeamEvent + ( + Event *ev + ) + + { + if ( !bind_info ) + bind_info = CreateBindInfo(); + + bind_info->moveteam = ev->GetString( 1 ); + } + +void Entity::TriggerEvent + ( + Event *ev + ) + + { + const char *name; + Event *event; + Entity *ent; + TargetList *tlist; + int i; + int num; + + name = ev->GetString( 1 ); + + if ( !name ) + return; + + // Check for object commands + if ( name[ 0 ] == '$' ) + { + tlist = world->GetTargetList( str( name + 1 ) ); + num = tlist->list.NumObjects(); + for ( i = 1; i <= num; i++ ) + { + ent = tlist->list.ObjectAt( i ); + + assert( ent ); + + event = new Event( EV_Activate ); + event->SetSource( ev->GetSource() ); + event->SetThread( ev->GetThread() ); + event->SetLineNumber( ev->GetLineNumber() ); + event->AddEntity( this ); + ent->ProcessEvent( event ); + } + } + else if ( name[ 0 ] == '*' ) // Check for entnum commands + { + if ( !IsNumeric( &name[ 1 ] ) ) + { + ev->Error( "Expecting numeric value for * command, but found '%s'\n", &name[ 1 ] ); + } + else + { + ent = G_GetEntity( atoi( &name[ 1 ] ) ); + if ( ent ) + { + event = new Event( EV_Activate ); + event->SetSource( ev->GetSource() ); + event->SetThread( ev->GetThread() ); + event->SetLineNumber( ev->GetLineNumber() ); + event->AddEntity( this ); + ent->ProcessEvent( event ); + } + else + { + ev->Error( "Entity not found for * command\n" ); + } + } + return; + } + else + { + ev->Error( "Invalid entity reference '%s'.\n", name ); + } + } + +void Entity::setAlpha + ( + float alpha + ) + + { + if ( alpha > 1.0f ) + { + alpha = 1.0f; + } + if ( alpha < 0.0f ) + { + alpha = 0.0f; + } + edict->s.alpha = alpha; + } + +void Entity::setScale + ( + float scale + ) + + { + edict->s.scale = scale; + } + +void Entity::setSolidType + ( + solid_t type + ) + + { + if ( + ( !LoadingSavegame ) && + ( type == SOLID_BSP ) && + ( this != world ) && + ( + !model.length() || + ( + ( model[ 0 ] != '*' ) && + ( !strstr( model.c_str(), ".bsp" ) ) + ) + ) + ) + { + error( "setSolidType", "SOLID_BSP entity at x%.2f y%.2f z%.2f with no BSP model", origin[ 0 ], origin[ 1 ], origin[ 2 ] ); + } + edict->solid = type; + + // + // set the appropriate contents type + if ( edict->solid == SOLID_BBOX ) + { + if ( !getContents() ) + setContents( CONTENTS_SOLID ); + } + else if ( edict->solid == SOLID_NOT ) + { + if ( getContents() == CONTENTS_SOLID ) + setContents( 0 ); + } + else if ( edict->solid == SOLID_BSP ) + { + if ( !getContents() ) + setContents( CONTENTS_SOLID ); + } + + link(); + + edict->svflags &= ~SVF_NOCLIENT; + if ( hidden() ) + { + edict->svflags |= SVF_NOCLIENT; + } + } + +void Entity::setSize + ( + Vector min, + Vector max + ) + + { + Vector delta; + + if ( flags & FL_ROTATEDBOUNDS ) + { + vec3_t tempmins, tempmaxs; + + // + // rotate the mins and maxs for the model + // + min.copyTo( tempmins ); + max.copyTo( tempmaxs ); + + CalculateRotatedBounds2( edict->s.mat, tempmins, tempmaxs ); + + mins = Vector( tempmins ); + maxs = Vector( tempmaxs ); + size = max - min; + + mins.copyTo( edict->mins ); + maxs.copyTo( edict->maxs ); + edict->radius = size.length() * 0.5; + edict->radius2 = edict->radius * edict->radius; + } + else + { + if ( ( min == edict->mins ) && ( max == edict->maxs ) ) + { + return; + } + + mins = min; + maxs = max; + size = max - min; + + mins.copyTo( edict->mins ); + maxs.copyTo( edict->maxs ); + + // + // get the full mins and maxs for this model + // + /* if ( gi.IsModel( edict->s.modelindex ) ) + { + vec3_t fullmins, fullmaxs; + Vector delta; + + gi.CalculateBounds( edict->s.modelindex, edict->s.scale, fullmins, fullmaxs ); + + delta = Vector( fullmaxs ) - Vector( fullmins ); + edict->radius = delta.length() * 0.5f; + edict->radius2 = edict->radius * edict->radius; + } + else */ + { + edict->radius = size.length() * 0.5; + edict->radius2 = edict->radius * edict->radius; + } + } + + link(); + } + +Vector Entity::getLocalVector + ( + const Vector &vec + ) + + { + Vector pos; + + pos[ 0 ] = vec * orientation[ 0 ]; + pos[ 1 ] = vec * orientation[ 1 ]; + pos[ 2 ] = vec * orientation[ 2 ]; + + return pos; + } + +void Entity::link + ( + void + ) + + { + if ( !level._cleanup ) + gi.linkentity( edict ); + + absmin = edict->absmin; + absmax = edict->absmax; + centroid = ( absmin + absmax ) * 0.5f; + centroid.copyTo( edict->centroid ); + + // If this has a parent, then set the areanum the same + // as the parent's + if ( edict->s.parent != ENTITYNUM_NONE ) + { + edict->areanum = g_entities[ edict->s.parent ].areanum; + } + } + +void Entity::addOrigin + ( + const Vector &add + ) + + { + setOrigin( GetLocalOrigin() + add ); + } + +void Entity::setOrigin + ( + void + ) + + { + setOrigin( GetLocalOrigin() ); + } + +void Entity::setOrigin + ( + const Vector &org + ) + + { + Entity * ent; + int i,num; + + if ( bind_info && bind_info->bindmaster ) + { + SetLocalOrigin( org ); + + if ( bind_info->bind_use_my_angles ) + MatrixTransformVector( GetLocalOrigin(), orientation, origin ); + else + MatrixTransformVector( GetLocalOrigin(), bind_info->bindmaster->orientation, origin ); + + origin += bind_info->bindmaster->origin; + origin.copyTo( edict->s.netorigin ); + } + // If entity has a parent, then set the origin as the + // centroid of the parent, and set edict->s.netorigin + // as the local origin of the entity which will be used + // to position this entity on the client. + else if ( edict->s.parent != ENTITYNUM_NONE ) + { + orientation_t orient; + + VectorClear( edict->s.netorigin ); + ent = ( Entity * )G_GetEntity( edict->s.parent ); + + //ent->GetTag(( edict->s.tag_num & TAG_MASK, &origin ); + ent->GetTag( edict->s.tag_num & TAG_MASK, &orient ); + + MatrixTransformVector( edict->s.attach_offset, orient.axis, origin ); + + //origin += edict->s.attach_offset; + origin += orient.origin; + + SetLocalOrigin( vec_zero ); + } + else + { + origin = org; + SetLocalOrigin( org ); + origin.copyTo( edict->s.netorigin ); + } + + origin.copyTo( edict->s.origin ); + origin.copyTo( edict->currentOrigin ); + + link(); + +#if 0 + if ( this->isClient() ) + { + i = CurrentAnim(); + j = CurrentFrame(); + + G_DrawCoordSystem( origin, orientation[0], orientation[1], orientation[2], 30 ); + gi.Printf( "%s:legs anim:%s frame %i\n", this->getClassname(), gi.Anim_NameForNum( edict->s.modelindex, i ), j ); + } +#endif + + if ( bind_info ) + { + // Go through and set our children + + num = bind_info->numchildren; + for( i = 0; ( i < MAX_MODEL_CHILDREN ) && num; i++ ) + { + if ( bind_info->children[ i ] == ENTITYNUM_NONE ) + { + continue; + } + ent = ( Entity * )G_GetEntity( bind_info->children[ i ] ); + if ( ent ) + { + ent->setOrigin(); + } + num--; + } + + + /* for( ent = bind_info->teamchain; ent != NULL; ent = ent->bind_info->teamchain ) + { + if ( ent->bind_info->teammaster == this ) + ent->setOrigin(); + } */ + } + } + +void Entity::GetRawTag + ( + int tagnum, + orientation_t * orient, + bodypart_t part + ) + + { + int anim; + int frame; + + anim = CurrentAnim(part); + frame = CurrentFrame(part); + + //If we don't have a valid animation, we can't get a tag + if ( anim < 0 ) return; + + *orient = gi.Tag_OrientationEx( edict->s.modelindex, CurrentAnim( legs ), CurrentFrame( legs ), tagnum & TAG_MASK, + edict->s.scale, edict->s.bone_tag, edict->s.bone_quat, 0, 0, 1.0f, ( edict->s.anim & ANIM_BLEND ) != 0, + ( edict->s.torso_anim & ANIM_BLEND ) != 0, CurrentAnim( torso ), CurrentFrame( torso ), 0, 0, 1.0f ); + } + +qboolean Entity::GetRawTag + ( + const char *name, + orientation_t * orient, + bodypart_t part + ) + { + int tagnum; + + tagnum = gi.Tag_NumForName( edict->s.modelindex, name ); + + if ( tagnum < 0 ) + return false; + + GetRawTag( tagnum, orient, part ); + return true; + } + +void Entity::GetTag + ( + int tagnum, + orientation_t * orient + ) + + { + orientation_t orn; + int i; + + GetRawTag( tagnum, &orn ); + + VectorCopy( origin, orient->origin ); + + for ( i = 0 ; i < 3 ; i++ ) + { + VectorMA( orient->origin, orn.origin[i], orientation[i], orient->origin ); + } + MatrixMultiply( orn.axis, orientation, orient->axis ); + } + +qboolean Entity::GetTag + ( + const char *name, + orientation_t * orient + ) + { + int tagnum; + + tagnum = gi.Tag_NumForName( edict->s.modelindex, name ); + + if ( tagnum < 0 ) + return false; + + GetTag( tagnum, orient ); + return true; + } + +void Entity::GetTag + ( + int tagnum, + Vector *pos, + Vector *forward, + Vector *left, + Vector *up + ) + + { + orientation_t orn; + + GetTag( tagnum, &orn); + + if ( pos ) + { + *pos = Vector( orn.origin ); + } + if ( forward ) + { + *forward = Vector( orn.axis[ 0 ] ); + } + if ( left ) + { + *left = Vector( orn.axis[ 1 ] ); + } + if ( up ) + { + *up = Vector( orn.axis[ 2 ] ); + } + } + +qboolean Entity::GetTag + ( + const char *name, + Vector *pos, + Vector *forward, + Vector *left, + Vector *up + ) + + { + int tagnum; + + tagnum = gi.Tag_NumForName( edict->s.modelindex, name ); + + if ( tagnum < 0 ) + return false; + + GetTag( tagnum, pos, forward, left, up ); + return true; + } + +void Entity::addAngles + ( + const Vector &add + ) + + { + if ( bind_info && bind_info->bindmaster ) + { + setAngles( localangles + add ); + } + else + { + setAngles( angles + add ); + } + } + +void Entity::setAngles + ( + void + ) + + { + if ( bind_info && bind_info->bindmaster ) + { + setAngles( localangles ); + } + else + { + setAngles( angles ); + } + } + + +void Entity::setAngles + ( + const Vector &ang + ) + + { + Entity * ent; + + int num,i; + + angles[ 0 ] = AngleMod( ang[ 0 ] ); + angles[ 1 ] = AngleMod( ang[ 1 ] ); + angles[ 2 ] = AngleMod( ang[ 2 ] ); + + localangles = angles; + if ( bind_info && bind_info->bindmaster ) + { + float mat[3][3]; + AnglesToAxis( localangles, mat ); + R_ConcatRotations( mat, bind_info->bindmaster->orientation, orientation ); + MatrixToEulerAngles( orientation, angles ); + } + else if ( edict->s.parent != ENTITYNUM_NONE ) + { + Entity *parent; + Vector tagPos; + Vector tagForward; + Vector forwardAngles; + + vec3_t tempAxis[3]; + vec3_t tempAxis2[3]; + vec3_t finalAxis[3]; + vec3_t tempForward; + vec3_t finalForward; + + + parent = ( Entity * )G_GetEntity( edict->s.parent ); + parent->GetTag( edict->s.tag_num , &tagPos , &tagForward ); + + + forwardAngles = tagForward.toAngles(); + forwardAngles.copyTo( tempForward ); + + AnglesToAxis( tempForward, tempAxis ); + AnglesToAxis( edict->s.attach_angles_offset , tempAxis2 ); + MatrixMultiply( tempAxis2, tempAxis, finalAxis ); + AxisToAngles( finalAxis, finalForward ); + angles = finalForward; + + AnglesToAxis( angles, orientation ); + + } + else + { + AnglesToAxis( angles, orientation ); + } + + angles.copyTo( edict->s.netangles ); + angles.copyTo( edict->s.angles ); + angles.copyTo( edict->currentAngles ); + // Fill the edicts matrix + VectorCopy( orientation[ 0 ], edict->s.mat[ 0 ] ); + VectorCopy( orientation[ 1 ], edict->s.mat[ 1 ] ); + VectorCopy( orientation[ 2 ], edict->s.mat[ 2 ] ); + + if (this->isSubclassOf( Player ) ) + { + Player *player = (Player*)this ; + player->GetVAngles().copyTo( edict->s.viewangles ); + } + else + { + edict->s.viewangles[0] = 0.0f ; + edict->s.viewangles[1] = 0.0f ; + edict->s.viewangles[2] = 0.0f ; + } + + if ( bind_info ) + { + // Go through and set our children + + num = bind_info->numchildren; + + for( i = 0 ; (i < MAX_MODEL_CHILDREN) && num ; i++ ) + { + if ( bind_info->children[i] == ENTITYNUM_NONE ) + continue; + ent = ( Entity * )G_GetEntity( bind_info->children[i] ); + if ( ent ) + { + ent->setAngles(); + VectorClear( ent->edict->s.netangles ); + } + num--; + } + + /* for( ent = bind_info->teamchain; ent != NULL; ent = ent->bind_info->teamchain ) + { + if ( ent->bind_info->teammaster == this ) + ent->setAngles(); + } */ + } + } + +qboolean Entity::droptofloor + ( + float maxfall + ) + + { + trace_t trace; + Vector end; + Vector start; + + //start = origin + Vector( "0 0 1" ); + start = origin; + end = origin; + end[ 2 ]-= maxfall; + + trace = G_Trace( start, mins, maxs, end, this, edict->clipmask, false, "Entity::droptofloor" ); + if ( ( trace.fraction == 1.0f ) || trace.startsolid || trace.allsolid || !trace.ent ) + { + groundentity = world->edict; + return false; + } + + setOrigin( trace.endpos ); + + groundentity = trace.ent; + + return true; + } + +void Entity::DamageType + ( + Event *ev + ) + + { + str damage; + damage = ev->GetString( 1 ); + if ( damage == "all" ) + { + damage_type = -1; + } + else + { + damage_type = MOD_NameToNum( damage ); + } + } + +void Entity::Damage + ( + Entity *inflictor, + Entity *attacker, + float damage, + const Vector &position, + const Vector &direction, + const Vector &normal, + int knockback, + int dflags, + int meansofdeath, + int surface_number, + int bone_number, + Entity *weapon + ) + + { + Event *ev; + + // if our damage types do not match, return + if ( !MOD_matches( meansofdeath, damage_type ) ) + { + return; + } + + if ( !attacker ) + { + attacker = world; + } + if ( !inflictor ) + { + inflictor = world; + } + + ev = new Event( EV_Damage ); + ev->AddFloat( damage ); + ev->AddEntity ( inflictor ); + ev->AddEntity ( attacker ); + ev->AddVector ( position ); + ev->AddVector ( direction ); + ev->AddVector ( normal ); + ev->AddInteger( knockback ); + ev->AddInteger( dflags ); + ev->AddInteger( meansofdeath ); + ev->AddInteger( surface_number ); + ev->AddInteger( bone_number ); + ev->AddEntity( weapon ); + ProcessEvent ( ev ); + } + +void Entity::DamageEvent + ( + Event *ev + ) + + { + Entity *inflictor; + Entity *attacker; + float damage; + Vector dir; + Vector momentum; + Event *event; + float m; + + if ( ( takedamage == DAMAGE_NO ) || ( movetype == MOVETYPE_NOCLIP ) ) + { + return; + } + + damage = ev->GetFloat( 1 ); + inflictor = ev->GetEntity( 2 ); + attacker = ev->GetEntity( 3 ); + + if ( !inflictor || !attacker ) + return; + + // figure momentum add + if ( ( inflictor != world ) && + ( movetype != MOVETYPE_NONE ) && + ( movetype != MOVETYPE_STATIONARY ) && + ( movetype != MOVETYPE_BOUNCE ) && + ( movetype != MOVETYPE_PUSH ) && + ( movetype != MOVETYPE_STOP ) ) + { + dir = origin - ( inflictor->origin + ( inflictor->mins + inflictor->maxs ) * 0.5f ); + dir.normalize(); + + if ( mass < 50 ) + { + m = 50; + } + else + { + m = mass; + } + + momentum = dir * damage * ( 1700.0f / (float)m ); + velocity += momentum; + } + + // check for godmode or invincibility + if ( flags & FL_GODMODE ) + { + return; + } + + // team play damage avoidance + //if ( ( global->teamplay == 1 ) && ( edict->team > 0 ) && ( edict->team == attacker->edict->team ) ) + // { + // return; + // } + + if ( !multiplayerManager.inMultiplayer() && isSubclassOf( Player ) ) + { + damage *= 0.15f; + } + + if ( deadflag ) + { + // Check for gib. + if ( inflictor->isSubclassOf( Projectile ) ) + { + Event *gibEv; + + health -= damage; + + gibEv = new Event( EV_Gib ); + gibEv->AddEntity( this ); + gibEv->AddFloat( health ); + ProcessEvent( gibEv ); + } + return; + } + + // do the damage + health -= damage; + if ( health <= 0.0f ) + { + if ( attacker ) + { + event = new Event( EV_GotKill ); + event->AddEntity( this ); + event->AddFloat( damage ); + event->AddEntity( inflictor ); + event->AddInteger( ev->GetInteger( 9 ) ); + event->AddInteger( 0 ); + attacker->ProcessEvent( event ); + } + + event = new Event( EV_Killed ); + event->AddEntity( attacker ); + event->AddFloat( damage ); + event->AddEntity( inflictor ); + ProcessEvent( event ); + return; + } + + event = new Event( EV_Pain ); + event->AddFloat( damage ); + event->AddEntity( attacker ); + ProcessEvent( event ); + } + +void Entity::Stun + ( + float time + ) + + { + Event *ev = new Event( EV_Stun ); + ev->AddFloat( time ); + ProcessEvent( ev ); + } + +/* +============ +CanDamage + +Returns true if the inflictor can directly damage the target. Used for +explosions and melee attacks. +============ +*/ +qboolean Entity::CanDamage + ( + const Entity *target, + const Entity *skip_ent + ) + + { + trace_t trace; + Vector pos; + const Entity *skip_entity; + //int maskToUse = MASK_SHOT; + int maskToUse = CONTENTS_SOLID; + + if ( skip_ent ) + skip_entity = skip_ent; + else + skip_entity = this; + + trace = G_Trace( origin, vec_origin, vec_origin, target->centroid, skip_entity, maskToUse, false, "Entity::CanDamage 1" ); + if ( ( trace.fraction == 1.0f ) || ( trace.ent == target->edict ) ) + { + return true; + } + pos = target->centroid + Vector( 15.0f, 15.0f, 0.0f ); + trace = G_Trace( origin, vec_origin, vec_origin, pos, skip_entity, maskToUse, false, "Entity::CanDamage 3" ); + if ( ( trace.fraction == 1.0f ) || ( trace.ent == target->edict ) ) + { + return true; + } + pos = target->centroid + Vector( -15.0f, 15.0f, 0.0f ); + trace = G_Trace( origin, vec_zero, vec_zero, pos, skip_entity, maskToUse, false, "Entity::CanDamage 4" ); + if ( ( trace.fraction == 1.0f ) || ( trace.ent == target->edict ) ) + { + return true; + } + pos = target->centroid + Vector( 15.0f, -15.0f, 0.0f ); + trace = G_Trace( origin, vec_zero, vec_zero, pos, skip_entity, maskToUse, false, "Entity::CanDamage 5" ); + if ( ( trace.fraction == 1.0f ) || ( trace.ent == target->edict ) ) + { + return true; + } + pos = target->centroid + Vector( -15.0f, -15.0f, 0.0f ); + trace = G_Trace( origin, vec_zero, vec_zero, pos, skip_entity, maskToUse, false, "Entity::CanDamage 6" ); + if ( ( trace.fraction == 1.0f ) || ( trace.ent == target->edict ) ) + { + return true; + } + + return false; + } + +qboolean Entity::IsTouching + ( + const Entity *e1 + ) + + { + if ( e1->absmin.x > absmax.x ) + { + return false; + } + if ( e1->absmin.y > absmax.y ) + { + return false; + } + if ( e1->absmin.z > absmax.z ) + { + return false; + } + if ( e1->absmax.x < absmin.x ) + { + return false; + } + if ( e1->absmax.y < absmin.y ) + { + return false; + } + if ( e1->absmax.z < absmin.z ) + { + return false; + } + + return true; + } + +void Entity::FadeNoRemove + ( + Event *ev + ) + + { + float rate; + float target; + float myalpha; + + if ( ev->NumArgs() > 1 ) + { + target = ev->GetFloat( 2 ); + } + else + { + target = 0; + } + + if ( ev->NumArgs() > 0 ) + { + rate = ev->GetFloat( 1 ); + assert( rate ); + if ( rate > 0.0f ) + rate = FRAMETIME / rate; + } + else + { + rate = 0.03f; + } + + myalpha = edict->s.alpha; + myalpha -= rate; + + if ( myalpha < target ) + myalpha = target; + + setAlpha( myalpha ); + + if ( myalpha > target ) + { + PostEvent( *ev, FRAMETIME ); + } + + G_SetConstantLight( &edict->s.constantLight, &myalpha, &myalpha, &myalpha, 0 ); + } + +void Entity::FadeOut + ( + Event *ev + ) + + { + float myscale; + float myalpha; + + myscale = edict->s.scale; + myscale -= 0.03f; + myalpha = edict->s.alpha; + myalpha -= 0.03f; + if ( myscale < 0.0f ) + myscale = 0.0f; + if ( myalpha < 0.0f ) + myalpha = 0.0f; + + if ( ( myscale <= 0.0f ) && ( myalpha <= 0.0f ) ) + { + PostEvent( EV_Remove, 0.0f ); + } + else + { + PostEvent( *ev, FRAMETIME ); + } + + setScale( myscale ); + setAlpha( myalpha ); + } + +void Entity::FadeIn + ( + Event *ev + ) + + { + float rate; + float target; + float myalpha; + + if ( ev->NumArgs() > 1 ) + { + target = ev->GetFloat( 2 ); + } + else + { + target = 1; + } + + if ( ev->NumArgs() > 0 ) + { + rate = ev->GetFloat( 1 ); + assert( rate ); + if ( rate > 0.0f ) + rate = FRAMETIME / rate; + } + else + { + rate = 0.03f; + } + + myalpha = edict->s.alpha; + myalpha += rate; + + if ( myalpha > target ) + myalpha = target; + + if ( myalpha < target ) + { + PostEvent( *ev, FRAMETIME ); + } + setAlpha( myalpha ); + } + +void Entity::Fade + ( + Event *ev + ) + + { + float rate; + float target; + float myalpha; + + if ( ev->NumArgs() > 1 ) + { + target = ev->GetFloat( 2 ); + } + else + { + target = 0; + } + + if ( ev->NumArgs() > 0 ) + { + rate = ev->GetFloat( 1 ); + assert( rate ); + if ( rate > 0.0f ) + rate = FRAMETIME / rate; + } + else + { + rate = 0.03f; + } + + myalpha = edict->s.alpha; + myalpha -= rate; + + if ( myalpha <= 0.0f ) + { + PostEvent( EV_Remove, 0.0f ); + return; + } + + if ( myalpha < target ) + myalpha = target; + + if ( myalpha > target ) + { + PostEvent( *ev, FRAMETIME ); + } + + setAlpha( myalpha ); + G_SetConstantLight( &edict->s.constantLight, &myalpha, &myalpha, &myalpha, 0 ); + } + +void Entity::SetMassEvent + ( + Event *ev + ) + + { + mass = ev->GetInteger( 1 ); + } + +void Entity::CheckGround + ( + void + ) + + { + Vector point; + trace_t trace; + + if ( flags & ( FL_SWIM | FL_FLY ) ) + { + return; + } + + if ( velocity.z > 100.0f ) + { + groundentity = NULL; + return; + } + + // if the hull point one-quarter unit down is solid the entity is on ground + point = origin; + point.z -= sv_groundtracelength->value; + + trace = G_Trace( origin, mins, maxs, point, this, edict->clipmask, false, "Entity::CheckGround" ); + + if (!(trace.surfaceFlags & SURF_TERRAIN)) + { + point.z = origin.z - 0.25f; // put groundtrace dist back to 0.25f for non-terrain check + trace = G_Trace( origin, mins, maxs, point, this, edict->clipmask, false, "Entity::CheckGround" ); + } + + // check steepness + if ( ( trace.plane.normal[ 2 ] <= 0.7f ) && !trace.startsolid ) + { + groundentity = NULL; + return; + } + + groundentity = trace.ent; + groundplane = trace.plane; + groundcontents = trace.contents; + + if ( !trace.startsolid && !trace.allsolid ) + { + setOrigin( trace.endpos ); + velocity.z = 0; + } + } + +void Entity::BecomeSolid + ( + Event *ev + ) + + { + if ( ( model.length() ) && ( ( model[ 0 ] == '*' ) || ( strstr( model.c_str(), ".bsp" ) ) ) ) + { + setSolidType( SOLID_BSP ); + } + else + { + setSolidType( SOLID_BBOX ); + } + } + +void Entity::BecomeNonSolid + ( + Event *ev + ) + + { + setSolidType( SOLID_NOT ); + } + +void Entity::Ghost + ( + Event *ev + ) + + { + // Make not solid, but send still send over whether it is hidden or not + setSolidType( SOLID_NOT ); + edict->svflags &= ~SVF_NOCLIENT; + } + +void Entity::LoopSound + ( + Event *ev + ) + + { + str sound_name; + float volume = DEFAULT_VOL; + float min_dist = DEFAULT_MIN_DIST; + str min_dist_string; + + + if (ev->NumArgs() < 1) + return; + + // Get parameters + + sound_name = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + volume = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + { + min_dist_string = ev->GetString( 3 ); + + if ( min_dist_string == LEVEL_WIDE_STRING ) + min_dist = LEVEL_WIDE_MIN_DIST; + else + min_dist = ev->GetFloat( 3 ); + + if ( min_dist >= LEVEL_WIDE_MIN_DIST_CUTOFF ) + min_dist = LEVEL_WIDE_MIN_DIST; + } + + // Add this sound to loop + + LoopSound( sound_name, volume, min_dist ); + } + +void Entity::LoopSound( const str &sound_name, float volume, float min_dist ) + { + const char *name = NULL; + str random_alias; + + + // Get the real sound to be played + + if ( sound_name.length() > 0 ) + { + // Get the real sound to play + + name = gi.GlobalAlias_FindRandom( sound_name.c_str() ); + + if ( !name ) + { + random_alias = GetRandomAlias( sound_name ).c_str(); + + if ( random_alias.length() > 0 ) + name = random_alias.c_str(); + } + + if ( !name ) + name = sound_name.c_str(); + + // Add the looping sound to the entity + + edict->s.loopSound = gi.soundindex( name ); + edict->s.loopSoundVolume = volume; + edict->s.loopSoundMinDist = min_dist; + } + } + +void Entity::StopLoopSound( Event *ev ) + { + StopLoopSound(); + } + +void Entity::StopLoopSound( void ) + { + edict->s.loopSound = 0; + } + +void Entity::Sound( Event *ev ) + { + str sound_name; + float volume; + int channel; + float min_dist; + int i; + str min_dist_string; + + + // Set defaults + + volume = DEFAULT_VOL; + min_dist = DEFAULT_MIN_DIST; + channel = CHAN_BODY; + + // Get sound parameters + + for ( i = 1 ; i <= ev->NumArgs() ; i++ ) + { + switch (i-1) + { + case 0: + sound_name = ev->GetString( i ); + break; + case 1: + channel = ev->GetInteger( i ); + break; + case 2: + volume = ev->GetFloat( i ); + break; + case 3: + min_dist_string = ev->GetString( i ); + + if ( min_dist_string == LEVEL_WIDE_STRING ) + min_dist = LEVEL_WIDE_MIN_DIST; + else + min_dist = ev->GetFloat( i ); + + if ( min_dist >= LEVEL_WIDE_MIN_DIST_CUTOFF ) + min_dist = LEVEL_WIDE_MIN_DIST; + break; + default: + break; + } + } + + Sound( sound_name, channel, volume, min_dist, NULL ); + } + +void Entity::StopSound + ( + Event *ev + ) + + { + if (ev->NumArgs() < 1) + StopSound( CHAN_BODY ); + else + StopSound( ev->GetInteger( 1 ) ); + } + +void Entity::StopSound + ( + int channel + ) + + { + gi.StopSound( entnum, channel ); + } + +void Entity::SetLight + ( + Event *ev + ) + + { + float r, g, b; + + if ( ev->NumArgs() == 1 ) + { + Vector tmp; + + tmp = ev->GetVector( 1 ); + r = tmp.x; + g = tmp.y; + b = tmp.z; + } + else + { + r = ev->GetFloat( 1 ); + g = ev->GetFloat( 2 ); + b = ev->GetFloat( 3 ); + lightRadius = ev->GetFloat( 4 ); + } + + G_SetConstantLight( &edict->s.constantLight, &r, &g, &b, &lightRadius ); + } + +void Entity::LightOn + ( + Event *ev + ) + + { + G_SetConstantLight( &edict->s.constantLight, NULL, NULL, NULL, &lightRadius ); + } + +void Entity::LightOff + ( + Event *ev + ) + + { + float radius = 0; + + G_SetConstantLight( &edict->s.constantLight, NULL, NULL, NULL, &radius ); + } + +void Entity::LightRed + ( + Event *ev + ) + + { + float r; + + r = ev->GetFloat( 1 ); + G_SetConstantLight( &edict->s.constantLight, &r, NULL, NULL, NULL ); + } + +void Entity::LightGreen + ( + Event *ev + ) + + { + float g; + + g = ev->GetFloat( 1 ); + G_SetConstantLight( &edict->s.constantLight, NULL, &g, NULL, NULL ); + } + +void Entity::LightBlue + ( + Event *ev + ) + + { + float b; + + b = ev->GetFloat( 1 ); + G_SetConstantLight( &edict->s.constantLight, NULL, NULL, &b, NULL ); + } + +void Entity::LightRadius + ( + Event *ev + ) + + { + lightRadius = ev->GetFloat( 1 ); + G_SetConstantLight( &edict->s.constantLight, NULL, NULL, NULL, &lightRadius ); + } + +void Entity::LightStyle + ( + Event *ev + ) + + { + int style; + + style = ev->GetInteger( 1 ); + G_SetConstantLight( &edict->s.constantLight, NULL, NULL, NULL, NULL, &style ); + } + +void Entity::SetHealth + ( + Event *ev + ) + + { + health = ev->GetFloat( 1 ); + if( max_health < health ) + { + max_health = health; + } + + } + + +//-------------------------------------------------------------- +// +// Name: setGameplayHealth +// Class: Entity +// +// Description: This function acts as a filter to the real function. +// It gets data from the database, and then passes it +// along to the original event. This is here as an attempt +// to sway people into using the database standard instead of +// hardcoded numbers. +// +// Parameters: Event *ev +// str -- The value keyword from the database (low, medium, high, etc). +// +// Returns: None +// +//-------------------------------------------------------------- +void Entity::setGameplayHealth( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasFormula("Health") ) + return; + + str healthstr = ev->GetString( 1 ); + float healthmod = 1.0f; + if ( gpm->getDefine(healthstr) != "" ) + healthmod = (float)atof(gpm->getDefine(healthstr)); + GameplayFormulaData fd(this); + float finalhealth = gpm->calculate("Health", fd, healthmod); + Event *newev = new Event(EV_SetHealth); + newev->AddFloat(finalhealth); + ProcessEvent(newev); +} + +//---------------------------------------------------------------- +// Name: addHealth +// Class: Entity +// +// Description: Adds health to the entity up to the max health of the entity or the +// max health specified (if its not 0) +// +// Parameters: float healthToAdd - health we are going to add to the entity +// float maxHealth - if not 0, the max health the entity can have +// +// Returns: none +//---------------------------------------------------------------- + +void Entity::addHealth( float healthToAdd, float maxHealth ) +{ + float tempMaxHealth; + float newHealth; + + // Get the max health + + if ( maxHealth ) + tempMaxHealth = maxHealth; + else + tempMaxHealth = max_health; + + // Calculate the new health + + newHealth = health + healthToAdd; + + // Set the new health + + if ( newHealth > tempMaxHealth ) + setHealth( tempMaxHealth ); + else + setHealth( newHealth ); +} + +void Entity::GetHealth + ( + Event *ev + ) + + { + ev->ReturnFloat( health ); + } + +void Entity::SetMaxHealth + ( + Event *ev + ) + { + max_health = ev->GetFloat( 1 ); + } + +void Entity::SetSize + ( + Event *ev + ) + + { + Vector min, max; + + min = ev->GetVector( 1 ); + max = ev->GetVector( 2 ); + setSize( min, max ); + } + +void Entity::SetMins + ( + Event *ev + ) + + { + Vector min; + + min = ev->GetVector( 1 ); + setSize( min, maxs ); + } + +void Entity::SetMaxs + ( + Event *ev + ) + + { + Vector max; + + max = ev->GetVector( 1 ); + setSize( mins, max ); + } + +void Entity::GetMins + ( + Event *ev + ) + + { + ev->ReturnVector( mins ); + } + +void Entity::GetMaxs + ( + Event *ev + ) + + { + ev->ReturnVector( maxs ); + } + + +void Entity::SetScale + ( + Event *ev + ) + + { + setScale( ev->GetFloat( 1 ) ); + } + +void Entity::setRandomScale( Event *ev ) +{ + float minScale; + float maxScale; + + minScale = ev->GetFloat( 1 ); + maxScale = ev->GetFloat( 2 ); + + setScale( G_Random( maxScale - minScale ) + minScale ); +} + +void Entity::SetAlpha + ( + Event *ev + ) + + { + setAlpha( ev->GetFloat( 1 ) ); + } + +void Entity::SetOrigin + ( + Event *ev + ) + + { + setOrigin( ev->GetVector( 1 ) ); + } + +void Entity::GetOrigin + ( + Event *ev + ) + + { + ev->ReturnVector( origin ); + } + +void Entity::SetTargetName + ( + Event *ev + ) + + { + SetTargetName( ev->GetString( 1 ) ); + } + +//---------------------------------------------------------------- +// Name: GetRawTargetName +// Class: Entity +// +// Description: Gets the targetname of the entity without the leading $ +// +// Parameters: None +// +// Returns: str targetname (through ev) - the targetname of the entity +//---------------------------------------------------------------- + +void Entity::GetRawTargetName + ( + Event *ev + ) + + { + ev->ReturnString( targetname ); + } + +//---------------------------------------------------------------- +// Name: GetTargetName +// Class: Entity +// +// Description: Gets the targetname of the entity with the leading $ +// +// Parameters: None +// +// Returns: str targetname (through ev) - the targetname of the entity +//---------------------------------------------------------------- + +void Entity::GetTargetName + ( + Event *ev + ) + + { + str nameToReturn; + + nameToReturn = "$"; + nameToReturn += targetname; + + ev->ReturnString( nameToReturn ); + } + +void Entity::SetTarget + ( + Event *ev + ) + + { + SetTarget( ev->GetString( 1 ) ); + } + +void Entity::getTarget( Event *ev ) +{ + str nameToReturn; + bool wantsPrefix; + + if ( ev->NumArgs() > 0 ) + wantsPrefix = ev->GetBoolean( 1 ); + else + wantsPrefix = false; + + if ( wantsPrefix ) + { + nameToReturn = "$"; + } + + nameToReturn += target; + + ev->ReturnString( nameToReturn ); +} + +//-------------------------------------------------------------- +// Name: GetTargetEntity() +// Class: Entity +// +// Description: Returns the Entity of the target +// +// Parameters: Event *ev +// +// Returns: None ( Entity, though, through the event ) +//-------------------------------------------------------------- +void Entity::GetTargetEntity( Event *ev ) +{ + TargetList *tlist = world->GetTargetList( target, false ); + + if ( tlist ) + { + ev->ReturnEntity( tlist->GetNextEntity( NULL ) ); + } + else + { + ev->ReturnEntity( NULL ); + } +} + + +void Entity::SetKillTarget + ( + Event *ev + ) + + { + SetKillTarget( ev->GetString( 1 ) ); + } + +//----------------------------------------------------- +// +// Name: GetModelName +// Class: Entity +// +// Description: Retrieves the model name +// +// Parameters: event - the event to request the model name +// +// Returns: None +//----------------------------------------------------- +void Entity::GetModelName(Event* ev) +{ + ev->ReturnString(model.c_str()); +} + +void Entity::SetAngles + ( + Event *ev + ) + + { + setAngles( ev->GetVector( 1 ) ); + } + +//----------------------------------------------------- +// +// Name: GetAngles +// Class: Entity +// +// Description: Retrieves the entity's angles +// +// Parameters: event - event to request the entity's angles +// +// Returns: None +//----------------------------------------------------- +void Entity::GetAngles(Event* ev) +{ + ev->ReturnVector(angles); +} + + +Vector Entity::GetControllerAngles + ( + int num + ) + + { + Vector controller_angles; + + assert( ( num >= 0 ) && ( num < NUM_BONE_CONTROLLERS ) ); + + if ( ( num < 0 ) || ( num >= NUM_BONE_CONTROLLERS ) ) + { + error( "GetControllerAngles", "Bone controller index out of range (%d)\n", num ); + return vec_zero; + } + + controller_angles = edict->s.bone_angles[ num ]; + + return controller_angles; + } + +void Entity::SetControllerAngles + ( + int num, + vec3_t angles + ) + + { + assert( ( num >= 0 ) && ( num < NUM_BONE_CONTROLLERS ) ); + + if ( ( num < 0 ) || ( num >= NUM_BONE_CONTROLLERS ) ) + { + error( "SetControllerAngles", "Bone controller index out of range (%d)\n", num ); + return; + } + + VectorCopy( angles, edict->s.bone_angles[ num ] ); + EulerToQuat( edict->s.bone_angles[ num ], edict->s.bone_quat[ num ] ); + } + +void Entity::SetControllerAngles + ( + Event *ev + ) + + { + int num; + Vector angles; + + if ( ev->NumArgs() < 2 ) + return; + + num = ev->GetInteger( 1 ); + angles = ev->GetVector( 2 ); + + SetControllerAngles( num, angles ); + } + +void Entity::SetControllerTag + ( + int num, + int tag_num + ) + + { + assert( ( num >= 0 ) && ( num < NUM_BONE_CONTROLLERS ) ); + + if ( ( num < 0 ) || ( num >= NUM_BONE_CONTROLLERS ) ) + { + error( "SetControllerTag", "Bone controller index out of range (%d)\n", num ); + return; + } + + edict->s.bone_tag[ num ] = tag_num; + } + + + +//=============================================================== +// Name: SetFullTraceEvent +// Class: Entity +// +// Description: Sets the fulltrace flag. This flag is used by +// G_PushMove to determine if the object needs a +// fulltrace or not. Default for flag is false. +// +// Parameters: Event* -- first argument is boolean +// +// Returns: None +// +//=============================================================== +inline void Entity::SetFullTraceEvent +( + Event *ev +) +{ + _fulltrace = ev->GetBoolean( 1 ); +} + + +void Entity::RegisterAlias + ( + Event *ev + ) + + { + char parameters[100]; + int i; + + // Get the parameters for this alias command + + parameters[0] = 0; + + for( i = 3 ; i <= ev->NumArgs() ; i++ ) + { + strcat( parameters, ev->GetString( i ) ); + strcat( parameters, " "); + } + + gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), ev->GetString( 2 ), parameters ); + } + +void Entity::Cache + ( + Event *ev + ) + + { + CacheResource( ev->GetString( 1 ), this ); + } + +void Entity::RegisterAliasAndCache + ( + Event *ev + ) + + { + RegisterAlias(ev); + + CacheResource( ev->GetString( 2 ), this ); + } + +void Entity::Sound + ( + const str &sound_name, + int channel, + float volume, + float min_dist, + Vector *sound_origin, + float pitch_modifier, + qboolean onlySendToThisEntity + ) + { + const char *name = NULL; + vec3_t org; + str random_alias; + + + if ( sound_name.length() > 0 ) + { + // Get the real sound to play + + name = gi.GlobalAlias_FindRandom( sound_name.c_str() ); + + if ( !name ) + { + random_alias = GetRandomAlias( sound_name ).c_str(); + + if ( random_alias.length() > 0 ) + name = random_alias.c_str(); + } + + if ( !name ) + name = sound_name.c_str(); + + // Play the sound + + if ( name != NULL ) + { + if ( sound_origin != NULL) + { + sound_origin->copyTo( org ); + entnum = ENTITYNUM_NONE; + } + else + { + VectorCopy( edict->s.origin, org ); + } + + gi.Sound( &org, entnum, channel, name, volume, min_dist, pitch_modifier, onlySendToThisEntity ); + } + } + else + { + warning( "Sound", "Null sample pointer" ); + } + } + +qboolean Entity::attach + ( + int parent_entity_num, + int tag_num, + qboolean use_angles, + Vector offset, + Vector angles_offset + ) + + { + int i; + Entity * parent; + + if ( entnum == parent_entity_num ) + { + warning("attach","Trying to attach to oneself." ); + return false; + } + + if ( edict->s.parent != ENTITYNUM_NONE ) + detach(); + + // + // make sure this is a modelanim entity so that the attach works properly + // + if ( edict->s.eType == ET_GENERAL ) + { + edict->s.eType = ET_MODELANIM; + } + + // + // get the parent + // + parent = ( Entity * )G_GetEntity( parent_entity_num ); + + if ( !parent->bind_info ) + parent->bind_info = CreateBindInfo(); + + if (parent->bind_info->numchildren < MAX_MODEL_CHILDREN) + { + // + // find a free spot in the parent + // + for ( i=0; i < MAX_MODEL_CHILDREN; i++ ) + if ( parent->bind_info->children[i] == ENTITYNUM_NONE ) + { + break; + } + edict->s.parent = parent_entity_num; + setSolidType( SOLID_NOT ); + parent->bind_info->children[i] = entnum; + parent->bind_info->numchildren++; + if ( tag_num >= 0 ) + { + edict->s.tag_num = tag_num; + } + edict->s.attach_use_angles = use_angles; + offset.copyTo( edict->s.attach_offset ); + angles_offset.copyTo( edict->s.attach_angles_offset ); + setOrigin(); + return true; + } + return false; + } + +void Entity::KillAttach + ( + Event *ev + ) + + { + int i; + Entity *child = NULL; + + + if ( bind_info ) + { + // Kill all of this entities children + + for ( i = 0 ; i < MAX_MODEL_CHILDREN; i++ ) + { + if ( bind_info->children[i] != ENTITYNUM_NONE ) + { + // Remove child + child = ( Entity * )G_GetEntity( bind_info->children[i] ); + + if ( child ) + child->ProcessEvent( EV_Remove ); + + // Remove child from this entity + bind_info->children[i] = ENTITYNUM_NONE; + } + } + + bind_info->numchildren = 0; + } + } + +void Entity::detach + ( + void + ) + + { + int i; + int num; + Entity * parent; + + if ( edict->s.parent == ENTITYNUM_NONE ) + return; + + parent = ( Entity * )G_GetEntity( edict->s.parent ); + + if (!parent) + return; + + if ( parent->bind_info) + { + for ( i=0,num = parent->bind_info->numchildren; i < MAX_MODEL_CHILDREN; i++ ) + { + if ( parent->bind_info->children[i] == ENTITYNUM_NONE ) + { + continue; + } + if (parent->bind_info->children[i] == entnum) + { + parent->bind_info->children[i] = ENTITYNUM_NONE; + parent->bind_info->numchildren--; + break; + } + num--; + if (!num) + break; + } + } + + edict->s.parent = ENTITYNUM_NONE; + setOrigin( origin ); + } + +void Entity::Flags( Event *ev ) + { + const char *flag; + int mask; + int action; + int i; + + for ( i = 1; i <= ev->NumArgs(); i++ ) + { + action = FLAG_IGNORE; + flag = ev->GetString( i ); + switch( flag[0] ) + { + case '+': + action = FLAG_ADD; + flag++; + break; + case '-': + action = FLAG_CLEAR; + flag++; + break; + default: + ev->Error( "Entity::Flags", "First character is not '+' or '-', assuming '+'\n" ); + action = FLAG_ADD; + break; + } + + // + // WARNING: please change the Event decleration, + // to match this function, if flags are added or + // deleted the event must be updated. + // + if ( !stricmp( flag, "blood" ) ) + mask = FL_BLOOD; + else if ( !stricmp( flag, "explode" ) ) + mask = FL_DIE_EXPLODE; + else if ( !stricmp( flag, "die_gibs" ) ) + mask = FL_DIE_GIBS; + else if ( !stricmp( flag, "autoaim" ) ) + mask = FL_AUTOAIM; + else if ( !stricmp( flag, "god" ) ) + mask = FL_GODMODE; + else if ( !stricmp( flag, "notarget" ) ) + mask = FL_NOTARGET; + else + { + mask = 0; + action = FLAG_IGNORE; + ev->Error( "Unknown flag '%s'", flag ); + } + switch (action) + { + case FLAG_ADD: + flags |= mask; + break; + case FLAG_CLEAR: + flags &= ~mask; + break; + case FLAG_IGNORE: + break; + } + } + if ( !com_blood->integer ) + { + if ( flags & (FL_BLOOD|FL_DIE_GIBS) ) + { + flags &= ~FL_BLOOD; + flags &= ~FL_DIE_GIBS; + } + } + } + + +void Entity::Effects( Event *ev ) + { + const char *flag; + int mask=0; + int action; + int i; + + for ( i = 1; i <= ev->NumArgs(); i++ ) + { + action = 0; + flag = ev->GetString( i ); + switch( flag[0] ) + { + case '+': + action = FLAG_ADD; + flag++; + break; + case '-': + action = FLAG_CLEAR; + flag++; + break; + default: + ev->Error( "Entity::Effects", "First character is not '+' or '-', assuming '+'\n" ); + action = FLAG_ADD; + break; + } + + // + // WARNING: please change the Event decleration, + // to match this function, if flags are added or + // deleted the event must be updated. + // + if ( !stricmp( flag, "everyframe" ) ) + mask = EF_EVERYFRAME; + else + { + action = FLAG_IGNORE; + ev->Error( "Unknown token %s.", flag ); + } + + switch (action) + { + case FLAG_ADD: + edict->s.eFlags |= mask; + break; + case FLAG_CLEAR: + edict->s.eFlags &= ~mask; + break; + case FLAG_IGNORE: + break; + } + } + } + +void Entity::RenderEffects( Event *ev ) + { + const char *flag; + int mask=0; + int action; + int i; + + for ( i = 1; i <= ev->NumArgs(); i++ ) + { + action = 0; + flag = ev->GetString( i ); + switch( flag[0] ) + { + case '+': + action = FLAG_ADD; + flag++; + break; + case '-': + action = FLAG_CLEAR; + flag++; + break; + default: + ev->Error( "Entity::RenderEffects", "First character is not '+' or '-', assuming '+'\n" ); + action = FLAG_ADD; + break; + } + + // + // WARNING: please change the Event decleration, + // to match this function, if flags are added or + // deleted the event must be updated. + // + if ( !stricmp( flag, "dontdraw" ) ) + mask = RF_DONTDRAW; + else if ( !stricmp( flag, "betterlighting" ) ) + mask = RF_EXTRALIGHT; + else if ( !stricmp ( flag, "lensflare" ) ) + mask = RF_LENSFLARE; + else if ( !stricmp ( flag, "viewlensflare" ) ) + mask = RF_VIEWLENSFLARE; + else if ( !stricmp ( flag, "lightoffset" ) ) + mask = RF_LIGHTOFFSET; + else if ( !stricmp( flag, "skyorigin" ) ) + mask = RF_SKYORIGIN; + else if ( !stricmp( flag, "fullbright" ) ) + mask = RF_FULLBRIGHT; + else if ( !stricmp( flag, "minlight" ) ) + mask = RF_MINLIGHT; + else if ( !stricmp( flag, "additivedynamiclight" ) ) + mask = RF_ADDITIVE_DLIGHT; + else if ( !stricmp( flag, "lightstyledynamiclight" ) ) + mask = RF_LIGHTSTYLE_DLIGHT; + else if ( !stricmp( flag, "shadow" ) ) + mask = RF_SHADOW; + else if ( !stricmp( flag, "shadowFromBip01" ) ) + mask = RF_SHADOW_FROM_BIP01; + else if ( !stricmp( flag, "preciseshadow" ) ) + mask = RF_SHADOW_PRECISE; + else if ( !stricmp( flag, "dontInheritAlpha" ) ) + mask = RF_CHILDREN_DONT_INHERIT_ALPHA; + else if ( !stricmp( flag, "invisible" ) ) + mask = RF_INVISIBLE; + else if ( !stricmp( flag, "depthhack" ) ) + mask = RF_DEPTHHACK; + else + { + action = FLAG_IGNORE; + ev->Error( "Unknown token %s.", flag ); + } + + switch (action) + { + case FLAG_ADD: + edict->s.renderfx |= mask; + break; + case FLAG_CLEAR: + edict->s.renderfx &= ~mask; + break; + case FLAG_IGNORE: + break; + } + } + } + +void Entity::SVFlags + ( + Event *ev + ) + + { + const char *flag; + int mask=0; + int action; + int i; + + for ( i = 1; i <= ev->NumArgs(); i++ ) + { + action = 0; + flag = ev->GetString( i ); + switch( flag[0] ) + { + case '+': + action = FLAG_ADD; + flag++; + break; + case '-': + action = FLAG_CLEAR; + flag++; + break; + default: + ev->Error( "Entity::SVFlags", "First character is not '+' or '-', assuming '+'\n" ); + action = FLAG_ADD; + break; + } + + // + // WARNING: please change the Event decleration, + // to match this function, if flags are added or + // deleted the event must be updated. + // + if ( !stricmp( flag, "broadcast" ) ) + mask = SVF_BROADCAST; + else if ( !stricmp( flag, "sendonce" ) ) + mask = SVF_SENDONCE; + else + { + action = FLAG_IGNORE; + ev->Error( "Unknown token %s.", flag ); + } + + switch (action) + { + case FLAG_ADD: + edict->svflags |= mask; + break; + case FLAG_CLEAR: + edict->svflags &= ~mask; + break; + case FLAG_IGNORE: + break; + } + } + + if ( edict->svflags & SVF_SENDONCE ) + { + // Turn this entity into an event if the SENDONCE flag is sent + edict->s.eType = ET_EVENTS; + edict->svflags &= ~SVF_SENT; + } + } + +void Entity::BroadcastSound + ( + float rad, + int soundType //Defaults to SOUNDTYPE_GENERAL + ) + + { + if ( !( this->flags & FL_NOTARGET ) ) + { + G_BroadcastSound( this, centroid, rad, soundType ); + } + } + +void Entity::BroadcastSound + ( + Event *ev + ) + + { + float rad = SOUND_RADIUS; + int soundTypeIdx = SOUNDTYPE_GENERAL; + str soundTypeStr = ""; + + if ( !( this->flags & FL_NOTARGET ) ) + { + + if (ev->NumArgs() > 0 ) //<-- At least 1 Parameter + { + rad = ev->GetFloat( 1 ); + + if(ev->NumArgs() > 1 ) //<-- At least 2 Parameters + { + soundTypeStr = ev->GetString( 2 ); + soundTypeIdx = Soundtype_string_to_int( soundTypeStr ); + } + } + + if (soundTypeIdx == SOUNDTYPE_FOOTSTEPS_RUN || soundTypeIdx == SOUNDTYPE_FOOTSTEPS_WALK ) + rad = ModifyFootstepSoundRadius( rad , soundTypeIdx ); + + + BroadcastSound( rad, soundTypeIdx ); + } + } + +float Entity::ModifyFootstepSoundRadius + ( + float radius, + int soundTypeIdx + ) + + { + trace_t trace; + Vector end; + Vector start; + + start = origin; + end = origin; + end[2]-= 1000.0f; + int surftype; + + trace = G_Trace( start, mins, maxs, end, this, edict->clipmask, false, "Entity::ModifyFootstepsRadius" ); + surftype = trace.surfaceFlags & MASK_SURF_TYPE; + + switch ( surftype ) + { + case SURF_TYPE_DIRT: + radius *= .5f; + break; + case SURF_TYPE_ROCK: + break; + case SURF_TYPE_METAL: + radius *= 1.5; + break; + case SURF_TYPE_GRILL: + radius *= 1.25f; + break; + case SURF_TYPE_ORGANIC: + radius *= .5f; + break; + case SURF_TYPE_SQUISHY: + radius *= .5f; + break; + case SURF_TYPE_SAND: + radius *= .5f; + break; + case SURF_TYPE_SNOW: + radius *= .5f; + break; + case SURF_TYPE_METAL_DUCT: + radius *= 1.25f; + break; + case SURF_TYPE_METAL_HOLLOW: + radius *= 1.25f; + break; + case SURF_TYPE_CARPET: + radius *= .5f; + break; + } + + //Tone Radius Down for Walking + if (soundTypeIdx == SOUNDTYPE_FOOTSTEPS_WALK ) + radius *= .75f; + + return radius; + } + +void Entity::Think + ( + void + ) + + { + } + +void Entity::SetWaterType + ( + void + ) + + { + qboolean isinwater; + + watertype = gi.pointcontents( origin, 0 ); + isinwater = watertype & MASK_WATER; + + if ( isinwater ) + { + waterlevel = 1; + } + else + { + waterlevel = 0; + } + } + +void Entity::DamageSkin + ( + trace_t * trace, + float damage + ) + + { + /* FIXME : Do we need damage skins? + int surface; + + // FIXME handle different bodyparts + surface = trace->intersect.surface; + if ( !edict->s.surfaces[ surface ] ) + { + edict->s.surfaces[ surface ]++; + } + */ + } + +void Entity::Kill + ( + Event *ev + ) + + { + health = 0.0f; + Damage( this, this, 10.0f, origin, vec_zero, vec_zero, 0, 0, MOD_SUICIDE ); + } + + +void Entity::SurfaceCommand + ( + const char * surf_name, + const char * token + ) + + { + const char * current_surface_name; + int surface_num; + int mask; + int action; + qboolean do_all = false; + qboolean mult = false; + + + if ( surf_name[ strlen( surf_name ) - 1 ] == '*' ) + { + mult = true; + surface_num = 0; + } + else if ( str( surf_name ) != str( "all" ) ) + { + surface_num = gi.Surface_NameToNum( edict->s.modelindex, surf_name ); + + if (surface_num < 0) + { + warning( "SurfaceCommand", "group %s not found for entity %s (%d), model %s.\n", surf_name, targetname.c_str(), entnum, model.c_str() ); + return; + } + } + else + { + surface_num = 0; + do_all = true; + } + + action = 0; + switch( token[0] ) + { + case '+': + action = FLAG_ADD; + token++; + break; + case '-': + action = FLAG_CLEAR; + token++; + break; + default: + warning( "Entity::SurfaceModelEvent", "First character is not '+' or '-', assuming '+' for entity %s (%d), model %s\n", targetname.c_str(), entnum, model.c_str() ); + action = FLAG_ADD; + break; + } + // + // WARNING: please change the Event decleration, + // to match this function, if flags are added or + // deleted the event must be updated. + // + if (!stricmp( token, "skin1")) + { + mask = MDL_SURFACE_SKINOFFSET_BIT0; + } + else if (!strcmpi (token, "skin2")) + { + mask = MDL_SURFACE_SKINOFFSET_BIT1; + } + else if (!strcmpi (token, "nodraw")) + { + mask = MDL_SURFACE_NODRAW; + } + else if (!strcmpi (token, "crossfade")) + { + mask = MDL_SURFACE_CROSSFADE_SKINS; + } + else + { + mask = 0; + warning( "SurfaceCommand", "Unknown token %s. for entity %s (%d), model %s", token, targetname.c_str(), entnum, model.c_str() ); + action = FLAG_IGNORE; + } + for( ; surface_num < numsurfaces ; surface_num++ ) + { + if ( mult ) + { + current_surface_name = gi.Surface_NumToName( edict->s.modelindex, surface_num ); + + if ( Q_stricmpn( current_surface_name, surf_name, strlen( surf_name ) - 1) != 0 ) + continue; + } + + switch (action) + { + case FLAG_ADD: + edict->s.surfaces[ surface_num ] |= mask; + break; + case FLAG_CLEAR: + edict->s.surfaces[ surface_num ] &= ~mask; + break; + case FLAG_IGNORE: + break; + } + + if ( !do_all && !mult ) + break; + } + } + +void Entity::SurfaceModelEvent + ( + Event *ev + ) + + { + const char * surf_name; + const char * token; + int i; + + surf_name = ev->GetString( 1 ); + + for ( i = 2; i <= ev->NumArgs() ; i++ ) + { + token = ev->GetString( i ); + SurfaceCommand( surf_name, token ); + } + } + +void Entity::AttachEvent + ( + Event * ev + ) + { + Entity * parent; + const char * bone; + int tagnum; + qboolean use_angles = true; + Vector offset; + Vector angles_offset; + + parent = ev->GetEntity( 1 ); + bone = ev->GetString( 2 ); + + if ( ev->NumArgs() > 2 ) + use_angles = ev->GetInteger( 3 ); + + if ( ev->NumArgs() > 3 ) + offset = ev->GetVector( 4 ); + + if ( ev->NumArgs() > 4 ) + angles_offset = ev->GetVector( 5 ); + + if ( !parent ) + return; + + tagnum = gi.Tag_NumForName( parent->edict->s.modelindex, bone ); + if ( tagnum >= 0 ) + { + attach( parent->entnum, tagnum, use_angles, offset, angles_offset ); + } + else + { + warning( "AttachEvent", "Tag %s not found", bone ); + } + } + +void Entity::AttachModelEvent + ( + Event * ev + ) + { + Entity * obj; + const char * bone; + str modelname; + int tagnum; + float remove_time,fade_time,fade_delay; + Vector offset; + Vector angles_offset; + qboolean use_angles = false; + + obj = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + obj->bind_info = CreateBindInfo(); + + modelname = ev->GetString( 1 ); + bone = ev->GetString( 2 ); + if ( ev->NumArgs() > 2 ) + { + obj->setScale( ev->GetFloat( 3 ) ); + } + if ( ev->NumArgs() > 3 ) + { + obj->SetTargetName( ev->GetString( 4 ) ); + } + + if ( ev->NumArgs() > 4 ) + obj->bind_info->detach_at_death = ev->GetInteger( 5 ); + + if ( ev->NumArgs() > 5 ) + { + remove_time = ev->GetFloat( 6 ); + + if ( remove_time > 0.0f ) + { + Event *remove_event = new Event( EV_Remove ); + obj->PostEvent( remove_event, remove_time ); + } + } + + if ( ev->NumArgs() > 6 ) + { + Event *fade_event; + + fade_time = ev->GetFloat( 7 ); + + if ( fade_time > 0.0f ) + { + obj->setAlpha( 0.0f ); + + fade_event = new Event( EV_FadeIn ); + fade_event->AddFloat( fade_time ); + obj->PostEvent( fade_event, 0.0f ); + } + } + + if ( ev->NumArgs() > 7 ) + { + Event *fade_event; + + fade_delay = ev->GetFloat( 8 ); + + if ( fade_delay != -1.0f ) + { + if ( ev->NumArgs() > 8 ) + fade_time = ev->GetFloat( 9 ); + else + fade_time = 0.0f; + + fade_event = new Event( EV_Fade ); + + if ( fade_time > 0.0f ) + fade_event->AddFloat( fade_time ); + + obj->PostEvent( fade_event, fade_delay ); + } + } + + if ( ev->NumArgs() > 9 ) + offset = ev->GetVector( 10 ); + + if ( ev->NumArgs() > 10 ) + { + angles_offset = ev->GetVector( 11 ); + use_angles = false; + } + + obj->setModel( modelname ); + + if ( !obj->animate ) + { + Animate *newAnimate = 0; + newAnimate = new Animate; + if ( newAnimate ) + obj->animate = newAnimate; + } + + int anim_num = gi.Anim_Random ( obj->edict->s.modelindex, "idle" ); + if ( anim_num != -1 && obj->animate ) + { + obj->animate->NewAnim( anim_num ); + } + + tagnum = gi.Tag_NumForName( edict->s.modelindex, bone ); + if ( tagnum >= 0 ) + { + if ( !obj->attach( this->entnum, tagnum, use_angles, offset, angles_offset ) ) + { + //warning( "AttachModelEvent", "Could not attach model %s", modelname.c_str() ); + delete obj; + return; + } + } + else + { + warning( "AttachModelEvent", "Tag %s not found", bone ); + } + } + +void Entity::RemoveAttachedModelEvent + ( + Event *ev + ) + { + const char *tag_name; + int tag_num; + int num; + int i; + Entity *ent; + float fade_rate = 0; + Event *fade_event; + str model_name; + + if ( bind_info ) + { + tag_name = ev->GetString( 1 ); + tag_num = gi.Tag_NumForName( edict->s.modelindex, tag_name ); + + if ( ev->NumArgs() > 1 ) + fade_rate = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + model_name = ev->GetString( 3 ); + + if ( tag_num >= 0 ) + { + num = bind_info->numchildren; + + for ( i = 0 ; (i < MAX_MODEL_CHILDREN) && num ; i++ ) + { + if ( bind_info->children[i] == ENTITYNUM_NONE ) + continue; + + ent = ( Entity * )G_GetEntity( bind_info->children[i] ); + + if ( ent && ent->edict->s.tag_num == tag_num ) + { + if ( !model_name.length() || ( model_name == ent->model ) ) + { + if ( fade_rate ) + { + fade_event = new Event( EV_Fade ); + fade_event->AddFloat( fade_rate ); + fade_event->AddFloat( 0.0f ); + ent->PostEvent( fade_event, 0.0f ); + } + + ent->PostEvent( EV_Remove, fade_rate ); + } + } + + num--; + } + } + } + } + +void Entity::removeAttachedModelByTargetname( Event *ev ) +{ + str targetNameToRemove; + + targetNameToRemove = ev->GetString( 1 ); + + removeAttachedModelByTargetname( targetNameToRemove ); +} + +void Entity::removeAttachedModelByTargetname( const str &targetNameToRemove ) +{ + int num; + int i; + Entity *ent; + + if ( bind_info ) + { + num = bind_info->numchildren; + + for ( i = 0 ; (i < MAX_MODEL_CHILDREN) && num ; i++ ) + { + if ( bind_info->children[i] == ENTITYNUM_NONE ) + continue; + + ent = ( Entity * )G_GetEntity( bind_info->children[i] ); + + if ( ent && stricmp( ent->targetname, targetNameToRemove.c_str() ) == 0 ) + { + ent->PostEvent( EV_Remove, 0.0f ); + } + + num--; + } + } +} + +void Entity::DetachEvent + ( + Event * ev + ) + + { + if ( edict->s.parent == ENTITYNUM_NONE ) + { + return; + } + detach(); + } + +void Entity::TakeDamageEvent + ( + Event * ev + ) + { + takedamage = DAMAGE_YES; + } + +void Entity::NoDamageEvent + ( + Event * ev + ) + { + takedamage = DAMAGE_NO; + } + +void Entity::Gravity + ( + Event *ev + ) + + { + gravity = ev->GetFloat( 1 ); + } + +void Entity::UseBoundingBoxEvent + ( + Event *ev + ) + { + edict->svflags |= SVF_USEBBOX; + } + +void Entity::HurtEvent + ( + Event *ev + ) + { + Vector normal; + float dmg; + int means_of_death; + Vector direction; + + if ( ev->NumArgs() < 1 ) + { + dmg = 50.0f; + } + else + { + dmg = ev->GetFloat( 1 ); + } + + if ( ev->NumArgs() > 1 ) + means_of_death = MOD_NameToNum( ev->GetString( 2 ) ); + else + means_of_death = MOD_CRUSH; + + if ( ev->NumArgs() > 2 ) + { + direction = ev->GetVector( 3 ); + direction.normalize(); + } + else + { + direction = vec_zero; + } + + normal = Vector( orientation[ 0 ] ); + Damage( world, world, dmg, centroid, direction, normal, (int)dmg, 0, means_of_death ); + } + +void Entity::IfSkillEvent + ( + Event *ev + ) + + { + float skilllevel; + + skilllevel = ev->GetFloat( 1 ); + + if ( skill->value == skilllevel ) + { + int argc; + int numargs; + Event *event; + int i; + + numargs = ev->NumArgs(); + argc = numargs - 2 + 1; + + event = new Event( ev->GetToken( 2 ) ); + + for( i = 1; i < argc; i++ ) + { + event->AddToken( ev->GetToken( 2 + i ) ); + } + ProcessEvent( event ); + } + } + +void Entity::Censor + ( + Event *ev + ) + + { + Vector delta; + float oldsize; + float newsize; + + if ( com_blood->integer ) + return; + + oldsize = size.length(); + setSolidType( SOLID_NOT ); + setModel( "censored.tik" ); + gi.CalculateBounds( edict->s.modelindex, 1.0f, mins, maxs ); + delta = maxs - mins; + newsize = delta.length(); + edict->s.scale = oldsize / newsize; + mins *= edict->s.scale; + maxs *= edict->s.scale; + setSize( mins, maxs ); + setOrigin(); + } + +void Entity::StationaryEvent + ( + Event *ev + ) + + { + setMoveType( MOVETYPE_STATIONARY ); + } + +void Entity::Explosion + ( + Event *ev + ) + + { + str expmodel; + str tag_name; + //orientation_t orient; + Vector explosion_origin; + + expmodel = ev->GetString( 1 ); + explosion_origin = origin; + + if ( ev->NumArgs() > 1 ) + { + tag_name = ev->GetString( 2 ); + + //if ( GetRawTag( tag_name.c_str(), &orient, legs ) ) + // VectorAdd( orient.origin, origin, explosion_origin ); + + GetTag( tag_name.c_str(), &explosion_origin ); + } + + ExplosionAttack( explosion_origin, this, expmodel ); + } + +//---------------------------------------------------------------- +// Name: DoRadiusDamage +// Class: Entity +// +// Description: Does radius damage from entity origin, +// optionally re-posting itself to some future time +// +// Parameters: Event *ev +// Event params: (1) float damage +// (2) const char * means of death string +// (3) float radius +// (4) float knockback +// (5) (optional) bool constant damage over distance +// (6) (optional) float forward re-posting time +// +// Returns: NULL +//---------------------------------------------------------------- + +void Entity::DoRadiusDamage( Event *ev ) +{ + Entity *owner = this; + float damage = ev->GetFloat(1); + const char *modstring = ev->GetString(2); + float radius = ev->GetFloat(3); + float knockback = ev->GetFloat(4); + bool constant_damage = false; + float damageDone; + + if (ev->NumArgs() >= 5) // constant damage info is supplied + constant_damage = ev->GetBoolean(5); + + if ( this->isSubclassOf( Projectile ) ) + { + Projectile *projectile = (Projectile *)this; + + owner = projectile->getOwner(); + } + + if ( owner && owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)owner; + + damage = player->getDamageDone( damage, MOD_NameToNum( modstring ), false ); + } + + damageDone = RadiusDamage( this, + owner, + damage, + this, + MOD_NameToNum(modstring), + radius, + knockback, + constant_damage + ); + + if ( damageDone && this->isSubclassOf( Projectile ) ) + { + Projectile *projectile = (Projectile *)this; + + projectile->didDamage(); + } + + if (ev->NumArgs() == 6) + { + // if repost time is set, re-post this event + float postTime = ev->GetFloat(6); + if (postTime >= FRAMETIME) + { + Event *ev2 = new Event(ev); + PostEvent(ev2,postTime); + } + } +} + +void Entity::SelfDetonate + ( + Event *ev + ) + { + if ( explosionModel.length() == 0 ) + explosionModel = "fx/fx-sml-exp.tik"; + + ExplosionAttack( origin , this , explosionModel ); + } + +void Entity::Shader + ( + Event *ev + ) + + { + const char * token; + + if ( gi.IsModel( edict->s.modelindex ) ) + { + ev->Error( "shader event being called on TIKI model\n" ); + } + // + // get sub shader command + // + token = ev->GetString( 1 ); + + // + // WARNING: please change the Event decleration, + // to match this function, if flags are added or + // deleted the event must be updated. + // + if (!strcmpi( token, "translation")) + { + float x, y; + + x = ev->GetFloat( 2 ); + y = ev->GetFloat( 3 ); + TRANSLATION_TO_PKT( (int)x, edict->s.tag_num ); + TRANSLATION_TO_PKT( (int)y, edict->s.skinNum ); + } + else if (!strcmpi( token, "offset")) + { + float x, y; + + x = ev->GetFloat( 2 ); + y = ev->GetFloat( 3 ); + OFFSET_TO_PKT( x, edict->s.tag_num ); + OFFSET_TO_PKT( y, edict->s.skinNum ); + } + else if (!strcmpi (token, "rotation")) + { + float rot; + + rot = ev->GetFloat( 2 ); + ROTATE_TO_PKT( rot, edict->s.tag_num ); + } + else if (!strcmpi (token, "frame")) + { + edict->s.frame = ev->GetInteger( 2 ); + } + else if (!strcmpi (token, "wavebase")) + { + float base; + + base = ev->GetFloat( 2 ); + BASE_TO_PKT( base, edict->s.surfaces[ 0 ] ); + } + else if (!strcmpi (token, "waveamp")) + { + float amp; + + amp = ev->GetFloat( 2 ); + AMPLITUDE_TO_PKT( amp, edict->s.surfaces[ 1 ] ); + } + else if (!strcmpi (token, "wavephase")) + { + float phase; + + phase = ev->GetFloat( 2 ); + PHASE_TO_PKT( phase, edict->s.surfaces[ 2 ] ); + } + else if (!strcmpi (token, "wavefreq")) + { + float freq; + + freq = ev->GetFloat( 2 ); + FREQUENCY_TO_PKT( freq, edict->s.surfaces[ 3 ] ); + } + } + +void Entity::DropToFloorEvent + ( + Event *ev + ) + + { + float range; + + if ( ev->NumArgs() > 0 ) + { + range = ev->GetFloat( 1 ); + } + else + { + range = WORLD_SIZE; + } + if ( !droptofloor( range ) ) + { + } + } + + +//************************************************************************* +// +// BIND code +// +//************************************************************************* + +qboolean Entity::isBoundTo + ( + const Entity *master + ) + + { + Entity *ent; + + if ( bind_info ) + { + for( ent = bind_info->bindmaster; ent != NULL; ent = ent->bind_info->bindmaster ) + { + if ( ent == master ) + { + return true; + } + } + } + + return false; + } + +void Entity::bind + ( + Entity *master, + qboolean use_my_angles + ) + + { + float mat[ 3 ][ 3 ]; + float local[ 3 ][ 3 ]; + Vector ang; + + assert( master ); + if ( !master ) + { + warning( "bind", "Null master entity" ); + return; + } + + if ( master == this ) + { + warning( "bind", "Trying to bind to oneself." ); + return; + } + + if ( !bind_info ) + bind_info = CreateBindInfo(); + + // unbind myself from my master + unbind(); + + bind_info->bindmaster = master; + edict->s.bindparent = master->entnum; + bind_info->bind_use_my_angles = use_my_angles; + + // We are now separated from our previous team and are either + // an individual, or have a team of our own. Now we can join + // the new bindmaster's team. Bindmaster must be set before + // joining the team, or we will be placed in the wrong position + // on the team. + joinTeam( master ); + + // calculate local angles + TransposeMatrix( bind_info->bindmaster->orientation, mat ); + R_ConcatRotations( mat, orientation, local ); + MatrixToEulerAngles( local, ang ); + setAngles( ang ); + + setOrigin( getParentVector( GetLocalOrigin() - bind_info->bindmaster->origin ) ); + + return; + } + +void Entity::unbind + ( + void + ) + + { + Entity *prev; + Entity *next; + Entity *last; + Entity *ent; + + + if ( !bind_info || !bind_info->bindmaster ) + return; + + //bindmaster = NULL; + + // Check this GAMEFIX - should it be origin? + SetLocalOrigin( edict->s.origin ); + localangles = Vector( edict->s.angles ); + + if ( !bind_info->teammaster ) + { + bind_info->bindmaster = NULL; + edict->s.bindparent = ENTITYNUM_NONE; + //Teammaster already has been freed + return; + } + + // We're still part of a team, so that means I have to extricate myself + // and any entities that are bound to me from the old team. + // Find the node previous to me in the team + prev = bind_info->teammaster; + + for( ent = bind_info->teammaster->bind_info->teamchain; ent && ( ent != this ); ent = ent->bind_info->teamchain ) + { + prev = ent; + } + + // If ent is not pointing to me, then something is very wrong. + assert( ent ); + if ( !ent ) + { + error( "unbind", "corrupt team chain\n" ); + } + + // Find the last node in my team that is bound to me. + // Also find the first node not bound to me, if one exists. + last = this; + for( next = bind_info->teamchain; next != NULL; next = next->bind_info->teamchain ) + { + if ( !next->isBoundTo( this ) ) + { + break; + } + + // Tell them I'm now the teammaster + next->bind_info->teammaster = this; + last = next; + } + + // disconnect the last member of our team from the old team + last->bind_info->teamchain = NULL; + + // connect up the previous member of the old team to the node that + // follow the last node bound to me (if one exists). + if ( bind_info->teammaster != this ) + { + prev->bind_info->teamchain = next; + if ( !next && ( bind_info->teammaster == prev ) ) + { + prev->bind_info->teammaster = NULL; + } + } + else if ( next ) + { + // If we were the teammaster, then the nodes that were not bound to me are now + // a disconnected chain. Make them into their own team. + for( ent = next; ent->bind_info->teamchain != NULL; ent = ent->bind_info->teamchain ) + { + ent->bind_info->teammaster = next; + } + next->bind_info->teammaster = next; + next->flags &= ~FL_TEAMSLAVE; + } + + // If we don't have anyone on our team, then clear the team variables. + if ( bind_info->teamchain ) + { + // make myself my own team + bind_info->teammaster = this; + } + else + { + // no longer a team + bind_info->teammaster = NULL; + } + + flags &= ~FL_TEAMSLAVE; + bind_info->bindmaster = NULL; + edict->s.bindparent = ENTITYNUM_NONE; + } + +void Entity::EventUnbind + ( + Event *ev + ) + + { + unbind(); + } + +void Entity::BindEvent + ( + Event *ev + ) + + { + Entity *ent; + + ent = ev->GetEntity( 1 ); + if ( ent ) + { + bind( ent ); + } + } + + +Vector Entity::getParentVector + ( + const Vector &vec + ) + + { + Vector pos; + + if ( !bind_info || !bind_info->bindmaster ) + { + return vec; + } + + pos[ 0 ] = vec * bind_info->bindmaster->orientation[ 0 ]; + pos[ 1 ] = vec * bind_info->bindmaster->orientation[ 1 ]; + pos[ 2 ] = vec * bind_info->bindmaster->orientation[ 2 ]; + + return pos; + } + +// +// Team methods +// + +void Entity::joinTeam + ( + Entity *teammember + ) + + { + Entity *ent; + Entity *master; + Entity *prev; + Entity *next; + + if ( !bind_info ) + bind_info = CreateBindInfo(); + + if ( bind_info->teammaster && ( bind_info->teammaster != this ) ) + { + quitTeam(); + } + + assert( teammember ); + if ( !teammember ) + { + warning( "joinTeam", "Null entity" ); + return; + } + + if ( !teammember->bind_info ) + teammember->bind_info = CreateBindInfo(); + + master = teammember->bind_info->teammaster; + if ( !master ) + { + master = teammember; + teammember->bind_info->teammaster = teammember; + teammember->bind_info->teamchain = this; + + // make anyone who's bound to me part of the new team + for( ent = bind_info->teamchain; ent != NULL; ent = ent->bind_info->teamchain ) + { + ent->bind_info->teammaster = master; + } + } + else + { + // skip past the chain members bound to the entity we're teaming up with + prev = teammember; + next = teammember->bind_info->teamchain; + if ( bind_info->bindmaster ) + { + // if we have a bindmaster, joing after any entities bound to the entity + // we're joining + while( next && (( Entity *)next)->isBoundTo( teammember ) ) + { + prev = next; + next = next->bind_info->teamchain; + } + } + else + { + // if we're not bound to someone, then put us at the end of the team + while( next ) + { + prev = next; + next = next->bind_info->teamchain; + } + } + + // make anyone who's bound to me part of the new team and + // also find the last member of my team + for( ent = this; ent->bind_info->teamchain != NULL; ent = ent->bind_info->teamchain ) + { + ent->bind_info->teamchain->bind_info->teammaster = master; + } + + prev->bind_info->teamchain = this; + ent->bind_info->teamchain = next; + } + + bind_info->teammaster = master; + flags |= FL_TEAMSLAVE; + } + +void Entity::quitTeam + ( + void + ) + + { + Entity *ent; + + if ( !bind_info || !bind_info->teammaster ) + { + return; + } + + if ( bind_info->teammaster == this ) + { + if ( !bind_info->teamchain->bind_info->teamchain ) + { + bind_info->teamchain->bind_info->teammaster = NULL; + } + else + { + // make next teammate the teammaster + for( ent = bind_info->teamchain; ent; ent = ent->bind_info->teamchain ) + { + ent->bind_info->teammaster = bind_info->teamchain; + } + } + + bind_info->teamchain->flags &= ~FL_TEAMSLAVE; + } + else + { + assert( flags & FL_TEAMSLAVE ); + assert( bind_info->teammaster->bind_info->teamchain ); + + ent = bind_info->teammaster; + while( ent->bind_info->teamchain != this ) + { + // this should never happen + assert( ent->bind_info->teamchain ); + + ent = ent->bind_info->teamchain; + } + + ent->bind_info->teamchain = bind_info->teamchain; + + if ( !bind_info->teammaster->bind_info->teamchain ) + { + bind_info->teammaster->bind_info->teammaster = NULL; + } + } + + bind_info->teammaster = NULL; + bind_info->teamchain = NULL; + flags &= ~FL_TEAMSLAVE; + } + +void Entity::EventQuitTeam + ( + Event *ev + ) + + { + quitTeam(); + } + + +void Entity::JoinTeam + ( + Event *ev + ) + + { + Entity *ent; + + ent = ev->GetEntity( 1 ); + if ( ent ) + { + joinTeam( ent ); + } + } + +void Entity::AddToSoundManager + ( + Event *ev + ) + + { + SoundMan.AddEntity( this ); + } + +inline qboolean Entity::HitSky + ( + const trace_t *trace + ) + + { + assert( trace ); + if ( trace->surfaceFlags & SURF_SKY ) + { + return true; + } + return false; + } + +qboolean Entity::HitSky + ( + void + ) + + { + return HitSky( &level.impact_trace ); + } + +void Entity::SetAngleEvent + ( + Event *ev + ) + { + Vector movedir; + + movedir = G_GetMovedir( ev->GetFloat( 1 ) ); + setAngles( movedir.toAngles() ); + } + +void Entity::NoLerpThisFrame( void ) +{ + gentity_t *checkEdict; + + edict->s.eFlags ^= EF_TELEPORT_BIT; + + // Make sure no one is standing on us + + for( checkEdict = active_edicts.next ; checkEdict != &active_edicts ; checkEdict = checkEdict->next ) + { + assert( checkEdict ); + assert( checkEdict->inuse ); + + if ( checkEdict->entity && checkEdict->entity->groundentity == edict ) + { + checkEdict->entity->groundentity = NULL; + } + } +} + +void Entity::Postthink + ( + void + ) + + { + } + + +//----------------------------------------------------- +// +// Name: TouchTriggersEvent +// Class: Entity +// +// Description: Specifies the entity can touch triggers by setting +// the touch triggers flag. +// +// Parameters: ev - the event that specifies whether to turn +// touch triggers on or off. If no param is specified, +// touchtriggers is true. +// +// Returns: None +//----------------------------------------------------- +void Entity::TouchTriggersEvent(Event *ev) +{ + if ( ( ev->NumArgs() == 0 ) || ( ev->GetBoolean( 1 ) == true ) ) + { + flags |= FL_TOUCH_TRIGGERS; + turnThinkOn(); + } + else + { + flags &= ~FL_TOUCH_TRIGGERS; + } + +} + +void Entity::IncreaseShotCount + ( + Event *ev + ) + { + int parent_ent_num = edict->s.parent; + Entity *parent = ( Entity * )G_GetEntity( parent_ent_num ); + + if ( !parent->isSubclassOf(Actor) ) + return; + + Actor* act; + act = (Actor*)parent; + + act->shotsFired++; + } + +void Entity::DeathSinkStart + ( + Event *ev + ) + { + float time; + + // Stop the sink when we can't be seen anymore + + if ( ( maxs[2] >= 0.0f ) && ( maxs[2] < 200.0f ) ) + time = maxs[2] / 20.0f; + else + time = 1.0f; + + PostEvent( EV_Remove, time ); + + // Start the sinking + + ProcessEvent( EV_DeathSink ); + } + +void Entity::DeathSink + ( + Event *ev + ) + { + // Sink just a little + + origin[2] -= 1.0f; + setOrigin( origin ); + + // Make sure the sink happens again next frame + + PostEvent( EV_DeathSink, FRAMETIME ); + } + +void Entity::LookAtMe + ( + Event *ev + ) + { + if ( ev->NumArgs() > 0 ) + look_at_me = ev->GetBoolean( 1 ); + else + look_at_me = true; + } + +void Entity::ProjectilesCanStickToMe( Event *ev ) +{ + projectilesCanStickToMe = ev->GetBoolean( 1 ); +} + +void Entity::VelocityModified + ( + void + ) + { + } + +//-------------------------------------------------------------- +// +// Name: DetachAllChildren +// Class: Entity +// +// Description: Detaches all attached models to this entity +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Entity::DetachAllChildren(Event *ev) + { + if ( !bind_info ) // Abort if bind_info is NULL for some reason + return; + + for ( int i=0; ichildren[i] == ENTITYNUM_NONE ) + continue; + + Entity *ent = ( Entity * )G_GetEntity( bind_info->children[i] ); + if ( ent ) + ent->PostEvent( EV_Remove, 0.0f ); + } + } + +inline void Entity::Archive( Archiver &arc ) +{ + int tempInt; + qboolean is_archived; + qboolean true_bool = true; + qboolean false_bool = false; + + + Listener::Archive( arc ); + + arc.ArchiveVector( &_localOrigin ); + + _lastTouchedList.Archive( arc ); + + arc.ArchiveBool( &_fulltrace ); + arc.ArchiveInteger( &_groupID ); + + // Don't archive entnum, it will be set elsewhere + //int entnum; + + G_ArchiveEdict( arc, edict ); + + arc.ArchiveString(&_archetype); + if(arc.Loading()) + { + edict->s.archeTypeIndex = gi.archetypeindex(_archetype); + } + + arc.ArchiveBool( &_missionObjective ); + arc.ArchiveString( &_targetPos ); + arc.ArchiveBool( &_networkDetail ); + + if(arc.Loading()) + { + edict->s.missionObjective = _missionObjective; + if(_missionObjective == true) + G_AddEntityToExtraList(entnum); + } + + // Don't archive client + //gclient_t *client; + + arc.ArchiveInteger( &spawnflags ); + + arc.ArchiveString( &model ); + + if ( arc.Loading() && model.length() ) + setModel( model.c_str() ); + + arc.ArchiveVector( &total_delta ); + arc.ArchiveVector( &mins ); + arc.ArchiveVector( &maxs ); + arc.ArchiveVector( &absmin ); + arc.ArchiveVector( &absmax ); + arc.ArchiveVector( ¢roid ); + arc.ArchiveVector( &velocity ); + arc.ArchiveVector( &avelocity ); + arc.ArchiveVector( &origin ); + arc.ArchiveVector( &angles ); + arc.ArchiveVector( &size ); + arc.ArchiveInteger( &movetype ); + arc.ArchiveInteger( &mass ); + arc.ArchiveFloat( &gravity ); + arc.ArchiveRaw( orientation, sizeof( orientation ) ); + + arc.ArchiveVector( &localangles ); + + if ( arc.Saving() ) + { + if ( groundentity ) + { + tempInt = groundentity - g_entities; + } + else + { + tempInt = -1; + } + } + + arc.ArchiveInteger( &tempInt ); + + if ( arc.Loading() ) + { + if ( tempInt == -1 ) + { + groundentity = NULL; + } + else + { + groundentity = &g_entities[ tempInt ]; + } + } + + arc.ArchiveRaw( &groundplane, sizeof( groundplane ) ); + arc.ArchiveInteger( &groundcontents ); + + arc.ArchiveInteger( &numsurfaces ); + + arc.ArchiveFloat( &lightRadius ); + + arc.ArchiveString( &target ); + arc.ArchiveString( &targetname ); + arc.ArchiveString( &killtarget ); + + if ( arc.Loading() ) + { + // Don't set this here, they are handled in the world + //SetTargetName( targetname.c_str() ); + + // Not needed + //SetTarget( target.c_str() ); + } + + arc.ArchiveFloat( &health ); + arc.ArchiveFloat( &max_health ); + arc.ArchiveInteger( &deadflag ); + arc.ArchiveInteger( &flags ); + + arc.ArchiveInteger( &watertype ); + arc.ArchiveInteger( &waterlevel ); + + ArchiveEnum( takedamage, damage_t ); + arc.ArchiveInteger( &damage_type ); + + arc.ArchiveBoolean( &look_at_me ); + arc.ArchiveBool( &projectilesCanStickToMe ); + + arc.ArchiveString( &explosionModel ); + + entityVars.Archive( arc ); + + arc.ArchiveUnsigned( &_affectingViewModes ); + + arc.ArchiveVector( &watch_offset ); + + // Pluggable modules + + if ( arc.Loading() ) + { + arc.ArchiveBoolean( &is_archived ); + + if ( is_archived ) + { + animate = new Animate( this ); + arc.ArchiveObject( animate ); + } + + arc.ArchiveBoolean( &is_archived ); + + if ( is_archived ) + { + mover = new Mover( this ); + arc.ArchiveObject( mover ); + } + + arc.ArchiveBoolean( &is_archived ); + + if ( is_archived ) + { + bind_info = CreateBindInfo(); + bind_info->Archive( arc ); + } + + arc.ArchiveBoolean( &is_archived ); + + if ( is_archived ) + { + morph_info = CreateMorphInfo(); + morph_info->Archive( arc ); + } + } + else + { + if ( animate ) + { + arc.ArchiveBoolean( &true_bool ); + arc.ArchiveObject( animate ); + } + else + { + arc.ArchiveBoolean( &false_bool ); + } + + if ( mover ) + { + arc.ArchiveBoolean( &true_bool ); + arc.ArchiveObject( mover ); + } + else + { + arc.ArchiveBoolean( &false_bool ); + } + + if ( bind_info ) + { + arc.ArchiveBoolean( &true_bool ); + bind_info->Archive( arc ); + } + else + { + arc.ArchiveBoolean( &false_bool ); + } + + if ( morph_info ) + { + arc.ArchiveBoolean( &true_bool ); + morph_info->Archive( arc ); + } + else + { + arc.ArchiveBoolean( &false_bool ); + } + } + + if ( arc.Saving() ) + { + if ( ObjectProgram ) + { + arc.ArchiveBoolean( &true_bool ); + ObjectProgram->Archive( arc ); + } + else + { + arc.ArchiveBoolean( &false_bool ); + } + } + else + { + arc.ArchiveBoolean( &is_archived ); + + if ( is_archived ) + ObjectProgram->Archive( arc ); + else + ObjectProgram = NULL; + } + + // Archiving of the Damage Modification System + if ( arc.Saving() ) + { + bool exists; + int type; + if ( damageModSystem ) + { + exists = true; + arc.ArchiveBool( &exists ); + + int numobj = damageModSystem->getModifierList().NumObjects(); + arc.ArchiveInteger( &numobj ); + for ( tempInt = 1; tempInt <= numobj; tempInt++ ) + { + DamageModifier *dmod = damageModSystem->getModifierList().ObjectAt ( tempInt ); + if ( dmod ) + { + type = (int)dmod->getType(); + arc.ArchiveInteger( &type ); + dmod->Archive(arc); + } + else + { + // How did a NULL get in the container?? + assert( 0 ); + } + } + } + else + { + exists = false; + arc.ArchiveBool( &exists ); + } + } + else // Loading + { + bool exists; + int numobj, type; + arc.ArchiveBool( &exists ); + if ( exists ) + { + damageModSystem = new DamageModificationSystem(); + arc.ArchiveInteger( &numobj ); + for ( tempInt = 1; tempInt <= numobj; tempInt++ ) + { + arc.ArchiveInteger( &type ); + DamageModifier *newMod = 0; + switch ( type ) + { + case TIKI_NAME: + newMod = new DamageModifierTikiName(); + break; + case NAME: + newMod = new DamageModifierName(); + break; + case GROUP: + newMod = new DamageModifierGroup(); + break; + case ACTOR_TYPE: + newMod = new DamageModifierActorType(); + break; + case TARGETNAME: + newMod = new DamageModifierTargetName(); + break; + case DAMAGE_TYPE: + newMod = new DamageModifierDamageType(); + break; + } + + if ( newMod ) + { + newMod->Archive(arc); + + damageModSystem->addDamageModifier( newMod ); + } + } + } + } + + // Archive the useData member if it exists + if ( arc.Saving() ) + { + bool exists = false; + if ( useData ) + { + exists = true; + arc.ArchiveBool( &exists ); + useData->Archive(arc); + } + else + arc.ArchiveBool( &exists ); + } + else // Loading + { + bool exists; + arc.ArchiveBool( &exists ); + if ( exists ) + { + useData = new UseData(); + useData->Archive(arc); + } + } + + + +} + +// Animate interface + +inline int Entity::CurrentFrame + ( + bodypart_t part + ) + + { + if ( animate ) + return animate->CurrentFrame( part ); + else + return 0; + } + +inline int Entity::CurrentAnim + ( + bodypart_t part + ) + + { + if ( animate ) + return animate->CurrentAnim( part ); + else + return 0; + } + +void Entity::PassToAnimate + ( + Event *ev + ) + + { + Event *new_event; + + if ( !animate ) + animate = new Animate( this ); + + new_event = new Event( ev ); + animate->ProcessEvent( new_event ); + } + +void Entity::SetObjectProgram + ( + Event *ev + ) + { + ObjectProgram = new Program; + + if ( !ObjectProgram ) + return; + + ObjectProgram->Load( ev->GetString( 1 ) ); + + + //CThread *gamescript = 0; + + //gamescript = Director.CreateThread( "obj_main" , ObjectProgram ); + //gamescript->DelayedStart( 0 ); + + } + +void Entity::SetWatchOffset( Event *ev ) +{ + watch_offset = ev->GetVector( 1 ); +} + + +void Entity::ExecuteProgram + ( + Event *ev + ) + { + float exeTime = 0; + + if ( !ObjectProgram ) + return; + + if ( ev->NumArgs() > 0 ) + exeTime = ev->GetFloat( 1 ); + + CThread *gamescript = 0; + gamescript = Director.CreateThread( "obj_main" , ObjectProgram ); + gamescript->DelayedStart( exeTime ); + } + + +// BindInfo interface + +inline BindInfo *CreateBindInfo( void ) + { + BindInfo *new_bind_info; + + new_bind_info = new BindInfo; + + if ( !new_bind_info ) + gi.Error( ERR_DROP, "Couldn't alloc BindInfo" ); + + return new_bind_info; + } + +// MorphInfo interface + +void Entity::MorphEvent + ( + Event *ev + ) + { + str morph_target_name; + int morph_index; + float final_percent = 100; + float morph_time = 0.5; + qboolean return_to_zero = false; + int i; + qboolean override = true; + int morph_channel = MORPH_CHAN_NONE; + qboolean channel_being_used; + qboolean override_all = false; + qboolean matching_channel; + qboolean unmorph = false; + + // Get parms + + morph_target_name = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + final_percent = ev->GetFloat( 2 ); + if ( ev->NumArgs() > 2 ) + morph_time = ev->GetFloat( 3 ); + if ( ev->NumArgs() > 3 ) + return_to_zero = ev->GetBoolean( 4 ); + if ( ev->NumArgs() > 4 ) + override = ev->GetBoolean( 5 ); + if ( ev->NumArgs() > 5 ) + morph_channel = ev->GetInteger( 6 ); + + // See if this is an expression + + if ( strnicmp( morph_target_name.c_str(), "exp_", 4 ) == 0 ) + { + // Process this expression + + dtikimorphtarget_t *morph_targets; + Event *new_event; + const char *morph_name; + int number_of_morph_targets; + + morph_targets = gi.GetExpression( edict->s.modelindex, morph_target_name.c_str(), &number_of_morph_targets ); + + if ( morph_targets ) + { + for ( i = 0 ; i < number_of_morph_targets ; i++ ) + { + morph_name = gi.Morph_NameForNum( edict->s.modelindex, morph_targets[ i ].morph_index ); + + if ( morph_name ) + { + new_event = new Event( EV_Morph ); + new_event->AddString( morph_name ); + new_event->AddFloat( morph_targets[ i ].percent ); + new_event->AddFloat( morph_time ); + new_event->AddInteger( return_to_zero ); + new_event->AddInteger( override ); + new_event->AddInteger( morph_channel ); + ProcessEvent( new_event ); + } + } + + return; + } + } + + // Find this morph target + + morph_index = gi.Morph_NumForName( edict->s.modelindex, morph_target_name.c_str() ); + + // Check unmorphing stuff + + if ( ( stricmp( morph_target_name.c_str(), "morph_base" ) == 0 ) || + ( stricmp( morph_target_name.c_str(), "morph_mouth_base" ) == 0 ) || + ( stricmp( morph_target_name.c_str(), "morph_brows_base" ) == 0 ) || + ( stricmp( morph_target_name.c_str(), "morph_eyes_base" ) == 0 ) ) + unmorph = true; + + if ( morph_index == -1 && !unmorph ) + return; + + if ( morph_channel == MORPH_CHAN_NONE ) + morph_channel = GetMorphChannel( morph_target_name.c_str() ); + + // Make sure we have a morph controller block + + if ( !morph_info ) + morph_info = CreateMorphInfo(); + + // Deal with current morphs + + channel_being_used = false; + + if ( unmorph && ( morph_channel == MORPH_CHAN_NONE ) ) + override_all = true; + + for( i = 0 ; i < NUM_MORPH_CONTROLLERS ; i++ ) + { + if ( morph_info->controllers[ i ].index != -1 ) + { + // See if this is a matching channel + + if ( MorphChannelMatches( morph_channel, morph_info->controllers[ i ].channel ) ) + matching_channel = true; + else + matching_channel = false; + + if ( override_all || ( matching_channel && override ) ) + { + if ( morph_info->controllers[ i ].final_percent != 0 ) + { + // Override this morph + + morph_info->controllers[ i ].speed = ( morph_info->controllers[ i ].current_percent * FRAMETIME ) / morph_time; + morph_info->controllers[ i ].final_percent = 0; + + StartMorphController(); + } + } + else if ( matching_channel ) + { + channel_being_used = true; + } + } + } + + // If just unmorphing stuff, stop here + + if ( unmorph ) + return; + + // If not overriding and the channel is already being used, stop here + + if ( !override && channel_being_used ) + return; + + // Find a free morph controller & add this morph + + for( i = 0 ; i < NUM_MORPH_CONTROLLERS ; i++ ) + { + if ( morph_info->controllers[ i ].index == -1 ) + { + morph_info->controllers[ i ].index = morph_index; + morph_info->controllers[ i ].current_percent = 0; + morph_info->controllers[ i ].final_percent = final_percent; + morph_info->controllers[ i ].speed = (final_percent * FRAMETIME ) / morph_time; + morph_info->controllers[ i ].return_to_zero = return_to_zero; + morph_info->controllers[ i ].channel = morph_channel; + + StartMorphController(); + + break; + } + } + } + +void Entity::UnmorphEvent + ( + Event *ev + ) + { + str morph_target_name; + int morph_index; + int i; + float morph_time = 0.5; + + morph_target_name = ev->GetString( 1 ); + + if ( ev->NumArgs() > 2 ) + morph_time = ev->GetFloat( 3 ); + + // See if this is an expression + + if ( strnicmp( morph_target_name.c_str(), "exp_", 4 ) == 0 ) + { + // Process this expression + + dtikimorphtarget_t *morph_targets; + Event *new_event; + const char *morph_name; + int number_of_morph_targets; + + morph_targets = gi.GetExpression( edict->s.modelindex, morph_target_name.c_str(), &number_of_morph_targets ); + + if ( morph_targets ) + { + for ( i = 0 ; i < number_of_morph_targets ; i++ ) + { + morph_name = gi.Morph_NameForNum( edict->s.modelindex, morph_targets[ i ].morph_index ); + + if ( morph_name ) + { + new_event = new Event( EV_Unmorph ); + new_event->AddString( morph_name ); + new_event->AddFloat( morph_time ); + ProcessEvent( new_event ); + } + } + + return; + } + } + + morph_index = gi.Morph_NumForName( edict->s.modelindex, morph_target_name.c_str() ); + + if ( morph_index == -1 || !morph_info ) + return; + + // Find this morph controller + + for( i = 0 ; i < NUM_MORPH_CONTROLLERS ; i++ ) + { + if ( ( morph_info->controllers[ i ].index == morph_index ) && ( morph_info->controllers[ i ].final_percent != 0 ) ) + { + morph_info->controllers[ i ].speed = (morph_info->controllers[ i ].final_percent * FRAMETIME ) / morph_time; + morph_info->controllers[ i ].final_percent = 0.0f; + + StartMorphController(); + + break; + } + } + } + +void Entity::MorphControl + ( + Event *ev + ) + { + int i; + qboolean process_next_frame = false; + morph_t *controller; + + for( i = 0 ; i < NUM_MORPH_CONTROLLERS ; i++ ) + { + controller = &morph_info->controllers[ i ]; + + if ( controller->index != -1 ) + { + // Lerp the morph percent + + if ( controller->current_percent != controller->final_percent ) + { + if ( controller->current_percent < controller->final_percent ) + { + controller->current_percent += controller->speed; + + if ( controller->current_percent > controller->final_percent ) + controller->current_percent = controller->final_percent; + } + else + { + controller->current_percent -= controller->speed; + + if ( controller->current_percent < controller->final_percent ) + controller->current_percent = controller->final_percent; + } + } + + if ( controller->current_percent == 0.0f ) + { + controller->index = -1; + controller->current_percent = 0.0f; + } + + // Return to zero if necessary + + if ( controller->current_percent == controller->final_percent && controller->return_to_zero ) + controller->final_percent = 0.0f; + + if ( controller->current_percent != controller->final_percent ) + process_next_frame = true; + } + + // Copy to edict + + edict->s.morph_controllers[ i ].index = controller->index; + edict->s.morph_controllers[ i ].percent = controller->current_percent / 100.0f; + } + + if ( process_next_frame ) + PostEvent( EV_MorphControl, FRAMETIME ); + else + morph_info->controller_on = false; + } + +int Entity::GetMorphChannel + ( + const char *morph_name + ) + { + int morph_channel; + + if ( stricmp( morph_name, "morph_a-i" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_c-t" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_e" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_f-v" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_l-th" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_m-b-p" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_o" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_q-w" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_u" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_frown" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_sneer-l" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_sneer-r" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + else if ( stricmp( morph_name, "morph_mouth_base" ) == 0 ) + morph_channel = MORPH_CHAN_MOUTH; + + else if ( stricmp( morph_name, "morph_brows-up" ) == 0 ) + morph_channel = MORPH_CHAN_BROW; + else if ( stricmp( morph_name, "morph_brows_base" ) == 0 ) + morph_channel = MORPH_CHAN_BROW; + + else if ( stricmp( morph_name, "morph_brow-ldn" ) == 0 ) + morph_channel = MORPH_CHAN_LEFT_BROW; + + else if ( stricmp( morph_name, "morph_brow-rdn" ) == 0 ) + morph_channel = MORPH_CHAN_RIGHT_BROW; + + else if ( stricmp( morph_name, "morph_lid-lshut" ) == 0 ) + morph_channel = MORPH_CHAN_LEFT_LID; + + else if ( stricmp( morph_name, "morph_lid-rshut" ) == 0 ) + morph_channel = MORPH_CHAN_RIGHT_LID; + + else if ( stricmp( morph_name, "morph_eyeshut" ) == 0 ) + morph_channel = MORPH_CHAN_EYES; + else if ( stricmp( morph_name, "morph_eyes_base" ) == 0 ) + morph_channel = MORPH_CHAN_EYES; + + else + morph_channel = MORPH_CHAN_NONE; + + return morph_channel; + } + +void Entity::StartMorphController + ( + void + ) + { + if ( !morph_info->controller_on ) + { + morph_info->controller_on = true; + CancelEventsOfType( EV_MorphControl ); + PostEvent( EV_MorphControl, FRAMETIME ); + } + } + + +void Entity::SetAnimOnAttachedModel + ( + const str &AnimName, + const str &TagName + ) + + { + int tag_num = gi.Tag_NumForName( this->edict->s.modelindex , TagName.c_str() ); + + if ( bind_info ) + { + Entity *attachment = 0; + + for ( int i = 0; i < MAX_MODEL_CHILDREN; i++ ) + { + + //Check for valid entities + if( bind_info->children[i] == ENTITYNUM_NONE ) + continue; + + attachment = ( Entity * )G_GetEntity( bind_info->children[i] ); + + if ( attachment->edict->s.tag_num == tag_num ) + { + if ( !attachment->animate ) + { + attachment->animate = new Animate; + } + + int anim_num = gi.Anim_Random ( attachment->edict->s.modelindex, AnimName.c_str() ); + if ( anim_num != -1 ) + { + attachment->animate->NewAnim( anim_num ); + } + } + } + } + } + +void Entity::SetAnimOnAttachedModel + ( + Event *ev + ) + { + str attachmentAnim = ev->GetString( 1 ); + str TagName = ev->GetString( 2 ); + + SetAnimOnAttachedModel( attachmentAnim , TagName ); + } + + +void Entity::SetCinematicAnim + ( + const str &AnimName + ) + { + if (!animate) + { + animate = new Animate( this ); + } + + int anim_num = gi.Anim_Random ( edict->s.modelindex, AnimName.c_str() ); + if ( anim_num != -1 ) + { + animate->NewAnim( anim_num ); + animate->SetAnimDoneEvent( new Event (EV_CinematicAnimDone) ); + } + + gravity = 0.0f; + edict->contents = CONTENTS_SETCLIP ; + edict->clipmask = MASK_SETCLIP ; + } + + +void Entity::SetCinematicAnim + ( + Event *ev + ) + { + str animName = ev->GetString( 1 ); + SetCinematicAnim( animName); + } + + +void Entity::CinematicAnimDone + ( + void + ) + { + gravity = 1.0f ; + edict->contents &= ~CONTENTS_SETCLIP ; + edict->clipmask = MASK_SOLID ; + } + +void Entity::CinematicAnimDone + ( + Event *ev + ) + { + CinematicAnimDone(); + } + + +void Entity::SetEntityExplosionModel + ( + Event *ev + ) + { + explosionModel = ev->GetString( 1 ); + } + +//---------------------------------------------------------------- +// Name: ProjectileAtk +// Class: Entity +// +// Description: launches a projectile either from the player +// or from a tag +// +// Parameters: event ev contains projectile name and an +// optional tag name +// +// Returns: None +//---------------------------------------------------------------- +void Entity::ProjectileAtk( Event *ev ) +{ + str projectileName = ev->GetString( 1 ); + + Vector position; + Vector direction; + + if ( ev->NumArgs() > 1 ) + { + // Projectile name, tagName + str tagName( ev->GetString( 2 ) ); + GetTag( tagName.c_str(), &position, &direction ); + } + else + { + // Projectile name, Player is the target + position = origin; + gentity_t *ed; + Entity* enemy = NULL; + for( int i = 0 ; i < game.maxclients; i++ ) + { + ed = &g_entities[ i ]; + + if ( !ed->inuse || !ed->entity ) + continue; + + enemy = ed->entity; + } + if ( enemy ) + { + direction = enemy->origin - origin; + direction.normalize(); + } + else + { + angles.AngleVectors( &direction, NULL, NULL ); + } + } + + ProjectileAttack( position, direction, this, projectileName.c_str(), 1.0f, 0.0f ); +} + +//---------------------------------------------------------------- +// Name: ProjectileAttackPoint +// Class: Entity +// +// Description: launches a projectile towards the given point +// +// Parameters: event ev contains projectile name and entity name +// +// Returns: None +//---------------------------------------------------------------- +void Entity::ProjectileAttackPoint( Event *ev ) +{ + const str projectileName( ev->GetString( 1 ) ); + const Vector targetPosition( ev->GetVector( 2 ) ); + + Vector direction( targetPosition - origin ); + direction.normalize(); + Projectile *projectile = ProjectileAttack( origin, direction, this, projectileName.c_str(), 1.0f, 0.0f ); + if ( ev->NumArgs() > 2 ) + { + Angle launchAngle( ev->GetFloat( 3 ) ); + Trajectory trajectory( origin, targetPosition, launchAngle, projectile->gravity * sv_currentGravity->value ); + + projectile->velocity = trajectory.GetInitialVelocity(); + Vector launchDirection( projectile->velocity ); + launchDirection.normalize(); + projectile->angles = launchDirection.toAngles(); + + projectile->CancelEventsOfType( EV_Projectile_Explode ); + Event *event = new Event( EV_Projectile_Explode ); + if ( ev->NumArgs() > 3 ) + { + projectile->PostEvent( event, ev->GetFloat( 4 ) ); + } + else + { + projectile->PostEvent( event, trajectory.GetTravelTime() ); + } + } +} + +//---------------------------------------------------------------- +// Name: ProjectileAttackEntity +// Class: Entity +// +// Description: launches a projectile either from the named entity +// +// Parameters: event ev contains projectile name and entity name +// +// Returns: None +//---------------------------------------------------------------- +void Entity::ProjectileAttackEntity( Event *ev ) +{ + const str projectileName( ev->GetString( 1 ) ); + + Entity *target = ev->GetEntity( 2 ); + if ( target ) + { + Vector direction( target->origin - origin ); + direction.normalize(); + Projectile *projectile = ProjectileAttack( origin, direction, this, projectileName.c_str(), 1.0f, 0.0f ); + + if ( !projectile ) + return; + + if ( ev->NumArgs() > 2 ) + { + Angle launchAngle( ev->GetFloat( 3 ) ); + Trajectory trajectory( origin, target->centroid, launchAngle, projectile->gravity * -sv_currentGravity->value ); + + projectile->velocity = trajectory.GetInitialVelocity(); + Vector launchDirection( projectile->velocity ); + launchDirection.normalize(); + projectile->angles = launchDirection.toAngles(); + + projectile->CancelEventsOfType( EV_Projectile_Explode ); + Event *event = new Event( EV_Projectile_Explode ); + if ( ev->NumArgs() > 3 ) + { + projectile->PostEvent( event, ev->GetFloat( 4 ) ); + } + else + { + projectile->PostEvent( event, trajectory.GetTravelTime() ); + } + } + } +} + +//---------------------------------------------------------------- +// Name: ProjectileAttackFromTag +// Class: Entity +// +// Description: launches a projectile either from the named tag +// +// Parameters: event ev contains projectile name and tag name +// +// Returns: None +//---------------------------------------------------------------- +void Entity::ProjectileAttackFromTag( Event *ev ) +{ + str projectileName( ev->GetString( 1 ) ); + str tagName( ev->GetString( 2 ) ); + + Vector position; + Vector direction; + GetTag( tagName.c_str(), &position, &direction ); + + float speed = 0.0f; + if ( ev->NumArgs() > 3 ) + { + speed = ev->GetFloat( 4 ); + } + + Projectile *projectile = ProjectileAttack( position, direction, this, projectileName.c_str(), 1.0f, speed ); + if ( ev->NumArgs() > 4 ) + { + projectile->CancelEventsOfType( EV_Projectile_Explode ); + Event *event = new Event( EV_Projectile_Explode ); + projectile->PostEvent( event, ev->GetFloat( 5 ) ); + } +} + +//---------------------------------------------------------------- +// Name: ProjectileAttackFromPoint +// Class: Entity +// +// Description: launches a projectile either from the desired +// location facing the desired direction +// +// Parameters: event ev contains projectile name, the +// position and direction for the new projectile +// +// Returns: None +//---------------------------------------------------------------- +void Entity::ProjectileAttackFromPoint( Event *ev ) +{ + str projectileName( ev->GetString( 1 ) ); + + Vector position( ev->GetVector( 2 ) ); + Vector direction( ev->GetVector( 3 ) ); + + float speed = 0.0f; + if ( ev->NumArgs() > 3 ) + { + speed = ev->GetFloat( 4 ); + } + + Vector forward; + direction.AngleVectors( &forward ); + Projectile *projectile = ProjectileAttack( origin + position, forward, this, projectileName.c_str(), 1.0f, speed ); + if ( ev->NumArgs() > 4 ) + { + projectile->CancelEventsOfType( EV_Projectile_Explode ); + Event *event = new Event( EV_Projectile_Explode ); + projectile->PostEvent( event, ev->GetFloat( 5 ) ); + } +} + +void Entity::TraceAtk + ( + Event *ev + ) + { + Vector position; + Vector forward; + Vector right; + Vector up; + float range; + float damage; + float knockback = 0.0; + str means_of_death_string = "bullet"; + int means_of_death; + //int offsetPitch = 0; + + damage = ev->GetFloat( 1 ); + range = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + means_of_death_string = ev->GetString( 3 ); + + if ( ev->NumArgs() > 3 ) + knockback = ev->GetFloat( 4 ); + + if ( ev->NumArgs() > 4 ) + { + str tag_name; + + tag_name = ev->GetString( 5 ); + + GetTag( tag_name.c_str(), &position, &forward, &right, &up ); + } + else + { + position = origin; + angles.AngleVectors( &forward, &right, &up ); + } + + means_of_death = MOD_NameToNum( means_of_death_string ); + + /* + if ( ev->NumArgs() > 5 ) + offsetPitch = ev->GetInteger( 6 ); + + if ( offsetPitch ) + { + Vector ForAngles; + ForAngles = forward.toAngles(); + + ForAngles[YAW] = AngleNormalize180( ForAngles[YAW] ); + ForAngles[PITCH] = AngleNormalize180( ForAngles[PITCH] ); + ForAngles[ROLL] = AngleNormalize180( ForAngles[ROLL] ); + offsetPitch = AngleNormalize180(offsetPitch); + + ForAngles[PITCH] += offsetPitch; + + + ForAngles[YAW] = AngleNormalize360( ForAngles[YAW] ); + ForAngles[PITCH] = AngleNormalize360( ForAngles[PITCH] ); + ForAngles[ROLL] = AngleNormalize360( ForAngles[ROLL] ); + + ForAngles.AngleVectors( &forward ); + } + */ + + BulletAttack( position, forward, right, up, range, damage, knockback, 0, means_of_death, vec_zero, 1, this ); + } + +qboolean Entity::MorphChannelMatches + ( + int morph_channel1, + int morph_channel2 + ) + { + // Nothing matches with non + + if ( morph_channel1 == MORPH_CHAN_NONE ) + return false; + + // See if they match exactly + + if ( morph_channel1 == morph_channel2 ) + return true; + + // Check special cases + + if ( ( morph_channel1 == MORPH_CHAN_BROW ) && ( ( morph_channel2 == MORPH_CHAN_LEFT_BROW ) || ( morph_channel2 == MORPH_CHAN_RIGHT_BROW ) ) ) + return true; + + if ( ( morph_channel2 == MORPH_CHAN_BROW ) && ( ( morph_channel1 == MORPH_CHAN_LEFT_BROW ) || ( morph_channel1 == MORPH_CHAN_RIGHT_BROW ) ) ) + return true; + + if ( ( morph_channel1 == MORPH_CHAN_EYES ) && ( ( morph_channel2 == MORPH_CHAN_LEFT_LID ) || ( morph_channel2 == MORPH_CHAN_RIGHT_LID ) ) ) + return true; + + if ( ( morph_channel2 == MORPH_CHAN_EYES ) && ( ( morph_channel1 == MORPH_CHAN_LEFT_LID ) || ( morph_channel1 == MORPH_CHAN_RIGHT_LID ) ) ) + return true; + + return false; + } + +inline MorphInfo *CreateMorphInfo( void ) + { + MorphInfo *new_morph_info; + + new_morph_info = new MorphInfo; + + if ( !new_morph_info ) + gi.Error( ERR_DROP, "Couldn't alloc MorphInfo" ); + + return new_morph_info; + } + +#define CONTENTS_OPERATION_ADD 0 +#define CONTENTS_OPERATION_SUBTRACT 1 +#define CONTENTS_OPERATION_SET 2 + +void Entity::Contents + ( + Event* ev + ) + { + if(ev == 0) + return; + + int operation; + const char* contentType; + unsigned long contents = 0; + unsigned long newFlags = 0; + + contents = getContents(); + + for(int i = 1; i <= ev->NumArgs(); i++) + { + contentType = ev->GetString(i); + + if( ( contentType == 0 ) || ( strlen( contentType ) == 0 ) ) + continue; + + // Get operation type + + if(contentType[0] == '+') + { + operation = CONTENTS_OPERATION_ADD; + contentType++; + } + else if(contentType[0] == '-') + { + operation = CONTENTS_OPERATION_SUBTRACT; + contentType++; + } + else + operation = CONTENTS_OPERATION_SET; + + // Get contents type + + if(stricmp(contentType, "shootable") == 0) + newFlags = CONTENTS_SHOOTABLE_ONLY; + else if(stricmp(contentType, "targetable") == 0) + { + newFlags = CONTENTS_TARGETABLE; + setSolidType(SOLID_BBOX); + } + else if(stricmp(contentType, "body") == 0) + newFlags = CONTENTS_BODY; + else if(stricmp(contentType, "solid") == 0) + newFlags = CONTENTS_SOLID; + else if(stricmp(contentType, "usable") == 0) + newFlags = CONTENTS_USABLE; + else if(stricmp(contentType, "setclip") == 0) + newFlags = CONTENTS_SETCLIP; + else if(stricmp(contentType, "playerclip") == 0) + newFlags = CONTENTS_PLAYERCLIP; + else if(stricmp(contentType, "monsterclip") == 0) + newFlags = CONTENTS_MONSTERCLIP; + else if(stricmp(contentType, "cameraclip") == 0) + newFlags = CONTENTS_CAMERACLIP; + else if(stricmp(contentType, "weaponclip") == 0) + newFlags = CONTENTS_WEAPONCLIP; + else if(stricmp(contentType, "corpse") == 0) + newFlags = CONTENTS_CORPSE; + else if(stricmp(contentType, "all") == 0) + newFlags = 0xFFFFFFFF; + + // Change contents appropriatly + + if ( operation == CONTENTS_OPERATION_ADD ) + contents |= newFlags; + else if ( operation == CONTENTS_OPERATION_SUBTRACT ) + contents &= ~newFlags; + else if ( operation == CONTENTS_OPERATION_SET ) + contents = newFlags; + } + + setContents(contents); + link(); + } + +void Entity::setMask( Event* ev ) +{ + int i; + int operation; + const char* maskType; + unsigned long currentMask; + unsigned long newMask = 0; + + + // Get the current mask + + currentMask = edict->clipmask; + + // Loop through all of the parms and change the mask appropriately + + for( i = 1 ; i <= ev->NumArgs() ; i++ ) + { + maskType = ev->GetString( i ); + + if( ( !maskType ) || ( strlen( maskType ) == 0 ) ) + continue; + + // Get operation type + + if ( maskType[0] == '+' ) + { + operation = CONTENTS_OPERATION_ADD; + maskType++; + } + else if ( maskType[0] == '-' ) + { + operation = CONTENTS_OPERATION_SUBTRACT; + maskType++; + } + else + { + operation = CONTENTS_OPERATION_SET; + } + + // Get contents type + + // Masks + + if ( stricmp( maskType, "solid" ) == 0 ) + newMask = MASK_SOLID; + else if ( stricmp( maskType, "usable" ) == 0 ) + newMask = MASK_USABLE; + else if ( stricmp( maskType, "playersolid" ) == 0 ) + newMask = MASK_PLAYERSOLID; + else if ( stricmp( maskType, "deadsolid" ) == 0 ) + newMask = MASK_DEADSOLID; + else if ( stricmp( maskType, "monstersolid" ) == 0 ) + newMask = MASK_MONSTERSOLID; + else if ( stricmp( maskType, "water" ) == 0 ) + newMask = MASK_WATER; + else if ( stricmp( maskType, "opaque" ) == 0 ) + newMask = MASK_OPAQUE; + else if ( stricmp( maskType, "shot" ) == 0 ) + newMask = MASK_SHOT; + else if ( stricmp( maskType, "projectile" ) == 0 ) + newMask = MASK_PROJECTILE; + else if ( stricmp( maskType, "melee" ) == 0 ) + newMask = MASK_MELEE; + else if ( stricmp( maskType, "pathsolid" ) == 0 ) + newMask = MASK_PATHSOLID; + else if ( stricmp( maskType, "camerasolid" ) == 0 ) + newMask = MASK_CAMERASOLID; + else if ( stricmp( maskType, "setclip" ) == 0 ) + newMask = MASK_SETCLIP; + + // Contents + + else if ( stricmp( maskType, "contents_solid" ) == 0 ) + newMask = CONTENTS_SOLID; + else if ( stricmp( maskType, "contents_usable" ) == 0 ) + newMask = CONTENTS_USABLE; + else if ( stricmp( maskType, "contents_setclip" ) == 0 ) + newMask = CONTENTS_SETCLIP; + else if ( stricmp( maskType, "contents_targetable" ) == 0 ) + newMask = CONTENTS_TARGETABLE; + else if ( stricmp( maskType, "contents_playerclip" ) == 0 ) + newMask = CONTENTS_PLAYERCLIP; + else if ( stricmp( maskType, "contents_monsterclip" ) == 0 ) + newMask = CONTENTS_MONSTERCLIP; + else if ( stricmp( maskType, "contents_cameraclip" ) == 0 ) + newMask = CONTENTS_CAMERACLIP; + else if ( stricmp( maskType, "contents_weaponclip" ) == 0 ) + newMask = CONTENTS_WEAPONCLIP; + else if ( stricmp( maskType, "contents_shootable" ) == 0 ) + newMask = CONTENTS_SHOOTABLE_ONLY; + else if ( stricmp( maskType, "contents_body" ) == 0 ) + newMask = CONTENTS_BODY; + else if ( stricmp( maskType, "contents_corpse" ) == 0 ) + newMask = CONTENTS_CORPSE; + + // All + + else if ( stricmp( maskType, "all" ) == 0 ) + newMask = 0xFFFFFFFF; + + // Change mask appropriately + + if ( operation == CONTENTS_OPERATION_ADD ) + currentMask |= newMask; + else if ( operation == CONTENTS_OPERATION_SUBTRACT ) + currentMask &= ~newMask; + else if ( operation == CONTENTS_OPERATION_SET ) + currentMask = newMask; + } + + // Save the new mask + + edict->clipmask = currentMask; +} + +void Entity::getCustomShaderInfo( const str &customShader, str &shaderName, str &soundName ) +{ + // Setup the defaults + + shaderName = customShader; + soundName = ""; + + // See if we need to get info from the database + + if ( ( customShader.length() > 4 ) && ( stricmp( customShader.c_str() + customShader.length() - 4, ".gdb" ) == 0 ) ) + { + str gameplayObjectName = customShader; + gameplayObjectName.CapLength( customShader.length() - 4 ); + + shaderName = G_GetDatabaseString( "CustomShader", gameplayObjectName, "ShaderName" ); + soundName = G_GetDatabaseString( "CustomShader", gameplayObjectName, "SoundName" ); + } +} + +//---------------------------------------------------------------- +// Name: setCustomShader +// Class: Entity +// +// Description: Sets the custom shader for this entity +// +// Parameters: const char *customShader - name of the shader +// +// Returns: none +//---------------------------------------------------------------- + +void Entity::setCustomShader( const char *customShader ) +{ + str shaderName; + str soundName; + + if ( !customShader || ( strlen( customShader ) == 0 ) ) + return; + + getCustomShaderInfo( customShader, shaderName, soundName ); + + // Apply the custom shader + + edict->s.customShader = gi.imageindex( shaderName ); + edict->s.eFlags |= EF_EFFECT_CUSTOM; + + // Start the loop sound associated with the customshader + + if ( soundName.length() ) + { + LoopSound( soundName ); + } +} + +//---------------------------------------------------------------- +// Name: setCustomShader +// Class: Entity +// +// Description: Sets the custom shader for this entity +// +// Parameters: Event *ev - event that contains the name of the shader +// +// Returns: none +//---------------------------------------------------------------- + +void Entity::setCustomShader( Event *ev ) +{ + setCustomShader( ev->GetString( 1 ) ); +} + +//---------------------------------------------------------------- +// Name: clearCustomShader +// Class: Entity +// +// Description: Clears the custom shader for this entity if it matches the shader name passed in +// +// Parameters: const char *customShader - name of the shader to clear, if NULL will always clear +// +// Returns: none +//---------------------------------------------------------------- + +void Entity::clearCustomShader( const char *customShader ) +{ + int tempImageIndex; + str shaderName; + str soundName; + + if ( customShader && edict->s.customShader ) + { + getCustomShaderInfo( customShader, shaderName, soundName ); + + // Only clear the custom shader if it matches the one passed in + + tempImageIndex = gi.imageindex( shaderName ); + + if ( edict->s.customShader == tempImageIndex ) + { + edict->s,customShader = 0; + edict->s.eFlags &= ~EF_EFFECT_CUSTOM; + } + + // Get rid of the loopsound associated with this custom shader + + if ( soundName.length() ) + { + StopLoopSound(); + } + } + else + { + edict->s,customShader = 0; + edict->s.eFlags &= ~EF_EFFECT_CUSTOM; + } +} + +//---------------------------------------------------------------- +// Name: clearCustomShader +// Class: Entity +// +// Description: Clears the custom shader for this entity if it matches the shader name passed in +// +// Parameters: Event *ev - event that optionally contains the name of the shader to clear +// +// Returns: none +//---------------------------------------------------------------- + +void Entity::clearCustomShader( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + clearCustomShader( ev->GetString( 1 ) ); + else + clearCustomShader(); +} + +bool Entity::hasCustomShader( const char *customShader ) +{ + // See if any custom shader is currently being used + + if ( !( edict->s.eFlags & EF_EFFECT_CUSTOM ) ) + return false; + + if ( customShader && edict->s.customShader ) + { + int tempImageIndex; + + // Check to see if the shader matches the one passed in + + tempImageIndex = gi.imageindex( customShader ); + + if ( edict->s.customShader != tempImageIndex ) + { + // Not the correct shader + + return false; + } + } + + // Everything matches + + return true; +} + +void Entity::setCustomEmitter( const char *customEmitter ) +{ + if ( !customEmitter ) + return; + + edict->s.customEmitter = gi.imageindex( customEmitter ); + edict->s.eFlags |= EF_EMITTER_CUSTOM; +} + +void Entity::setCustomEmitter( Event *ev ) +{ + setCustomEmitter( ev->GetString( 1 ) ); +} + +void Entity::clearCustomEmitter( const char *customEmitter ) +{ + int tempImageIndex; + + if ( customEmitter && edict->s.customEmitter ) + { + // Only clear the custom shader if it matches the one passed in + + tempImageIndex = gi.imageindex( customEmitter ); + + if ( edict->s.customEmitter == tempImageIndex ) + { + edict->s,customEmitter = 0; + edict->s.eFlags &= ~EF_EMITTER_CUSTOM; + } + + } + else + { + edict->s,customEmitter = 0; + edict->s.eFlags &= ~EF_EMITTER_CUSTOM; + } +} + +void Entity::clearCustomEmitter( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + clearCustomEmitter( ev->GetString( 1 ) ); + else + clearCustomEmitter(); +} + +void Entity::hideFeaturesForFade( void ) +{ + Event *event; + + // Shut the eyes + + event = new Event( EV_Morph ); + event->AddString( "morph_lid-lshut" ); + event->AddFloat( 100.0f ); + event->AddFloat( 0.05f ); + ProcessEvent ( event ); + + event = new Event( EV_Morph ); + event->AddString( "morph_lid-rshut" ); + event->AddFloat( 100.0f ); + event->AddFloat( 0.05f ); + ProcessEvent ( event ); + + // Hide the eyes and mouth surfaces + + event = new Event( EV_SurfaceModelEvent ); + event->AddString( "material3" ); + event->AddString( "+nodraw" ); + ProcessEvent ( event ); + + // Don't let the entity blink + + event = new Event( EV_Actor_Blink ); + event->AddInteger( 0 ); + ProcessEvent ( event ); +} + +void Entity::showFeaturesForFade( void ) +{ + Event *event; + + // Open the eyes + + event = new Event( EV_Unmorph ); + event->AddString( "morph_lid-lshut" ); + event->AddFloat( 100.0f ); + event->AddFloat( 0.5f ); + ProcessEvent ( event ); + + event = new Event( EV_Unmorph ); + event->AddString( "morph_lid-rshut" ); + event->AddFloat( 100.0f ); + event->AddFloat( 0.5f ); + ProcessEvent ( event ); + + // Show the eyes and mouth surfaces + + event = new Event( EV_SurfaceModelEvent ); + event->AddString( "material3" ); + event->AddString( "-nodraw" ); + ProcessEvent ( event ); + + // Allow the entity to blink again + + event = new Event( EV_Actor_Blink ); + event->AddInteger( 1 ); + ProcessEvent ( event ); +} + +void Entity::DisplayEffect ( Event *ev ) +{ + str effectType; + str effectName; + Event *event; + int i; + Entity *ent; + bool passEvent = false; + bool cancelEvents = false; + Vector effectPosition; + GameplayManager *gpm; + str gameplayObjectName; + + + // Get the parms + + effectType = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + { + effectName = ev->GetString( 2 ); + } + else + { + const char *delimiterPtr; + + // There is only 1 parm, so lets see if the type and name are both crammed into the 1 parm + + delimiterPtr = strstr( effectType.c_str(), "-" ); + + if ( delimiterPtr ) + { + // Effect name is everything after the - + effectName = delimiterPtr + 1; + + // Effect type is everything before the - + effectType.CapLength( delimiterPtr - effectType ); + } + } + + // Get a pointer to the gameplay manager + + gpm = GameplayManager::getTheGameplayManager(); + + // See if there is a gameplay object (if necessary) + + if ( ( strnicmp( effectType.c_str(), "TransportOut", strlen( "TransportOut" ) ) == 0 ) || + ( strnicmp( effectType.c_str(), "TransportIn", strlen( "TransportIn" ) ) == 0 ) || + ( strnicmp( effectType.c_str(), "FadeOut", strlen( "FadeOut" ) ) == 0 ) || + ( strnicmp( effectType.c_str(), "FadeIn", strlen( "FadeIn" ) ) == 0 ) ) + { + if ( ( strnicmp( effectType.c_str(), "TransportOut", strlen( "TransportOut" ) ) == 0 ) ) + { + gameplayObjectName = "TransportOut"; + } + else if ( ( strnicmp( effectType.c_str(), "TransportIn", strlen( "TransportIn" ) ) == 0 ) ) + { + gameplayObjectName = "TransportIn"; + } + else if ( ( strnicmp( effectType.c_str(), "FadeOut", strlen( "FadeOut" ) ) == 0 ) ) + { + gameplayObjectName = "FadeOut"; + } + else if ( ( strnicmp( effectType.c_str(), "FadeIn", strlen( "FadeIn" ) ) == 0 ) ) + { + gameplayObjectName = "FadeIn"; + } + + gameplayObjectName += effectName; + + if ( !gpm->hasObject( gameplayObjectName ) ) + { + gi.WDPrintf( "Can't find object called %s to use in DisplayEffect\n", gameplayObjectName.c_str() ); + return; + } + } + + if ( effectType == "TransportOut" ) + { + str shaderName; + str soundName; + str effectTikiName; + str animName; + str effectPos; + str currentEffectPos; + float effectTime; + float transportTime; + int numEffects; + int immobilize; + int hideFeatures; + int attach; + + + // Get all of the info from the gameplay database + + shaderName = gpm->getStringValue( gameplayObjectName, "ShaderName" ); + soundName = gpm->getStringValue( gameplayObjectName, "SoundName" ); + effectTikiName = gpm->getStringValue( gameplayObjectName, "EffectName" ); + attach = (int) gpm->getFloatValue( gameplayObjectName, "AttachEffect" ); + animName = gpm->getStringValue( gameplayObjectName, "AnimName" ); + + effectPos = gpm->getStringValue( gameplayObjectName, "EffectPosName" ); + numEffects = (int) gpm->getFloatValue( gameplayObjectName, "NumEffects" ); + + effectTime = gpm->getFloatValue( gameplayObjectName, "EffectTime" ); + transportTime = gpm->getFloatValue( gameplayObjectName, "TransportTime" ); + + immobilize = (int) gpm->getFloatValue( gameplayObjectName, "Immobilize" ); + hideFeatures = (int) gpm->getFloatValue( gameplayObjectName, "HideFeatures" ); + + setCustomShader( shaderName ); + setAlpha( 0.0f ); + + edict->s.renderfx |= RF_FORCE_ALPHA_EFFECTS; + edict->s.renderfx &= ~RF_FORCE_ALPHA; + + event = new Event( EV_FadeIn ); + event->AddFloat( effectTime ); + ProcessEvent( event ); + + event = new Event( EV_DisplayEffect ); + event->AddString( "TransportOut2" ); + event->AddString( effectName ); + PostEvent( event, effectTime ); + + if ( hideFeatures ) + hideFeaturesForFade(); + + if ( edict->s.parent == ENTITYNUM_NONE ) + { + if ( soundName.length() ) + { + Sound( soundName ); + } + + if ( effectTikiName.length() > 4 ) + { + Vector pos; + + currentEffectPos = effectPos; + + for ( i = 0 ; i < numEffects ; i++ ) + { + if ( !effectPos.length() || effectPos == "centroid" ) + pos = centroid; + else if ( effectPos == "origin" ) + pos = origin; + else if ( effectPos == "randombone" ) + { + int tagNum; + + tagNum = (int) G_Random( gi.NumTags( edict->s.modelindex ) ); + currentEffectPos = gi.Tag_NameForNum( edict->s.modelindex, tagNum ); + + GetTag( tagNum, &pos, NULL ); + } + else if ( gi.Tag_NumForName( edict->s.modelindex, effectPos.c_str() ) >= 0 ) + GetTag( effectPos, &pos, NULL ); + else + pos = centroid; + + if ( attach ) + attachEffect( effectTikiName, currentEffectPos, effectTime + transportTime ); + else + SpawnEffect( effectTikiName, pos, angles, effectTime + transportTime ); + } + } + } + + if ( animate && animName.length() && animate->HasAnim( animName.c_str() ) ) + { + animate->RandomAnimate( animName.c_str() ); + } + + if ( immobilize ) + { + takedamage = DAMAGE_NO; + flags |= FL_IMMOBILE; + } + + passEvent = true; + } + else if ( effectType == "TransportOut2" ) + { + str shaderName; + float transportTime; + + // Get all of the info from the gameplay database + + shaderName = gpm->getStringValue( gameplayObjectName, "ShaderName" ); + transportTime = gpm->getFloatValue( gameplayObjectName, "TransportTime" ); + + CancelEventsOfType( EV_FadeIn ); + + edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS; + + setCustomShader( shaderName.c_str() ); + setAlpha( 1.0f ); + edict->s.renderfx |= RF_FORCE_ALPHA; + + event = new Event( EV_FadeNoRemove ); + event->AddFloat( transportTime ); + event->AddFloat( 0.0f ); + ProcessEvent( event ); + + event = new Event( EV_DisplayEffect ); + event->AddString( "TransportOut3" ); + event->AddString( effectName ); + PostEvent( event, transportTime ); + } + else if ( effectType == "TransportOut3" ) + { + str shaderName; + int hideFeatures; + + // Get all of the info from the gameplay database + + shaderName = gpm->getStringValue( gameplayObjectName, "ShaderName" ); + + hideFeatures = (int) gpm->getFloatValue( gameplayObjectName, "HideFeatures" ); + + if ( hideFeatures ) + showFeaturesForFade(); + + if ( shaderName.length() ) + { + clearCustomShader( shaderName ); + } + + // Finish up the transport out + + event = new Event( EV_EntityRenderEffects ); + event->AddString( "-shadow" ); + ProcessEvent( event ); + + takedamage = DAMAGE_YES; + flags &= ~FL_IMMOBILE; + } + else if ( effectType == "TransportIn" ) + { + str shaderName; + str soundName; + str effectPos; + str currentEffectPos; + str effectTikiName; + float effectTime; + float transportTime; + int numEffects; + int immobilize; + int attach; + int hideFeatures; + + // Cancel other display events so we don't get any overlap (can't do this because it breaks animations that + // have both since animate posts all frame commands un front + + //CancelEventsOfType( EV_DisplayEffect ); + clearCustomShader(); + + // Get all of the info from the gameplay database + + shaderName = gpm->getStringValue( gameplayObjectName, "ShaderName" ); + soundName = gpm->getStringValue( gameplayObjectName, "SoundName" ); + effectTikiName = gpm->getStringValue( gameplayObjectName, "EffectName" ); + attach = (int) gpm->getFloatValue( gameplayObjectName, "AttachEffect" ); + + effectPos = gpm->getStringValue( gameplayObjectName, "EffectPosName" ); + numEffects = (int) gpm->getFloatValue( gameplayObjectName, "NumEffects" ); + + effectTime = gpm->getFloatValue( gameplayObjectName, "EffectTime" ); + transportTime = gpm->getFloatValue( gameplayObjectName, "TransportTime" ); + + immobilize = (int) gpm->getFloatValue( gameplayObjectName, "Immobilize" ); + hideFeatures = (int) gpm->getFloatValue( gameplayObjectName, "HideFeatures" ); + + edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS; + + if ( shaderName.length() ) + { + setCustomShader( shaderName ); + } + + setAlpha( 0.0f ); + edict->s.renderfx |= RF_FORCE_ALPHA; + + event = new Event( EV_FadeIn ); + event->AddFloat( transportTime ); + ProcessEvent( event ); + + event = new Event( EV_DisplayEffect ); + event->AddString( "TransportIn2" ); + event->AddString( effectName ); + PostEvent( event, transportTime ); + + if ( hideFeatures ) + hideFeaturesForFade(); + + if ( edict->s.parent == ENTITYNUM_NONE ) + { + if ( soundName.length() ) + { + Sound( soundName ); + } + + if ( effectTikiName.length() > 4 ) + { + Vector pos; + + currentEffectPos = effectPos; + + for ( i = 0 ; i < numEffects ; i++ ) + { + if ( !effectPos.length() || effectPos == "centroid" ) + pos = centroid; + else if ( effectPos == "origin" ) + pos = origin; + else if ( effectPos == "randombone" ) + { + int tagNum; + + tagNum = (int) G_Random( gi.NumTags( edict->s.modelindex ) ); + currentEffectPos = gi.Tag_NameForNum( edict->s.modelindex, tagNum ); + + GetTag( tagNum, &pos, NULL ); + } + else if ( gi.Tag_NumForName( edict->s.modelindex, effectPos.c_str() ) >= 0 ) + GetTag( effectPos, &pos, NULL ); + else + pos = centroid; + + if ( attach ) + attachEffect( effectTikiName, currentEffectPos, effectTime + transportTime ); + else + SpawnEffect( effectTikiName, pos, angles, effectTime + transportTime ); + } + } + } + + if ( immobilize ) + { + takedamage = DAMAGE_NO; + flags |= FL_IMMOBILE; + } + + passEvent = true; + cancelEvents = true; + } + else if ( effectType == "TransportIn2" ) + { + str shaderName; + float effectTime; + + // Get all of the info from the gameplay database + + shaderName = gpm->getStringValue( gameplayObjectName, "ShaderName" ); + effectTime = gpm->getFloatValue( gameplayObjectName, "EffectTime" ); + + CancelEventsOfType( EV_FadeIn ); + edict->s.renderfx &= ~RF_FORCE_ALPHA; + + if ( shaderName.length() ) + { + setCustomShader( shaderName ); + } + + setAlpha( 1.0f ); + + if ( shaderName.length() ) + { + edict->s.renderfx |= RF_FORCE_ALPHA_EFFECTS; + + event = new Event( EV_FadeNoRemove ); + event->AddFloat( effectTime ); + event->AddFloat( 0.0f ); + ProcessEvent( event ); + } + + event = new Event( EV_DisplayEffect ); + event->AddString( "TransportIn3" ); + event->AddString( effectName ); + PostEvent( event, effectTime ); + } + else if ( effectType == "TransportIn3" ) + { + str shaderName; + str animName; + int hideFeatures; + + // Get all of the info from the gameplay database + + shaderName = gpm->getStringValue( gameplayObjectName, "ShaderName" ); + animName = gpm->getStringValue( gameplayObjectName, "AnimName" ); + + hideFeatures = (int) gpm->getFloatValue( gameplayObjectName, "HideFeatures" ); + + if ( shaderName.length() ) + { + clearCustomShader( shaderName ); + edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS; + } + + if ( animate && animName.length() && animate->HasAnim( animName.c_str() ) ) + { + animate->RandomAnimate( animName.c_str() ); + } + + CancelEventsOfType( EV_FadeNoRemove ); + + if ( hideFeatures ) + showFeaturesForFade(); + + setAlpha( 1.0f ); + + event = new Event( EV_EntityRenderEffects ); + event->AddString( "+shadow" ); + ProcessEvent( event ); + + takedamage = DAMAGE_YES; + flags &= ~FL_IMMOBILE; + } + else if ( effectType == "FadeOut" ) + { + str shaderName; + str soundName; + str effectTikiName; + str effectPos; + str currentEffectPos; + float effectTime; + float fadeTime; + int numEffects; + int attach; + int hideFeatures; + + // Get all of the info from the gameplay database + + shaderName = gpm->getStringValue( gameplayObjectName, "ShaderName" ); + soundName = gpm->getStringValue( gameplayObjectName, "SoundName" ); + effectTikiName = gpm->getStringValue( gameplayObjectName, "EffectName" ); + attach = (int) gpm->getFloatValue( gameplayObjectName, "AttachEffect" ); + + effectPos = gpm->getStringValue( gameplayObjectName, "EffectPosName" ); + numEffects = (int) gpm->getFloatValue( gameplayObjectName, "NumEffects" ); + + effectTime = gpm->getFloatValue( gameplayObjectName, "EffectTime" ); + fadeTime = gpm->getFloatValue( gameplayObjectName, "FadeTime" ); + hideFeatures = (int) gpm->getFloatValue( gameplayObjectName, "HideFeatures" ); + + setCustomShader( shaderName ); + + setAlpha( 0.0f ); + + edict->s.renderfx |= RF_FORCE_ALPHA_EFFECTS; + edict->s.renderfx &= ~RF_FORCE_ALPHA; + + if ( effectTime > 0.0f ) + { + event = new Event( EV_FadeIn ); + event->AddFloat( effectTime ); + ProcessEvent( event ); + } + + event = new Event( EV_DisplayEffect ); + event->AddString( "FadeOut2" ); + event->AddString( effectName ); + PostEvent( event, effectTime ); + + if ( hideFeatures ) + hideFeaturesForFade(); + + if ( edict->s.parent == ENTITYNUM_NONE ) + { + // Play sound + + if ( soundName.length() ) + { + Sound( soundName ); + } + + // Spawn an effect + + if ( effectTikiName.length() > 4 ) + { + Vector pos; + + currentEffectPos = effectPos; + + for ( i = 0 ; i < numEffects ; i++ ) + { + if ( !effectPos.length() || effectPos == "centroid" ) + pos = centroid; + else if ( effectPos == "origin" ) + pos = origin; + else if ( effectPos == "randombone" ) + { + int tagNum; + + tagNum = (int) G_Random( gi.NumTags( edict->s.modelindex ) ); + currentEffectPos = gi.Tag_NameForNum( edict->s.modelindex, tagNum ); + + GetTag( tagNum, &pos, NULL ); + } + else if ( gi.Tag_NumForName( edict->s.modelindex, effectPos.c_str() ) >= 0 ) + GetTag( effectPos, &pos, NULL ); + else + pos = centroid; + + if ( attach ) + attachEffect( effectTikiName, currentEffectPos, effectTime + fadeTime ); + else + SpawnEffect( effectTikiName, pos, angles, effectTime + fadeTime ); + } + } + } + + passEvent = true; + } + else if ( effectType == "FadeOut2" ) + { + str shaderName; + float fadeTime; + + // Get all of the info from the gameplay database + + shaderName = gpm->getStringValue( gameplayObjectName, "ShaderName" ); + fadeTime = gpm->getFloatValue( gameplayObjectName, "FadeTime" ); + + CancelEventsOfType( EV_FadeIn ); + edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS; + + setCustomShader( shaderName ); + setAlpha( 1.0f ); + edict->s.renderfx |= RF_FORCE_ALPHA; + + event = new Event( EV_FadeNoRemove ); + event->AddFloat( fadeTime ); + event->AddFloat( 0.0f ); + ProcessEvent( event ); + + event = new Event( EV_DisplayEffect ); + event->AddString( "FadeOut3" ); + event->AddString( effectName ); + PostEvent( event, fadeTime ); + } + else if ( effectType == "FadeOut3" ) + { + /* int hideFeatures; + + hideFeatures = gpm->getFloatValue( gameplayObjectName, "HideFeatures" ); + + if ( hideFeatures ) + showFeaturesForFade(); */ + } + else if ( effectType == "FadeIn" ) + { + str shaderName; + str soundName; + str effectTikiName; + str effectPos; + str currentEffectPos; + //float effectTime; + float fadeTime; + int numEffects; + int attach; + int hideFeatures; + + // Get all of the info from the gameplay database + + shaderName = gpm->getStringValue( gameplayObjectName, "ShaderName" ); + soundName = gpm->getStringValue( gameplayObjectName, "SoundName" ); + effectTikiName = gpm->getStringValue( gameplayObjectName, "EffectName" ); + attach = (int) gpm->getFloatValue( gameplayObjectName, "AttachEffect" ); + + effectPos = gpm->getStringValue( gameplayObjectName, "EffectPosName" ); + numEffects = (int) gpm->getFloatValue( gameplayObjectName, "NumEffects" ); + + //effectTime = gpm->getFloatValue( gameplayObjectName, "EffectTime" ); + fadeTime = gpm->getFloatValue( gameplayObjectName, "FadeTime" ); + hideFeatures = (int) gpm->getFloatValue( gameplayObjectName, "HideFeatures" ); + + setCustomShader( shaderName ); + + setAlpha( 0.0f ); + + edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS; + edict->s.renderfx |= RF_FORCE_ALPHA; + + event = new Event( EV_FadeIn ); + event->AddFloat( fadeTime ); + ProcessEvent( event ); + + event = new Event( EV_DisplayEffect ); + event->AddString( "FadeIn2" ); + event->AddString( effectName ); + PostEvent( event, fadeTime ); + + if ( hideFeatures ) + hideFeaturesForFade(); + + if ( edict->s.parent == ENTITYNUM_NONE ) + { + // Play sound + + if ( soundName.length() ) + { + Sound( soundName ); + } + + // Spawn an effect + + if ( effectTikiName.length() > 4 ) + { + Vector pos; + + currentEffectPos = effectPos; + + for ( i = 0 ; i < numEffects ; i++ ) + { + if ( !effectPos.length() || effectPos == "centroid" ) + pos = centroid; + else if ( effectPos == "origin" ) + pos = origin; + else if ( effectPos == "randombone" ) + { + int tagNum; + + tagNum = (int) G_Random( gi.NumTags( edict->s.modelindex ) ); + currentEffectPos = gi.Tag_NameForNum( edict->s.modelindex, tagNum ); + + GetTag( tagNum, &pos, NULL ); + } + else if ( gi.Tag_NumForName( edict->s.modelindex, effectPos.c_str() ) >= 0 ) + GetTag( effectPos, &pos, NULL ); + else + pos = centroid; + + if ( attach ) + attachEffect( effectTikiName, currentEffectPos, fadeTime + fadeTime ); + else + SpawnEffect( effectTikiName, pos, angles, fadeTime + fadeTime ); + } + } + } + + passEvent = true; + } + else if ( effectType == "FadeIn2" ) + { + str shaderName; + float effectTime; + + // Get all of the info from the gameplay database + + shaderName = gpm->getStringValue( gameplayObjectName, "ShaderName" ); + effectTime = gpm->getFloatValue( gameplayObjectName, "EffectTime" ); + + CancelEventsOfType( EV_FadeIn ); + + setAlpha( 1.0f ); + + if ( shaderName.length() > 0 ) + { + setCustomShader( shaderName ); + + edict->s.renderfx |= RF_FORCE_ALPHA_EFFECTS; + edict->s.renderfx |= RF_FORCE_ALPHA; + + event = new Event( EV_FadeNoRemove ); + event->AddFloat( effectTime ); + event->AddFloat( 0.0f ); + ProcessEvent( event ); + } + + event = new Event( EV_DisplayEffect ); + event->AddString( "FadeIn3" ); + event->AddString( effectName ); + PostEvent( event, effectTime ); + } + else if ( effectType == "FadeIn3" ) + { + str shaderName; + int hideFeatures; + + // Get all of the info from the gameplay database + + shaderName = gpm->getStringValue( gameplayObjectName, "ShaderName" ); + hideFeatures = (int) gpm->getFloatValue( gameplayObjectName, "HideFeatures" ); + + CancelEventsOfType( EV_FadeNoRemove ); + + edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS; + edict->s.renderfx &= ~RF_FORCE_ALPHA; + + if ( hideFeatures ) + showFeaturesForFade(); + + clearCustomShader( shaderName ); + setAlpha( 1.0f ); + } + else if ( effectType == "start_invisibility" ) + { + edict->s.eFlags |= EF_EFFECT_ELECTRIC; + setAlpha( 0.1f ); + + edict->s.renderfx |= RF_FORCE_ALPHA; + + addAffectingViewModes( gi.GetViewModeMask( "forcevisible" ) ); + + passEvent = true; + } + else if ( effectType == "stop_invisibility" ) + { + edict->s.eFlags &= ~EF_EFFECT_ELECTRIC; + setAlpha( 1.0f ); + + edict->s.renderfx &= ~RF_FORCE_ALPHA; + + removeAffectingViewModes( gi.GetViewModeMask( "forcevisible" ) ); + + passEvent = true; + } + else if ( effectType == "electric" ) + { + edict->s.eFlags |= EF_EFFECT_ELECTRIC; + passEvent = true; + } + else if ( effectType == "noelectric" ) + { + edict->s.eFlags &= ~EF_EFFECT_ELECTRIC; + passEvent = true; + } + else if ( effectType == "failure" ) + { + edict->s.eFlags |= EF_BEHAVIOR_FAILURE; + passEvent = true; + } + else if ( effectType == "nofailure" ) + { + edict->s.eFlags &= ~EF_BEHAVIOR_FAILURE; + passEvent = true; + } + else if ( ( effectType == "transport_out" ) || ( effectType == "transport_in" ) || ( effectType == "borg_transport_out" ) || + ( effectType == "borg_transport_in" ) ) + { + gi.WDPrintf( "Support for %s will soon be removed please use the new way\n", effectType.c_str() ); + + event = new Event( EV_DisplayEffect ); + + if ( effectType == "transport_out" ) + { + event->AddString( "TransportOut" ); + event->AddString( "Federation" ); + } + else if ( effectType == "transport_in" ) + { + event->AddString( "TransportIn" ); + event->AddString( "Federation" ); + } + else if ( effectType == "borg_transport_out" ) + { + event->AddString( "TransportOut" ); + event->AddString( "Borg" ); + } + else if ( effectType == "borg_transport_in" ) + { + event->AddString( "TransportIn" ); + event->AddString( "Borg" ); + } + + ProcessEvent( event ); + return; + } + + if ( passEvent && bind_info && bind_info->numchildren ) + { + for( i = 0 ; i < MAX_MODEL_CHILDREN ; i++ ) + { + if ( bind_info->children[ i ] == ENTITYNUM_NONE ) + continue; + + ent = G_GetEntity( bind_info->children[ i ] ); + + if ( ent ) + { + /* if ( cancelEvents ) + { + // Cancel other display events so we don't get any overlap + + ent->CancelEventsOfType( EV_DisplayEffect ); + } */ + + ent->ProcessEvent( *ev ); + } + } + } +} + +//---------------------------------------------------------------- +// Name: clearDisplayEffects +// Class: Entity +// +// Description: Clears all of the display type of effects and sets the alpha back to normal +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- + +void Entity::clearDisplayEffects( void ) +{ + if ( edict->s.eFlags | EF_EFFECTS ) + { + edict->s.eFlags &= ~EF_EFFECTS; + setAlpha( 1.0f ); + + edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS; + edict->s.renderfx &= ~RF_FORCE_ALPHA; + } +} + +void Entity::SpawnEffect( Event *ev ) + { + //const str &name, const Vector &origin, const Vector &angles, float removeTime ) + str tagName; + str modelName; + Vector tagPos; + Vector tagForward; + Vector tagAngles; + float removeTime; + + + modelName = ev->GetString( 1 ); + + tagName = ev->GetString( 2 ); + GetTag( tagName, &tagPos, &tagForward ); + + tagAngles = tagForward.toAngles(); + + if ( ev->NumArgs() > 2 ) + removeTime = ev->GetFloat( 3 ); + else + removeTime = 0.0f; + + SpawnEffect( modelName, tagPos, tagAngles, removeTime ); + } + +Entity *Entity::SpawnEffect( const str &name, const Vector &origin, const Vector &angles, float removeTime ) +{ + Entity *newEntity; + str modelName; + int nameLength; + + nameLength = name.length(); + + if ( stricmp( name.c_str() + nameLength - 4, ".gdb" ) == 0 ) + { + str gameplayObjectName; + GameplayManager *gpm; + + gpm = GameplayManager::getTheGameplayManager(); + + gameplayObjectName = name; + gameplayObjectName.CapLength( nameLength - 4 ); + + if ( gpm->hasObject( gameplayObjectName ) ) + { + modelName = gpm->getStringValue( gameplayObjectName, "ModelName" ); + } + else + { + const char *dash; + + dash = strstr( gameplayObjectName.c_str(), "-" ); + + if ( dash ) + { + gameplayObjectName.CapLength( dash - gameplayObjectName.c_str() ); + gameplayObjectName += "-default"; + + if ( gpm->hasObject( gameplayObjectName ) ) + { + modelName = gpm->getStringValue( gameplayObjectName, "ModelName" ); + } + else + { + gi.WDPrintf( "%s not find in the gameplay database\n", name.c_str() ); + return NULL; + } + } + else + { + gi.WDPrintf( "%s not find in the gameplay database\n", name.c_str() ); + return NULL; + } + } + } + else + { + modelName = name; + } + + newEntity = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + newEntity->setModel( modelName ); + + newEntity->angles = angles; + newEntity->setAngles(); + + newEntity->setOrigin( origin ); + + newEntity->setSolidType( SOLID_NOT ); + + if ( removeTime > 0.0f ) + newEntity->PostEvent( EV_Remove, removeTime ); + + newEntity->CancelEventsOfType( EV_ProcessInitCommands ); + newEntity->ProcessInitCommands( newEntity->edict->s.modelindex ); + + newEntity->animate->RandomAnimate( "idle" ); + + return newEntity; +} + +Entity* Entity::SpawnSound( const str &sound, const Vector &pos, float volume, float removeTime ) +{ + Entity *newEntity; + Vector soundOrigin; + newEntity = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + newEntity->setOrigin( pos ); + newEntity->setSolidType( SOLID_NOT ); + + if ( removeTime > 0.0f ) + newEntity->PostEvent( EV_Remove, removeTime ); + + soundOrigin = pos; + newEntity->CancelEventsOfType( EV_ProcessInitCommands ); + newEntity->ProcessInitCommands( newEntity->edict->s.modelindex ); + newEntity->Sound( sound , CHAN_BODY, volume, -1.0f, &soundOrigin ); + return newEntity; +} + +void Entity::attachEffect( Event *ev ) + { + str tagName; + str modelName; + //Vector tagPos; + //Vector tagForward; + //Vector tagAngles; + float removeTime; + + + modelName = ev->GetString( 1 ); + + tagName = ev->GetString( 2 ); + //GetTag( tagName, &tagPos, &tagForward ); + + //tagAngles = tagForward.toAngles(); + + if ( ev->NumArgs() > 2 ) + removeTime = ev->GetFloat( 3 ); + else + removeTime = 0.0f; + + attachEffect( modelName, tagName, removeTime ); + } + +void Entity::attachEffect( const str &modelName, const str &tagName, float removeTime ) +{ + Event *newEvent = new Event( EV_AttachModel ); + newEvent->AddString( modelName ); + newEvent->AddString( tagName ); + newEvent->AddFloat( 1.0f ); + newEvent->AddString( "" ); + newEvent->AddInteger( 0 ); + newEvent->AddFloat( removeTime ); + ProcessEvent( newEvent ); +} + +void Entity::ForceAlpha( Event *ev ) + { + if ( ev->NumArgs() == 0 ) + edict->s.renderfx |= RF_FORCE_ALPHA; + else if ( ev->GetBoolean( 1 ) ) + edict->s.renderfx |= RF_FORCE_ALPHA; + else + edict->s.renderfx &= ~RF_FORCE_ALPHA; + } + +void Entity::CreateEarthquake( Event *ev ) + { + Earthquake *earthquake; + float duration; + float magnitude; + float distance = 0; + Event *newEvent; + + + if ( origin == vec_zero ) + { + gi.WDPrintf( "Earthquake being started when origin hasn't been set yet in model %s\n", model.c_str() ); + return; + } + + magnitude = ev->GetFloat( 1 ); + duration = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + distance = ev->GetFloat( 3 ); + + earthquake = new Earthquake; + + newEvent = new Event( EV_SetOrigin ); + newEvent->AddVector( origin ); + earthquake->ProcessEvent( newEvent ); + + newEvent = new Event( EV_Earthquake_SetMagnitude ); + newEvent->AddFloat( magnitude ); + earthquake->ProcessEvent( newEvent ); + + newEvent = new Event( EV_Earthquake_SetDuration ); + newEvent->AddFloat( duration ); + earthquake->ProcessEvent( newEvent ); + + if ( distance ) + { + newEvent = new Event( EV_Earthquake_SetDistance ); + newEvent->AddFloat( distance ); + earthquake->ProcessEvent( newEvent ); + } + + earthquake->ProcessEvent( EV_Trigger_Effect ); + + earthquake->PostEvent( EV_Remove, duration ); + } + +void Entity::SetFloatVar + ( + Event *ev + ) + + { + str var_name; + float value; + + var_name = ev->GetString( 1 ); + value = ev->GetFloat( 2 ); + + entityVars.SetVariable( var_name, value ); + } + +void Entity::SetVectorVar + ( + Event *ev + ) + + { + str var_name; + Vector value; + + var_name = ev->GetString( 1 ); + value = ev->GetVector( 2 ); + + entityVars.SetVariable( var_name, value ); + } + +void Entity::SetStringVar + ( + Event *ev + ) + + { + str var_name; + str value; + + var_name = ev->GetString( 1 ); + value = ev->GetString( 2 ); + + entityVars.SetVariable( var_name, value ); + } + +void Entity::doesVarExist( Event *ev ) +{ + ScriptVariable *var = NULL; + + + var = entityVars.GetVariable( ev->GetString( 1 ) ); + + if ( var ) + ev->ReturnFloat( 1.0f ); + else + ev->ReturnFloat( 0.0f ); +} + +void Entity::GetFloatVar + ( + Event *ev + ) + + { + str var_name; + ScriptVariable *var = NULL; + + var_name = ev->GetString( 1 ); + + var = entityVars.GetVariable( var_name ); + + if ( var ) + ev->ReturnFloat( var->floatValue() ); + else + { + gi.WDPrintf( "%s variable not found\n", var_name.c_str() ); + ev->ReturnFloat( 0.0f ); + } + } + +void Entity::RemoveVariable( Event* ev ) +{ + str var_name; + if( ev->NumArgs() > 0 ) + { + var_name = ev->GetString( 1 ); + if( var_name.length() > 0 ) + { + entityVars.RemoveVariable( var_name ); + } + } +} + +void Entity::GetVectorVar + ( + Event *ev + ) + + { + str var_name; + ScriptVariable *var = NULL; + + var_name = ev->GetString( 1 ); + + var = entityVars.GetVariable( var_name ); + + if ( var ) + ev->ReturnVector( var->vectorValue() ); + else + { + gi.WDPrintf( "%s variable not found\n", var_name.c_str() ); + ev->ReturnVector( Vector(0, 0, 0) ); + } + } + +void Entity::GetStringVar + ( + Event *ev + ) + + { + str var_name; + ScriptVariable *var = NULL; + + var_name = ev->GetString( 1 ); + + var = entityVars.GetVariable( var_name ); + + if ( var ) + ev->ReturnString( var->stringValue() ); + else + { + gi.WDPrintf( "%s variable not found\n", var_name.c_str() ); + ev->ReturnString( "" ); + } + } + +void Entity::SetUserVar1 + ( + Event *ev + ) + + { + entityVars.SetVariable( "uservar1", ev->GetString( 1 ) ); + } + +void Entity::SetUserVar2 + ( + Event *ev + ) + + { + entityVars.SetVariable( "uservar2", ev->GetString( 1 ) ); + } + +void Entity::SetUserVar3 + ( + Event *ev + ) + + { + entityVars.SetVariable( "uservar3", ev->GetString( 1 ) ); + } + +void Entity::SetUserVar4 + ( + Event *ev + ) + + { + entityVars.SetVariable( "uservar4", ev->GetString( 1 ) ); + } + +Vector Entity::GetClosestCorner( const Vector &position ) + { + Vector corner; + + Vector closestCorner; + Vector compare; + float distance; + float length; + + distance = 999999999.9f; + + for ( int i = 0 ; i < 4 ; i++ ) + { + // corner's based on a top down view of the bounding box -- I am returning + // the vector for the corner on a plane with the origin only. + switch ( i ) + { + // Upper Left + case 0: + corner.x = origin.x + mins.x; + corner.y = origin.y + maxs.y; + corner.z = origin.z; + break; + + // Upper Right + case 1: + corner.x = origin.x + maxs.x; + corner.y = origin.y + maxs.y; + corner.z = origin.z; + break; + + // Lower Left + case 2: + corner.x = origin.x + mins.x; + corner.y = origin.y + mins.y; + corner.z = origin.z; + break; + + case 3: + corner.x = origin.x + maxs.x; + corner.y = origin.y + mins.y; + corner.z = origin.z; + break; + } + + compare = corner - position; + length = compare.length(); + + if ( length < distance ) + { + closestCorner = corner; + distance = length; + } + } + + return closestCorner; + + } + +//---------------------------------------------------------------- +// Name: affectingViewMode +// Class: Entity +// +// Description: Adds a new viewmode to the entities applicable viewmodes +// +// Parameters: Event *ev - event (name of the view mode) +// +// Returns: None +//---------------------------------------------------------------- + +void Entity::affectingViewMode( Event *ev ) + { + addAffectingViewModes( gi.GetViewModeMask( ev->GetString( 1 ) ) ); + } + +//---------------------------------------------------------------- +// Name: addAffectingViewMode +// Class: Entity +// +// Description: Adds the specified viewmode bits to the entities applicable viewmodes +// +// Parameters: unsigned int mask - the bit mask of the relevant viewmodes +// +// Returns: None +//---------------------------------------------------------------- + +void Entity::addAffectingViewModes( unsigned int mask ) +{ + _affectingViewModes |= mask; + edict->s.affectingViewModes = _affectingViewModes; +} + +void Entity::removeAffectingViewModes( unsigned int mask ) +{ + _affectingViewModes &= ~mask; + edict->s.affectingViewModes = _affectingViewModes; +} + + +//-------------------------------------------------------------- +// Name: SetGroupID() +// Class: Entity +// +// Description: Grabs the ID from the event, and sends it to the +// group coordinator for registration. In the future +// we need to migrate this so that all group registration +// is done throught he group coordinator alone. +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Entity::SetGroupID( Event *ev ) +{ + AddToGroup( ev->GetInteger( 1 ) ); +} + +void Entity::AddToGroup( int ID ) +{ + groupcoordinator->AddEntityToGroup( this , ID ); +} + +//---------------------------------------------------------------- +// Name: TikiTodo and TikiNote +// Class: Entity +// +// Description: These commands may come from tiki files (via TikiMaster). +// They are here so the commands are not considered errors. +// In the future we might want cvars to print todo items. +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Entity::TikiTodo( Event *ev ) + { + } +void Entity::TikiNote( Event *ev ) + { + } + +//---------------------------------------------------------------- +// Name: MultiplayerEvent +// Class: Entity +// +// Description: This is a passthrough event. It only allows the contained event to be called +// if we are in a multiplayer game +// +// Parameters: Event *ev - contains the real event to process +// +// Returns: None +//---------------------------------------------------------------- + +void Entity::MultiplayerEvent( Event *ev ) +{ + // Make sure we are in a multiplayer game + + if ( multiplayerManager.inMultiplayer() ) + { + Event *event; + str eventName; + str token; + int i; + + // Get the event name + + eventName = ev->GetString( 1 ); + + event = new Event( eventName ); + + // Get all of the event parms + + for ( i = 2 ; i <= ev->NumArgs() ; i++ ) + { + event->AddToken( ev->GetToken( i ) ); + } + + // Process the event + + ProcessEvent( event ); + } +} + + +//-------------------------------------------------------------- +// +// Name: AddDamageModifier +// Class: Entity +// +// Description: Adds a new damage modifier to this entities list +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Entity::AddDamageModifier( Event *ev ) +{ + str damagemodtype, value; + float multiplier, chance = 1.0f, painBaseLine = 50.0f; + + damagemodtype = ev->GetString( 1 ); + value = ev->GetString( 2 ); + multiplier = ev->GetFloat( 3 ); + if ( ev->NumArgs() > 3 ) + chance = ev->GetFloat( 4 ); + if ( ev->NumArgs() > 4 ) + painBaseLine = ev->GetFloat( 5 ); + + if ( !damageModSystem ) + damageModSystem = new DamageModificationSystem; + + damageModSystem->addDamageModifier(damagemodtype, value, multiplier, chance, painBaseLine ); +} + +//-------------------------------------------------------------- +// +// Name: ResolveDamage +// Class: Entity +// +// Description: Calls the DamageModificationSystem to resolve damage +// +// Parameters: Damage &damage -- Reference to damage to modifiy +// +// Returns: +// +//-------------------------------------------------------------- +void Entity::ResolveDamage( ::Damage &damage ) +{ + if ( damageModSystem ) + damageModSystem->resolveDamage(damage); +} + +//-------------------------------------------------------------- +// Name: setMoveType() +// Class: Entity +// +// Description: Will convert a string to the appropriate moveType +// and then call setMoveType( type ) +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Entity::setMoveType( Event *ev ) +{ + str type; + type = ev->GetString( 1 ); + + if ( !stricmp( type.c_str() , "none" ) ) + { + velocity = vec_zero; + setMoveType( MOVETYPE_NONE ); + } + else if ( !stricmp( type.c_str() , "stationary" ) ) + setMoveType( MOVETYPE_STATIONARY ); + else if ( !stricmp( type.c_str() , "noclip" ) ) + setMoveType( MOVETYPE_NOCLIP ); + else if ( !stricmp( type.c_str() , "push" ) ) + setMoveType( MOVETYPE_PUSH ); + else if ( !stricmp( type.c_str() , "stop" ) ) + setMoveType( MOVETYPE_STOP ); + else if ( !stricmp( type.c_str() , "walk" ) ) + setMoveType( MOVETYPE_WALK ); + else if ( !stricmp( type.c_str() , "step" ) ) + setMoveType( MOVETYPE_STEP ); + else if ( !stricmp( type.c_str() , "fly" ) ) + setMoveType( MOVETYPE_FLY ); + else if ( !stricmp( type.c_str() , "toss" ) ) + setMoveType( MOVETYPE_TOSS ); + else if ( !stricmp( type.c_str() , "flymissile" ) ) + setMoveType( MOVETYPE_FLYMISSILE ); + else if ( !stricmp( type.c_str() , "bounce" ) ) + setMoveType( MOVETYPE_BOUNCE ); + else if ( !stricmp( type.c_str() , "slide" ) ) + setMoveType( MOVETYPE_SLIDE ); + else if ( !stricmp( type.c_str() , "rope" ) ) + setMoveType( MOVETYPE_ROPE ); + else if ( !stricmp( type.c_str() , "gib" ) ) + setMoveType( MOVETYPE_GIB ); + else if ( !stricmp( type.c_str() , "vehicle" ) ) + setMoveType( MOVETYPE_VEHICLE ); +} + + +//=============================================================== +// Name: buildUseData +// Class: Entity +// +// Description: Creates a usedata structure if need be. Also +// ensures the contents type of a usable object is +// set to something that can be detected by the +// use trace (so it can indeed be used). +// +// Testing this idea out. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void Entity::buildUseData +( + void +) +{ + if ( useData ) return ; + + useData = new UseData(); +} + + +//-------------------------------------------------------------- +// Name: useDataAnim +// Class: Entity +// +// Description: Sets the useData anim member +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Entity::useDataAnim( Event *ev ) +{ + buildUseData(); + useData->setUseAnim(ev->GetString( 1 )); +} + +//-------------------------------------------------------------- +// Name: useDataType +// Class: Entity +// +// Description: Sets the useData type member +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Entity::useDataType( Event *ev ) +{ + buildUseData(); + useData->setUseType(ev->GetString( 1 )); +} + +//-------------------------------------------------------------- +// Name: useDataThread +// Class: Entity +// +// Description: Sets the useData thread member +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Entity::useDataThread( Event *ev ) +{ + buildUseData(); + useData->setUseThread(ev->GetString( 1 )); +} + +//-------------------------------------------------------------- +// Name: useDataEvent +// Class: Entity +// +// Description: Sets the useData variables +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Entity::useDataEvent( Event *ev ) +{ + buildUseData(); + + useData->setUseAnim(ev->GetString( 1 )); + useData->setUseType(ev->GetString( 2 )); + useData->setUseThread(ev->GetString( 3 )); +} + +//-------------------------------------------------------------- +// Name: useDataMaxDist +// Class: Entity +// +// Description: Sets the maximum distance this entity can be used. +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Entity::useDataMaxDist( Event *ev ) +{ + buildUseData(); + useData->setUseMaxDist(ev->GetFloat( 1 )); +} + +//-------------------------------------------------------------- +// Name: useDataCount +// Class: Entity +// +// Description: Sets the number of times this entity can be used. +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Entity::useDataCount( Event *ev ) +{ + buildUseData(); + useData->setUseCount(ev->GetInteger( 1 )); +} + + +//-------------------------------------------------------------- +// Name: setArchetype +// Class: Entity +// +// Description: Sets the archetype name for this entity +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Entity::setArchetype( Event *ev ) +{ + _archetype = ev->GetString(1); + + //Sets the archetype index + edict->s.archeTypeIndex = gi.archetypeindex(_archetype); +} + + +//-------------------------------------------------------------- +// Name: getArchetype +// Class: Entity +// +// Description: Gets the archetype name for this entity, +// +// Parameters: None +// +// Returns: const str +//-------------------------------------------------------------- +const str Entity::getArchetype() const +{ + if ( _archetype.length() == 0 ) + return getName(); + + return _archetype; +} + +//----------------------------------------------------- +// +// Name: setMissionObjective +// Class: Entity +// +// Description: Sets the entity to be a mission objective or not. +// +// Parameters: ev +// +// Returns: None +//----------------------------------------------------- +void Entity::setMissionObjective(Event* ev) +{ + _missionObjective = ev->GetBoolean(1); + edict->s.missionObjective = ev->GetBoolean(1); + if( edict->s.missionObjective ) + { + G_AddEntityToExtraList(entnum); + } + else + { + G_RemoveEntityFromExtraList(entnum); + } +} + + +//-------------------------------------------------------------- +// Name: GetVelocity +// Class: Entity +// +// Description: Gets the Velocity of the Entity +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Entity::GetVelocity( Event *ev ) +{ + ev->ReturnVector( velocity ); +} + +//-------------------------------------------------------------- +// Name: SetVelocity +// Class: Entity +// +// Description: Sets the Velocity of the Entity +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Entity::SetVelocity( Event *ev ) +{ + velocity = ev->GetVector( 1 ); +} + +void Entity::startStasis( Event *ev ) +{ + startStasis(); +} + +void Entity::stopStasis( Event *ev ) +{ + stopStasis(); +} + +void Entity::startStasis( void ) +{ + // Immobilize the character + + flags |= FL_IMMOBILE; + flags |= FL_STUNNED; + + // Show an effect + + setCustomShader( "stasis" ); + + if ( animate ) + { + animate->StopAnimating(); + + if ( edict->s.torso_anim & ANIM_BLEND ) + animate->StopAnimating( torso ); + } +} + +void Entity::stopStasis( void ) +{ + // Unimmobilize the character + + flags &= ~FL_IMMOBILE; + flags &= ~FL_STUNNED; + + // Stop the effect + + clearCustomShader( "stasis" ); + + if ( this->isSubclassOf( Actor ) ) + { + Actor *actor = (Actor *)this; + + actor->resetStateMachine(); + } + else if ( this->isSubclassOf( Player ) ) + { + Player *player = (Player *)this; + + player->SetState("STAND", "START"); + } + +} + +void Entity::setTargetPos( Event *ev ) +{ + setTargetPos( ev->GetString( 1 ) ); +} + +void Entity::setTargetPos( const str &targetPos ) +{ + _targetPos = targetPos; +} + +str Entity::getTargetPos() +{ + return _targetPos; +} + +void Entity::addHealthOverTime( Event *ev ) +{ + Event *event; + float healthToAdd; + float healthToAddThisFrame; + float timeLeft; + int numFrames; + + healthToAdd = ev->GetFloat( 1 ); + timeLeft = ev->GetFloat( 2 ); + + // Figure out how much health to add this frame + + if ( timeLeft < level.frametime ) + numFrames = 1; + else + numFrames = (int) (timeLeft / level.frametime); + + healthToAddThisFrame = healthToAdd / numFrames; + + // Actually add the health to the entity + + addHealth( healthToAddThisFrame ); + + // Post the event for the next frame + + healthToAdd -= healthToAddThisFrame; + timeLeft -= level.frametime; + + //CancelEventsOfType( EV_AddHealthOverTime ); + + if ( timeLeft > 0.0f ) + { + event = new Event( EV_AddHealthOverTime ); + event->AddFloat( healthToAdd ); + event->AddFloat( timeLeft ); + PostEvent( event, level.frametime ); + } +} + +void Entity::simplePlayDialog( Event *ev ) +{ + str dialogName; + float volume = DEFAULT_VOL; + float min_dist = DEFAULT_MIN_DIST; + char localizedDialogName[MAX_QPATH]; + Player *player; + + + // Get all of the parms + + dialogName = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + volume = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + { + str minDistString; + + min_dist = ev->GetFloat( 3 ); + + minDistString = ev->GetString( 3 ); + + if ( stricmp( minDistString.c_str(), LEVEL_WIDE_STRING ) == 0 ) + min_dist = LEVEL_WIDE_MIN_DIST; + else + min_dist = ev->GetFloat( 3 ); + + if ( min_dist >= LEVEL_WIDE_MIN_DIST_CUTOFF ) + min_dist = LEVEL_WIDE_MIN_DIST; + } + + // Get the localized name of the dialog + + gi.LocalizeFilePath( dialogName.c_str(), localizedDialogName ); + + // Play the sound + + Sound( localizedDialogName, CHAN_DIALOG, volume, min_dist ); + + // Tell the player about the dialog + + player = GetPlayer( 0 ); + + if ( player ) + { + player->SetupDialog( NULL, localizedDialogName ); + } +} + +void Entity::warp( Event *ev ) +{ + Entity *ent; + int i; + + if ( ev->NumArgs() > 0 ) + setOrigin( ev->GetVector( 1 ) ); + else + setOrigin(); + + setAngles(); + + NoLerpThisFrame(); + + if ( bind_info ) + { + // Make sure everyone bound to us doesn't lerp + + for( ent = bind_info->teamchain; ent != NULL; ent = ent->bind_info->teamchain ) + { + if ( ent->bind_info->teammaster == this ) + { + ent->ProcessEvent( EV_Warp ); + } + } + + // Make sure everyone attached bound to us doesn't lerp + + for( i = 0; i < MAX_MODEL_CHILDREN ; i++ ) + { + if ( bind_info->children[ i ] == ENTITYNUM_NONE ) + { + continue; + } + + ent = G_GetEntity( bind_info->children[ i ] ); + + if ( ent ) + { + ent->ProcessEvent( EV_Warp ); + } + } + } +} + +void Entity::traceHitsEntity( Event *ev ) +{ + trace_t trace; + Vector end; + Vector start; + Vector dir; + str tagName; + float length; + Entity *entityToCheck; + + + // Get the event info + + tagName = ev->GetString( 1 ); + length = ev->GetFloat( 2 ); + entityToCheck = ev->GetEntity( 3 ); + + // Determine the start and end point of the trace + + GetTag( tagName, &start, &dir ); + + end = start + dir * length; + + // Do the actual trace + + trace = G_Trace( start, vec_zero, vec_zero, end, NULL, MASK_SHOT, false, "traceHitsEntity" ); + + // Determine if we hit this entity + + if ( trace.ent && entityToCheck && trace.ent->entity == entityToCheck ) + ev->ReturnFloat( 1.0f ); + else + ev->ReturnFloat( 0.0f ); +} + +void Entity::setOriginEveryFrame( Event *ev ) +{ + Event *repost; + repost = new Event(EV_SetOriginEveryFrame); + + setOrigin(); + + PostEvent( repost , FRAMETIME ); + +} + +void Entity::isWithinDistanceOf( Event *ev ) +{ + Entity *destination; + float distance; + float returnValue; + + destination = ev->GetEntity( 1 ); + distance = ev->GetFloat( 2 ); + returnValue = 0.0f; + + if ( WithinDistance( destination , distance ) ) + returnValue = 1.0; + + ev->ReturnFloat( returnValue ); +} + +void Entity::setNetworkDetail( Event *ev ) +{ + _networkDetail = true; +} + +bool Entity::isNetworkDetail( void ) +{ + return _networkDetail; +} diff --git a/dlls/game/entity.h b/dlls/game/entity.h new file mode 100644 index 0000000..97c90c8 --- /dev/null +++ b/dlls/game/entity.h @@ -0,0 +1,1132 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/entity.h $ +// $Revision:: 124 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:43a $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class for all enities that are controlled by the game. If you have any +// object that should be called on a periodic basis and it is not an entity, +// then you have to have an dummy entity that calls it. +// +// An entity in the game is any object that is not part of the world. Any non-world +// object that is visible in the game is an entity, although it is not required that +// all entities be visible to the player. Some objects are basically just virtual +// constructs that act as an instigator of certain actions, for example, some +// triggers are invisible and cannot be touched, but when activated by other +// objects can cause things to happen. +// +// All entities are capable of receiving messages from Sin or from other entities. +// Messages received by an entity may be ignored, passed on to their superclass, +// or acted upon by the entity itself. The programmer must decide on the proper +// action for the entity to take to any message. There will be many messages +// that are completely irrelevant to an entity and should be ignored. Some messages +// may require certain states to exist and if they are received by an entity when +// it these states don't exist may indicate a logic error on the part of the +// programmer or map designer and should be reported as warnings (if the problem is +// not severe enough for the game to be halted) or as errors (if the problem should +// not be ignored at any cost). +// + +class BindInfo; +class MorphInfo; +class Entity; + +#ifndef __ENTITY_H__ +#define __ENTITY_H__ + +#include "program.h" +#include "g_local.h" +#include "class.h" +#include "vector.h" +#include "script.h" +#include "listener.h" +#include "scriptvariable.h" +#include "DamageModification.hpp" +#include "UseData.h" + +// modification flags +#define FLAG_IGNORE 0 +#define FLAG_CLEAR 1 +#define FLAG_ADD 2 + +typedef enum + { + DAMAGE_NO, + DAMAGE_YES, // will take damage if hit + DAMAGE_AIM // auto targeting recognizes this + } damage_t; + +//deadflag +#define DEAD_NO 0 +#define DEAD_DYING 1 +#define DEAD_DEAD 2 +#define DEAD_RESPAWNABLE 3 + +// flags +#define FL_FLY (1<<0) +#define FL_SWIM (1<<1) // implied immunity to drowining +#define FL_INWATER (1<<2) +#define FL_GODMODE (1<<3) +#define FL_NOTARGET (1<<4) +#define FL_PARTIALGROUND (1<<5) // not all corners are valid +#define FL_TEAMSLAVE (1<<6) // not the first on the team +#define FL_NO_KNOCKBACK (1<<7) +#define FL_THINK (1<<8) +#define FL_BLOOD (1<<9) // when hit, it should bleed. +#define FL_DIE_GIBS (1<<10) // when it dies, it should gib +#define FL_DIE_EXPLODE (1<<11) // when it dies, it will explode +#define FL_ROTATEDBOUNDS (1<<12) // model uses rotated mins and maxs +#define FL_DONTSAVE (1<<13) // don't add to the savegame +#define FL_IMMOBILE (1<<14) // entity has been immobolized somehow +#define FL_PARTIAL_IMMOBILE (1<<15) // entity has been immobolized somehow +#define FL_STUNNED (1<<16) +#define FL_POSTTHINK (1<<17) // call a think function after the physics have been run +#define FL_TOUCH_TRIGGERS (1<<18) // should this entity touch triggers +#define FL_AUTOAIM (1<<19) // Autoaim on this entity + +// Create falgs + +#define ENTITY_CREATE_FLAG_ANIMATE (1<<0) +#define ENTITY_CREATE_FLAG_MOVER (1<<1) + + +// damage flags +#define DAMAGE_RADIUS 0x00000001 // damage was indirect +#define DAMAGE_NO_ARMOR 0x00000002 // armour does not protect from this damage +#define DAMAGE_ENERGY 0x00000004 // damage is from an energy based weapon +#define DAMAGE_NO_KNOCKBACK 0x00000008 // do not affect velocity, just view angles +#define DAMAGE_BULLET 0x00000010 // damage is from a bullet (used for ricochets) +#define DAMAGE_NO_PROTECTION 0x00000020 // armor, shields, invulnerability, and godmode have no effect +#define DAMAGE_NO_SKILL 0x00000040 // damage is not affected by skill level + +extern Event EV_ClientMove; +extern Event EV_ClientEndFrame; + +// Generic entity events +extern Event EV_Classname; +extern Event EV_Activate; +extern Event EV_Use; +extern Event EV_FadeNoRemove; +extern Event EV_FadeOut; +extern Event EV_FadeIn; +extern Event EV_Fade; +extern Event EV_Killed; +extern Event EV_GotKill; +extern Event EV_Pain; +extern Event EV_Damage; +extern Event EV_Stun; +extern Event EV_Gib; +extern Event EV_Kill; +extern Event EV_DeathSinkStart; +extern Event EV_TouchTriggers; +extern Event EV_DoRadiusDamage; + +// Physics events +extern Event EV_MoveDone; +extern Event EV_Touch; +extern Event EV_Contact; +extern Event EV_LostContact; +extern Event EV_Blocked; +extern Event EV_Attach; +extern Event EV_AttachModel; +extern Event EV_RemoveAttachedModel; +extern Event EV_RemoveAttachedModelByTargetname; +extern Event EV_Detach; +extern Event EV_UseBoundingBox; + +// Animation events +extern Event EV_NewAnim; +extern Event EV_LastFrame; +extern Event EV_TakeDamage; +extern Event EV_NoDamage; +extern Event EV_SetCinematicAnim ; + +// script stuff +extern Event EV_Model; +extern Event EV_Hide; +extern Event EV_Show; +extern Event EV_BecomeSolid; +extern Event EV_BecomeNonSolid; +extern Event EV_Sound; +extern Event EV_StopSound; +extern Event EV_Bind; +extern Event EV_Unbind; +extern Event EV_JoinTeam; +extern Event EV_QuitTeam; +extern Event EV_SetHealth; +extern Event EV_SetMaxHealth; +extern Event EV_SetSize; +extern Event EV_SetAlpha; +extern Event EV_SetOrigin; +extern Event EV_Warp; +extern Event EV_SetTargetName; +extern Event EV_SetTarget; +extern Event EV_SetKillTarget; +extern Event EV_SetAngles; +extern Event EV_SetAngle; +extern Event EV_RegisterAlias; +extern Event EV_Anim; +extern Event EV_StartAnimating; +extern Event EV_SurfaceModelEvent; +extern Event EV_ProcessInitCommands; +extern Event EV_Stop; +extern Event EV_StopLoopSound; +extern Event EV_SetControllerAngles; +extern Event EV_DisplayEffect; +extern Event EV_ForceAlpha; + +extern Event EV_SetFloatVar; +extern Event EV_SetVectorVar; +extern Event EV_SetStringVar; + +extern Event EV_GetFloatVar; +extern Event EV_GetVectorVar; +extern Event EV_GetStringVar; + +extern Event EV_RemoveVariable; +extern Event EV_DoesVarExist; + + +extern Event EV_UseDataAnim; +extern Event EV_UseDataType; +extern Event EV_UseDataThread; +extern Event EV_UseDataMaxDist; +extern Event EV_UseData; + +extern Event EV_CreateEarthquake; +extern Event EV_SpawnEffect; + +// Morph stuff + +extern Event EV_Morph; +extern Event EV_Unmorph; + +// dir is 1 +// power is 2 +// minsize is 3 +// maxsize is 4 +// percentage is 5 +// thickness 6 +// entity is 7 +// origin 8 + +// AI sound events +extern Event EV_BroadcastSound; +extern Event EV_HeardSound; +extern Event EV_Hurt; +extern Event EV_IfSkill; + +extern Event EV_SetArchetype; +extern Event EV_SetGameplayHealth; +extern Event EV_SetGameplayDamage; +extern Event EV_ProcessGameplayData; + +extern Event EV_StopStasis; + +extern Event EV_AddHealthOverTime; + +extern Event EV_SimplePlayDialog; +// Define ScriptMaster +class ScriptMaster; + +// +// Spawn args +// +// "spawnflags" +// "alpha" default 1.0 +// "model" +// "origin" +// "targetname" +// "target" +// +#define MAX_MODEL_CHILDREN 8 + +class Entity; +class Animate; +class Mover; + +class BindInfo + { + public: + + // Model Binding variables + int numchildren; + int children[MAX_MODEL_CHILDREN]; + + // Team variables + str moveteam; + Entity *teamchain; + Entity *teammaster; + + // Binding variables + Entity *bindmaster; + qboolean bind_use_my_angles; + //Vector localorigin; + //Vector localangles; + + qboolean detach_at_death; + + BindInfo(); + + void Archive( Archiver &arc ); + }; + +inline BindInfo::BindInfo() + { + int i; + + // model binding variables + numchildren = 0; + + for( i = 0 ; i < MAX_MODEL_CHILDREN ; i++ ) + children[i] = ENTITYNUM_NONE; + + detach_at_death = true; + + // team variables + teamchain = NULL; + teammaster = NULL; + + // bind variables + bindmaster = NULL; + + bind_use_my_angles = false; + } + +inline void BindInfo::Archive(Archiver &arc) + { + arc.ArchiveInteger( &numchildren ); + arc.ArchiveRaw( children, sizeof( children ) ); + arc.ArchiveString( &moveteam ); + arc.ArchiveObjectPointer( ( Class ** )&teamchain ); + arc.ArchiveObjectPointer( ( Class ** )&teammaster ); + arc.ArchiveObjectPointer( ( Class ** )&bindmaster ); + arc.ArchiveBoolean( &bind_use_my_angles ); + arc.ArchiveBoolean( &detach_at_death ); + } + +BindInfo *CreateBindInfo( void ); + +typedef struct + { + int index; + float current_percent; + float speed; + float final_percent; + qboolean return_to_zero; + int channel; + } morph_t; + +class MorphInfo + { + public: + MorphInfo(); + + morph_t controllers[ NUM_MORPH_CONTROLLERS ]; + qboolean controller_on; + + void Archive( Archiver &arc ); + }; + +inline MorphInfo::MorphInfo() + { + int i; + + for( i = 0 ; i < NUM_MORPH_CONTROLLERS ; i++ ) + { + controllers[ i ].index = -1; + controllers[ i ].current_percent = 0.0; + } + + controller_on = false; + } + +inline void MorphInfo::Archive(Archiver &arc) + { + int i; + + for( i = 0 ; i < NUM_MORPH_CONTROLLERS ; i++ ) + { + arc.ArchiveInteger( &controllers[ i ].index ); + arc.ArchiveFloat( &controllers[ i ].current_percent ); + arc.ArchiveFloat( &controllers[ i ].speed ); + arc.ArchiveFloat( &controllers[ i ].final_percent ); + arc.ArchiveBoolean( &controllers[ i ].return_to_zero ); + arc.ArchiveInteger( &controllers[ i ].channel ); + } + + arc.ArchiveBoolean( &controller_on ); + } + +MorphInfo *CreateMorphInfo( void ); + +typedef SafePtr EntityPtr; + +class Program; +class Entity : public Listener + { + private: + Vector _localOrigin; + Container _lastTouchedList ; + bool _fulltrace ; + int _groupID; + str _archetype; + bool _missionObjective; + str _targetPos; + + bool _networkDetail; + + protected: + void buildUseData(); + + public: + CLASS_PROTOTYPE( Entity ); + + // Construction / destruction + Entity(); + Entity( int create_flag ); + virtual ~Entity(); + + // Spawning variables + int entnum; + gentity_t *edict; + gclient_t *client; + int spawnflags; + + // Standard variables + str model; + + // Physics variables + Vector total_delta; // total unprocessed movement + Vector mins; + Vector maxs; + Vector absmin; + Vector absmax; + Vector centroid; + Vector velocity; + Vector avelocity; + Vector origin; + Vector angles; + Vector size; + int movetype; + int mass; + float gravity; // per entity gravity multiplier (1.0 is normal) + float orientation[3][3]; + Vector localangles; + + // Ground variables + gentity_t *groundentity; + cplane_t groundplane; + int groundcontents; + + // Surface variables + int numsurfaces; + + // Light variables + float lightRadius; + + // Targeting variables + str target; + str targetname; + str killtarget; + + // Character state + float health; + float max_health; + int deadflag; + int flags; + + // underwater variables + int watertype; + int waterlevel; + + // Pain and damage variables + damage_t takedamage; + int damage_type; + + qboolean look_at_me; + bool projectilesCanStickToMe; + + str explosionModel; + + ScriptVariableList entityVars; + + unsigned int _affectingViewModes; + Vector watch_offset; + + // Pluggable modules + + Animate *animate; + Mover *mover; + BindInfo *bind_info; + MorphInfo *morph_info; + Program *ObjectProgram; + DamageModificationSystem *damageModSystem; + UseData *useData; + + void Setup(); + + static Entity* FindEntityByName( const str &entityName ); + void SetEntNum( int num ); + void ClassnameEvent( Event *ev ); + void SpawnFlagsEvent( Event *ev ); + + const Vector InterceptTarget( const Vector &targetPosition, const Vector &targetVelocity, const float maxSpeed) const; + const Vector InterceptTargetXY( const Vector &targetPosition, const Vector &targetVelocity, const float maxSpeed) const; + float DistanceTo( const Vector &pos ); + float DistanceTo( const Entity *ent ); + qboolean WithinDistance( const Vector &pos, float dist ); + qboolean WithinDistance( const Entity *ent, float dist ); + bool WithinDistanceXY( const Vector &pos , float dist ); + bool WithinDistanceXY( const Entity *ent , float dist ); + + const char *Target( void ); + void SetTarget( const char *target ); + qboolean Targeted( void ); + const char *TargetName( void ); + void SetTargetName( const char *target ); + void SetKillTarget( const char *killtarget ); + const char *KillTarget( void ); + + const Vector & GetLocalOrigin( void ) const { return _localOrigin; } + void SetLocalOrigin( const Vector &localOrigin ) { _localOrigin = localOrigin; } + + virtual void setModel( const char *model ); + void setModel( const str &mdl ); + virtual void setViewModel( const char *model ); + void setViewModel( const str &mdl ); + void SetModelEvent( Event *ev ); + void SetTeamEvent( Event *ev ); + virtual void TriggerEvent( Event *ev ); + void hideModel( void ); + void EventHideModel( Event *ev ); + virtual void showModel( void ); + void EventShowModel( Event *ev ); + qboolean hidden( void ); + void ProcessInitCommandsEvent( Event *ev ); + void ProcessInitCommands( int index, qboolean cache = false ); + + void setAlpha( float alpha ); + float alpha( void ); + + void setMoveType( int type ); + void setMoveType( Event *ev ); + + int getMoveType( void ); + + void setSolidType( solid_t type ); + int getSolidType( void ); + + virtual Vector getParentVector( const Vector &vec ); + Vector getLocalVector( const Vector &vec ); + + virtual void setSize( Vector min, Vector max ); + virtual void setOrigin( const Vector &org ); + virtual void setOrigin( void ); + virtual void addOrigin( const Vector &org ); + virtual void setOriginEveryFrame( Event *ev ); + + void GetRawTag( int tagnum, orientation_t * orient, bodypart_t part = legs ); + qboolean GetRawTag( const char * tagname, orientation_t * orient, bodypart_t part = legs ); + + void GetTag( int tagnum, orientation_t * orient ); + qboolean GetTag( const char *name, orientation_t * orient ); + void GetTag( int tagnum, Vector *pos, Vector *forward = NULL, Vector *left = NULL, Vector *up = NULL ); + qboolean GetTag( const char *name, Vector *pos, Vector *forward = NULL, Vector *left = NULL, Vector *up = NULL ); + + virtual int CurrentFrame( bodypart_t part = legs ); + virtual int CurrentAnim( bodypart_t part = legs ); + + virtual void setAngles( const Vector &ang ); + virtual void setAngles( void ); + virtual void SetOrigin( Event *ev ); + void GetOrigin( Event *ev ); + + Vector GetControllerAngles( int num ); + void SetControllerAngles( int num, vec3_t angles ); + void SetControllerAngles( Event *ev ); + void SetControllerTag( int num, int tag_num ); + + void link( void ); + void unlink( void ); + + void setContents( int type ); + int getContents( void ); + void setScale( float scale ); + + qboolean droptofloor( float maxfall ); + qboolean isClient( void ); + + virtual void SetDeltaAngles( void ); + virtual void DamageEvent( Event *event ); + + void Damage( Entity *inflictor, + Entity *attacker, + float damage, + const Vector &position, + const Vector &direction, + const Vector &normal, + int knockback, + int flags, + int meansofdeath, + int surface_number = -1, + int bone_number = -1, + Entity *weapon = 0); + + void Stun( float time ); + + void DamageType( Event *ev ); + virtual qboolean CanDamage( const Entity *target, const Entity *skip_ent = NULL ); + + qboolean IsTouching( const Entity *e1 ); + + void FadeNoRemove( Event *ev ); + void FadeOut( Event *ev ); + void FadeIn( Event *ev ); + void Fade( Event *ev ); + + virtual void CheckGround( void ); + virtual qboolean HitSky( const trace_t *trace ); + virtual qboolean HitSky( void ); + + void BecomeSolid( Event *ev ); + void BecomeNonSolid( Event *ev ); + void SetHealth( Event *ev ); + void GetHealth( Event *ev ); + void SetMaxHealth( Event *ev ); + void SetSize( Event *ev ); + void SetMins( Event *ev ); + void SetMaxs( Event *ev ); + void GetMins( Event* ev ); + void GetMaxs( Event* ev ); + void SetScale( Event *ev ); + void setRandomScale( Event *ev ); + void SetAlpha( Event *ev ); + void SetTargetName( Event *ev ); + void GetTargetName( Event *ev ); + void GetRawTargetName( Event *ev ); + void SetTarget( Event *ev ); + void getTarget( Event *ev ); + void GetTargetEntity( Event *ev ); + void SetKillTarget( Event *ev ); + void GetModelName(Event* ev); + void SetAngles( Event *ev ); + void GetAngles(Event* ev); + void SetAngleEvent( Event *ev ); + void TouchTriggersEvent( Event *ev ); + void IncreaseShotCount( Event *ev ); + void GetVelocity( Event *ev ); + void SetVelocity( Event *ev ); + + // Support for checking/setting fulltrace flag + void SetFullTraceEvent( Event *ev ); + void setFullTrace( bool fulltrace ) { _fulltrace = fulltrace; } + bool usesFullTrace() { return _fulltrace ; } + + Vector GetClosestCorner( const Vector &position ); + + str GetRandomAlias( const str &name ); + void SetWaterType( void ); + + // model binding functions + qboolean attach( int parent_entity_num, int tag_num, qboolean use_angles = true, Vector attach_offset = Vector(0, 0, 0), Vector attach_angles_offset = Vector(0, 0, 0) ); + void detach( void ); + + void RegisterAlias( Event *ev ); + void RegisterAliasAndCache( Event *ev ); + void Cache( Event *ev ); + + qboolean GlobalAliasExists( const char *name ); + qboolean AliasExists( const char *name ); + + // Sound Stuff + void Sound( Event *ev ); + virtual void Sound( const str &sound_name, int channel = CHAN_BODY, float volume = -1.0f, float min_dist = -1.0f, Vector *origin = NULL, float pitch_modifier = 1.0f, qboolean onlySendToThisEntity = false ); + void StopSound( int channel ); + void StopSound( Event *ev ); + void LoopSound( Event *ev ); + void LoopSound( const str &sound_name, float volume = -1.0f, float min_dist = -1.0f ); + void StopLoopSound( Event *ev ); + void StopLoopSound( void ); + + // Light Stuff + void SetLight(Event *ev); + void LightOn(Event *ev); + void LightOff(Event *ev); + void LightRed(Event *ev); + void LightGreen(Event *ev); + void LightBlue(Event *ev); + void LightRadius(Event *ev); + void LightStyle(Event *ev); + void Flags( Event *ev ); + void Effects( Event *ev ); + void RenderEffects( Event *ev ); + void SVFlags( Event *ev ); + + void BroadcastSound( float pos = SOUND_RADIUS, int soundType = SOUNDTYPE_GENERAL ); + void BroadcastSound( Event *ev ); + float ModifyFootstepSoundRadius( float radius , int soundTypeIdx ); + void Kill( Event *ev ); + void SurfaceModelEvent( Event *ev ); + void SurfaceCommand( const char * surf_name, const char * token ); + + virtual void Postthink( void ); + virtual void Think( void ); + void DamageSkin( trace_t * trace, float damage ); + + void AttachEvent( Event *ev ); + void AttachModelEvent( Event *ev ); + void RemoveAttachedModelEvent( Event *ev ); + void removeAttachedModelByTargetname( Event *ev ); + void removeAttachedModelByTargetname( const str &targetNameToRemove ); + void DetachEvent( Event *ev ); + void TakeDamageEvent( Event *ev ); + void NoDamageEvent( Event *ev ); + void Gravity( Event *ev ); + //void GiveOxygen( float time ); + void UseBoundingBoxEvent( Event *ev ); + void HurtEvent( Event *ev ); + void IfSkillEvent( Event *ev ); + void SetMassEvent( Event *ev ); + void Censor( Event *ev ); + void Ghost( Event *ev ); + + void StationaryEvent( Event *ev ); + void Explosion( Event *ev ); + void SelfDetonate( Event *ev ); + void DoRadiusDamage( Event *ev ); + + void Shader( Event *ev ); + + void KillAttach( Event *ev ); + //void SetBloodModel( Event *ev ); + + void DropToFloorEvent( Event *ev ); + void SetAnimOnAttachedModel( Event *ev ); + void SetAnimOnAttachedModel( const str &AnimName, const str &TagName ); + void SetEntityExplosionModel( Event *ev ); + + virtual void SetCinematicAnim( const str &AnimName); + virtual void SetCinematicAnim( Event *ev ); + virtual void CinematicAnimDone( void ); + virtual void CinematicAnimDone( Event *ev ); + + // Binding methods + void joinTeam( Entity *teammember ); + void quitTeam( void ); + qboolean isBoundTo( const Entity *master ); + virtual void bind( Entity *master, qboolean use_my_angles=false ); + virtual void unbind( void ); + + void JoinTeam( Event *ev ); + void EventQuitTeam( Event *ev ); + void BindEvent( Event *ev ); + void EventUnbind( Event *ev ); + void AddToSoundManager( Event *ev ); + void NoLerpThisFrame( void ); + + virtual void addAngles( const Vector &add ); + + void DeathSinkStart( Event *ev ); + void DeathSink( Event *ev ); + + void LookAtMe( Event *ev ); + void ProjectilesCanStickToMe( Event *ev ); + void DetachAllChildren( Event *ev ); + + void MorphEvent( Event *ev ); + void UnmorphEvent( Event *ev ); + void MorphControl( Event *ev ); + int GetMorphChannel( const char *morph_name ); + void StartMorphController( void ); + qboolean MorphChannelMatches( int morph_channel1, int morph_channel2 ); + + void ProjectileAtk( Event *ev ); + void ProjectileAttackPoint( Event *ev ); + void ProjectileAttackEntity( Event *ev ); + void ProjectileAttackFromTag( Event *ev ); + void ProjectileAttackFromPoint( Event *ev ); + void TraceAtk( Event *ev ); + + virtual void VelocityModified( void ); + virtual void Archive( Archiver &arc ); + + virtual void PassToAnimate( Event *ev ); + void SetObjectProgram( Event *ev ); + void SetWatchOffset( Event *ev ); + void ExecuteProgram( Event *ev ); + + void Contents(Event* ev); + void setMask(Event* ev); + + void hideFeaturesForFade( void ); + void showFeaturesForFade( void ); + + void DisplayEffect( Event *ev ); + void clearDisplayEffects( void ); + + void getCustomShaderInfo( const str &customShader, str &shaderName, str &soundName ); + + void setCustomShader( const char *customShader ); + void setCustomShader( Event *ev ); + void clearCustomShader( const char *customShader = NULL ); + void clearCustomShader( Event *ev ); + bool hasCustomShader( const char *customShader = NULL ); + + void setCustomEmitter( const char *customEmitter ); + void setCustomEmitter( Event *ev ); + void clearCustomEmitter( const char *customEmitter = NULL ); + void clearCustomEmitter( Event *ev ); + + Entity *SpawnEffect( const str &name, const Vector &origin, const Vector &angles, float removeTime ); + Entity *SpawnSound( const str &sound, const Vector &pos, float volume , float removeTime ); + + void SpawnEffect( Event *ev ); + + void attachEffect( const str &modelName, const str &tagName, float removeTime ); + void attachEffect( Event *ev ); + + void ForceAlpha( Event *ev ); + void CreateEarthquake( Event *ev ); + + void SetFloatVar( Event *ev ); + void SetVectorVar( Event *ev ); + void SetStringVar( Event *ev ); + void GetFloatVar( Event *ev ); + void doesVarExist( Event *ev ); + void RemoveVariable( Event* ev ); + void GetVectorVar( Event *ev ); + void GetStringVar( Event *ev ); + void SetUserVar1( Event *ev ); + void SetUserVar2( Event *ev ); + void SetUserVar3( Event *ev ); + void SetUserVar4( Event *ev ); + void isWithinDistanceOf( Event *ev ); + + void affectingViewMode( Event *ev ); + void addAffectingViewModes( unsigned int mask ); + void removeAffectingViewModes( unsigned int mask ); + + void TikiNote( Event *ev ); + void TikiTodo( Event *ev ); + + void AddDamageModifier( Event *ev ); + void ResolveDamage( ::Damage &damage ); + + // Health interface + float getHealth( void ) { return health; }; + float getMaxHealth( void ) { return max_health; }; + void setHealth( float newHealth ) { health = newHealth; }; + void setMaxHealth( float newMaxHealth ) { max_health = newMaxHealth; }; + void addHealth( float healthToAdd, float maxHealth = 0.0f ); + void addHealthOverTime( Event *ev ); + + // Group Number Interface + void SetGroupID(Event *ev); + void AddToGroup( int ID ); + void SetGroupID(int ID) { _groupID = ID; } + int GetGroupID() { return _groupID; }; + + void MultiplayerEvent( Event *ev ); + + Container& GetLastTouchedList() { return _lastTouchedList ; } + + // Archetype name + virtual void setArchetype( Event *ev ); + const str getArchetype() const; + virtual const str getName() const { return ""; } + + void setMissionObjective(Event* ev); + + // Usable Entity functions + void useDataAnim( Event *ev ); + void useDataType( Event *ev ); + void useDataThread( Event *ev ); + void useDataMaxDist( Event *ev ); + void useDataCount( Event *ev ); + void useDataEvent( Event *ev ); + bool hasUseData(); + + + // GameplayManager interfaces to health and damage + void setGameplayHealth( Event *ev ); + void setGameplayDamage( Event *ev ); + virtual void processGameplayData( Event *ev ) {} + + // Think interface + + void turnThinkOn( void ) { flags |= FL_THINK; } + void turnThinkOff( void ) { flags &= ~FL_THINK; } + bool isThinkOn( void ) { return (flags & FL_THINK) ? true : false; } + + void startStasis( void ); + void stopStasis( void ); + void startStasis( Event *ev ); + void stopStasis( Event *ev ); + void setTargetPos( Event *ev ); + void setTargetPos( const str &targetPos ); + str getTargetPos(); + + void simplePlayDialog( Event *ev ); + + void warp( Event *ev ); + + void traceHitsEntity( Event *ev ); + + void setNetworkDetail( Event *ev ); + bool isNetworkDetail( void ); + }; + +inline bool Entity::hasUseData() + { + if ( useData ) + return true; + return false; + } + +inline int Entity::getSolidType() + { + return edict->solid; + } + +inline const Vector Entity::InterceptTarget( const Vector &targetPosition, const Vector &targetVelocity, const float maxSpeed) const + { + Vector myPosition (origin); + Vector predictedPosition = targetPosition + targetVelocity * ( Vector::Distance(targetPosition, myPosition) / maxSpeed ); + Vector desiredDirection = predictedPosition - myPosition; + + return desiredDirection.toAngles(); + } + +inline const Vector Entity::InterceptTargetXY( const Vector &targetPosition, const Vector &targetVelocity, const float maxSpeed) const + { + Vector myPosition (origin); + float distanceToTargetPosition = Vector::Distance(targetPosition, myPosition); + if ( !fSmallEnough( distanceToTargetPosition, 0.1f ) ) + { + Vector predictedPosition = targetPosition + targetVelocity * ( distanceToTargetPosition / maxSpeed ); + Vector desiredDirection = predictedPosition - myPosition; + desiredDirection.z = 0.0f; + return desiredDirection.toAngles(); + } + return angles; + } + +inline float Entity::DistanceTo(const Vector &pos) + { + Vector delta; + + delta = origin - pos; + return delta.length(); + } + +inline float Entity::DistanceTo(const Entity *ent) + { + Vector delta; + + assert( ent ); + + if ( !ent ) + return 999999.0f; // "Infinite" distance + + delta = origin - ent->origin; + return delta.length(); + } + +inline qboolean Entity::WithinDistance(const Vector &pos, float dist) + { + Vector delta; + delta = origin - pos; + + // check squared distance + return ( ( delta * delta ) < ( dist * dist ) ); + } + +inline qboolean Entity::WithinDistance(const Entity *ent, float dist) + { + Vector delta; + + //assert( ent ); + + if ( !ent ) + return false; + + delta = origin - ent->origin; + + // check squared distance + return ( ( delta * delta ) < ( dist * dist ) ); + } + +inline bool Entity::WithinDistanceXY( const Vector &pos, float dist ) +{ + float distance = Vector::DistanceXY( origin , pos ); + return distance <= dist; +} + +inline bool Entity::WithinDistanceXY( const Entity *ent , float dist ) +{ + if ( !ent ) + return false; + + float distance = Vector::DistanceXY( origin , ent->origin ); + return distance <= dist; +} + +inline const char *Entity::Target() + { + return target.c_str(); + } + +inline qboolean Entity::Targeted() + { + if ( !targetname.length() ) + return false; + + return true; + } + +inline const char *Entity::TargetName() + { + return targetname.c_str(); + } + +inline const char * Entity::KillTarget() + { + return killtarget.c_str(); + } + +inline qboolean Entity::hidden() + { + if ( edict->s.renderfx & RF_DONTDRAW ) + return true; + + return false; + } + +inline void Entity::setModel(const str &mdl) + { + setModel( mdl.c_str() ); + } + +inline void Entity::setViewModel(const str &mdl) + { + setViewModel( mdl.c_str() ); + } + +inline void Entity::SetModelEvent(Event *ev) + { + char modelname[256] ; + strcpy(modelname, ev->GetString( 1 ) ); + char *tmpPtr = strstr(modelname, "*"); + if (tmpPtr) + { + ev->SetString( 1, tmpPtr); + } + + setModel( ev->GetString( 1 ) ); + } + +inline void Entity::hideModel() + { + edict->s.renderfx |= RF_DONTDRAW; + if ( getSolidType() <= SOLID_TRIGGER ) + edict->svflags |= SVF_NOCLIENT; + } + +inline void Entity::showModel() + { + edict->s.renderfx &= ~RF_DONTDRAW; + edict->svflags &= ~SVF_NOCLIENT; + } + +inline float Entity::alpha() + { + return edict->s.alpha; + } + +inline void Entity::setMoveType(int type) + { + movetype = type; + } + +inline int Entity::getMoveType() + { + return movetype; + } + +inline void Entity::unlink() + { + gi.unlinkentity( edict ); + } + +inline void Entity::setContents(int type) + { + edict->contents = type; + } + +inline int Entity::getContents() + { + return edict->contents; + } + +inline qboolean Entity::isClient() + { + if ( client ) + return true; + + return false; + } + +inline void Entity::SetDeltaAngles() + { + int i; + + if ( !client ) + return; + + for( i = 0; i < 3; i++ ) + client->ps.delta_angles[ i ] = ANGLE2SHORT( client->ps.viewangles[ i ] ); + } + +inline qboolean Entity::GlobalAliasExists(const char *name) + { + assert( name ); + return ( gi.GlobalAlias_FindRandom( name ) != NULL ); + } + +inline qboolean Entity::AliasExists(const char *name) + { + assert( name ); + return ( gi.Alias_FindRandom( edict->s.modelindex, name ) != NULL ); + } + +inline str Entity::GetRandomAlias(const str &name) + { + str realname; + const char *s; + + s = gi.Alias_FindRandom( edict->s.modelindex, name.c_str() ); + if ( s ) + realname = s; + else + { + s = gi.GlobalAlias_FindRandom( name.c_str() ); + if ( s ) + realname = s; + } + + return realname; + } + +#include "worldspawn.h" + +#endif diff --git a/dlls/game/equipment.cpp b/dlls/game/equipment.cpp new file mode 100644 index 0000000..adda53e --- /dev/null +++ b/dlls/game/equipment.cpp @@ -0,0 +1,658 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/equipment.cpp $ +// $Revision:: 53 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: + +#include "_pch_cpp.h" +#include "equipment.h" +#include "player.h" +#include "weaputils.h" + +#define DETAILED_SCAN_TIME 1.5 +#define MAX_TARGET_DISTANCE 600.0f +#define MAX_TARGET_DISTANCE_SQUARED MAX_TARGET_DISTANCE * MAX_TARGET_DISTANCE + +Event EV_AirStrike +( + "airstrike", + EV_CODEONLY, + NULL, + NULL, + "call airstrike from equipment" +); +Event EV_ScanStart +( + "scanstart", + EV_TIKIONLY, + NULL, + NULL, + "The start of the scan" +); +Event EV_ScanEnd +( + "scanend", + EV_TIKIONLY, + NULL, + NULL, + "The end of the scan" +); +Event EV_Scanner +( + "hasscanner", + EV_TIKIONLY, + NULL, + NULL, + "The equipment has a scanner" +); +Event EV_Radar +( + "hasradar", + EV_TIKIONLY, + NULL, + NULL, + "The equipment has a radar" +); +Event EV_Equipment_HasModes +( + "hasmodes", + EV_TIKIONLY, + "sSSSSSSSSS", + "mode1 mode2 mode3 mode4 mode5 mode6 mode7 mode8 mode9 mode10", + "Specify the modes this equipment has." +); +Event EV_Equipment_ChangeMode +( + "changemode", + EV_TIKIONLY, + NULL, + NULL, + "Change to the next mode." +); +Event EV_Scan +( + "scan", + EV_TIKIONLY, + NULL, + NULL, + "Scan" +); +Event EV_Equipment_SetTypeName +( + "typeName", + EV_TIKIONLY, + "s", + "typeName", + "Sets the type name of this equipment" +); + +CLASS_DECLARATION( Weapon, Equipment, NULL) +{ + { &EV_AirStrike, &Equipment::airStrike }, + { &EV_ScanStart, &Equipment::scanStart }, + { &EV_ScanEnd, &Equipment::scanEnd }, + { &EV_Scanner, &Equipment::setScanner }, + { &EV_Radar, &Equipment::setRadar }, + { &EV_Equipment_HasModes, &Equipment::hasModes }, + { &EV_Equipment_ChangeMode, &Equipment::changeMode }, + { &EV_Equipment_SetTypeName, &Equipment::setTypeName }, + { &EV_Scan, &Equipment::scan }, + + { NULL, NULL } +}; + + +Equipment::Equipment() +{ + init(); +} + + +Equipment::Equipment( const char* file ) : + Weapon(file) +{ + init(); +} + +void Equipment::init( void ) +{ + scanning = false; + scanner = false; + radar = false; + _active = false; + + scanTime = 0; + _nextUseTime = 0.0f; + + _scannedEntity = NULL; + + _currentMode = 1; + _lastMode = 1; + + _scanEndFrame = -1; + + turnThinkOn(); +} + + +Equipment::~Equipment() +{ + _modes.FreeObjectList(); +} + +void Equipment::Think() +{ + if ( _modes.NumObjects() > 0 ) + { + str *modeName; + + // Make sure this mode is still available + + modeName = &_modes.ObjectAt( _currentMode ); + + if ( _currentMode != 1 && world->isAnyViewModeAvailable() && !world->isViewModeAvailable( modeName->c_str() ) ) + { + Event *newEvent; + + newEvent = new Event( EV_Equipment_ChangeMode ); + newEvent->AddInteger( 1 ); + ProcessEvent( newEvent ); + } + } + + Player* player = 0; + if( owner && owner->isSubclassOf(Player) ) + { + player= (Player*)(Sentient*)owner; + } + + if ( player && _active == qtrue) + { + player->client->ps.pm_flags &= ~( PMF_RADAR_MODE | PMF_SCANNER ); + if( hasRadar() ) + { + player->client->ps.pm_flags |= ( PMF_RADAR_MODE ); + } + + if( hasScanner() ) + { + player->client->ps.pm_flags |= PMF_SCANNER; + } + } +} + +//---------------------------------------------------------------- +// Name: airStrike +// Class: Equipment +// +// Description: does a few traces to find a sane location, places a model +// against the edge of the skybox, and starts it up to do an air strike +// +// Parameters: none +// +// Returns: none +//---------------------------------------------------------------- +void Equipment::airStrike(Event *ev) +{ + str modeName; + Player* player; + str airStrikeSound; + + + // Make sure the scanning stuff is ok + + if ( !scanner || !scanning ) + return; + + // Make sure we are still in the correct mode + + if ( ( _currentMode > _modes.NumObjects() ) || ( _currentMode < 1 ) ) + return; + + modeName = _modes.ObjectAt( _currentMode ); + + if ( stricmp( modeName, "torpedostrike") != 0 ) + return; + + // Make sure our owner is a player + + assert( owner ); + + if ( !owner || !owner->isSubclassOf( Player ) ) + return; + + player = (Player*)(Sentient*)owner; + + // Try to do the torpedo strike + + trace_t trace; + Vector start; + Vector forward,right; + Vector end; + int i; + Vector angles; + Vector dir; + bool hitSky; + Vector mins; + Vector maxs; + float bestFraction = 0.0f; + Vector skyPosition; + Vector bestSkyPosition; + + player->GetViewTrace( trace, MASK_PROJECTILE, 5000.0f ); + + start = trace.endpos; + hitSky = false; + + mins = Vector( -5.0f, -5.0f, -5.0f ); + maxs = Vector( 5.0f, 5.0f, 5.0f ); + + for ( i = 0 ; i <= 360 ; i += 45 ) + { + end = start + Vector( 0.0f, 0.0f, 10000.0f ); + + if ( i != 360 ) + { + angles = vec_zero; + angles[ YAW ] = i; + angles.AngleVectors( &dir ); + + end += dir * 7500.0f; + } + + // Try to trace for sky (that means we're outside and in range) + + trace = G_Trace( start, vec_zero, vec_zero, end, player, MASK_PROJECTILE, false, "airStrike::skyspot1" ); + + /* gentity_t *loopent = NULL; + + // if we hit a bmodel, move to the top of its box and try again + + while ( (trace.ent) && (trace.ent->bmodel) && (loopent != trace.ent) ) + { + loopent = trace.ent; + start[2] = loopent->absmax[2] + 5.0f; + trace = G_Trace( start, vec_zero,vec_zero, end, player, MASK_PROJECTILE, false, "airStrike::skyspot1" ); + } */ + + if ( trace.surfaceFlags & SURF_SKY ) + { + skyPosition = trace.endpos; + + trace = G_Trace( skyPosition, mins, maxs, start, player, MASK_PROJECTILE, false, "airStrike::skyspot2" ); + + if ( ( bestFraction == 0.0f ) || ( trace.fraction > bestFraction ) ) + { + bestFraction = trace.fraction; + bestSkyPosition = skyPosition; + } + + hitSky = true; + //break; + } + } + + // Show effects and play sound based on whether or not we hit + + if ( hitSky ) + { + right = bestSkyPosition; + forward = right - start; + + if (forward.normalize() > 4000) + right = start + (forward * 4000); // abusing right for final start point + else + right = trace.endpos; + + Projectile *airstrike; + airstrike = new Projectile; //this needs to be projectile so triggers respond properly (they won't respond to Entity) + airstrike->setModel("models/weapons/projectile_airstrike.tik"); + airstrike->setOrigin(right); + forward *= -1.0f; // airstrike model tag is backward, easier to just reverse the direction here + airstrike->setAngles(forward.toAngles()); + airstrike->PostEvent(EV_Remove,30); + + airStrikeSound = GetRandomAlias( "snd_strikeincoming" ); // play this sound at beginning of airstrike + + _nextUseTime = level.time + 30.0f; + + } + else + { + airStrikeSound = GetRandomAlias( "snd_strikeblocked" ); // play this sound if we can't see sky + } + + Sound( airStrikeSound ); +} + +void Equipment::scanStart(Event* ev) +{ + if(scanner == true) + scanning = qtrue; + + //if we end scan and start scan on the same frame, + //then ignore the end scan + if(_scanEndFrame == level.framenum) + { + PostEvent( EV_Scan, 0.05f ); + return; + } + + if ( shootingSkin ) + ChangeSkin( shootingSkin, true ); + + PostEvent( EV_Scan, 0.25f ); + + if ( level.time > _nextUseTime ) + { + CancelEventsOfType( EV_AirStrike ); + PostEvent(EV_AirStrike,5.0f); + } +} + + +void Equipment::scanEnd(Event* ev) +{ + if(scanner == true) + scanning = false; + + _scanEndFrame = level.framenum; + + if ( shootingSkin ) + ChangeSkin( shootingSkin, false ); + + CancelEventsOfType( EV_Scan ); +} + +void Equipment::scan( Event *ev ) +{ + + if ( owner && owner->isSubclassOf( Player ) ) + { + Event *newEvent; + Player *player = (Player *)(Entity *)owner; + + newEvent = new Event( EV_Player_DoUse ); + newEvent->AddEntity( this ); + player->ProcessEvent( newEvent ); + } + + CancelEventsOfType( EV_Scan ); + PostEvent( EV_Scan, 0.05f ); + +} + + +void Equipment::setScanner(Event* ev) +{ + if(ev == 0) + return; + + scanner = true; +} + + +void Equipment::setRadar(Event* ev) +{ + if(ev == 0) + return; + + radar = true; +} + +void Equipment::PutAway(void) +{ + Weapon::PutAway(); +} + + +void Equipment::ProcessTargetedEntity(EntityPtr entity) +{ + if(_scannedEntity != entity && entity != 0) + { + Player* player = (Player*)(Sentient*)owner; + float distanceSquared = DistanceSquared(entity->origin, player->client->ps.origin); + if(distanceSquared > MAX_TARGET_DISTANCE_SQUARED) + scanTime = (int) level.time; + } + + if(_scannedEntity != entity) + scanTime = (int) level.time; + + _scannedEntity = entity; + if(_scannedEntity == 0) + return; + + Weapon::ProcessTargetedEntity(entity); + + if(isScanning()) + { + entity->edict->s.eFlags &= ~EF_DISPLAY_DESC1; //turn off description 1 if displaying description 2. + entity->edict->s.eFlags |= EF_DISPLAY_DESC2; + + long timeElapsed = (long)(level.time - scanTime); + if(timeElapsed >= DETAILED_SCAN_TIME) + entity->edict->s.eFlags |= EF_DISPLAY_DESC3; + } + else + { + scanTime = (int)level.time; + entity->edict->s.eFlags &= ~EF_DISPLAY_DESC2; + entity->edict->s.eFlags &= ~EF_DISPLAY_DESC3; + } +} + +void Equipment::AttachToOwner(weaponhand_t hand) +{ + _active = true; + Weapon::AttachToOwner(hand); + + if ( _lastMode > 1 ) + { + changeMode( _lastMode ); + } +} + +void Equipment::hasModes( Event *ev ) +{ + int i; + str newMode; + + for ( i = 1 ; i <= ev->NumArgs() ; i++ ) + { + newMode = ev->GetString( i ); + + _modes.AddObject( newMode ); + } +} + +void Equipment::changeMode( Event *ev ) +{ + int newMode; + + if ( ev->NumArgs() > 0 ) + { + newMode = ev->GetInteger( 1 ); + } + else + { + newMode = _currentMode + 1; + } + + changeMode( newMode ); +} + +void Equipment::updateMode( void ) +{ + changeMode( _currentMode ); +} + +void Equipment::changeMode( int newMode ) +{ + int numModes; + Event *newEvent; + str *modeName; + + numModes = _modes.NumObjects(); + + if ( !numModes ) + return; + + // Set the desired mode + + _currentMode = newMode; + + if ( _currentMode > numModes ) + _currentMode = 1; + + // Continue until we have a valid mode set + + while( 1 ) + { + if (_currentMode == 1) // if mode is unset, query player to see if we want nightvision instead (divorced from equipment) + { + assert(owner); + Player* player; + + if ( owner->isSubclassOf(Player)) + player = (Player*)(Sentient*)owner; + else + player = NULL; + + if ( (player) && (player->client->ps.pm_flags & PMF_NIGHTVISION) ) + { + newEvent = new Event( EV_Sentient_SetViewMode ); + newEvent->AddString( "nightvision" ); + owner->ProcessEvent( newEvent ); + + return; + } + } + + modeName = &_modes.ObjectAt( _currentMode ); + + // Make sure this mode is available + + if ( _currentMode != 1 && world->isAnyViewModeAvailable() && !world->isViewModeAvailable( modeName->c_str() ) ) + { + // This mode isn't allowed + + _currentMode++; + + if ( _currentMode > numModes ) + _currentMode = 1; + + continue; + } + + // We have a valid mode so set it + + newEvent = new Event( EV_Sentient_SetViewMode ); + newEvent->AddString( modeName->c_str() ); + owner->ProcessEvent( newEvent ); + + return; + } +} + +void Equipment::resetMode( void ) +{ + changeMode( 1 ); +} + +void Equipment::Uninitialize( void ) +{ + if ( !_active ) + return; + + Weapon::Uninitialize(); + + if ( !owner ) + return; + + if ( !owner->isSubclassOf( Player ) ) + return; + + Player* player = (Player*)(Sentient*)owner; + if ( player ) + { + player->client->ps.pm_flags &= ~(PMF_SCANNER | PMF_RADAR_MODE); + } + + _lastMode = _currentMode; + + resetMode(); + + _active = false; +} + +void Equipment::Archive( Archiver &arc ) +{ + Weapon::Archive( arc ); + + arc.ArchiveBool( &scanner ); + arc.ArchiveBool( &scanning ); + arc.ArchiveInteger( &_scanEndFrame ); + arc.ArchiveInteger( &scanTime ); + arc.ArchiveBool( &radar ); + + _modes.Archive( arc ); + + arc.ArchiveInteger( &_currentMode ); + arc.ArchiveInteger( &_lastMode ); + + arc.ArchiveString( &_typeName ); + + arc.ArchiveSafePointer( &_scannedEntity ); + + arc.ArchiveBool( &_active ); + + arc.ArchiveFloat( &_nextUseTime ); +} + +void Equipment::setTypeName( Event *ev ) +{ + _typeName = ev->GetString( 1 ); +} + +int Equipment::getStat( int statNum ) +{ + if ( statNum == STAT_WEAPON_GENERIC1 ) + { + // Return the locking percentage for the torpedo strike + + str modeName; + + if ( !scanner || !scanning ) + return 0; + + if ( level.time < _nextUseTime ) + return 0; + + modeName = _modes.ObjectAt( _currentMode ); + + if ( modeName == "torpedostrike" ) + { + return (int)( ( ( level.time - scanTime ) / 5.0f ) * 100.0f ); + } + } + else if ( statNum == STAT_WEAPON_GENERIC2 ) + { + if ( level.time > _nextUseTime ) + return 100; + + return (int)( 100.0f - ( ( _nextUseTime - level.time ) / 30.0f ) * 100.0f ); + } + + return 0; +} diff --git a/dlls/game/equipment.h b/dlls/game/equipment.h new file mode 100644 index 0000000..90a7059 --- /dev/null +++ b/dlls/game/equipment.h @@ -0,0 +1,72 @@ +#ifndef EQUIPMENT_H +#define EQUIPMENT_H + +#include "weapon.h" + +class Equipment : public Weapon +{ + public: + + CLASS_PROTOTYPE(Equipment); + + Equipment(); + Equipment(const char* file); + ~Equipment(); + + void airStrike(Event *ev); + void scanStart(Event* ev); + void scanEnd(Event* ev); + void scan(Event* ev); + + void setScanner(Event* ev); + void setRadar(Event* ev); + void hasModes( Event *ev ); + void changeMode( Event *ev ); + void changeMode( int newMode ); + void updateMode( void ); + void resetMode( void ); + + bool hasScanner() { return scanner; } + bool hasRadar() { return radar; } + bool isScanning() { return scanning; } + + void setTypeName( Event *ev ); + str getTypeName( void ) const { return _typeName; } + + /*virtual*/ + void PutAway(void); + void ProcessTargetedEntity(EntityPtr entity); + void AttachToOwner( weaponhand_t hand ); + void Archive( Archiver &arc ); + void Uninitialize( void ); + void init( void ); + + /* virtual */ int getStat( int statNum ); + + /* virtual */ void Think(); + + private: + bool scanner; //the equipment has scanning capabilities. + bool scanning; //the equipment is scanning + //THIS MUST BE TRUE FOR SCANNING TO BE SET + + int _scanEndFrame; + int scanTime; //how long the player has been scanning. + + bool radar; + + Container _modes; + int _currentMode; + int _lastMode; + + str _typeName; + + EntityPtr _scannedEntity; + + bool _active; //true when armed, false when put away + + float _nextUseTime; +}; + + +#endif diff --git a/dlls/game/explosion.cpp b/dlls/game/explosion.cpp new file mode 100644 index 0000000..c27c5cb --- /dev/null +++ b/dlls/game/explosion.cpp @@ -0,0 +1,842 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/explosion.cpp $ +// $Revision:: 20 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Standard explosion object that is spawned by other entites and not map designers. +// Explosion is used by many of the weapons for the blast effect, but is also used +// by the Exploder and MultiExploder triggers. These triggers create one or more +// explosions each time they are activated. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "trigger.h" +#include "explosion.h" +#include "weaputils.h" +#include + +#define MULTI_USE (1<<0) +#define RANDOM_TIME (1<<1) +#define VISIBLE (1<<2) +#define RANDOM_SCALE (1<<3) +#define NO_EXPLOSIONS (1<<4) + +Event EV_Exploder_SetDmg +( + "dmg", + EV_SCRIPTONLY, + "i", + "damage", + "Sets the damage the explosion does." +); +Event EV_Exploder_SetDuration +( + "duration", + EV_SCRIPTONLY, + "f", + "duration", + "Sets the duration of the explosion." +); +Event EV_Exploder_SetWait +( + "wait", + EV_SCRIPTONLY, + "f", + "explodewait", + "Sets the wait time of the explosion." +); +Event EV_Exploder_SetRandom +( + "random", + EV_DEFAULT, + "f", + "randomness", + "Sets the randomness value of the explosion." +); + +void CreateExplosion( const Vector &pos, float damage, Entity *inflictor, Entity *attacker, Entity *ignore, + const char *explosionModel, float scale, float radius ) +{ + Explosion *explosion; + Event *ev; + + assert( inflictor ); + + if ( !inflictor ) + { + return; + } + + if ( !attacker ) + { + attacker = world; + } + + if ( !explosionModel ) + { + explosionModel = "fx_explosion.tik"; + } + + explosion = new Explosion; + + // Create a new explosion entity and set it off + explosion->setModel( explosionModel ); + + explosion->setSolidType( SOLID_NOT ); + explosion->CancelEventsOfType( EV_ProcessInitCommands ); + explosion->ProcessInitCommands( explosion->edict->s.modelindex ); + + explosion->owner = attacker->entnum; + explosion->edict->ownerNum = attacker->entnum; + explosion->setMoveType( MOVETYPE_FLYMISSILE ); + explosion->edict->clipmask = MASK_PROJECTILE; + explosion->setSize( explosion->mins, explosion->maxs ); + explosion->setOrigin( pos ); + explosion->origin.copyTo( explosion->edict->s.origin2 ); + + explosion->setScale( scale ); + + if ( explosion->dlight_radius ) + { + G_SetConstantLight( &explosion->edict->s.constantLight, + &explosion->dlight_color[0], + &explosion->dlight_color[1], + &explosion->dlight_color[2], + &explosion->dlight_radius + ); + } + + explosion->BroadcastSound(); + + explosion->animate->RandomAnimate( "idle", EV_StopAnimating ); + RadiusDamage( inflictor, attacker, damage, ignore, MOD_EXPLOSION, radius ); + + if ( explosion->life ) + { + ev = new Event( EV_Remove ); + explosion->PostEvent( ev, explosion->life ); + } +} + +/*****************************************************************************/ +/*QUAKED func_exploder (0 0.25 0.5) (0 0 0) (8 8 8) + + Spawns an explosion when triggered. Triggers any targets. + + "dmg" specifies how much damage to cause. Default indicates 120. + "key" The item needed to activate this. (default nothing) + "thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. +******************************************************************************/ + +CLASS_DECLARATION( Trigger, Exploder, "func_exploder" ) +{ + { &EV_Touch, NULL }, + { &EV_Trigger_Effect, &Exploder::MakeExplosion }, + { &EV_Exploder_SetDmg, &Exploder::SetDmg }, + { &EV_SetHealth, &Exploder::SetHealth }, + { &EV_SetGameplayDamage, &Exploder::setDamage }, + + { NULL, NULL } +}; + +//-------------------------------------------------------------- +// +// Name: setDamage +// Class: Exploder +// +// Description: This function acts as a filter to the real function. +// It gets data from the database, and then passes it +// along to the original event. This is here as an attempt +// to sway people into using the database standard instead of +// hardcoded numbers. +// +// Parameters: Event *ev +// str -- The value keyword from the database (low, medium, high, etc). +// +// Returns: None +// +//-------------------------------------------------------------- +void Exploder::setDamage( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasFormula("OffensiveDamage") ) + return; + + str damagestr = ev->GetString( 1 ); + float damagemod = 1.0f; + if ( gpm->getDefine(damagestr) != "" ) + damagemod = (float)atof(gpm->getDefine(damagestr)); + GameplayFormulaData fd(this, 0, 0, ""); + float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod); + Event *newev = new Event(EV_Exploder_SetDmg); + newev->AddInteger((int)finaldamage); + ProcessEvent(newev); +} + + +void Exploder::MakeExplosion( Event *ev ) +{ + CreateExplosion( origin, damage, this, ev->GetEntity( 1 ), this ); +} + +Exploder::Exploder() +{ + damage = 120.0f; + respondto = TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES; +} + +void Exploder::SetDmg( Event *ev ) +{ + damage = ev->GetInteger( 1 ); + if ( damage < 0 ) + { + damage = 0; + } +} + +void Exploder::SetHealth( Event *ev ) +{ + Trigger::EventSetHealth(ev); + triggerondeath = true ; +} + +/*****************************************************************************/ +/*QUAKED func_multi_exploder (0 0.25 0.5) ? MULTI_USE RANDOM_TIME VISIBLE RANDOM_SCALE + + Spawns an explosion when triggered. Triggers any targets. + size of brush determines where explosions will occur. + + "dmg" specifies how much damage to cause from each explosion. (Default 120) + "delay" delay before exploding (Default 0 seconds) + "duration" how long to explode for (Default 1 second) + "wait" time between each explosion (default 0.25 seconds) + "random" random factor (default 0.25) + "key" The item needed to activate this. (default nothing) + "thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. + "health" makes the object damageable + "scale" set the maximum size for spawned debris and explosions. + + MULTI_USE allows the func_multi_exploder to be used more than once + RANDOM_TIME adjusts the wait between each explosion by the random factor + VISIBLE allows you to make the trigger visible + RANDOM_SCALE scale explosions randomly. size will be between 0.25 and 1 times scale + +******************************************************************************/ + +CLASS_DECLARATION( Trigger, MultiExploder, "func_multi_exploder" ) +{ + { &EV_Touch, NULL }, + { &EV_Trigger_Effect, &MultiExploder::MakeExplosion }, + { &EV_Exploder_SetDmg, &MultiExploder::SetDmg }, + { &EV_Exploder_SetDuration, &MultiExploder::SetDuration }, + { &EV_Exploder_SetWait, &MultiExploder::SetWait }, + { &EV_Exploder_SetRandom, &MultiExploder::SetRandom }, + { &EV_SetHealth, &MultiExploder::SetHealth }, + { &EV_SetGameplayDamage, &MultiExploder::setDamage }, + + { NULL, NULL } +}; + + +//-------------------------------------------------------------- +// +// Name: setDamage +// Class: MultiExploder +// +// Description: This function acts as a filter to the real function. +// It gets data from the database, and then passes it +// along to the original event. This is here as an attempt +// to sway people into using the database standard instead of +// hardcoded numbers. +// +// Parameters: Event *ev +// str -- The value keyword from the database (low, medium, high, etc). +// +// Returns: None +// +//-------------------------------------------------------------- +void MultiExploder::setDamage( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasFormula("OffensiveDamage") ) + return; + + str damagestr = ev->GetString( 1 ); + float damagemod = 1.0f; + if ( gpm->getDefine(damagestr) != "" ) + damagemod = (float)atof(gpm->getDefine(damagestr)); + GameplayFormulaData fd(this, 0, 0, ""); + float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod); + Event *newev = new Event(EV_Exploder_SetDmg); + newev->AddInteger((int)finaldamage); + ProcessEvent(newev); +} + +void MultiExploder::MakeExplosion( Event *ev ) +{ + Vector pos; + float t, scale; + Entity *other; + Event *event; + + other = ev->GetEntity( 1 ); + + // make sure other is valid + if ( !other ) + { + other = world; + } + + // prevent the trigger from triggering again + trigger_time = -1; + + if ( !explode_time ) + { + hideModel(); + explode_time = level.time + duration; + } + + if ( spawnflags & RANDOM_TIME ) + { + t = explodewait * ( 1.0f + G_CRandom( randomness ) ); + } + else + { + t = explodewait; + } + + event = new Event( EV_Trigger_Effect ); + event->AddEntity( other ); + PostEvent( event, t ); + + if ( level.time > explode_time ) + { + if ( spawnflags & MULTI_USE ) + { + // + // reset the trigger in a half second + // + trigger_time = level.time + 0.5f; + explode_time = 0; + CancelEventsOfType( EV_Trigger_Effect ); + // + // reset health if necessary + // + health = max_health; + return; + } + else + { + PostEvent( EV_Remove, 0.0f ); + return; + } + } + + pos[ 0 ] = absmin[ 0 ] + G_Random( absmax[ 0 ] - absmin[ 0 ] ); + pos[ 1 ] = absmin[ 1 ] + G_Random( absmax[ 1 ] - absmin[ 1 ] ); + pos[ 2 ] = absmin[ 2 ] + G_Random( absmax[ 2 ] - absmin[ 2 ] ); + + if ( spawnflags & RANDOM_SCALE ) + { + scale = edict->s.scale * 0.25f; + scale += G_Random( 3.0f * scale ); + } + else + { + scale = 1.0f; + } + + CreateExplosion( pos, damage, this, other, this, NULL, scale ); +} + +MultiExploder::MultiExploder() +{ + if ( LoadingSavegame ) + { + return; + } + + damage = 120; + duration = 1.0; + explodewait = 0.25; + randomness = 0.25; + explode_time = 0; + + if ( spawnflags & VISIBLE ) + { + PostEvent( EV_Show, EV_POSTSPAWN ); + } + else + { + PostEvent( EV_Hide, EV_POSTSPAWN ); + } + + // So that we don't get deleted after we're triggered + count = -1; + + respondto = TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES; +} + +void MultiExploder::SetDmg( Event *ev ) +{ + damage = ev->GetInteger( 1 ); + if ( damage < 0 ) + { + damage = 0; + } +} + +void MultiExploder::SetDuration( Event *ev ) +{ + duration = ev->GetFloat( 1 ); +} + +void MultiExploder::SetWait( Event *ev ) +{ + explodewait = ev->GetFloat( 1 ); +} + +void MultiExploder::SetRandom( Event *ev ) +{ + randomness = ev->GetFloat( 1 ); +} + +void MultiExploder::SetHealth( Event *ev ) +{ + Trigger::EventSetHealth(ev); + triggerondeath = true ; +} + + + +#define METAL_DEBRIS (1<<5) +#define ROCK_DEBRIS (1<<6) +#define NOTSOLID (1<<7) + +/*****************************************************************************/ +/*QUAKED func_explodeobject (0 0.25 0.5) ? MULTI_USE RANDOM_TIME VISIBLE RANDOM_SCALE NO_EXPLOSIONS METAL_DEBRIS ROCK_DEBRIS NOTSOLID + + Spawns different kinds of debris when triggered. Triggers any targets. + size of brush determines where explosions and debris will be spawned. + + "dmg" specifies how much damage to cause from each explosion. (Default 120) + "delay" delay before exploding (Default 0 seconds) + "duration" how long to explode for (Default 1 second) + "wait" time between each explosion (default 0.25 seconds) + "random" random factor (default 0.25) + "health" if specified, object must be damaged to trigger + "key" The item needed to activate this. (default nothing) + "severity" how violent the debris should be ejected from the object( default 1.0 ) + "debrismodel" What kind of debris to spawn (default nothing) + "amount" how much debris to spawn for each explosion (default 4) + "thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. + "health" makes the object damageable + "scale" set the maximum size for spawned debris and explosions + "doexplosion" specifies to use an explosion (can also specify explosion model and explosion radius) + + MULTI_USE allows the func_explodeobject to be used more than once + RANDOM_TIME adjusts the wait between each explosion by the random factor + VISIBLE allows you to make the trigger visible + RANDOM_SCALE scale explosions and debris randomly. size will be between 0.25 and 1 times scale + NO_EXPLOSIONS, if checked no explosions will be created + METAL_DEBRIS automatically spawn metal debris, no need for debrismodel to be set + ROCK_DEBRIS automatically spawn rock debris, no need for debrismodel to be set + NOTSOLID debris is not solid + + other valid tiki files include: + + obj_debris_glass1-4.tik + obj_debris_wood1-4.tik + +******************************************************************************/ + +Event EV_ExplodeObject_SetSeverity +( + "severity", + EV_DEFAULT, + "f", + "newSeverity", + "How violently the debris should be ejected." +); +Event EV_ExplodeObject_SetDebrisModel +( + "debrismodel", + EV_DEFAULT, + "s", + "debrisModel", + "What kind of debris to spawn when triggered." +); +Event EV_ExplodeObject_SetDebrisAmount +( + "amount", + EV_DEFAULT, + "i", + "amountOfDebris", + "How much debris to spawn each time." +); +Event EV_ExplodeObject_SetSound +( + "setsound", + EV_DEFAULT, + "s", + "soundname", + "Sound played when the debris explosion occurs" +); +Event EV_ExplodeObject_SetupExplodeTiki +( + "explodetiki", + EV_TIKIONLY, + NULL, + NULL, + "Specifies that this is an exploding tiki file (visible and solid). Use only in tiki files" +); +Event EV_ExplodeObject_SetExplosion +( + "doexplosion", + EV_DEFAULT, + "SF", + "explosionModel explosionRadius", + "Causes the object to explode (use dmg to set the damage)" +); +Event EV_ExplodeObject_UseEarthquake +( + "explodeObject_UseEarthquake", + EV_DEFAULT, + NULL, + NULL, + "Causes the object to spawn an earthquake when it explodes" +); +Event EV_ExplodeObject_SetEarthquakeDuration +( + "explodeObject_EarthquakeDuration", + EV_DEFAULT, + "f", + "earthquakeDuration", + "Sets the earthquake duration" +); +Event EV_ExplodeObject_SetEarthquakeMagnitude +( + "explodeObject_EarthquakeMagnitude", + EV_DEFAULT, + "f", + "earthquakeMagnitude", + "Sets the earthquake magnitude" +); +Event EV_ExplodeObject_SetEarthquakeDistance +( + "explodeObject_EarthquakeDistance", + EV_DEFAULT, + "f", + "earthquakeDistance", + "Sets the earthquake distance" +); + +CLASS_DECLARATION( MultiExploder, ExplodeObject, "func_explodeobject" ) +{ + { &EV_Touch, NULL }, + { &EV_Trigger_Effect, &ExplodeObject::MakeExplosion }, + { &EV_ExplodeObject_SetSeverity, &ExplodeObject::SetSeverity }, + { &EV_ExplodeObject_SetDebrisModel, &ExplodeObject::SetDebrisModel }, + { &EV_ExplodeObject_SetDebrisAmount, &ExplodeObject::SetDebrisAmount }, + { &EV_ExplodeObject_SetupExplodeTiki, &ExplodeObject::ExplodeTiki }, + { &EV_Damage, &ExplodeObject::Damage }, + { &EV_ExplodeObject_SetSound, &ExplodeObject::SetSound }, + { &EV_ExplodeObject_SetExplosion, &ExplodeObject::SetExplosion }, + { &EV_ExplodeObject_UseEarthquake, &ExplodeObject::setSpawnEarthquake }, + { &EV_ExplodeObject_SetEarthquakeDuration, &ExplodeObject::setEarthquakeDuration }, + { &EV_ExplodeObject_SetEarthquakeMagnitude, &ExplodeObject::setEarthquakeMagnitude }, + { &EV_ExplodeObject_SetEarthquakeDistance, &ExplodeObject::setEarthquakeDistance }, + + { NULL, NULL } +}; + +void ExplodeObject::SetSound( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + debrissound = ev->GetString(1); +} + +void ExplodeObject::SetExplosion( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + explosionmodel = ev->GetString( 1 ); + if ( ev->NumArgs() > 1 ) + explosionradius = ev->GetFloat( 2 ); + + spawnflags &= ~NO_EXPLOSIONS; +} + +void ExplodeObject::Damage( Event *ev ) +{ + Entity::DamageEvent( ev ); +} + +void ExplodeObject::SetDebrisModel( Event *ev ) +{ + char string[ 1024 ]; + const char *ptr; + + // there could be multiple space delimited models, so we need to search for the spaces. + strcpy( string, ev->GetString( 1 ) ); + ptr = strtok( string, " " ); + while ( ptr ) + { + debrismodels.AddUniqueObject( str( ptr ) ); + CacheResource( ptr, this ); + ptr = strtok( NULL, " " ); + } +} + +void ExplodeObject::SetSeverity( Event *ev ) +{ + severity = ev->GetFloat( 1 ); +} + +void ExplodeObject::ExplodeTiki( Event *ev ) +{ + spawnflags |= VISIBLE; + spawnflags |= NO_EXPLOSIONS; + spawnflags |= NOTSOLID; + PostEvent( EV_Show, EV_POSTSPAWN ); + setSolidType( SOLID_BBOX ); +} + +void ExplodeObject::SetDebrisAmount( Event *ev ) +{ + debrisamount = ev->GetInteger( 1 ); +} + +void ExplodeObject::MakeExplosion( Event *ev ) +{ + Vector pos; + float t, scale; + Entity *other; + Event *event; + + other = ev->GetEntity( 1 ); + + // make sure other is valid + if ( !other ) + { + other = world; + } + + // prevent the trigger from triggering again + trigger_time = -1; + + if ( !explode_time ) + { + setSolidType( SOLID_NOT ); + hideModel(); + explode_time = level.time + duration; + } + + if ( _spawnEarthquake && !_haveSpawnedEarthquake ) + { + Event *earthquakeEvent = new Event( EV_CreateEarthquake ); + + earthquakeEvent->AddFloat( _earthquakeMagnitude ); + earthquakeEvent->AddFloat( _earthquakeDuration ); + earthquakeEvent->AddFloat( _earthquakeDistance ); + + ProcessEvent( earthquakeEvent ); + + _haveSpawnedEarthquake = true; + } + + if ( spawnflags & RANDOM_TIME ) + { + t = explodewait * ( 1.0f + G_CRandom( randomness ) ); + } + else + { + t = explodewait; + } + + event = new Event( EV_Trigger_Effect ); + event->AddEntity( other ); + PostEvent( event, t ); + + if ( level.time > explode_time ) + { + if ( spawnflags & MULTI_USE ) + { + // + // reset the trigger in a half second + // + trigger_time = level.time + 0.5f; + explode_time = 0; + CancelEventsOfType( EV_Trigger_Effect ); + // + // reset health if necessary + // + health = max_health; + if ( health ) + { + setSolidType( SOLID_BBOX ); + } + if ( spawnflags & VISIBLE ) + { + PostEvent( EV_Show, 0.5f ); + } + + _haveSpawnedEarthquake = false; + return; + } + else + { + PostEvent( EV_Remove, 0.0f ); + return; + } + } + + pos[ 0 ] = absmin[ 0 ] + G_Random( absmax[ 0 ] - absmin[ 0 ] ); + pos[ 1 ] = absmin[ 1 ] + G_Random( absmax[ 1 ] - absmin[ 1 ] ); + pos[ 2 ] = absmin[ 2 ] + G_Random( absmax[ 2 ] - absmin[ 2 ] ); + + if ( spawnflags & RANDOM_SCALE ) + { + scale = edict->s.scale * 0.25f; + scale += G_Random( 3.0f * scale ); + } + else + { + scale = 1.0f; + } + + if ( !( spawnflags & NO_EXPLOSIONS ) ) + { + CreateExplosion + ( + pos, + damage, + this, + other, + this, + explosionmodel.c_str(), + scale, + explosionradius + ); + } + if ( debrismodels.NumObjects() ) + { + TossObject *to; + int i; + + if ( debrissound.length() ) + { + Sound( debrissound ); + } + + for ( i = 0; i < debrisamount; i++ ) + { + int num; + + if ( spawnflags & RANDOM_SCALE ) + { + scale = edict->s.scale * 0.25f; + scale += G_Random( 3.0f * scale ); + } + else + { + scale = 1.0f; + } + + num = (int)G_Random( (float)debrismodels.NumObjects() ) + 1; + to = new TossObject( debrismodels.ObjectAt( num ) ); + to->setScale( scale ); + to->setOrigin( pos ); + to->SetVelocity( severity ); + if ( spawnflags & NOTSOLID ) + { + to->setSolidType( SOLID_NOT ); + } + pos[ 0 ] = absmin[ 0 ] + G_Random( absmax[ 0 ] - absmin[ 0 ] ); + pos[ 1 ] = absmin[ 1 ] + G_Random( absmax[ 1 ] - absmin[ 1 ] ); + pos[ 2 ] = absmin[ 2 ] + G_Random( absmax[ 2 ] - absmin[ 2 ] ); + } + } +} + +ExplodeObject::ExplodeObject() +{ + if ( !LoadingSavegame ) + { + duration = 1; + explodewait = 0.25f; + severity = 1.0f; + explosionradius = 0.0f; + debrismodels.ClearObjectList(); + explosionmodel = "fx_explosion.tik"; + debrisamount = 2; + if ( spawnflags & METAL_DEBRIS ) + { + debrismodels.AddUniqueObject( str( "fx/fx_debris_metal1.tik" ) ); + debrismodels.AddUniqueObject( str( "fx/fx_debris_metal2.tik" ) ); + debrismodels.AddUniqueObject( str( "fx/fx_debris_metal3.tik" ) ); + CacheResource( "fx/fx_debris_metal1.tik", this ); + CacheResource( "fx/fx_debris_metal2.tik", this ); + CacheResource( "fx/fx_debris_metal3.tik", this ); + } + else if ( spawnflags & ROCK_DEBRIS ) + { + debrismodels.AddUniqueObject( str( "fx/fx_debris_rock1.tik" ) ); + debrismodels.AddUniqueObject( str( "fx/fx_debris_rock2.tik" ) ); + debrismodels.AddUniqueObject( str( "fx/fx_debris_rock3.tik" ) ); + debrismodels.AddUniqueObject( str( "fx/fx_debris_rock4.tik" ) ); + CacheResource( "fx/fx_debris_rock1.tik", this ); + CacheResource( "fx/fx_debris_rock2.tik", this ); + CacheResource( "fx/fx_debris_rock3.tik", this ); + CacheResource( "fx/fx_debris_rock4.tik", this ); + } + } + + _spawnEarthquake = false; + _haveSpawnedEarthquake = false; + _earthquakeMagnitude = 1.0f; + _earthquakeDuration = 1.0f; + _earthquakeDistance = 500.0f; +} + +void ExplodeObject::setSpawnEarthquake( Event *ev ) +{ + _spawnEarthquake = true; +} + +void ExplodeObject::setEarthquakeDuration( Event *ev ) +{ + _spawnEarthquake = true; + _earthquakeDuration = ev->GetFloat( 1 ); +} + +void ExplodeObject::setEarthquakeMagnitude( Event *ev ) +{ + _spawnEarthquake = true; + _earthquakeMagnitude = ev->GetFloat( 1 ); +} + +void ExplodeObject::setEarthquakeDistance( Event *ev ) +{ + _spawnEarthquake = true; + _earthquakeDistance = ev->GetFloat( 1 ); +} diff --git a/dlls/game/explosion.h b/dlls/game/explosion.h new file mode 100644 index 0000000..d732753 --- /dev/null +++ b/dlls/game/explosion.h @@ -0,0 +1,164 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/explosion.h $ +// $Revision:: 12 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:53a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Standard explosion object that is spawned by other entites and not map designers. +// Explosion is used by many of the weapons for the blast effect, but is also used +// by the Exploder and MultiExploder triggers. These triggers create one or more +// explosions each time they are activated. +// + +#ifndef __EXPLOSION_H__ +#define __EXPLOSION_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" + + +class Exploder : public Trigger + { + private: + float damage; + + void MakeExplosion( Event *ev ); + void SetDmg( Event *ev ); + void SetHealth( Event *ev ); + void setDamage( Event *ev ); + + public: + CLASS_PROTOTYPE( Exploder ); + + Exploder(); + virtual void Archive( Archiver &arc ); + }; + + +inline void Exploder::Archive + ( + Archiver &arc + ) + + { + Trigger::Archive( arc ); + + arc.ArchiveFloat( &damage ); + } + +class MultiExploder : public Trigger + { + protected: + float explodewait; + float explode_time; + float duration; + int damage; + float randomness; + + void MakeExplosion( Event *ev ); + void SetDmg( Event *ev ); + void SetDuration( Event *ev ); + void SetWait( Event *ev ); + void SetRandom( Event *ev ); + void SetHealth( Event *ev ); + void setDamage( Event *ev ); + + public: + CLASS_PROTOTYPE( MultiExploder ); + + MultiExploder(); + virtual void Archive( Archiver &arc ); + }; + +inline void MultiExploder::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.ArchiveFloat( &explodewait ); + arc.ArchiveFloat( &explode_time ); + arc.ArchiveFloat( &duration ); + arc.ArchiveInteger( &damage ); + arc.ArchiveFloat( &randomness ); + } + +void CreateExplosion + ( + const Vector &pos, + float damage = 120, + Entity *inflictor = NULL, + Entity *attacker = NULL, + Entity *ignore = NULL, + const char *explosionModel = NULL, + float scale = 1.0f, + float radius = 0.0f + ); + +class ExplodeObject : public MultiExploder + { + private: + Container debrismodels; + int debrisamount; + float severity; + str debrissound; + str explosionmodel; + float explosionradius; + + bool _spawnEarthquake; + bool _haveSpawnedEarthquake; + float _earthquakeMagnitude; + float _earthquakeDuration; + float _earthquakeDistance; + + void SetDebrisModel( Event *ev ); + void SetSeverity( Event *ev ); + void SetDebrisAmount( Event *ev ); + void MakeExplosion( Event *ev ); + void ExplodeTiki( Event *ev ); + void Damage( Event *ev ); + void SetSound( Event *ev ); + void SetExplosion( Event *ev ); + + void setSpawnEarthquake( Event *ev ); + void setEarthquakeDuration( Event *ev ); + void setEarthquakeMagnitude( Event *ev ); + void setEarthquakeDistance( Event *ev ); + + public: + CLASS_PROTOTYPE( ExplodeObject ); + + ExplodeObject(); + virtual void Archive( Archiver &arc ); + }; + +inline void ExplodeObject::Archive( Archiver &arc ) +{ + MultiExploder::Archive( arc ); + + debrismodels.Archive( arc ); + arc.ArchiveInteger( &debrisamount ); + arc.ArchiveFloat( &severity ); + arc.ArchiveString( &debrissound ); + arc.ArchiveString( &explosionmodel ); + arc.ArchiveFloat( &explosionradius ); + + arc.ArchiveBool( &_spawnEarthquake ); + arc.ArchiveBool( &_haveSpawnedEarthquake ); + arc.ArchiveFloat( &_earthquakeMagnitude ); + arc.ArchiveFloat( &_earthquakeDuration ); + arc.ArchiveFloat( &_earthquakeDistance ); +} + +#endif /* explosion.h */ diff --git a/dlls/game/g_bot.cpp b/dlls/game/g_bot.cpp new file mode 100644 index 0000000..27f85ac --- /dev/null +++ b/dlls/game/g_bot.cpp @@ -0,0 +1,1027 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// g_bot.c + +#include "g_local.h" +#include "botlib.h" //bot lib interface + +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +#include "ai_main.h" + +#include "_pch_cpp.h" + +#include "mp_manager.hpp" + +static int g_numBots; +static char *g_botInfos[MAX_BOTS]; + + +int g_numArenas; +static char *g_arenaInfos[MAX_ARENAS]; + + +#define BOT_BEGIN_DELAY_BASE 2000 +#define BOT_BEGIN_DELAY_INCREMENT 1500 + +#define BOT_SPAWN_QUEUE_DEPTH 16 + +typedef struct { + int clientNum; + int spawnTime; +} botSpawnQueue_t; + +//static int botBeginDelay = 0; // bk001206 - unused, init +static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; + +vmCvar_t bot_minplayers; + +extern gentity_t *podium1; +extern gentity_t *podium2; +extern gentity_t *podium3; + + +float Cvar_VariableValue( const char *var_name ) { + char buf[128]; + + gi.Cvar_VariableStringBuffer(var_name, buf, sizeof(buf)); + return atof(buf); +} + + + +/* +=============== +G_ParseInfos +=============== +*/ +int G_ParseInfos( const char *buf, int max, char *infos[] ) { + char *token; + int count; + char key[MAX_TOKEN_CHARS]; + char info[MAX_INFO_STRING]; + + count = 0; + + while ( 1 ) { + token = (char *)COM_Parse( &buf ); + if ( !token[0] ) { + break; + } + if ( strcmp( token, "{" ) ) { + Com_Printf( "Missing { in info file\n" ); + break; + } + + if ( count == max ) { + Com_Printf( "Max infos exceeded\n" ); + break; + } + + info[0] = '\0'; + while ( 1 ) { + token = COM_ParseExt( (char **)&buf, qtrue ); + if ( !token[0] ) { + Com_Printf( "Unexpected end of info file\n" ); + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + Q_strncpyz( key, token, sizeof( key ) ); + + token = COM_ParseExt( (char **)&buf, qfalse ); + if ( !token[0] ) { + strcpy( token, "" ); + } + Info_SetValueForKey( info, key, token ); + } + //NOTE: extra space for arena number + infos[count] = (char *)gi.Malloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); + if (infos[count]) { + strcpy(infos[count], info); + count++; + } + } + return count; +} + +/* +=============== +G_LoadArenasFromFile +=============== +*/ +static void G_LoadArenasFromFile( char *filename ) { + return; + + /* int len; + fileHandle_t f; + char buf[MAX_ARENAS_TEXT]; + + len = gi.FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + gi.Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_ARENAS_TEXT ) { + gi.Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); + gi.FS_FCloseFile( f ); + return; + } + + gi.FS_Read( buf, len, f ); + buf[len] = 0; + gi.FS_FCloseFile( f ); + + g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] ); */ +} + +/* +=============== +G_LoadArenas +=============== +*/ +static void G_LoadArenas( void ) { + int numdirs; + vmCvar_t arenasFile; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i, n; + int dirlen; + + g_numArenas = 0; + + gi.Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM ); + if( *arenasFile.string ) { + G_LoadArenasFromFile(arenasFile.string); + } + else { + G_LoadArenasFromFile("scripts/arenas.txt"); + } + + // get all arenas from .arena files + numdirs = gi.FS_GetFileList("scripts", ".arena", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadArenasFromFile(filename); + } + gi.Printf( va( "%i arenas parsed\n", g_numArenas ) ); + + for( n = 0; n < g_numArenas; n++ ) { + Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) ); + } +} + + +/* +=============== +G_GetArenaInfoByNumber +=============== +*/ +const char *G_GetArenaInfoByMap( const char *map ) { + int n; + + for( n = 0; n < g_numArenas; n++ ) { + if( Q_stricmp( (char *)Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) { + return g_arenaInfos[n]; + } + } + + return NULL; +} + +char* Q_strrchr( const char* string, int c ) // from q_shared.c, but obviously not used for anything else +{ + char cc = c; + char *s; + char *sp=(char *)0; + + s = (char*)string; + + while (*s) + { + if (*s == cc) + sp = s; + s++; + } + if (cc == 0) + sp = s; + + return sp; +} + + +/* +================= +PlayerIntroSound +================= +*/ +static void PlayerIntroSound( const char *modelAndSkin ) { + char model[MAX_QPATH]; + char *skin; + + Q_strncpyz( model, modelAndSkin, sizeof(model) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } + else { + skin = model; + } + + if( Q_stricmp( skin, "default" ) == 0 ) { + skin = model; + } + +// gi.SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) ); +} + +/* +=============== +G_AddRandomBot +=============== +*/ +void G_AddRandomBot( int team ) { + int i, n, num; + float skill; + char *value, netname[36], *teamstr; + gclient_t *cl; + + num = 0; + for ( n = 0; n < g_numBots ; n++ ) { + value = (char *)Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i=0 ; i< maxclients->integer ; i++ ) { + cl = game.clients + i; +// if ( cl->pers.connected != CON_CONNECTED ) { // FIXME what's the replacement here? +// continue; +// } + if ( !(g_entities[cl->ps.clientNum].svflags & SVF_BOT) ) { + continue; + } +// if ( team >= 0 && cl->sess.sessionTeam != team ) { // FIXME what's the replacement here? +// continue; +// } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if (i >= maxclients->integer) { + num++; + } + } + num = (int) random() * num; + for ( n = 0; n < g_numBots ; n++ ) { + value = (char *)Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i=0 ; i< maxclients->integer ; i++ ) { + cl = game.clients + i; +// if ( cl->pers.connected != CON_CONNECTED ) { +// continue; +// } + if ( !(g_entities[cl->ps.clientNum].svflags & SVF_BOT) ) { + continue; + } +// if ( team >= 0 && cl->sess.sessionTeam != team ) { +// continue; +// } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if (i >= maxclients->integer) { + num--; + if (num <= 0) { + skill = gi.Cvar_VariableValue( "g_spSkill" ); + if (team == TEAM_RED) teamstr = "red"; + else if (team == TEAM_BLUE) teamstr = "blue"; + else teamstr = ""; + strncpy(netname, value, sizeof(netname)-1); + netname[sizeof(netname)-1] = '\0'; + Q_CleanStr(netname); + gi.SendConsoleCommand( va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) ); // was EXEC_INSERT, + return; + } + } + } +} + +/* +=============== +G_RemoveRandomBot +=============== +*/ +int G_RemoveRandomBot( int team ) { + int i; + char netname[36]; + gclient_t *cl; + + for ( i=0 ; i< maxclients->integer ; i++ ) { + cl = game.clients + i; +// if ( cl->pers.connected != CON_CONNECTED ) { +// continue; +// } + if ( !(g_entities[cl->ps.clientNum].svflags & SVF_BOT) ) { + continue; + } +// if ( team >= 0 && cl->sess.sessionTeam != team ) { +// continue; +// } + strcpy(netname, cl->pers.netname); + Q_CleanStr(netname); + gi.SendConsoleCommand(va("kick %s\n", netname) ); // was EXEC_INSERT, + return qtrue; + } + return qfalse; +} + +/* +=============== +G_CountHumanPlayers +=============== +*/ +int G_CountHumanPlayers( int team ) { + int i, num; + gclient_t *cl; + + num = 0; + for ( i=0 ; i< maxclients->integer ; i++ ) { + cl = game.clients + i; +// if ( cl->pers.connected != CON_CONNECTED ) { +// continue; +// } + if ( g_entities[cl->ps.clientNum].svflags & SVF_BOT ) { + continue; + } +// if ( team >= 0 && cl->sess.sessionTeam != team ) { +// continue; +// } + num++; + } + return num; +} + +/* +=============== +G_CountBotPlayers +=============== +*/ +int G_CountBotPlayers( int team ) { + int i, n, num; + gclient_t *cl; + + num = 0; + for ( i=0 ; i< maxclients->integer ; i++ ) { + cl = game.clients + i; +// if ( cl->pers.connected != CON_CONNECTED ) { +// continue; +// } + if ( !(g_entities[cl->ps.clientNum].svflags & SVF_BOT) ) { + continue; + } +// if ( team >= 0 && cl->sess.sessionTeam != team ) { +// continue; +// } + num++; + } + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + num++; + } + return num; +} + +/* +=============== +G_CheckMinimumPlayers +=============== +*/ +void G_CheckMinimumPlayers( void ) { + int minplayers; + int humanplayers, botplayers; + static int checkminimumplayers_time; + + if (level.intermissiontime) return; + //only check once each 10 seconds + if (checkminimumplayers_time > level.time - 10000) { + return; + } + checkminimumplayers_time = (int) level.time; + gi.Cvar_Update(&bot_minplayers); + minplayers = bot_minplayers.integer; + if (minplayers <= 0) return; + + if (g_gametype->integer >= GT_TEAM) { + if (minplayers >= maxclients->integer / 2) { + minplayers = (maxclients->integer / 2) -1; + } + + humanplayers = G_CountHumanPlayers( TEAM_RED ); + botplayers = G_CountBotPlayers( TEAM_RED ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_RED ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_RED ); + } + // + humanplayers = G_CountHumanPlayers( TEAM_BLUE ); + botplayers = G_CountBotPlayers( TEAM_BLUE ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_BLUE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_BLUE ); + } + } + else if (g_gametype->integer == GT_TOURNAMENT ) { + if (minplayers >= maxclients->integer) { + minplayers = maxclients->integer-1; + } + humanplayers = G_CountHumanPlayers( -1 ); + botplayers = G_CountBotPlayers( -1 ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_FREE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + // try to remove spectators first + if (!G_RemoveRandomBot( TEAM_SPECTATOR )) { + // just remove the bot that is playing + G_RemoveRandomBot( -1 ); + } + } + } + else if (g_gametype->integer == GT_FFA) { + if (minplayers >= maxclients->integer) { + minplayers = maxclients->integer-1; + } + humanplayers = G_CountHumanPlayers( TEAM_FREE ); + botplayers = G_CountBotPlayers( TEAM_FREE ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_FREE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_FREE ); + } + } +} + +/* +=============== +G_CheckBotSpawn +=============== +*/ +void G_CheckBotSpawn( void ) { + int n; + char userinfo[MAX_INFO_VALUE]; + + G_CheckMinimumPlayers(); + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + G_ClientBegin( &g_entities[botSpawnQueue[n].clientNum], NULL ); + botSpawnQueue[n].spawnTime = 0; + + if( g_gametype->integer == GT_SINGLE_PLAYER ) { + gi.getUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof(userinfo) ); + PlayerIntroSound( (char *)Info_ValueForKey (userinfo, "model") ); + } + } +} + +/* +=============== +G_RemoveQueuedBotBegin + +Called on client disconnect to make sure the delayed spawn +doesn't happen on a freed index +=============== +*/ +void G_RemoveQueuedBotBegin( int clientNum ) { + int n; + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( botSpawnQueue[n].clientNum == clientNum ) { + botSpawnQueue[n].spawnTime = 0; + return; + } + } +} + +int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart); + +/* +=============== +G_BotConnect +=============== +*/ +qboolean G_BotConnect( int clientNum, qboolean restart ) { + bot_settings_t settings; + char userinfo[MAX_INFO_STRING]; + + gi.getUserinfo( clientNum, userinfo, sizeof(userinfo) ); + + Q_strncpyz( settings.characterfile, (char *)Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) ); + settings.skill = atof( (char *)Info_ValueForKey( userinfo, "skill" ) ); + Q_strncpyz( settings.team, (char *)Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); + + if (!BotAISetupClient( clientNum, &settings, restart )) { + gi.DropClient( clientNum, "BotAISetupClient failed" ); + return qfalse; + } + + return qtrue; +} + +char *G_GetBotInfoByName( char *name ); + +/* +=============== +G_AddBot +=============== +*/ +static void G_AddBot( char *name, float skill, const char *team, const char *specialty, char *altname) { + int clientNum; + char *botinfo; + gentity_t *bot; + char *key; + char *s; + char *botname; + char *model; + char *headmodel; + char userinfo[MAX_INFO_STRING]; + + // get the botinfo from bots.txt + botinfo = G_GetBotInfoByName( name ); + if ( !botinfo ) { + gi.Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name ); + return; + } + + // create the bot's userinfo + userinfo[0] = '\0'; + + botname = (char *)Info_ValueForKey( botinfo, "funname" ); + if( !botname[0] ) { + botname = (char *)Info_ValueForKey( botinfo, "name" ); + } + // check for an alternative name + if (altname && altname[0]) { + botname = altname; + } + Info_SetValueForKey( userinfo, "name", botname ); + Info_SetValueForKey( userinfo, "rate", "25000" ); + Info_SetValueForKey( userinfo, "snaps", "20" ); + Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) ); + + if ( skill >= 1 && skill < 2 ) { + Info_SetValueForKey( userinfo, "handicap", "50" ); + } + else if ( skill >= 2 && skill < 3 ) { + Info_SetValueForKey( userinfo, "handicap", "70" ); + } + else if ( skill >= 3 && skill < 4 ) { + Info_SetValueForKey( userinfo, "handicap", "90" ); + } + + key = "mp_playermodel"; + model = (char *)Info_ValueForKey( botinfo, key ); + if ( !*model ) { + model = "models/char/munro.tik"; + } + Info_SetValueForKey( userinfo, key, model ); + key = "team_model"; + Info_SetValueForKey( userinfo, key, model ); + + key = "headmodel"; + headmodel = (char *)Info_ValueForKey( botinfo, key ); + if ( !*headmodel ) { + headmodel = model; + } + Info_SetValueForKey( userinfo, key, headmodel ); + key = "team_headmodel"; + Info_SetValueForKey( userinfo, key, headmodel ); + + key = "gender"; + s = (char *)Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "male"; + } + Info_SetValueForKey( userinfo, "sex", s ); + + key = "color1"; + s = (char *)Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "4"; + } + Info_SetValueForKey( userinfo, key, s ); + + key = "color2"; + s = (char *)Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "5"; + } + Info_SetValueForKey( userinfo, key, s ); + + s = (char *)Info_ValueForKey(botinfo, "aifile"); + if (!*s ) { + gi.Printf( S_COLOR_RED "Error: bot has no aifile specified\n" ); + return; + } + + // have the server allocate a client slot + clientNum = gi.BotAllocateClient(); + if ( clientNum == -1 ) { + gi.Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" ); + gi.Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" ); + return; + } + + bot = &g_entities[ clientNum ]; +// bot->svflags |= SVF_BOT; +// bot->inuse = qtrue; + + // register the userinfo + Info_SetValueForKey( userinfo, "characterfile", (char *)Info_ValueForKey( botinfo, "aifile" ) ); + Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) ); + + //Set the password if there is one. + if(strlen(password->string) != 0) + { + Info_SetValueForKey( userinfo, "password", password->string ); + } + + gi.setUserinfo( clientNum, userinfo ); + + // have it connect to the game as a normal client + if ( G_ClientConnect( clientNum, qtrue, qtrue, qtrue ) ) { + return; + } + + G_ClientBegin( &g_entities[clientNum],NULL ); + + g_entities[clientNum].svflags |= SVF_BOT; + + // added so bots have numbered-"player" targetnames + Player *plyr = (Player *)g_entities[clientNum].entity; + char targetname[11]; + sprintf(targetname,"player%d",clientNum); + plyr->SetTargetName(targetname); + + if ( multiplayerManager.checkGameType("teamdm") || + multiplayerManager.checkGameType("destruction") || + multiplayerManager.checkGameType("ctf") ) { + + Player *player = (Player *)bot->entity; + + if ((team) && (*team)) + multiplayerManager.joinTeam( player, team ); + + char teamchar[4]; + if (multiplayerManager.getPlayersTeam(player)->getName() == "Blue") { // not sure why bots need team info in two keypairs in different formats, but they do + team = "blue"; + sprintf(teamchar,"%d",TEAM_BLUE); + } + else { + team = "red"; + sprintf(teamchar,"%d",TEAM_RED); + } + + if ((specialty) && (*specialty)) { + player->edict->svflags |= SVF_BOT; + multiplayerManager.playerCommand( player, "setSpecialty", specialty ); + } + + Info_SetValueForKey( userinfo, "team", team ); + Info_SetValueForKey( userinfo, "t", teamchar ); + gi.setUserinfo( clientNum, userinfo ); + + } + + return; +} + + +/* +=============== +Svcmd_AddBot_f +=============== +*/ +void SV_AddBot_f( void ) { + float skill; + char name[MAX_TOKEN_CHARS]; + char altname[MAX_TOKEN_CHARS]; + char string[MAX_TOKEN_CHARS]; + char team[MAX_TOKEN_CHARS]; + char specialty[MAX_TOKEN_CHARS]; + + // are bots enabled? + if ( !gi.Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + // name + strncpy(name, gi.argv( 1), sizeof(name)); + if ( !name[0] ) { + gi.Printf( "Usage: Addbot [skill 1-5] [team] [specialty (infiltrator, medic, technician, demolitionist, heavyweapons, sniper)] [altname]\n" ); + return; + } + + // skill + strncpy(string,gi.argv( 2),sizeof(string)); + if ( !string[0] ) { + skill = 4; + } + else { + skill = atof( string ); + } + + // team + strncpy(team,gi.argv( 3),sizeof(team)); + + // specialty + strncpy(specialty,gi.argv(4),sizeof(specialty)); + + // alternative name + strncpy(altname,gi.argv( 5),sizeof(altname)); + + G_AddBot( name, skill, team, specialty, altname ); +} + +/* +=============== +Svcmd_BotList_f +=============== +*/ +void Svcmd_BotList_f( void ) { + int i; + char name[MAX_TOKEN_CHARS]; + char funname[MAX_TOKEN_CHARS]; + char model[MAX_TOKEN_CHARS]; + char aifile[MAX_TOKEN_CHARS]; + + gi.Printf("^1name model aifile funname\n"); + for (i = 0; i < g_numBots; i++) { + strcpy(name, (char *)Info_ValueForKey( g_botInfos[i], "name" )); + if ( !*name ) { + strcpy(name, "UnnamedPlayer"); + } + strcpy(funname, (char *)Info_ValueForKey( g_botInfos[i], "funname" )); + if ( !*funname ) { + strcpy(funname, ""); + } + strcpy(model, (char *)Info_ValueForKey( g_botInfos[i], "model" )); + if ( !*model ) { + strcpy(model, "visor/default"); + } + strcpy(aifile, (char *)Info_ValueForKey( g_botInfos[i], "aifile")); + if (!*aifile ) { + strcpy(aifile, "bots/default_c.c"); + } + gi.Printf(va("%-16s %-16s %-20s %-20s\n", name, model, aifile, funname)); + } +} + + +/* +=============== +G_SpawnBots +=============== +*/ +static void G_SpawnBots( char *botList, int baseDelay ) { + char *bot; + char *p; + float skill; + int delay; + char bots[MAX_INFO_VALUE]; + +// podium1 = NULL; +// podium2 = NULL; +// podium3 = NULL; + + skill = gi.Cvar_VariableValue( "g_spSkill" ); + if( skill < 1 ) { + gi.cvar_set( "g_spSkill", "1" ); + skill = 1; + } + else if ( skill > 5 ) { + gi.cvar_set( "g_spSkill", "5" ); + skill = 5; + } + + Q_strncpyz( bots, botList, sizeof(bots) ); + p = &bots[0]; + delay = baseDelay; + while( *p ) { + //skip spaces + while( *p && *p == ' ' ) { + p++; + } + if( !p ) { + break; + } + + // mark start of bot name + bot = p; + + // skip until space of null + while( *p && *p != ' ' ) { + p++; + } + if( *p ) { + *p++ = 0; + } + + // we must add the bot this way, calling G_AddBot directly at this stage + // does "Bad Things" + gi.SendConsoleCommand(va("addbot %s %f free %i\n", bot, skill, delay) ); // EXEC_INSERT, + + delay += BOT_BEGIN_DELAY_INCREMENT; + } +} + + +/* +=============== +G_LoadBotsFromFile +=============== +*/ +static void G_LoadBotsFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_BOTS_TEXT]; + + len = gi.FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + gi.Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_BOTS_TEXT ) { + gi.Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); + gi.FS_FCloseFile( f ); + return; + } + + gi.FS_Read( buf, len, f ); + buf[len] = 0; + gi.FS_FCloseFile( f ); + + g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] ); +} + +/* +=============== +G_LoadBots +=============== +*/ +static void G_LoadBots( void ) { + vmCvar_t botsFile; + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i; + int dirlen; + + if ( !gi.Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + g_numBots = 0; + + gi.Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); + if( *botsFile.string ) { + G_LoadBotsFromFile(botsFile.string); + } + else { + G_LoadBotsFromFile("botfiles/bots.txt"); + } + + // get all bots from .bot files + numdirs = gi.FS_GetFileList("scripts", ".bot", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadBotsFromFile(filename); + } + gi.Printf( va( "%i bots parsed\n", g_numBots ) ); +} + + + +/* +=============== +G_GetBotInfoByNumber +=============== +*/ +char *G_GetBotInfoByNumber( int num ) { + if( num < 0 || num >= g_numBots ) { + gi.Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); + return NULL; + } + return g_botInfos[num]; +} + + +/* +=============== +G_GetBotInfoByName +=============== +*/ +char *G_GetBotInfoByName( char *name ) { + int n; + char *value; + + for ( n = 0; n < g_numBots ; n++ ) { + value = (char *)Info_ValueForKey( g_botInfos[n], "name" ); + if ( !Q_stricmp( value, name ) ) { + return g_botInfos[n]; + } + } + + return NULL; +} + +/* +=============== +G_InitBots +=============== +*/ +void G_InitBots( qboolean restart ) { + int fragLimit; + int timeLimit; + const char *arenainfo; + char *strValue; + int basedelay; + char map[MAX_QPATH]; + char serverinfo[MAX_INFO_STRING]; + + G_LoadBots(); + G_LoadArenas(); + + gi.Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO ); + + if( g_gametype->integer == GT_SINGLE_PLAYER ) { + gi.SV_GetServerinfo( serverinfo, sizeof(serverinfo) ); + Q_strncpyz( map, (char *)Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) ); + arenainfo = G_GetArenaInfoByMap( map ); + if ( !arenainfo ) { + return; + } + + strValue = (char *)Info_ValueForKey( arenainfo, "fraglimit" ); + fragLimit = atoi( strValue ); + if ( fragLimit ) { + gi.cvar_set( "fraglimit", strValue ); + } + else { + gi.cvar_set( "fraglimit", "0" ); + } + + strValue = (char *)Info_ValueForKey( arenainfo, "timelimit" ); + timeLimit = atoi( strValue ); + if ( timeLimit ) { + gi.cvar_set( "timelimit", strValue ); + } + else { + gi.cvar_set( "timelimit", "0" ); + } + + if ( !fragLimit && !timeLimit ) { + gi.cvar_set( "fraglimit", "10" ); + gi.cvar_set( "timelimit", "0" ); + } + + basedelay = BOT_BEGIN_DELAY_BASE; + strValue = (char *)Info_ValueForKey( arenainfo, "special" ); + if( Q_stricmp( strValue, "training" ) == 0 ) { + basedelay += 10000; + } + + if( !restart ) { + G_SpawnBots( (char *)Info_ValueForKey( arenainfo, "bots" ), basedelay ); + } + } +} diff --git a/dlls/game/g_local.h b/dlls/game/g_local.h new file mode 100644 index 0000000..9f43c0b --- /dev/null +++ b/dlls/game/g_local.h @@ -0,0 +1,190 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/g_local.h $ +// $Revision:: 20 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// local definitions for game module +// + +#ifndef __G_LOCAL_H__ +#define __G_LOCAL_H__ + +#include "q_shared.h" + +// define GAME_INCLUDE so that game.h does not define the +// short, server-visible gclient_t and gentity_t structures, +// because we define the full size ones in this file +#define GAME_INCLUDE +#include "g_public.h" +#include "bg_public.h" +#include "container.h" +#include "str.h" + +// times for posting events +// Even though negative times technically don't make sense, the effect is to +// sort events that take place at the start of a map so that they are executed +// in the proper order. For example, spawnargs must occur before any script +// commands take place, while unused entities must be removed before the spawnargs +// are parsed. + +#define EV_REMOVE -12.0f // remove any unused entities before spawnargs are parsed +#define EV_PRIORITY_SPAWNARG -11.0f // for priority spawn args passed in by the bsp file +#define EV_SPAWNARG -10.0f // for spawn args passed in by the bsp file +#define EV_LINKDOORS -9.0f // for finding out which doors are linked together +#define EV_LINKBEAMS -9.0f // for finding out the endpoints of beams +#define EV_SETUP_ROPEPIECE -8.0f +#define EV_SETUP_ROPEBASE -7.0f +#define EV_PROCESS_INIT -6.0f + +#define EV_POSTSPAWN -1.0f // for any processing that must occur after all objects are spawned + +#define SOUND_RADIUS 1500.0f // Sound travel distance for AI + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0f * (random() - 0.5f)) + +// predefine Entity so that we can add it to gentity_t without any errors +class Entity; + +#define MAX_NETNAME 36 + +// client data that stays across multiple level loads +typedef struct + { + char userinfo[MAX_INFO_STRING]; + char netname[MAX_NETNAME]; + char mp_playermodel[MAX_QPATH]; + char dm_morph_c1[MAX_INFO_STRING]; + //char lastTeam[ 16 ]; + float enterTime; + + qboolean mp_lowBandwidth; + //qboolean mp_savingDemo; + //float userFov; + + // values saved and restored from edicts when changing levels + int health; + int max_health; + + } client_persistant_t; + +// this structure is cleared on each PutClientInServer(), +// except for 'client->pers' +typedef struct gclient_s + { + // known to server + playerState_t ps; // communicated by server to clients + int ping; + + // private to game + client_persistant_t pers; + vec3_t cmd_angles; // angles sent over in the last command + } gclient_t; + +struct gentity_s + { + entityState_t s; // communicated by server to clients + struct gclient_s *client; // NULL if not a player + qboolean inuse; + qboolean linked; // qfalse if not in any good cluster + int linkcount; + + int svflags; // SVF_NOCLIENT, SVF_BROADCAST, etc + + qboolean bmodel; // if false, assume an explicit mins / maxs bounding box + // only set by gi.SetBrushModel + vec3_t mins, maxs; + int contents; // CONTENTS_TRIGGER, CONTENTS_SOLID, CONTENTS_BODY, etc + // a non-solid entity should set to 0 + + vec3_t absmin, absmax; // derived from mins/maxs and origin + rotation + + float radius; // radius of object + vec3_t centroid; // centroid, to be used with radius + int areanum; // areanum needs to be seen inside the game as well + + // currentOrigin will be used for all collision detection and world linking. + // it will not necessarily be the same as the trajectory evaluation for the current + // time, because each entity must be moved one at a time after time is advanced + // to avoid simultanious collision issues + vec3_t currentOrigin; + vec3_t currentAngles; + + int ownerNum; // objects never interact with their owners, to + // prevent player missiles from immediately + // colliding with their owner + + solid_t solid; // Added for FAKK2 + + // DO NOT MODIFY ANYTHING ABOVE THIS, THE SERVER + // EXPECTS THE FIELDS IN THAT ORDER! + + //================================ + + Entity *entity; + float freetime; // svs.time when the object was freed + float spawntime; // svs.time when the object was spawned + + float radius2; // squared radius of object. Used in findradius in g_utils.cpp + + char entname[ 64 ]; + + // GAMEFIX Moved some of the old fields here for the game code. These + // might still be needed or might not :-) + int clipmask; + + gentity_t *next; + gentity_t *prev; + }; + +typedef enum + { + legs, + torso, + all + } bodypart_t; + +//bot settings from i +typedef struct bot_settings_s +{ + char characterfile[MAX_QPATH]; + float skill; + char team[MAX_QPATH]; +} bot_settings_t; + +// for bot chats +#define SAY_ALL 0 +#define SAY_TEAM 1 +#define SAY_TELL 2 + +typedef enum +{ + MP_ITEM_TYPE_NORMAL, + MP_ITEM_TYPE_POWERUP, + MP_ITEM_TYPE_RUNE, + MP_ITEM_TYPE_WEAPON, + MP_ITEM_TYPE_ARMOR +} MultiplayerItemType; + +#include "vector.h" +#include "Linklist.h" +#include "class.h" +#include "game.h" +#include "g_main.h" +#include "listener.h" +#include "g_utils.h" +#include "g_spawn.h" +#include "g_phys.h" +#include "debuglines.h" + +#endif // __G_LOCAL_H__ diff --git a/dlls/game/g_main.cpp b/dlls/game/g_main.cpp new file mode 100644 index 0000000..e034934 --- /dev/null +++ b/dlls/game/g_main.cpp @@ -0,0 +1,2389 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/g_main.cpp $ +// $Revision:: 104 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// +#include "_pch_cpp.h" + +#define SAVEGAME_VERSION 8 +#define PERSISTANT_VERSION 1 + +#include "g_utils.h" +#include "gamecmds.h" +#include "entity.h" +#include "vector.h" +#include "scriptmaster.h" +#include "navigate.h" +#include "player.h" +#include "gravpath.h" +#include "camera.h" +#include "level.h" +#include "viewthing.h" +#include "ipfilter.h" +#include +#include "mp_manager.hpp" +#include +#include +#include "teammateroster.hpp" + +Vector vec_origin = Vector(0, 0, 0); +Vector vec_zero = Vector(0, 0, 0); + +qboolean LoadingSavegame = false; +qboolean LoadingServer = false; +game_import_t gi; +game_export_t globals; + +gentity_t *g_entities = NULL; +gentity_t active_edicts; +gentity_t free_edicts; + +Container extraEntitiesList; + +int sv_numtraces; + +usercmd_t *current_ucmd; + +void (*ServerError )( int level, const char *fmt, ... ); + +/* +=============== +G_Error + +Abort the server with a game error +=============== +*/ +void G_Error( int level, const char *fmt, ... ) +{ + va_list argptr; + char error[ 4096 ]; + + va_start( argptr, fmt ); + vsprintf( error, fmt, argptr ); + va_end( argptr ); + + assert( 0 ); + + throw error; +} + +/* +=============== +G_ExitWithError + +Calls the server's error function with the last error that occurred. +Should only be called after an exception. +=============== +*/ +void G_ExitWithError( const char *error ) +{ + static char G_ErrorMessage[ 4096 ]; + + //ServerError( ERR_DROP, error ); + + Q_strncpyz( G_ErrorMessage, error, sizeof( G_ErrorMessage ) ); + + globals.error_message = G_ErrorMessage; +} + +/* +================= +G_CleanupGame + +Frees up resources from current level +================= +*/ +extern "C" void G_CleanupGame( qboolean restart ) +{ + try + { + gi.DPrintf ("==== CleanupGame ====\n"); + + level.CleanUp( restart ); + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + +int BotAIShutdown( int restart ); +/* +================= +G_ShutdownGame + +Frees up any resources +================= +*/ +extern "C" void G_ShutdownGame( void ) +{ + try + { + gi.DPrintf ("==== ShutdownGame ====\n"); + + // close the player log file if necessary + ClosePlayerLogFile(); + + // shut down bots + if ( gi.Cvar_VariableIntegerValue( "bot_enable" ) ) { + BotAIShutdown( qfalse ); // BOTLIB was restart + } + + // clear out the behavior package list + ClearBehaviorPackageList(); + + level.CleanUp( false ); + + L_ShutdownEvents(); + + // destroy the game data + G_DeAllocGameData(); + + // Delete the GameplayManager + GameplayManager::shutdown(); + } + + + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + +/* +============ +G_InitGame + +This will be called when the dll is first loaded, which +only happens when a new game is begun +============ +*/ +extern "C" void G_InitGame( int startTime, int randomSeed ) +{ + gi.DPrintf ("==== InitGame ====\n"); + + // Install our own error handler, since we can't + // call the EXE's handler from within a C++ class + ServerError = gi.Error; + gi.Error = G_Error; + + // If we get an error, call the server's error function + try + { + srand( randomSeed ); + + // setup all the cvars the game needs + CVAR_Init(); + + // Need to fill the BehaviorPackageList from the file + FillBehaviorPackageList(); + + // initialize the game variables + gameVars.ClearList(); + level.intermission_advancetime= (float)g_intermissiontime->integer; + level.fixedframetime = ( 1.0f / sv_fps->value ); + level.startTime = startTime; + + G_InitConsoleCommands(); + L_InitEvents(); + + sv_numtraces = 0; + + // setup ViewMaster + Viewmodel.Init(); + + // Initialize deathmatch manager + multiplayerManager.init(); + + game.maxentities = maxentities->integer; + if (maxclients->integer * 8 > game.maxentities) + { + game.maxentities = maxclients->integer * 8; + } + game.maxclients = maxclients->integer; + + GameplayManager::create(); + + G_AllocGameData(); + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + +void G_AllocGameData( void ) +{ + int i; + + // de-allocate from previous level + G_DeAllocGameData(); + + // Initialize debug lines + G_AllocDebugLines(); + + // initialize all entities for this game + game.maxentities = maxentities->integer; + g_entities = ( gentity_t * )gi.Malloc (game.maxentities * sizeof(g_entities[0]) ); + + // clear out the entities + memset( g_entities, 0, sizeof( g_entities[ 0 ] ) * game.maxentities ); + globals.gentities = g_entities; + globals.max_entities = game.maxentities; + + // Add all the edicts to the free list + LL_Reset( &free_edicts, next, prev ); + LL_Reset( &active_edicts, next, prev ); + for( i = 0; i < game.maxentities; i++ ) + { + LL_Add( &free_edicts, &g_entities[ i ], next, prev ); + } + + // initialize all clients for this game + game.clients = ( gclient_t * )gi.Malloc( game.maxclients * sizeof( game.clients[ 0 ] ) ); + memset( game.clients, 0, game.maxclients * sizeof( game.clients[ 0 ] ) ); + for( i = 0; i < game.maxclients; i++ ) + { + // set client fields on player ents + g_entities[ i ].client = game.clients + i; + + G_InitClientPersistant (&game.clients[i]); + + playersLastTeam[ i ] = ""; + } + globals.num_entities = game.maxclients; + + // Tell the server about our data + gi.LocateGameData( g_entities, globals.num_entities, sizeof( gentity_t ), &game.clients[0].ps, sizeof( game.clients[0] ) ); + + if ( gi.Cvar_VariableIntegerValue( "bot_enable" ) ) { + BotAISetup( 0);//restart ); + BotAILoadMap( 0);//restart ); + G_InitBots( 0);//restart ); + } + +} + +void G_DeAllocGameData( void ) +{ + // Initialize debug lines + G_DeAllocDebugLines(); + + // free up the entities + if ( g_entities ) + { + gi.Free( g_entities ); + g_entities = NULL; + } + + // free up the clients + if ( game.clients ) + { + gi.Free( game.clients ); + game.clients = NULL; + } +} + +extern "C" void G_SpawnEntities( const char *mapname, const char *entities, int levelTime ) +{ + try + { + level.NewMap( mapname, entities, levelTime ); + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + +//---------------------------------------------------------------- +// Name: G_PostLoad +// Class: +// +// Description: Does everything necessary after a load has happened +// +// Parameters: None +// +// Returns: none +//---------------------------------------------------------------- + +extern "C" void G_PostLoad( void ) +{ + try + { + level.postLoad(); + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + +//---------------------------------------------------------------- +// Name: G_PostSublevelLoad +// Class: +// +// Description: Does everything necessary after a sublevel has beend loaded +// +// Parameters: const char *mapName - map name string (also possibly contains spawn position) +// +// Returns: none +//---------------------------------------------------------------- + +extern "C" void G_PostSublevelLoad( const char *spawnPosName ) +{ + try + { + level.postSublevelLoad( spawnPosName ); + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + +void G_ArchivePersistantData( Archiver &arc, qboolean sublevelTransition ) +{ + gentity_t *ed; + int i; + + for( i = 0; i < game.maxclients; i++ ) + { + Entity *ent; + + ed = &g_entities[ i ]; + if ( !ed->inuse || !ed->entity ) + continue; + + ent = ed->entity; + if ( !ent->isSubclassOf( Player ) ) + continue; + ( ( Player * )ent )->ArchivePersistantData( arc, sublevelTransition ); + } +} + +qboolean G_ArchivePersistant( const char *name, qboolean loading, qboolean sublevelTransition ) +{ + int version; + Archiver arc; + + if ( loading ) + { + if ( !arc.Read( name, false ) ) + { + return false; + } + + arc.ArchiveInteger( &version ); + if ( version < PERSISTANT_VERSION ) + { + gi.Printf( "Persistant data from an older version (%d) of %s.\n", version, GAME_NAME ); + arc.Close(); + return false; + } + else if ( version > PERSISTANT_VERSION ) + { + gi.DPrintf( "Persistant data from newer version %d of %s.\n", version, GAME_NAME ); + arc.Close(); + return false; + } + } + else + { + arc.Create( name ); + + version = PERSISTANT_VERSION; + arc.ArchiveInteger( &version ); + } + + + arc.ArchiveObject( &gameVars ); + G_ArchivePersistantData( arc, sublevelTransition ); + + arc.Close(); + return true; +} + + +extern "C" qboolean G_ReadPersistant( const char *name, qboolean sublevelTransition ) +{ + try + { + return G_ArchivePersistant( name, true, sublevelTransition ); + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } + return false; +} + +/* +============ +G_WritePersistant + +This will be called whenever the game goes to a new level, + +A single player death will automatically restore from the +last save position. +============ +*/ + +extern "C" void G_WritePersistant( const char *name, qboolean sublevelTransition ) +{ + try + { + G_ArchivePersistant( name, false, sublevelTransition ); + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + + +/* +================= +LevelArchiveValid +================= +*/ +qboolean LevelArchiveValid( Archiver &arc ) +{ + int version; + int savegame_version; + + // read the version number + arc.ArchiveInteger( &version ); + arc.ArchiveInteger( &savegame_version ); + + if ( version < GAME_API_VERSION ) + { + gi.Printf( "Savegame from an older version (%d) of %s.\n", version, GAME_NAME ); + return false; + } + else if ( version > GAME_API_VERSION ) + { + gi.Printf( "Savegame from version %d of %s.\n", version, GAME_NAME ); + return false; + } + + if ( savegame_version < SAVEGAME_VERSION ) + { + gi.Printf( "Savegame from an older version (%d) of %s.\n", version, GAME_NAME ); + return false; + } + else if ( savegame_version > SAVEGAME_VERSION ) + { + gi.Printf( "Savegame from version %d of %s.\n", version, GAME_NAME ); + return false; + } + return true; +} + +str getAliasParmString( AliasListNode_t *aliasNode ) +{ + str parms; + + if ( aliasNode->global_flag ) + { + parms += "global "; + } + + if ( aliasNode->stop_flag ) + { + parms += "stop "; + } + + if ( aliasNode->timeout > 0.0f ) + { + parms += "timeout "; + parms += aliasNode->timeout; + parms += " "; + } + + if ( aliasNode->maximum_use > 0 ) + { + parms += "maxuse "; + parms += aliasNode->maximum_use; + parms += " "; + } + + if ( aliasNode->weight > 0 ) + { + parms += "weight "; + parms += aliasNode->weight; + parms += " "; + } + + if ( strlen( aliasNode->anim_name ) > 0 ) + { + parms += "anim "; + parms += aliasNode->anim_name; + parms += " "; + } + + if ( aliasNode->loop_anim ) + { + parms += "loopanim 1 "; + } + + return parms; +} + +void ArchiveAliases( Archiver &arc ) +{ + int i; + byte another; + AliasList_t *alias_list; + AliasListNode_t *alias_node; + AliasActorNode_t *actor_node; + str alias_name; + str realName; + str model_name; + const char *name; + int model_index; + int actor_number; + int number_of_times_played; + byte been_played_this_loop; + int last_time_played; + + + if ( arc.Saving() ) + { + for( i = 0 ; i < MAX_MODELS ; i++ ) + { + alias_list = (AliasList_t *)gi.Alias_GetList( i ); + + if ( alias_list ) + alias_node = alias_list->data_list; + else + alias_node = NULL; + + if ( alias_node ) + { + name = gi.NameForNum( i ); + + if ( name ) + { + another = true; + arc.ArchiveByte( &another ); + + model_name = name; + arc.ArchiveString( &model_name ); + + // Go through all aliases in this model + + while ( alias_node ) + { + str parms; + + another = true; + arc.ArchiveByte( &another ); + + alias_name = alias_node->alias_name; + realName = alias_node->real_name; + + arc.ArchiveString( &alias_name ); + + arc.ArchiveString( &realName ); + + parms = getAliasParmString( alias_node ); + + arc.ArchiveString( &parms ); + + arc.ArchiveInteger( &alias_node->number_of_times_played ); + arc.ArchiveByte( &alias_node->been_played_this_loop ); + arc.ArchiveInteger( &alias_node->last_time_played ); + + actor_node = alias_node->actor_list; + + // Go through actor info + + while ( actor_node ) + { + another = true; + arc.ArchiveByte( &another ); + + arc.ArchiveInteger( &actor_node->actor_number ); + arc.ArchiveInteger( &actor_node->number_of_times_played ); + arc.ArchiveByte( &actor_node->been_played_this_loop ); + arc.ArchiveInteger( &actor_node->last_time_played ); + + actor_node = actor_node->next; + } + + another = false; + arc.ArchiveByte( &another ); + + alias_node = alias_node->next; + } + + another = false; + arc.ArchiveByte( &another ); + } + } + } + + another = false; + arc.ArchiveByte( &another ); + } + else + { + arc.ArchiveByte( &another ); + + while( another ) + { + arc.ArchiveString( &model_name ); + + model_index = gi.modelindex( model_name.c_str() ); + + arc.ArchiveByte( &another ); + + while( another ) + { + str parms; + + // Read in aliases + + arc.ArchiveString( &alias_name ); + + arc.ArchiveString( &realName ); + arc.ArchiveString( &parms ); + + arc.ArchiveInteger( &number_of_times_played ); + arc.ArchiveByte( &been_played_this_loop ); + arc.ArchiveInteger( &last_time_played ); + + // If the alias doesn't exist yet, add it + + if ( !gi.Alias_Find( model_index, alias_name.c_str() ) ) + { + gi.Alias_Add( model_index, alias_name, realName, parms ); + } + + // Now update the alias + + gi.Alias_UpdateDialog( model_index, alias_name.c_str(), number_of_times_played, been_played_this_loop, last_time_played ); + + arc.ArchiveByte( &another ); + + while( another ) + { + // Read in actor infos + + arc.ArchiveInteger( &actor_number ); + arc.ArchiveInteger( &number_of_times_played ); + arc.ArchiveByte( &been_played_this_loop ); + arc.ArchiveInteger( &last_time_played ); + + gi.Alias_AddActorDialog( model_index, alias_name.c_str(), actor_number, number_of_times_played, been_played_this_loop, last_time_played ); + + arc.ArchiveByte( &another ); + } + + arc.ArchiveByte( &another ); + } + + arc.ArchiveByte( &another ); + } + } +} + +/* +================= +G_ArchiveLevel + +================= +*/ +qboolean G_ArchiveLevel( const char *filename, qboolean autosave, qboolean loading ) +{ + try + { + int i; + int num; + Archiver arc; + gentity_t *edict; + + // Heuristic Stuff ///////////////////////////////////// + edict = &g_entities[ 0 ]; + + if ( edict->inuse && edict->entity ) + { + Player *player = (Player *)edict->entity; + if ( player->p_heuristics ) + player->p_heuristics->SaveHeuristics( player ); + } + ////////////////////////////////////////////// + + + if ( loading ) + { + LoadingSavegame = true; + LoadingServer = true; + + // Get rid of anything left over from the last level + level.CleanUp( false ); + + arc.Read( filename ); + + if ( !LevelArchiveValid( arc ) ) + { + arc.Close(); + return false; + } + + // Read in the pending events. These are read in first in case + // later objects need to post events. + L_UnarchiveEvents( arc ); + } + else + { + int temp; + + if ( autosave ) + { + for( i = 0; i < game.maxclients; i++ ) + { + edict = &g_entities[ i ]; + if ( !edict->inuse && !edict->entity ) + { + continue; + } + + delete edict->entity; + } + } + + arc.Create( filename ); + + // write out the version number + temp = GAME_API_VERSION; + arc.ArchiveInteger( &temp ); + temp = SAVEGAME_VERSION; + arc.ArchiveInteger( &temp ); + + + + // Write out the pending events. These are written first in case + // later objects need to post events when reading the archive. + L_ArchiveEvents( arc ); + } + // archive the game object + arc.ArchiveObject( &game ); + + // archive the game variables + arc.ArchiveObject( &gameVars ); + + // archive Level + arc.ArchiveObject( &level ); + + if ( arc.Loading() ) + { + // Set up for a new map + thePathManager.Init( level.mapname.c_str() ); + } + + // archive script librarian + arc.ArchiveObject( &ScriptLib ); + + // archive gravity paths + arc.ArchiveObject( &gravPathManager ); + + // archive camera paths + arc.ArchiveObject( &CameraMan ); + + // archive paths + arc.ArchiveObject( &thePathManager ); + + // archive script controller + arc.ArchiveObject( &Director ); + + // archive lightstyles + arc.ArchiveObject( &lightStyles ); + + // Archive the level vars + + if ( arc.Loading() ) + { + levelVars.ClearList(); + } + + arc.ArchiveObject( &levelVars ); + + if ( arc.Saving() ) + { + // count the entities + num = 0; + for( i = 0; i < globals.num_entities; i++ ) + { + edict = &g_entities[ i ]; + if ( edict->inuse && edict->entity && !( edict->entity->flags & FL_DONTSAVE ) ) + { + num++; + } + } + } + + // archive all the entities + arc.ArchiveInteger( &globals.num_entities ); + arc.ArchiveInteger( &num ); + + if ( arc.Saving() ) + { + // write out the world + arc.ArchiveObject( world ); + + for( i = 0; i < globals.num_entities; i++ ) + { + edict = &g_entities[ i ]; + if ( !edict->inuse || !edict->entity || ( edict->entity->flags & FL_DONTSAVE ) ) + { + continue; + } + + arc.ArchiveObject( edict->entity ); + } + } + else + { + // Tell the server about our data + gi.LocateGameData( g_entities, globals.num_entities, sizeof( gentity_t ), &game.clients[0].ps, sizeof( game.clients[0] ) ); + + // read in the world + arc.ReadObject(); + + for( i = 0; i < num; i++ ) + { + arc.ReadObject(); + } + } + + if ( arc.Loading() ) + program.FreeData(); + + arc.ArchiveObject( &program ); + + // archive script controller + //arc.ArchiveObject( &Director ); + + ArchiveAliases( arc ); + + //Archive the teammate roster + TeammateRoster::getInstance()->Archive( arc ); + + // Archive the gameplay manager's database deltas. + GameplayManager::getTheGameplayManager()->Archive( arc ); + + arc.Close(); + + if ( arc.Loading() ) + { + LoadingSavegame = false; + + // call the precache scripts + level.Precache(); + } + else + { + gi.centerprintf( &g_entities[ 0 ], CENTERPRINT_IMPORTANCE_NORMAL, "@textures/menu/gamesaved" ); + } + + if ( arc.Loading() ) + { + // Make sure all code that needs to setup the player after they have been loaded is run + + for( i = 0; i < game.maxclients; i++ ) + { + edict = &g_entities[ i ]; + + if ( edict->inuse && edict->entity ) + { + Player *player = (Player *)edict->entity; + player->Loaded(); + } + } + } + + return true; + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } + return false; +} + +/* +================= +G_WriteLevel + +================= +*/ +extern "C" void G_WriteLevel( const char *filename, qboolean autosave ) +{ + game.autosaved = autosave; + G_ArchiveLevel( filename, autosave, false ); + game.autosaved = false; +} + +/* +================= +G_ReadLevel + +SpawnEntities will already have been called on the +level the same way it was when the level was saved. + +That is necessary to get the baselines set up identically. + +The server will have cleared all of the world links before +calling ReadLevel. + +No clients are connected yet. +================= +*/ +extern "C" qboolean G_ReadLevel( const char *filename ) +{ + qboolean status; + + status = G_ArchiveLevel( filename, false, true ); + // if the level load failed make sure that these variables are not set + if ( !status ) + { + LoadingSavegame = false; + LoadingServer = false; + } + return status; +} + +/* +================= +G_LevelArchiveValid +================= +*/ +extern "C" qboolean G_LevelArchiveValid( const char *filename ) +{ + try + { + qboolean ret; + + Archiver arc; + + if ( !arc.Read( filename ) ) + { + return false; + } + + ret = LevelArchiveValid( arc ); + + arc.Close(); + + return ret; + } + + catch( const char *error ) + { + G_ExitWithError( error ); + return false; + } +} + +extern "C" qboolean G_InMultiplayerGame( void ) +{ + return multiplayerManager.inMultiplayer(); +} + +// BOTLIB additions +void SV_AddBot_f( void ); +int BotAIStartFrame(int time); +// + +/* +================= +GetGameAPI + +Returns a pointer to the structure with all entry points +and global variables +================= +*/ +extern "C" game_export_t *GetGameAPI( game_import_t *import ) +{ + gi = *import; + + globals.apiversion = GAME_API_VERSION; + globals.Init = G_InitGame; + globals.Shutdown = G_ShutdownGame; + globals.Cleanup = G_CleanupGame; + globals.SpawnEntities = G_SpawnEntities; + globals.PostLoad = G_PostLoad; + globals.PostSublevelLoad = G_PostSublevelLoad; + + globals.WritePersistant = G_WritePersistant; + globals.ReadPersistant = G_ReadPersistant; + globals.WriteLevel = G_WriteLevel; + globals.ReadLevel = G_ReadLevel; + globals.LevelArchiveValid = G_LevelArchiveValid; + + globals.inMultiplayerGame = G_InMultiplayerGame; + + globals.ClientThink = G_ClientThink; + globals.ClientConnect = G_ClientConnect; + globals.ClientUserinfoChanged = G_ClientUserinfoChanged; + globals.ClientDisconnect = G_ClientDisconnect; + globals.ClientBegin = G_ClientBegin; + globals.ClientCommand = G_ClientCommand; + + globals.ConsoleCommand = G_ConsoleCommand; + globals.RunFrame = G_RunFrame; + globals.SendEntity = G_SendEntity; + globals.UpdateEntityStateForClient = G_UpdateEntityStateForClient; + globals.UpdatePlayerStateForClient = G_UpdatePlayerStateForClient; + globals.ExtraEntitiesToSend = G_ExtraEntitiesToSend; + + globals.GetEntityCurrentAnimFrame = G_GetEntityCurrentAnimFrame; + + // the following are added for BOTLIB support + globals.AddBot_f = SV_AddBot_f; + globals.BotAIStartFrame = BotAIStartFrame; + // end botlib additions + + globals.GetTotalGameFrames = G_GetTotalGameFrames; + + globals.gentitySize = sizeof(gentity_t); + globals.error_message = NULL; + + return &globals; +} + +/* +================= +G_ClientEndServerFrames +================= +*/ +void G_ClientEndServerFrames( void ) +{ + int i; + gentity_t *ent; + + // calc the player views now that all pushing + // and damage has been added + for( i = 0; i < maxclients->integer; i++ ) + { + ent = g_entities + i; + if ( !ent->inuse || !ent->client || !ent->entity ) + { + continue; + } + + ent->entity->ProcessEvent( EV_ClientEndFrame ); + } +} + +//================== +//FindIntermissionPoint +//================== +void FindIntermissionPoint( void ) +{ + Entity *ent, *target; + vec3_t dir; + + // find the intermission spot + ent = G_FindClass( NULL, "info_player_intermission" ); + + if ( ent ) + { + level.m_intermission_origin = ent->origin; + level.m_intermission_angle = ent->angles; + + // if it has a target, look towards it + if ( ent->target ) + { + target = G_FindTarget( NULL, ent->Target() ); + if ( target ) + { + VectorSubtract( target->origin, level.m_intermission_origin, dir ); + vectoangles( dir, level.m_intermission_angle ); + } + } + } + else + { + level.m_intermission_origin = vec_zero; + level.m_intermission_angle = vec_zero; + } +} + +void G_MoveClientToIntermission( Entity *ent ) +{ + assert( ent ); + + if ( !ent ) + return; + + if ( ent->isClient() ) + { + Player *player = ( Player * )ent; + + if ( multiplayerManager.inMultiplayer() && ( level.m_intermission_angle != vec_zero ) && ( level.m_intermission_origin != vec_zero ) ) + { + // Each client should now be at the intermission spot + player->setOrigin( level.m_intermission_origin ); + player->setAngles( level.m_intermission_angle ); + player->SetViewAngles( level.m_intermission_angle ); + player->CameraCut(); + } + + // Set a PMF flag to allow them to see the scoreboard + player->client->ps.pm_flags |= PMF_INTERMISSION; + player->flags |= FL_IMMOBILE; + } +} + +// This is for multiplayer exiting +void G_BeginIntermission2( void ) +{ + gentity_t *client; + Entity *ent; + int i; + + if ( level.intermissiontime ) + { + // already activated + return; + } + + // Save the time intermission started + level.intermissiontime = level.time; + // Freeze all players + level.playerfrozen = true; + // Find an intermission spot + FindIntermissionPoint(); + + // find an intermission spot + ent = G_FindClass( NULL, "info_player_intermission" ); + + // Only do the camera stuff if the node exists. + if ( ent ) + { + SetCamera( ent, CAMERA_SWITCHTIME ); + } + + // Display scores for all the clients + for( i = 0; i < maxclients->integer; i++ ) + { + client = g_entities + i; + if ( !client->inuse ) + { + continue; + } + + ent = G_GetEntity( client->s.number ); + G_MoveClientToIntermission( ent ); + } + + // tell the script that the player's not ready so that if we return to this map, + // we can do something about it. + Director.PlayerNotReady(); +} + +void G_BeginIntermission( const char *map ) +{ + gentity_t *client; + Entity *ent; + Entity *path; + int i; + Event *event; + + assert( map ); + if ( !map ) + { + gi.WDPrintf( "G_BeginIntermission : Null map name\n" ); + return; + } + + if ( level.intermissiontime ) + { + // already activated + return; + } + + + if ( multiplayerManager.inMultiplayer() ) + { + level.intermissiontime = level.time + mp_intermissionTime->integer; + } + else + { + level.intermissiontime = level.time + 1.0f; + G_FadeOut( 1.0f ); + G_FadeSound( 1.0f ); + } + + level.nextmap = map; + + if ( gi.areSublevels( level.mapname, map ) || !level._showIntermission ) + { + // We don't want a intermission (sublevel or something has specified no intermission ) + + level.intermissiontime = level.time; + level.exitintermission = true; + + Director.PlayerNotReady(); + + return; + } + + // find an intermission spot + ent = G_FindClass( NULL, "info_player_intermission" ); + + // Only do the camera stuff if the node exists. + if ( ent ) + { + SetCamera( ent, CAMERA_SWITCHTIME ); + event = new Event( EV_Camera_Orbit ); + + // Find the end node + path = G_FindTarget( NULL, "endnode1" ); + if ( path ) + { + event->AddEntity( path ); + ent->ProcessEvent( event ); + event = new Event( EV_Camera_Cut ); + ent->ProcessEvent( event ); + } + } + + // Display scores for all the clients + for( i = 0; i < maxclients->integer; i++ ) + { + client = g_entities + i; + if ( !client->inuse ) + { + continue; + } + + ent = G_GetEntity( client->s.number ); + G_MoveClientToIntermission( ent ); + } + + // tell the script that the player's not ready so that if we return to this map, + // we can do something about it. + Director.PlayerNotReady(); +} + +/* +============= +G_ExitLevel +============= +*/ +void G_ExitLevel( void ) +{ + static const char *seps = " ,\n\r"; + char command[ 256 ]; + int j; + gentity_t *ent; + + + // Don't allow exit level if the mission was failed + + if ( level.mission_failed ) + return; + + // close the player log file if necessary + ClosePlayerLogFile(); + + // kill the sounds + Com_sprintf( command, sizeof( command ), "stopsound\n" ); + gi.SendConsoleCommand( command ); + + if ( multiplayerManager.inMultiplayer() ) + { + if ( strlen( sv_nextmap->string ) ) + { + // The nextmap cvar was set (possibly by a vote - so go ahead and use it) + level.nextmap = sv_nextmap->string; + gi.cvar_set( "nextmap", "" ); + } + /* if ( strlen( sv_maplist->string ) ) // Use the next map in the maplist + { + char *s,*f,*t; + + f = NULL; + s = strdup( sv_maplist->string ); + t = strtok( s, seps ); + while ( t != NULL ) + { + if ( !stricmp( t, level.mapname.c_str() ) ) + { + // it's in the list, go to the next one + t = strtok( NULL, seps ); + if ( t == NULL ) // end of list, go to first one + { + if ( f == NULL ) // there isn't a first one, same level + { + level.nextmap = level.mapname; + } + else + { + level.nextmap = f; + } + } + else + { + level.nextmap = t; + } + free( s ); + goto out; + } + + // set the first map + if (!f) + { + f = t; + } + t = strtok(NULL, seps); + } + free( s ); + } +out: */ + // level.nextmap should be set now, but if it isn't use the same map + if ( level.nextmap.length() == 0 ) + { + // Stay on the same map since no nextmap was set + Com_sprintf( command, sizeof( command ), "gamemap \"%s\"\n", level.mapname.c_str() ); + gi.SendConsoleCommand( command ); + } + else // use the level.nextmap variable + { + Com_sprintf( command, sizeof( command ), "gamemap \"%s\"\n", level.nextmap.c_str() ); + gi.SendConsoleCommand( command ); + } + } + else + { + Com_sprintf( command, sizeof( command ), "gamemap \"%s\"\n", level.nextmap.c_str() ); + gi.SendConsoleCommand( command ); + } + + // Tell all the clients that the level is done + for( j = 0; j < game.maxclients; j++ ) + { + ent = &g_entities[ j ]; + if ( !ent->inuse || !ent->entity ) + { + continue; + } + + ent->entity->ProcessEvent( EV_Player_EndLevel ); + } + + level.nextmap = ""; + + level.exitintermission = 0; + level.intermissiontime = 0; + + G_ClientEndServerFrames(); + + // tell the script that the player's not ready so that if we return to this map, + // we can do something about it. + Director.PlayerNotReady(); +} + +void G_CheckIntermissionExit( void ) +{ + // Nobody clicked + if ( !level.exitintermission ) + { + return; + } + + G_ExitLevel(); +} + +/* +================ +G_RunFrame + +Advances the world by one server frame +================ +*/ +extern "C" void G_RunFrame( int levelTime, int frameTime ) +{ + gentity_t *edict; + Entity *ent; + int num; + qboolean showentnums; + qboolean showactnums; + int start; + int end; + + try + { + if ( level.restart ) + { + level.Restart(); + } + + level.update(levelTime, frameTime); + + if ( g_showmem->integer ) + { + DisplayMemoryUsage(); + } + + // exit intermissions + if ( level.exitintermission ) + { + G_ExitLevel(); + return; + } + + // Reset debug lines + G_InitDebugLines(); + + // testing coordinate system + if ( csys_draw->integer ) + { + G_DrawCSystem(); + } + + thePathManager.ShowNodes(); + + AI_DisplayInfo(); + + // don't show entnums during deathmatch + showentnums = ( sv_showentnums->integer && ( !multiplayerManager.inMultiplayer() || sv_cheats->integer ) ); + showactnums = ( sv_showactnums->integer && ( !multiplayerManager.inMultiplayer() || sv_cheats->integer ) ); + + // Wake up any monsters in the area + AI_SenseEnemies(); + + // Process most of the events before the physics are run + // so that we can affect the physics immediately + L_ProcessPendingEvents(); + + // + // treat each object in turn + // + for( edict = active_edicts.next, num = 0; edict != &active_edicts; edict = level.next_edict, num++ ) + { + assert( edict ); + assert( edict->inuse ); + assert( edict->entity ); + + level.next_edict = edict->next; + + // Paranoia - It's a way of life + assert( num <= MAX_GENTITIES ); + if ( num > MAX_GENTITIES ) + { + gi.WDPrintf( "Possible infinite loop in G_RunFrame.\n"); + break; + } + + ent = edict->entity; + if ( g_timeents->integer ) + { + start = gi.Milliseconds(); + G_RunEntity( ent ); + end = gi.Milliseconds(); + + if ( g_timeents->value <= (float)( end - start ) ) + { + gi.DPrintf( "%d: '%s'(%d) : %d\n", level.framenum, ent->targetname.c_str(), ent->entnum, end - start ); + } + } + else + { + G_RunEntity( ent ); + } + + if ( ( edict->svflags & SVF_SENDONCE ) && ( edict->svflags & SVF_SENT ) ) + { + // Entity has been sent once, and is marked as such, then remove it + ent->PostEvent( EV_Remove, 0.0f ); + } + + if ( showentnums ) + { + G_DrawDebugNumber( ent->origin + Vector( 0.0f, 0.0f, ent->maxs.z + 2.0f ), ent->entnum, 2.0f, 1.0f, 1.0f, 0.0f ); + } + if ( showactnums && ent->isSubclassOf( Actor ) ) + { + G_DrawDebugNumber( ent->origin + Vector( 0.0f, 0.0f, ent->maxs.z + 2.0f ), ent->entnum, 2.0f, 1.0f, 0.0f, 0.0f ); + } + } + + multiplayerManager.update( level.frametime ); + + // Process any pending events that got posted during the physics code. + L_ProcessPendingEvents(); + + // build the playerstate_t structures for all players + G_ClientEndServerFrames(); + + // see if we should draw the bounding boxes + G_ClientDrawBoundingBoxes(); + + // see if we should draw all of the splines + G_ClientDrawSplines(); + + // show how many traces the game code is doing + if ( sv_traceinfo->integer ) + { + if ( sv_traceinfo->integer == 3 ) + { + gi.DebugPrintf( "%0.2f : Total traces %d\n", level.time, sv_numtraces ); + } + else + { + gi.DPrintf( "%0.2f : Total traces %d\n", level.time, sv_numtraces ); + } + } + + // reset out count of the number of game traces + sv_numtraces = 0; + + level.framenum++; + + // we increment time so that events that occurr before we think again are automatically on the next frame + levelTime += frameTime; + level.setTime( levelTime, frameTime ); + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + +extern "C" qboolean G_SendEntity( gentity_t *clientEntity, gentity_t *entityToSend ) +{ + try + { + if ( clientEntity->entity && clientEntity->entity->isSubclassOf( Player ) && entityToSend->entity ) + { + Player *player; + + player = ( Player *)clientEntity->entity; + + return player->ShouldSendToClient( entityToSend->entity ); + } + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } + + return false; +} + +extern "C" void G_UpdateEntityStateForClient( gentity_t *clientEntity, entityState_t *state ) +{ + try + { + if ( clientEntity->entity && clientEntity->entity->isSubclassOf( Player ) ) + { + Player *player; + + player = ( Player *)clientEntity->entity; + + player->UpdateEntityStateForClient( state ); + } + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } + +} + +extern "C" void G_UpdatePlayerStateForClient( gentity_t *clientEntity, playerState_t *state ) +{ + try + { + if ( clientEntity->entity && clientEntity->entity->isSubclassOf( Player ) ) + { + Player *player; + + player = ( Player *)clientEntity->entity; + + player->UpdatePlayerStateForClient( state ); + } + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } + +} + +extern "C" void G_ExtraEntitiesToSend( gentity_t *clientEntity, int *numExtraEntities, int *extraEntities ) +{ + try + { + *numExtraEntities = 0; + + if ( !multiplayerManager.inMultiplayer() && clientEntity->entity && clientEntity->entity->isSubclassOf( Player ) ) + { + Player *player; + + player = ( Player *)clientEntity->entity; + + player->ExtraEntitiesToSendToClient( numExtraEntities, extraEntities ); + + //add the extra entities from the extra entity list... + for(int i = 1; i <= extraEntitiesList.NumObjects(); i++) + { + extraEntities[*numExtraEntities] = extraEntitiesList.ObjectAt(i); + (*numExtraEntities)++; + } + } + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + +void G_AddEntityToExtraList( int entityNum ) +{ + extraEntitiesList.AddUniqueObject( entityNum ); +} + + +void G_RemoveEntityFromExtraList( int entityNum ) +{ + int index; + + index = extraEntitiesList.IndexOfObject( entityNum ); + + if ( index > 0 ) + { + extraEntitiesList.RemoveObjectAt( index ); + } +} + +extern "C" int G_GetEntityCurrentAnimFrame( int entnum, int bodyPart ) +{ + try + { + if ( entnum < 0 || entnum >= ENTITYNUM_NONE ) + { + return -1; + } + + if ( !g_entities[ entnum ].inuse || !g_entities[ entnum ].entity ) + { + return -1; + } + + return g_entities[ entnum ].entity->CurrentFrame( (bodypart_t)bodyPart ); + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } + + return -1; +} + +extern "C" void G_ClientThink( gentity_t *ent, usercmd_t *ucmd ) +{ + try + { + if ( ent->entity ) + { + current_ucmd = ucmd; + ent->entity->ProcessEvent( EV_ClientMove ); + current_ucmd = NULL; + } + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + + +void G_SentInitialMessages( void ) +{ +} + + +/* +=========== +G_ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the game. This will happen every level load. +============ +*/ +extern "C" void G_ClientBegin( gentity_t *ent, const usercmd_t *cmd ) +{ + try + { + if ( ent->inuse && ent->entity ) + { + // the client has cleared the client side viewangles upon + // connecting to the server, which is different than the + // state when the game is saved, so we need to compensate + // with deltaangles + if( ent->client->ps.pm_type != PM_SECRET_MOVE_MODE ) + ent->entity->SetDeltaAngles(); + } + else + { + Player *player; + + // a spawn point will completely reinitialize the entity + level.spawn_entnum = ent->s.number; + player = new Player; + } + + if ( level.intermissiontime && ent->entity ) + { + G_MoveClientToIntermission( ent->entity ); + } + else + { + // Record the time entered + ent->client->pers.enterTime = level.time; + // send effect if in a multiplayer game + if ( game.maxclients > 1 ) + { + gi.Printf ( "%s entered the game\n", ent->client->pers.netname ); + } + } + + // make sure all view stuff is valid + if ( ent->entity ) + { + ent->entity->ProcessEvent( EV_ClientEndFrame ); + + if ( !Director.PlayerReady() ) + { + // let any threads waiting on us know they can go ahead + Director.PlayerSpawned(); + } + + GameplayManager::getTheGameplayManager()->processPendingMessages(); + G_SentInitialMessages( ); + } + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + + + +qboolean G_duplicateName( const char *name ) +{ + gentity_t *edict; + int i; + + for ( i = 0; i < game.maxclients; i++ ) + { + edict = &g_entities[i]; + + if ( !edict->inuse || !edict->entity || !edict->client ) + continue; + + if ( strcmp( edict->client->pers.netname, name ) == 0 ) + { + return true; + } + + } + + return false; +} + +void G_BuildUniqueName( const char *newName, char *name ) +{ + char builtName[ MAX_NETNAME ]; + int i; + + for ( i = 2 ; i <= 9 ; i++ ) + { + sprintf( builtName, "%s%d", newName, i ); + + if ( !G_duplicateName( builtName ) ) + { + strcpy( name, builtName ); + return; + } + } + + sprintf( builtName, "%s?", newName ); + + strcpy( name, builtName ); +} + +/* +=========== +G_ClientUserInfoChanged + +called whenever the player updates a userinfo variable. + +The game can override any of the settings in place +(forcing skins or names, etc) before copying it off. +============ +*/ +extern "C" void G_ClientUserinfoChanged( gentity_t *ent, const char *userinfo ) +{ + const char *s; + int playernum; + Player *player; + //float fov; + //Event *ev; + bool autoSwitchWeapons; + char tempName[ MAX_NETNAME ]; + int i; + bool validName; + + try + { + if ( !ent ) + { + assert( 0 ); + return; + } + + player = ( Player * )ent->entity; + + /* + if ( !player ) + { + assert( 0 ); + return; + } + */ + + // set name + s = Info_ValueForKey( userinfo, "name" ); + if ( !s ) + { + assert( 0 ); + return; + } + + // Setup the name + + strncpy( tempName, s, MAX_NETNAME - 1 - 3 ); + + // Make sure there is a valid name + + validName = false; + + for ( i = 0 ; i < strlen( tempName ) ; i++ ) + { + if ( ( tempName[ i ] == '^' ) && ( tempName[ i + 1 ] >= '0' ) && ( tempName[ i + 1 ] <= '9' ) ) + { + i++; + continue; + } + + validName = true; + } + + if ( !validName ) + { + strcpy( tempName, "RedShirt" ); + } + + if ( strcmp( ent->client->pers.netname, tempName ) != 0 ) + { + if ( G_duplicateName( tempName ) ) + G_BuildUniqueName( tempName, ent->client->pers.netname ); + else + strncpy( ent->client->pers.netname, tempName, MAX_NETNAME - 1 - 3 ); + } + + // Strip out bad characters + + for ( i = 0 ; i < strlen( ent->client->pers.netname ) ; i++ ) + { + if ( ent->client->pers.netname[ i ] == ':' ) + { + ent->client->pers.netname[ i ] = '.'; + } + } + + // Make sure color returns to normal at end of the name + + if ( strstr( ent->client->pers.netname, "^" ) ) + { + int length = strlen( ent->client->pers.netname ); + + if ( ( length < 2 ) || ( ent->client->pers.netname[ length - 2 ] != '^' ) || ( ent->client->pers.netname[ length - 1 ] != '8' ) ) + { + ent->client->pers.netname[ length ] = '^'; + ent->client->pers.netname[ length + 1 ] = '8'; + ent->client->pers.netname[ length + 2 ] = 0; + } + } + + // set deathmatch model + s = Info_ValueForKey( userinfo, "mp_playermodel" ); + if ( !s ) + { + assert( 0 ); + return; + } + + multiplayerManager.changePlayerModel( player, s ); + + strncpy( ent->client->pers.mp_playermodel, s, sizeof( ent->client->pers.mp_playermodel ) - 1 ); + + // Low bandwidth + s = Info_ValueForKey( userinfo, "mp_lowBandwidth" ); + if ( !s ) + { + assert( 0 ); + return; + } + + ent->client->pers.mp_lowBandwidth = atoi( s ); + + // Persistant morph controllers + s = Info_ValueForKey( userinfo, "dm_morph_c1" ); + if ( !s ) + { + assert( 0 ); + return; + } + strncpy( ent->client->pers.dm_morph_c1, s, sizeof( ent->client->pers.dm_morph_c1 ) - 1 ); + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + playernum = ent - g_entities; + gi.setConfigstring( CS_PLAYERS + playernum, va( "name\\%s", ent->client->pers.netname ) ); + //gi.setConfigstring( CS_PLAYERS + playernum, va( "%s", ent->client->pers.netname ) ); + + multiplayerManager.changePlayerName( player, ent->client->pers.netname ); + + if ( player ) + { + float fov; + Event *ev; + + // Fov + + fov = (float)atof( Info_ValueForKey( userinfo, "userFov" ) ); + if ( fov < 1.0f ) + { + fov = sv_defaultFov->value; + } + else if ( fov > 160.0f ) + { + fov = 160.0f; + } + + player->userFov = fov; + + ev = new Event( EV_Player_Fov ); + ev->AddFloat( fov ); + player->ProcessEvent( ev ); + + // autoSwitchWeapons + + if ( atoi( Info_ValueForKey( userinfo, "mp_autoSwitchWeapons" ) ) ) + autoSwitchWeapons = true; + else + autoSwitchWeapons = false; + + player->setAutoSwitchWeapons( autoSwitchWeapons ); + + // Saving demo + s = Info_ValueForKey( userinfo, "mp_savingDemo" ); + + if ( !s ) + { + assert( 0 ); + return; + } + + player->mp_savingDemo = atoi( s ); + } + + // save off the userinfo in case we want to check something later + strncpy( ent->client->pers.userinfo, userinfo, sizeof( ent->client->pers.userinfo ) - 1 ); + + Info_SetValueForKey(ent->client->pers.userinfo, "name", ent->client->pers.netname); + gi.setUserinfo(playernum, ent->client->pers.userinfo); + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + +qboolean G_BotConnect( int clientNum, qboolean restart ); + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +Called again for every map change or tournement restart. + +The session information will be valid after exit. + +Return NULL if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be true the very first time a client connects +to the server machine, but false on map changes and tournement +restarts. +============ +*/ +extern "C" const char *G_ClientConnect( int clientNum, qboolean firstTime, qboolean isBot, qboolean checkPassword ) +{ + const char *value; + gentity_t *ent; + char userinfo[ MAX_INFO_STRING ]; + gclient_t *client; + + try + { + ent = &g_entities[ clientNum ]; + gi.getUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check to see if they are on the banned IP list + value = Info_ValueForKey( userinfo, "ip" ); + if ( SV_FilterPacket( value ) ) + { + return "$$BannedIP$$"; + } + + if ( checkPassword ) + { + // check for a password + value = Info_ValueForKey( userinfo, "password" ); + if ( strcmp( password->string, value ) != 0 ) + { + return "$$InvalidPassword$$"; + } + } + + // they can connect + ent->client = game.clients + clientNum; + client = ent->client; + + // read or initialize the session data + // if there is already a body waiting for us (a loadgame), just + // take it, otherwise spawn one from scratch + if ( firstTime && !ent->entity ) + { + memset( client, 0, sizeof( *client ) ); + + // clear the respawning variables + if ( !game.autosaved ) + { + G_InitClientPersistant( client ); + playersLastTeam[ clientNum ] = ""; + } + } + + + if( isBot ) { + ent->svflags |= SVF_BOT; + //ent->inuse = qtrue; + if( !G_BotConnect( clientNum, !firstTime ) ) { + return "BotConnectfailed"; + } + } + + + + G_ClientUserinfoChanged( ent, userinfo ); + + if ( firstTime && ( game.maxclients > 1 ) ) + { + gi.Printf( "%s connected\n", ent->client->pers.netname ); + } + + LoadingServer = false; + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } + + return NULL; +} + +/* +=========== +G_ClientDisconnect + +called when a player drops from the server + +============ +*/ +extern "C" void G_ClientDisconnect( gentity_t *ent ) +{ + try + { + if ( !ent || ( !ent->client ) || ( !ent->entity ) ) + { + return; + } + + Player *player; + + player = ( Player * )ent->entity; + player->Disconnect(); + + delete ent->entity; + ent->entity = NULL; + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + +/* +================= +G_ClientDrawSplines +================= +*/ +void G_ClientDrawSplines( void ) +{ + int i; + Entity *entity; + gentity_t *ed; + + + if ( !sv_showsplines->integer ) + return; + + for ( i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + continue; + + entity = g_entities[i].entity; + + if ( entity->isSubclassOf( SplinePath ) ) + { + SplinePath *path = ( SplinePath *)entity; + + SplinePath *node; + BSpline *splinePath; + + splinePath = new BSpline; + splinePath->SetType( SPLINE_CLAMP ); + + node = path; + + while( node != NULL ) + { + splinePath->AppendControlPoint( node->origin, node->angles, node->speed ); + + node = node->GetNext(); + + if ( node == path ) + break; + } + + G_Color3f( 1.0f, 1.0f, 0.0f ); + splinePath->DrawCurve( 10 ); + + delete splinePath; + continue; + } + } +} + +/* +================= +G_ClientDrawBoundingBoxes +================= +*/ +void G_ClientDrawBoundingBoxes( void ) +{ + gentity_t *edict; + Entity *ent; + Vector eye; + + // don't show bboxes during deathmatch + if ( + ( !g_showgravpath->integer && !sv_showbboxes->integer ) || + ( multiplayerManager.inMultiplayer() && !sv_cheats->integer ) + ) + { + return; + } + + if ( sv_showbboxes->integer ) + { + edict = g_entities; + ent = edict->entity; + if ( ent ) + { + eye = ent->origin; + ent = findradius( NULL, eye, 1000.0f ); + while( ent ) + { + switch ((int)sv_showbboxes->integer) + { + case 1: + if ( ent->edict != edict && ent->edict->s.solid) + { + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 1.0f, 1.0f, 0.0f, 1.0f ); + } + break; + case 2: + if ( ent->edict != edict && ent->edict->s.solid) + { + G_DebugBBox( vec_zero, ent->edict->absmin, ent->edict->absmax, 1.0f, 0.0f, 1.0f, 1.0f ); + } + break; + case 3: + if ( ent->edict->s.modelindex && !(ent->edict->s.renderfx & RF_DONTDRAW) ) + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 1.0f, 1.0f, 0.0f, 1.0f ); + break; + case 4: + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 1.0f, 1.0f, 0.0f, 1.0f ); + break; + case 5: + default: + if ( ent->animate && gi.IsModel( ent->edict->s.modelindex ) ) + { + vec3_t mins, maxs; + + gi.Frame_Bounds( ent->edict->s.modelindex, ent->CurrentAnim(), ent->CurrentFrame(), ent->edict->s.scale, mins, maxs ); + G_DebugBBox( ent->origin, mins, maxs, 0.0f, 1.0f, 0.0f, 1.0f ); + } + else + { + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 1.0f, 1.0f, 0.0f, 1.0f ); + } + break; + } + ent = findradius( ent, eye, 1000.0f ); + } + } + } + + if ( g_showgravpath->integer ) + { + // Draw the gravity node paths + gravPathManager.DrawGravPaths(); + } +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +extern "C" int G_GetTotalGameFrames( void ) +{ + Player* player = GetPlayer(0); + + if(player != 0) + return player->getTotalGameFrames(); + + return 0; +} + +//====================================================================== + +#ifndef GAME_HARD_LINKED +// this is only here so the functions in q_shared.c and q_shwin.c can link +void Com_Error ( int level, const char *error, ... ) +{ + va_list argptr; + char text[4096]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + gi.Error( level, "%s", text); +} + +void Sys_Error( const char *error, ... ) +{ + va_list argptr; + char text[4096]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + gi.Error (ERR_FATAL, "%s", text); +} + +void Com_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[4096]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + gi.DPrintf ("%s", text); +} + +void Com_WPrintf( const char *msg, ... ) +{ + va_list argptr; + char text[4096]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + gi.WDPrintf ("%s", text); +} + +#endif diff --git a/dlls/game/g_main.h b/dlls/game/g_main.h new file mode 100644 index 0000000..c33dbed --- /dev/null +++ b/dlls/game/g_main.h @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/g_main.h $ +// $Revision:: 19 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Global header file for g_main.cpp +// + +#ifndef __G_MAIN_H__ +#define __G_MAIN_H__ + +#include "g_local.h" +#include "gamecvars.h" +#include "level.h" + +extern Vector vec_origin; +extern Vector vec_zero; + +extern qboolean LoadingSavegame; +extern qboolean LoadingServer; + +extern game_import_t gi; +extern game_export_t globals; + +extern gentity_t *g_entities; +extern gentity_t active_edicts; +extern gentity_t free_edicts; + +extern int sv_numtraces; + +extern usercmd_t *current_ucmd; + +void G_BeginIntermission( const char *map ); +void G_MoveClientToIntermission( Entity *client ); +void G_WriteClient( Archiver &arc, gclient_t *client ); +void G_AllocGameData( void ); +void G_DeAllocGameData( void ); +void G_ClientDrawBoundingBoxes( void ); +void G_ClientDrawSplines( void ); + +void G_ExitWithError( const char *error ); + +void G_CheckIntermissionExit( void ); +void G_BeginIntermission2( void ); + +extern "C" { + void G_SpawnEntities( const char *mapname, const char *entities, int time ); + void G_PostLoad( void ); + void G_SublevelPostLoad( const char *mapName ); + void G_ClientEndServerFrames( void ); + void G_ClientThink( gentity_t *ent, usercmd_t *cmd ); + const char *G_ClientConnect( int clientNum, qboolean firstTime, qboolean isBot, qboolean checkPassword ); + void G_ClientUserinfoChanged( gentity_t *ent, const char *userinfo ); + void G_ClientDisconnect( gentity_t *ent ); + void G_ClientBegin( gentity_t *ent, const usercmd_t *cmd ); + void G_WritePersistant( const char *filename, qboolean sublevelTransition ); + qboolean G_ReadPersistant( const char *filename, qboolean sublevelTransition ); + void G_WriteLevel( const char *filename, qboolean autosave ); + qboolean G_ReadLevel( const char *filename ); + qboolean G_LevelArchiveValid( const char *filename ); + void G_InitGame( int startTime, int randomSeed ); + void G_ShutdownGame( void ); + void G_CleanupGame( qboolean restart ); + void G_RunFrame( int levelTime, int frametime ); + void G_ServerCommand( void ); + void G_ClientThink( gentity_t *ent, usercmd_t *ucmd ); + qboolean G_SendEntity( gentity_t *clientEntity, gentity_t *entityToSend ); + void G_UpdateEntityStateForClient( gentity_t *clientEntity, entityState_t *state ); + void G_UpdatePlayerStateForClient( gentity_t *clientEntity, playerState_t *state ); + void G_ExtraEntitiesToSend( gentity_t *clientEntity, int *numExtraEntities, int *extraEntities ); + void G_AddEntityToExtraList(int entityNum); + void G_RemoveEntityFromExtraList(int entityNum); + int G_GetEntityCurrentAnimFrame( int entityNum, int bodyPart ); + int G_GetTotalGameFrames( void ); + } + +void ClosePlayerLogFile( void ); + +qboolean SV_FilterPacket( const char *from ); +void SVCmd_AddIP_f( void ); +void SVCmd_RemoveIP_f( void ); +void SVCmd_ListIP_f( void ); +void SVCmd_WriteIP_f( void ); + +#endif /* g_main.h */ diff --git a/dlls/game/g_phys.cpp b/dlls/game/g_phys.cpp new file mode 100644 index 0000000..278b3d2 --- /dev/null +++ b/dlls/game/g_phys.cpp @@ -0,0 +1,1694 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/g_phys.cpp $ +// $Revision:: 32 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "animate.h" +#include "sentient.h" +#include "actor.h" +#include "vehicle.h" +#include "weaputils.h" +#include "mp_manager.hpp" + +/* + + +pushmove objects do not obey gravity, and do not interact with each other or trigger fields, but block normal movement and push normal objects when they move. + +onground is set for toss objects when they come to a complete rest. it is set for steping or walking objects + +doors, plats, etc are SOLID_BSP, and MOVETYPE_PUSH +bonus items are SOLID_TRIGGER touch, and MOVETYPE_TOSS +corpses are SOLID_NOT and MOVETYPE_TOSS +crates are SOLID_BBOX and MOVETYPE_TOSS +walking monsters are SOLID_SLIDEBOX and MOVETYPE_STEP +flying/floating monsters are SOLID_SLIDEBOX and MOVETYPE_FLY + +solid_edge items only clip against bsp models. + +*/ + +typedef struct +{ + Entity *ent; + Vector localorigin; + Vector origin; + Vector localangles; + Vector angles; + float deltayaw; +} pushed_t; + +pushed_t pushed[ MAX_GENTITIES ]; +pushed_t *pushed_p; + +Entity *obstacle; + +/* +============ +G_FixEntityPosition + +============ +*/ +Entity *G_FixEntityPosition( Entity *ent ) +{ + int mask; + trace_t trace; + Vector start; + + mask = ent->edict->clipmask; + if ( !mask ) + { + mask = MASK_SOLID; + } + + start = ent->origin; + start.z += 8.0f; + if ( ent->client ) + { + trace = G_Trace( start, ent->mins, ent->maxs, ent->origin, ent, mask, true, "G_TestEntityPosition1" ); + } + else + { + trace = G_Trace( start, ent->mins, ent->maxs, ent->origin, ent, mask, false, "G_TestEntityPosition2" ); + } + + if ( trace.startsolid ) + { + //return g_entities->entity; + assert( trace.ent ); + assert( trace.ent->entity ); + return trace.ent->entity; + } + + ent->setOrigin( trace.endpos ); + + return NULL; +} + +/* +============ +G_TestEntityPosition + +============ +*/ +Entity *G_TestEntityPosition( Entity *ent ) +{ + int mask; + trace_t trace; + + mask = ent->edict->clipmask; + if ( !mask ) + mask = MASK_SOLID; + + if ( ent->client ) + { + trace = G_Trace( ent->origin, ent->mins, ent->maxs, ent->origin, ent, mask, true, "G_TestEntityPosition1" ); + } + else + { + trace = G_Trace( ent->origin, ent->mins, ent->maxs, ent->origin, ent, mask, false, "G_TestEntityPosition2" ); + } + + if ( trace.startsolid ) + { + //return g_entities->entity; + assert( trace.ent ); + assert( trace.ent->entity ); + return trace.ent->entity; + } + + return NULL; +} + +/* +================ +G_CheckVelocity +================ +*/ +void G_CheckVelocity( Entity *ent ) +{ + int i; + + // + // bound velocity + // + for( i = 0; i < 3; i++ ) + { + if ( ent->velocity[ i ] > sv_maxvelocity->value ) + { + ent->velocity[ i ] = sv_maxvelocity->value; + } + else if ( ent->velocity[ i ] < -sv_maxvelocity->value ) + { + ent->velocity[ i ] = -sv_maxvelocity->value; + } + } +} + +/* +================== +G_Impact + +Two entities have touched, so run their touch functions +================== +*/ +void G_Impact( Entity *e1, const trace_t *trace ) +{ + gentity_t *e2; + Event *ev; + + e2 = trace->ent; + + level.impact_trace = *trace; + + // touch anything, including the world + if ( e1->edict->solid != SOLID_NOT ) + { + ev = new Event( EV_Touch ); + ev->AddEntity( e2->entity ); + e1->ProcessEvent( ev ); + } + + // entity could have been removed, so check if he's in use before sending the event + if ( + e2->entity && + ( e2->solid != SOLID_NOT ) && + ( !( e2->contents & CONTENTS_SHOOTABLE_ONLY ) ) && + ( e2->entity != world ) + ) + { + ev = new Event( EV_Touch ); + ev->AddEntity( e1 ); + e2->entity->ProcessEvent( ev ); + } + + memset( &level.impact_trace, 0, sizeof( level.impact_trace ) ); +} + +/* +================== +G_ClipVelocity + +Slide off of the impacting object +returns the blocked flags (1 = floor, 2 = step / wall) +================== +*/ +#define STOP_EPSILON 0.1 + +int G_ClipVelocity( const Vector& in, const Vector& normal, Vector& out, float overbounce ) +{ + int i; + int blocked; + float backoff; + + blocked = 0; + + if ( normal[ 2 ] > 0.0f ) + { + // floor + blocked |= 1; + } + if ( !normal[ 2 ] ) + { + // step + blocked |= 2; + } + + backoff = ( in * normal ) * overbounce; + + out = in - ( normal * backoff ); + + for( i = 0; i < 3; i++ ) + { + if ( ( out[ i ] > -STOP_EPSILON ) && ( out[ i ] < STOP_EPSILON ) ) + { + out[ i ] = 0.0f; + } + } + + return blocked; +} + +/* +============ +G_FlyMove + +The basic solid body movement clip that slides along multiple planes +Returns the clipflags if the velocity was modified (hit something solid) +1 = floor +2 = wall / step +4 = dead stop +============ +*/ +#define MAX_CLIP_PLANES 5 + +int G_FlyMove( Entity *ent, const Vector &basevel, float time, int mask ) +{ + Entity *hit; + gentity_t *edict; + int bumpcount, numbumps; + Vector dir; + float d; + int numplanes; + vec3_t planes[ MAX_CLIP_PLANES ]; + Vector primal_velocity, original_velocity, new_velocity; + int i, j; + trace_t trace; + Vector end; + float time_left; + int blocked; +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + Vector move; + Vector v; +#endif + +****************************************************************************/ + + + edict = ent->edict; + + numbumps = 4; + + blocked = 0; + original_velocity = ent->velocity; + primal_velocity = ent->velocity; + numplanes = 0; + +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 1 + +****************************************************************************/ + + time_left = time; +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#else + time_left = 1.0;//time; + + v = ent->total_delta; + v[ 1 ] = -v[ 1 ]; // sigh... + MatrixTransformVector( v, ent->orientation, move ); + move += ent->velocity * time; + ent->total_delta = vec_zero; +#endif + +****************************************************************************/ + + + ent->groundentity = NULL; + for( bumpcount = 0; bumpcount < numbumps; bumpcount++ ) + { +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 1 + +****************************************************************************/ + + end = ent->origin + ( time_left * ( ent->velocity + basevel ) ); +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#else + end = ent->origin + time_left * move; +#endif + +****************************************************************************/ + + + Vector test; + test = ent->origin; + test[2] += 25.0f; + + trace = G_Trace( ent->origin, ent->mins, ent->maxs, end, ent, mask, false, "G_FlyMove" ); + //trace = G_Trace( test, ent->mins, ent->maxs, end, ent, mask |CONTENTS_MONSTERCLIP|CONTENTS_PLAYERCLIP , false, "G_FlyMove" ); + /* + if ( + ( trace.allsolid ) || + ( + ( trace.startsolid ) && + ( ent->movetype == MOVETYPE_VEHICLE ) + ) + ) + { + // entity is trapped in another solid + ent->velocity = vec_zero; + return 3; + } + */ + if ( trace.fraction > 0.0f ) + { + // actually covered some distance + ent->setOrigin( trace.endpos ); + original_velocity = ent->velocity; + numplanes = 0; + } + + if ( trace.fraction == 1.0f ) + { + // moved the entire distance + break; + } + + + assert( trace.ent ); + hit = trace.ent->entity; + + if ( trace.plane.normal[ 2 ] > 0.7f ) + { + // floor + blocked |= 1; + if ( hit->getSolidType() == SOLID_BSP ) + { + ent->groundentity = hit->edict; + ent->groundplane = trace.plane; + ent->groundcontents = trace.contents; + } + } + + if ( !trace.plane.normal[ 2 ] ) + { + // step + blocked |= 2; + } + + // + // run the impact function + // + G_Impact( ent, &trace ); + if ( !edict->inuse ) + { + break; // removed by the impact function + } + + time_left -= time_left * trace.fraction; + + // cliped to another plane + if ( numplanes >= MAX_CLIP_PLANES ) + { + // this shouldn't really happen + ent->velocity = vec_zero; + return 3; + } + + VectorCopy( trace.plane.normal, planes[ numplanes ] ); + numplanes++; + + // + // modify original_velocity so it parallels all of the clip planes + // + for( i = 0; i < numplanes; i++ ) + { + G_ClipVelocity( original_velocity, Vector( planes[ i ] ), new_velocity, 1.01f ); + for( j = 0; j < numplanes; j++ ) + { + if ( j != i ) + { + if ( ( new_velocity * planes[ j ] ) < 0.0f ) + { + // not ok + break; + } + } + } + + if ( j == numplanes ) + { + break; + } + } + + if ( i != numplanes ) + { + // go along this plane + ent->velocity = new_velocity; + } + else + { + // go along the crease + if ( numplanes != 2 ) + { + ent->velocity = vec_zero; + return 7; + } + CrossProduct( planes[ 0 ], planes[ 1 ], dir ); + d = dir * ent->velocity; + ent->velocity = dir * d; + } + + // + // if original velocity is against the original velocity, stop dead + // to avoid tiny occilations in sloping corners + // + if ( ( ent->velocity * primal_velocity ) <= 0.0f ) + { + ent->velocity = vec_zero; + return blocked; + } + } + + return blocked; +} + + +/* +============ +G_AddGravity + +============ +*/ +void G_AddGravity( Entity *ent ) +{ + float grav; + + if ( ent->waterlevel > 2 ) + { + grav = ent->gravity * 60.0f * level.frametime; + } + else + { + grav = ent->gravity * sv_currentGravity->value * level.frametime; + } + + if ( ent->isSubclassOf( Vehicle ) ) + ent->velocity[ 2 ] -= grav + 500.0f; + else + ent->velocity[ 2 ] -= grav; +} + +/* +=============================================================================== + +PUSHMOVE + +=============================================================================== +*/ + +/* +============ +G_PushEntity + +Does not change the entities velocity at all +============ +*/ +trace_t G_PushEntity( Entity *ent, const Vector &push ) +{ + trace_t trace; + Vector start; + Vector end; + int mask; + gentity_t *edict; + + start = ent->origin; + end = start + push; + +retry: + if ( ent->edict->clipmask ) + { + mask = ent->edict->clipmask; + } + else + { + mask = MASK_SOLID; + } + + if ( ent->usesFullTrace() && ( !multiplayerManager.inMultiplayer() || multiplayerManager.fullCollision() ) ) + trace = G_FullTrace( start, ent->mins, ent->maxs, end, ent, mask, false, "G_PushEntity" ); + else + trace = G_Trace( start, ent->mins, ent->maxs, end, ent, mask, false, "G_PushEntity" ); + + edict = ent->edict; + + ent->setOrigin( trace.endpos ); + + if ( trace.fraction != 1.0f || ( trace.startsolid && trace.ent && trace.ent->entity != world ) ) + { + G_Impact( ent, &trace ); + + // if the pushed entity went away and the pusher is still there + if ( ( !trace.ent || !trace.ent->inuse ) && edict->inuse ) + { + // move the pusher back and try again + ent->setOrigin( start ); + goto retry; + } + } + + if ( edict && ( edict != ent->edict ) ) + { + if ( ent->flags & FL_TOUCH_TRIGGERS ) + { + G_TouchTriggers( ent ); + } + } + + return trace; +} + +/* +============ +G_SlideEntity +============ +*/ +trace_t G_SlideEntity( Entity *ent, const Vector &push ) +{ + trace_t trace; + Vector start; + Vector end; + int mask; + + start = ent->origin; + end = start + push; + + if ( ent->edict->clipmask ) + { + mask = ent->edict->clipmask; + } + else + { + mask = MASK_SOLID; + } + + trace = G_Trace( start, ent->mins, ent->maxs, end, ent, mask, false, "G_SlideEntity" ); + + ent->setOrigin( trace.endpos ); + + return trace; +} + + +/* +================ +G_SnapPosition + +================ +*/ +/* +qboolean G_SnapPosition + ( + Entity *ent + ) + + { + int x, y, z; + Vector offset( 0, -1, 1 ); + Vector base; + + base = ent->origin; + for ( z = 0; z < 3; z++ ) + { + ent->origin.z = base.z + offset[ z ]; + for ( y = 0; y < 3; y++ ) + { + ent->origin.y = base.y + offset[ y ]; + for ( x = 0; x < 3; x++ ) + { + ent->origin.x = base.x + offset[ x ]; + if ( G_TestEntityPosition( ent ) ) + { + ent->origin.x += offset[ x ]; + ent->origin.y += offset[ y ]; + ent->origin.z += offset[ z ]; + ent->setOrigin( ent->origin ); + return true; + } + } + } + } + + // can't find a good position, so put him back. + ent->origin = base; + + return false; + } +*/ + +/* +============ +G_Push + +Objects need to be moved back on a failed push, +otherwise riders would continue to slide. +============ +*/ +qboolean G_Push( Entity *pusher, const Vector &pushermove, const Vector &pusheramove ) +{ + Entity *check, *block; + gentity_t *edict; + Vector move, amove; + Vector mins, maxs; + Vector save; + pushed_t *p; + Vector org, org2, move2; + Vector norm; + float mat[ 3 ][ 3 ]; + pushed_t *pusher_p; + float radius; + int i, num; + int touch[ MAX_GENTITIES ]; + gentity_t *next; + + // save the pusher's original position + pusher_p = pushed_p; + pushed_p->ent = pusher; + pushed_p->localorigin = pusher->GetLocalOrigin(); + pushed_p->origin = pusher->origin; + pushed_p->localangles = pusher->localangles; + pushed_p->angles = pusher->angles; + + if ( pusher->client ) + { + pushed_p->deltayaw = pusher->client->ps.delta_angles[ YAW ]; + } + + pushed_p++; + + if ( pushed_p >= &pushed[ MAX_GENTITIES ] ) + { + gi.Error( ERR_FATAL, "Pushed too many entities." ); + } + + // move the pusher to it's final position + pusher->addAngles( pusheramove ); + pusher->addOrigin( pushermove ); + + if ( pusher->edict->solid == SOLID_NOT ) + { + // Doesn't push anything + return true; + } + + // change the move to worldspace + move = pusher->origin - pusher_p->origin; + amove = pusher->angles - pusher_p->angles; + + // we need this for pushing things later + AnglesToAxis( amove, mat ); + + // find the bounding box + mins = pusher->absmin; + maxs = pusher->absmax; + + // Add in entities that are within the pusher + + num = gi.AreaEntities( mins, maxs, touch, MAX_GENTITIES, qfalse ); + + // Add in entities that are standing on the pusher + + for( edict = active_edicts.next; edict != &active_edicts; edict = next ) + { + assert( edict ); + assert( edict->inuse ); + assert( edict->entity ); + + next = edict->next; + check = edict->entity; + + if ( check->groundentity == pusher->edict ) + { + // Make sure not in list already + + for( i = 0; i < num; i++ ) + { + if ( touch [ i ] == check->edict - g_entities ) + break; + } + + if ( i == num ) + { + touch[ num ] = check->edict - g_entities; + num++; + } + } + } + + for( i = 0; i < num; i++ ) + { + edict = &g_entities[ touch[ i ] ]; + assert( edict ); + assert( edict->inuse ); + assert( edict->entity ); + check = edict->entity; + + if ( ( check->movetype == MOVETYPE_PUSH ) || + ( check->movetype == MOVETYPE_STOP ) || + ( check->movetype == MOVETYPE_NONE ) || + ( check->movetype == MOVETYPE_NOCLIP ) ) + { + continue; + } + + if ( ( check->edict->contents == CONTENTS_SHOOTABLE_ONLY ) || ( check->edict->contents == CONTENTS_CORPSE ) ) + continue; + + // if the entity is standing on the pusher, it will definitely be moved + if ( check->groundentity != pusher->edict ) + { + // Only move triggers and non-solid objects if they're sitting on a moving object + if ( ( check->edict->solid == SOLID_TRIGGER ) || ( check->edict->solid == SOLID_NOT ) ) + { + continue; + } + + // see if the ent needs to be tested + if ( ( check->absmin[ 0 ] >= maxs[ 0 ] ) || + ( check->absmin[ 1 ] >= maxs[ 1 ] ) || + ( check->absmin[ 2 ] >= maxs[ 2 ] ) || + ( check->absmax[ 0 ] <= mins[ 0 ] ) || + ( check->absmax[ 1 ] <= mins[ 1 ] ) || + ( check->absmax[ 2 ] <= mins[ 2 ] ) ) + { + continue; + } + + // see if the ent's bbox is inside the pusher's final position + if ( !G_TestEntityPosition( check ) ) + { + continue; + } + } + + if ( + ( pusher->movetype == MOVETYPE_PUSH ) || + ( check->groundentity == pusher->edict ) + ) + { + pushed_p->localorigin = check->GetLocalOrigin(); + pushed_p->localangles = check->localangles; + // move this entity + pushed_p->ent = check; + pushed_p->origin = check->origin; + pushed_p->angles = check->angles; + pushed_p++; + + if ( pushed_p >= &pushed[ MAX_GENTITIES ] ) + { + gi.Error( ERR_FATAL, "Pushed too many entities." ); + } + + // save off the origin + save = check->GetLocalOrigin(); + + // try moving the contacted entity + move2 = move; + + // FIXME: doesn't rotate monsters? + if ( check->client ) + { + check->client->ps.delta_angles[YAW] += ANGLE2SHORT( amove[ YAW ] ); + } + + // get the radius of the entity + if ( check->size.x > check->size.z ) + { + radius = check->size.z * 0.5f; + } + else + { + radius = check->size.x * 0.5f; + } + + // figure movement due to the pusher's amove + org = check->origin - pusher->origin; + org.z += radius; + + MatrixTransformVector( org, mat, org2 ); + + move2 += org2 - org; + + //FIXME + // We should probably do a flymove here so that we slide against other objects + check->addOrigin( check->getParentVector( move2 ) ); + + // may have pushed them off an edge + if ( check->groundentity != pusher->edict ) + { + check->groundentity = NULL; + } + + block = G_TestEntityPosition( check ); + if ( block ) + { + block = G_FixEntityPosition( check ); + } + if ( !block ) + { + // pushed ok + check->link(); + + // impact? + continue; + } + + // try to snap it to a good position + /* + if ( G_SnapPosition( check ) ) + { + // snapped ok. we don't have to link since G_SnapPosition does it for us. + continue; + } + */ + + // if it is ok to leave in the old position, do it + // this is only relevent for riding entities, not pushed + // FIXME: this doesn't acount for rotation + check->setOrigin( save ); + block = G_TestEntityPosition( check ); + if ( !block ) + { + pushed_p--; + continue; + } + } + + if ( check->edict->solid == SOLID_NOT ) + continue; + + // save off the obstacle so we can call the block function + obstacle = check; + + // move back any entities we already moved + // go backwards, so if the same entity was pushed + // twice, it goes back to the original position + for( p = pushed_p - 1; p >= pushed; p-- ) + { + p->ent->angles = p->angles; + p->ent->origin = p->origin; + + p->ent->localangles = p->localangles; + p->ent->SetLocalOrigin( p->localorigin ); + + if ( p->ent->client ) + { + p->ent->client->ps.delta_angles[ YAW ] = (int) p->deltayaw; + } + } + + // Only "really" move it in order so that the bound coordinate system is correct + for( p = pushed; p < pushed_p; p++ ) + { + p->ent->setAngles(); + p->ent->setOrigin(); + } + + return false; + } + + //FIXME: is there a better way to handle this? + // see if anything we moved has touched a trigger + for( p = pushed_p - 1; p >= pushed; p-- ) + { + if ( p->ent->flags & FL_TOUCH_TRIGGERS ) + { + G_TouchTriggers( p->ent ); + } + } + + return true; +} + +/* +================ +G_PushMove +================ +*/ +qboolean G_PushMove( Entity *ent, const Vector &move, const Vector &amove ) +{ + Entity *part; + Vector m, a; + Event *ev; + + m = move; + a = amove; + + pushed_p = pushed; + + part = ent; + + while( part ) + { + if ( !G_Push( part, m, a ) ) + { + // move was blocked + // call the pusher's "blocked" function + // otherwise, just stay in place until the obstacle is gone + ev = new Event( EV_Blocked ); + ev->AddEntity( obstacle ); + part->ProcessEvent( ev ); + return false; + } + + m = vec_zero; + a = vec_zero; + + if ( part->bind_info ) + part = part->bind_info->teamchain; + else + part = NULL; + } + + return true; +} + +/* +================ +G_Physics_Pusher + +Bmodel objects don't interact with each other, but +push all box objects +================ +*/ +void G_Physics_Pusher( Entity *ent ) +{ + Vector move, amove; + Entity *part, *mv; + Event *ev; + + // team slaves are only moved by their captains + if ( ent->flags & FL_TEAMSLAVE ) + { + return; + } + + // Check if anyone on the team is moving + part = ent; + + while( part ) + { + if ( ( part->velocity != vec_zero ) || ( part->avelocity != vec_zero ) ) + { + break; + } + + if ( part->bind_info ) + part = part->bind_info->teamchain; + else + part = NULL; + } + + // make sure all team slaves can move before commiting + // any moves or calling any think functions + // if the move is blocked, all moved objects will be backed out + pushed_p = pushed; + while( part ) + { + move = part->velocity * level.frametime; + amove = part->avelocity * level.frametime; + + if ( !G_Push( part, move, amove ) ) + { + // move was blocked + break; + } + + if ( part->bind_info ) + part = part->bind_info->teamchain; + else + part = NULL; + } + + if ( part ) + { + // the move failed, bump all movedone times + mv = ent; + + while( mv ) + { + mv->PostponeEvent( EV_MoveDone, FRAMETIME ); + + if ( mv->bind_info ) + mv = mv->bind_info->teamchain; + else + mv = NULL; + } + + // if the pusher has a "blocked" function, call it + // otherwise, just stay in place until the obstacle is gone + ev = new Event( EV_Blocked ); + ev->AddEntity( obstacle ); + part->ProcessEvent( ev ); + } +} + +//================================================================== + +/* +============= +G_Physics_Noclip + +A moving object that doesn't obey physics +============= +*/ +void G_Physics_Noclip( Entity *ent ) +{ + ent->angles += ent->avelocity * level.frametime; + ent->origin += ent->velocity * level.frametime; + ent->link(); +} + +/* +============================================================================== + +TOSS / BOUNCE + +============================================================================== +*/ + +/* +============= +G_Physics_Toss + +Toss, bounce, and fly movement. When onground, do nothing. +============= +*/ +void G_Physics_Toss( Entity *ent ) +{ + trace_t trace; + Vector move; + float backoff; + Entity *slave; + qboolean wasinwater; + qboolean isinwater; + Vector origin2; + Vector basevel; + gentity_t *edict; + qboolean onconveyor; + + // if not a team captain, so movement will be handled elsewhere + if ( ent->flags & FL_TEAMSLAVE ) + { + return; + } + + if ( ent->velocity[ 2 ] > 0 ) + { + ent->groundentity = NULL; + } + + // check for the groundentity going away + if ( ent->groundentity && !ent->groundentity->inuse ) + { + ent->groundentity = NULL; + } + + onconveyor = ( basevel != vec_zero ); + + // if onground, return without moving + if ( ent->groundentity && !onconveyor && ( ent->movetype != MOVETYPE_VEHICLE ) ) + { + if ( ent->avelocity.length() ) + { + // move angles + ent->setAngles( ent->angles + ( ent->avelocity * level.frametime ) ); + } + ent->velocity = vec_zero; + return; + } + + origin2 = ent->origin; + + //G_CheckVelocity( ent ); + + // add gravity + if ( !onconveyor && ( ent->movetype != MOVETYPE_FLY ) && ( ent->movetype != MOVETYPE_FLYMISSILE ) ) + { + if ( !( ent->flags & FL_FLY ) ) + G_AddGravity( ent ); + } + + // move angles + ent->setAngles( ent->angles + ( ent->avelocity * level.frametime ) ); + + // move origin + move = ( ent->velocity + basevel ) * level.frametime; + + edict = ent->edict; + if ( ent->movetype == MOVETYPE_VEHICLE ) + { + int mask; + + if ( ent->edict->clipmask ) + { + mask = ent->edict->clipmask; + } + else + { + mask = MASK_MONSTERSOLID; + } + G_FlyMove( ent, basevel, FRAMETIME, mask ); + if ( ent->flags & FL_TOUCH_TRIGGERS ) + { + G_TouchTriggers( ent ); + } + return; + } + else + { + trace = G_PushEntity( ent, move ); + } + + if ( ( trace.fraction == 0.0f ) && ( ent->movetype == MOVETYPE_SLIDE ) ) + { + // Check for slide by removing the downward velocity + Vector slide; + + slide[ 0 ] = move[ 0 ] * 0.7f; + slide[ 1 ] = move[ 1 ] * 0.7f; + slide[ 2 ] = 0; + + G_PushEntity( ent, slide ); + } + + if ( !edict->inuse ) + { + return; + } + + if ( trace.fraction < 1.0f ) + { + if ( ( ent->movetype == MOVETYPE_BOUNCE ) || ( ent->movetype == MOVETYPE_GIB ) ) + { + backoff = 1.5; + } + else + { + backoff = 1; + } + + if( ent->movetype != MOVETYPE_NONE) + G_ClipVelocity( ent->velocity, Vector( trace.plane.normal ), ent->velocity, backoff ); + + // stop if on ground + if ( trace.plane.normal[ 2 ] > 0.7f ) + { + //if ( ( ( ent->velocity[ 2 ] < 30.0f ) || ( ( ent->movetype != MOVETYPE_BOUNCE ) && ( ent->movetype != MOVETYPE_GIB ) ) ) && + if ( ( ( VectorLength( ent->velocity ) < 10.0f ) || ( ( ent->movetype != MOVETYPE_BOUNCE ) && ( ent->movetype != MOVETYPE_GIB ) ) ) && + ( ent->movetype != MOVETYPE_SLIDE ) ) + { + ent->groundentity = trace.ent; + ent->groundplane = trace.plane; + ent->groundcontents = trace.contents; + ent->velocity = vec_zero; + ent->avelocity = vec_zero; + ent->ProcessEvent( EV_Stop ); + } + else if ( ent->movetype == MOVETYPE_GIB ) + { + // Stop spinning after we bounce on the ground + ent->avelocity = vec_zero; + } + } + } + + if ( ( move[ 2 ] == 0.0f ) && onconveyor ) + { + // Check if we still have a ground + ent->CheckGround(); + } + + // check for water transition + wasinwater = ( ent->watertype & MASK_WATER ); + ent->watertype = gi.pointcontents( ent->origin, 0 ); + isinwater = ent->watertype & MASK_WATER; + + if ( isinwater ) + { + ent->waterlevel = 1; + } + else + { + ent->waterlevel = 0; + } + + if ( ( edict->spawntime < ( level.time - FRAMETIME ) ) && ( ent->mass > 0 ) ) + { + if ( !wasinwater && isinwater ) + { + ent->Sound( "impact_watersplash", CHAN_BODY, DEFAULT_VOL, DEFAULT_MIN_DIST, &origin2 ); + } + else if ( wasinwater && !isinwater ) + { + ent->Sound( "impact_leavewater", CHAN_BODY, DEFAULT_VOL, DEFAULT_MIN_DIST, &origin2 ); + } + } + + // GAMEFIX - Is this necessary? + // move teamslaves + if ( ent->bind_info ) + { + for( slave = ent->bind_info->teamchain ; slave ; slave = slave->bind_info->teamchain ) + { + slave->setOrigin( slave->GetLocalOrigin() ); + slave->setAngles( slave->localangles ); + } + } + + if ( ent->flags & FL_TOUCH_TRIGGERS ) + { + G_TouchTriggers( ent ); + } +} + +/* +=============================================================================== + +STEPPING MOVEMENT + +=============================================================================== +*/ + +void G_AddRotationalFriction( Entity *ent ) +{ + int n; + float adjustment; + + ent->angles += level.frametime * ent->avelocity; + adjustment = level.frametime * sv_stopspeed->value * sv_friction->value; + for( n = 0; n < 3; n++ ) + { + if ( ent->avelocity[ n ] > 0.0f ) + { + ent->avelocity[ n ] -= adjustment; + if ( ent->avelocity[ n ] < 0.0f ) + { + ent->avelocity[ n ] = 0.0f; + } + } + else + { + ent->avelocity[ n ] += adjustment; + if ( ent->avelocity[ n ] > 0.0f ) + { + ent->avelocity[ n ] = 0.0f; + } + } + } +} + +/* +============= +G_CheckWater + +============= +*/ + +void G_CheckWater( Entity *ent ) +{ + if ( ent->isSubclassOf( Actor ) ) + { + ( ( Actor * )ent )->movementSubsystem->CheckWater(); + } + else + { + ent->watertype = gi.pointcontents( ent->origin, 0 ); + if ( ent->watertype & MASK_WATER ) + { + ent->waterlevel = 1; + } + else + { + ent->waterlevel = 0; + } + } +} + +/* +============= +G_Physics_Step + +Monsters freefall when they don't have a ground entity, otherwise +all movement is done with discrete steps. + +This is also used for objects that have become still on the ground, but +will fall if the floor is pulled out from under them. +FIXME: is this true? +============= +*/ + +void G_Physics_Step( Entity *ent ) +{ + qboolean wasonground; + qboolean hitsound = false; + Vector vel; + float speed, newspeed, control; + float friction; + int mask; + Vector basevel; + + // airborn monsters should always check for ground + if ( !ent->groundentity ) + { + ent->CheckGround(); + } + + if ( ent->groundentity ) + { + wasonground = true; + // Add this change in ? + ent->velocity[ 2 ] = 0.0; + } + else + { + wasonground = false; + } + + G_CheckVelocity( ent ); + + if ( ent->avelocity != vec_zero ) + { + G_AddRotationalFriction( ent ); + } + + // add gravity except: + // flying monsters + // swimming monsters who are in the water + if ( !wasonground ) + { + if ( !( ent->flags & FL_FLY ) ) + { + if ( !( ( ent->flags & FL_SWIM ) && ( ent->waterlevel > 2 ) ) ) + { + if ( ent->velocity[ 2 ] < ( sv_currentGravity->value * ent->gravity * -0.1f ) ) + { + hitsound = true; + } + + // Testing water gravity. If this doesn't work, just restore the uncommented lines + //if ( ent->waterlevel == 0 ) + //{ + G_AddGravity( ent ); + //} + } + } + } + + // friction for flying monsters that have been given vertical velocity + if ( ( ent->flags & FL_FLY ) && ( ent->velocity.z != 0.0f ) ) + { + speed = fabs( ent->velocity.z ); + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + friction = sv_friction->value / 3.0f; + newspeed = speed - ( level.frametime * control * friction ); + if ( newspeed < 0.0f ) + { + newspeed = 0.0f; + } + newspeed /= speed; + ent->velocity.z *= newspeed; + } + + // friction for flying monsters that have been given vertical velocity + if ( ( ent->flags & FL_SWIM ) && ( ent->velocity.z != 0.0f ) ) + { + speed = fabs( ent->velocity.z ); + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + newspeed = speed - ( level.frametime * control * sv_waterfriction->value * ent->waterlevel ); + if ( newspeed < 0.0f ) + { + newspeed = 0.0f; + } + newspeed /= speed; + ent->velocity.z *= newspeed; + } + + if ( ent->velocity != vec_zero ) + { + // apply friction + // let dead monsters who aren't completely onground slide + if ( ( wasonground ) || ( ent->flags & ( FL_SWIM | FL_FLY ) ) ) + { + if ( !( ( ent->health <= 0.0f ) && !M_CheckBottom( ent ) ) ) + { + vel = ent->velocity; + vel.z = 0.0f; + speed = vel.length(); + if ( speed ) + { + friction = sv_friction->value; + + control = speed < sv_stopspeed->value ? sv_stopspeed->value : speed; + newspeed = speed - ( level.frametime * control * friction ); + + if ( newspeed < 0.0f ) + { + newspeed = 0.0f; + } + + newspeed /= speed; + + ent->velocity.x *= newspeed; + ent->velocity.y *= newspeed; + } + } + } + } + + if ( ( basevel != vec_zero ) || ( ent->velocity != vec_zero ) || ( ent->total_delta != vec_zero ) ) + { + if ( ent->edict->svflags & SVF_MONSTER ) + { + mask = ent->edict->clipmask; + } + else + { + mask = MASK_SOLID; + } + + G_FlyMove( ent, basevel, level.frametime, mask ); + + ent->link(); + + G_CheckWater( ent ); + if ( ent->flags & FL_TOUCH_TRIGGERS ) + { + G_TouchTriggers( ent ); + } + + if ( ent->groundentity && !wasonground && hitsound ) + { + ent->Sound( "impact_softland", CHAN_BODY, 0.5f ); + } + } +} + +//============================================================================ + +/* +================ +G_RunEntity + +================ +*/ +void G_RunEntity( Entity *ent ) +{ + gentity_t *edict; + + edict = ent->edict; + + if ( !edict->inuse ) + return; + + if ( ent->isThinkOn() ) + { + ent->Think(); + } + + // Only run physics if in use, not bound, and not immobilized + + if ( ( edict->s.parent == ENTITYNUM_NONE ) && !(ent->flags & FL_IMMOBILE) && !(ent->flags & FL_PARTIAL_IMMOBILE) ) + { + switch ( ( int )ent->movetype ) + { + case MOVETYPE_PUSH: + case MOVETYPE_STOP: + G_Physics_Pusher( ent ); + break; + case MOVETYPE_NONE: + case MOVETYPE_STATIONARY: + case MOVETYPE_WALK: + break; + case MOVETYPE_NOCLIP: + G_Physics_Noclip( ent ); + break; + case MOVETYPE_STEP: + G_Physics_Step( ent ); + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_GIB: + case MOVETYPE_FLY: + case MOVETYPE_FLYMISSILE: + case MOVETYPE_SLIDE: + case MOVETYPE_VEHICLE: + G_Physics_Toss( ent ); + //G_Physics_Step( ent ); + break; + default: + gi.Error( ERR_DROP, "G_Physics: bad movetype %i", ( int )ent->movetype ); + } + } + + if ( ent->flags & FL_POSTTHINK ) + { + ent->Postthink(); + } +} + + +//------------------------- CLASS ------------------------------ +// +// Name: Trajectory +// Base Class: None +// +// Description: This class computes trajectory information based +// on various known inputs. Each known input has a specilized ctor +// that computes all other values from those that ar given +// +// Method of Use: This class should not be aggegrated. It is intended +// for local/parameter use +// +//-------------------------------------------------------------- + +//---------------------------------------------------------------- +// Name: Trajectory +// Class: Trajectory +// +// Description: launchAngle based constructor +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +Trajectory::Trajectory( const Vector &launchPoint, const Vector &targetPoint, const Angle launchAngle, const float gravity ): + _launchPoint( launchPoint ), + _targetPoint( targetPoint ), + _launchAngle( launchAngle ), + _gravity( gravity ), + _travelTime( -1.0f ), + _initialVelocity( Vector::Identity() ) +{ + assert( gravity <= 0.0f ); + const Vector direction( targetPoint - launchPoint ); + const float horizontalDistance = direction.lengthXY(); + const float verticalDistance = -direction.z; + const float cosineOfLaunchAngle = cos( DEG2RAD( _launchAngle ) ); + const float sineOfLaunchAngle = sin( DEG2RAD( _launchAngle ) ); + + // Formula derived for information provided in "Physics for Game Developers" by David M. Bourrg + const float travelTimeSquared = ( ( 2.0f * horizontalDistance * sineOfLaunchAngle) / ( -gravity * cosineOfLaunchAngle) ) + + ( 2.0f * verticalDistance / -gravity); + + if ( travelTimeSquared > 0.0f ) + { + _travelTime = sqrt( travelTimeSquared ); + _initialVelocity = direction; + _initialVelocity.z = 0.0f; + _initialVelocity.normalize(); + _initialVelocity *= cosineOfLaunchAngle; + _initialVelocity.z = sineOfLaunchAngle; + _initialVelocity.normalize(); + + const float initialSpeed = horizontalDistance / ( cosineOfLaunchAngle * _travelTime ); + _initialVelocity *= initialSpeed; + + } + else + { + // launch angle and direction of travel do not allow projectile to reach destination + gi.WPrintf( "Failed Trajectory: LaunchPoint<%f,%f,%f>, Target Point<%f,%f,%f>, LaunchAngle<%f>\n", + launchPoint.x, launchPoint.y, launchPoint.z, + targetPoint.x, targetPoint.y, targetPoint.z, + launchAngle.GetValue() + ); + } +} + +//---------------------------------------------------------------- +// Name: Trajectory +// Class: Trajectory +// +// Description: initialSpeed based constructor +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +Trajectory::Trajectory( const Vector &launchPoint, const Vector &targetPoint, const float initialSpeed, const float gravity, const bool useHighTrajectory ): + _launchPoint( launchPoint ), + _targetPoint( targetPoint ), + _gravity( gravity ), + _travelTime( -1.0f ), + _initialVelocity( Vector::Identity() ) +{ + // Complex formula with multipage derivation. Math by Squirrel + const Vector direction( targetPoint - launchPoint ); + + Vector directionXY( direction ); + directionXY.z = 0.0f; + directionXY.normalize(); + + const double X = direction.lengthXY(); + const double Y = direction.z; + const double R = initialSpeed; + assert( gravity <= 0.0f ); + const double a = gravity; + + const double X2 = X * X; + const double Y2 = Y * Y; + const double R2 = R * R; + const double a2 = a * a; + + const double R4 = R2 * R2; + const double Ya = Y * a; + + const double denominator = 2.0 * ( 1.0 + (Y2 / X2) ); + + const bool test = (2*R2*Ya - X2*a2 > R2); + const double radical = R4 + 2*R2*Ya - X2*a2; + + const double numerator1 = R2 + Ya + sqrt( radical ); + const double numerator2 = R2 + Ya - sqrt( radical ); + + const double Vx2_1 = numerator1 / denominator; + const double Vx2_2 = numerator2 / denominator; + + const double Vx2 = useHighTrajectory ? Vx2_2 : Vx2_1; + + const double Vx = sqrt( Vx2 ); + double Vy = sqrt( R2 - Vx2 ); + if ( test && ! useHighTrajectory ) + { + Vy *= -1.0f; + } + const double launchRadians = atan2( Vy, Vx ); + const double launchQuakePitchDegrees = -RAD2DEG( launchRadians ); + + const double travelTime = X / Vx; + if( travelTime < 0.0 ) + return; + + _launchAngle = launchQuakePitchDegrees; + _initialVelocity = (directionXY * Vx) + Vector( 0.0f, 0.0f, Vy ); + _initialVelocity.normalize(); + _initialVelocity *= initialSpeed; + _travelTime = travelTime; +} diff --git a/dlls/game/g_phys.h b/dlls/game/g_phys.h new file mode 100644 index 0000000..40239d3 --- /dev/null +++ b/dlls/game/g_phys.h @@ -0,0 +1,119 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/g_phys.h $ +// $Revision:: 9 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Global header file for g_phys.cpp +// + +#ifndef __G_PHYS_H__ +#define __G_PHYS_H__ + +#include "g_local.h" +#include "entity.h" + +typedef enum + { + STEPMOVE_OK, + STEPMOVE_BLOCKED_BY_ENTITY, + STEPMOVE_BLOCKED_BY_WORLD, + STEPMOVE_BLOCKED_BY_WATER, + STEPMOVE_BLOCKED_BY_FALL, + STEPMOVE_BLOCKED_BY_DOOR, + STEPMOVE_STUCK + } stepmoveresult_t; + +// movetype values +typedef enum + { + MOVETYPE_NONE, // never moves + MOVETYPE_STATIONARY, // never moves but does collide agains push objects + MOVETYPE_NOCLIP, // origin and angles change with no interaction + MOVETYPE_PUSH, // no clip to world, push on box contact + MOVETYPE_STOP, // no clip to world, stops on box contact + MOVETYPE_WALK, // gravity + MOVETYPE_STEP, // gravity, special edge handling + MOVETYPE_FLY, + MOVETYPE_TOSS, // gravity + MOVETYPE_FLYMISSILE, // extra size to monsters + MOVETYPE_BOUNCE, + MOVETYPE_SLIDE, + MOVETYPE_ROPE, + MOVETYPE_GIB, + MOVETYPE_VEHICLE + } movetype_t; + +void G_RunEntity( Entity *ent ); +void G_Impact( Entity *e1, const trace_t *trace ); +qboolean G_PushMove( Entity *pusher, const Vector &move, const Vector &amove ); +void G_CheckWater( Entity *ent ); + +//------------------------- CLASS ------------------------------ +// +// Name: Angle +// Base Class: None +// +// Description: Angles and floats are not the same thing and should +// not be treated as such. In the long run we need to make this a +// real class. For now it is just a parameter for Trajectory +// +// Method of Use: Parameter type for Trajectory +// +//-------------------------------------------------------------- +class Angle +{ +public: + Angle( const float value):_value( value ) {} + operator float () { return _value; } + operator float const () const { return _value; } + float GetValue( void ) const { return _value; } + float & GetValue( void ) { return _value; } + +private: + float _value; +}; + +//------------------------- CLASS ------------------------------ +// +// Name: Trajectory +// Base Class: None +// +// Description: This class computes trajectory information based +// on various known inputs. Each known input has a specilized ctor +// that computes all other values from those that are given +// +// Method of Use: This class should not be aggegrated. It is intended +// for local/parameter use +// +//-------------------------------------------------------------- +class Trajectory +{ +public: + Trajectory( const Vector &launchPoint, const Vector &targetPoint, const Angle launchAngle, const float gravity ); + Trajectory( const Vector &launchPoint, const Vector &targetPoint, const float initialSpeed, const float gravity, const bool useHighTrajectory = false ); + const Vector & GetLaunchPoint( void ) const { return _launchPoint; } + const Vector & GetTargetPoint( void ) const { return _targetPoint; } + float GetLaunchAngle( void ) const { return _launchAngle; } + float GetTravelTime( void ) const { return _travelTime; } + const Vector & GetInitialVelocity( void ) const { return _initialVelocity; } + float GetGravity( void ) const { return _gravity; } + +private: + Vector _launchPoint; + Vector _targetPoint; + float _launchAngle; + float _travelTime; + Vector _initialVelocity; + float _gravity; +}; +#endif /* g_phys.h */ diff --git a/dlls/game/g_public.h b/dlls/game/g_public.h new file mode 100644 index 0000000..a049ab6 --- /dev/null +++ b/dlls/game/g_public.h @@ -0,0 +1,802 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/g_public.h $ +// $Revision:: 83 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:43a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Game module information visible to server + + +#ifndef __G_PUBLIC_H__ +#define __G_PUBLIC_H__ + +#include + +#define GAME_API_VERSION 4 + +#define FRAMETIME ( level.fixedframetime ) + +// entity->svFlags +// the server does not know how to interpret most of the values +// in entityStates (level eType), so the game must explicitly flag +// special server behaviors +#define SVF_NOCLIENT (1<<0) // don't send entity to clients, even if it has effects +#define SVF_BOT (1<<1) +#define SVF_BROADCAST (1<<2) // send to all connected clients +#define SVF_PORTAL (1<<3) // merge a second pvs at origin2 into snapshots +#define SVF_SENDPVS (1<<4) // even though it doesn't have a sound or modelindex, still run it through the pvs +#define SVF_USE_CURRENT_ORIGIN (1<<5) // entity->currentOrigin instead of entity->s.origin + // for link position (missiles and movers) +#define SVF_DEADMONSTER (1<<6) // treat as CONTENTS_DEADMONSTER for collision +#define SVF_MONSTER (1<<7) // treat as CONTENTS_MONSTER for collision +#define SVF_USEBBOX (1<<9) // do not perform perfect collision use the bbox instead +#define SVF_ONLYPARENT (1<<10) // only send this entity to its parent +#define SVF_HIDEOWNER (1<<11) // hide the owner of the client +#define SVF_MONSTERCLIP (1<<12) // treat as CONTENTS_MONSTERCLIP for collision +#define SVF_PLAYERCLIP (1<<13) // treat as CONTENTS_PLAYERCLIP for collision +#define SVF_SENDONCE (1<<14) // Send this entity over the network at least one time +#define SVF_SENT (1<<15) // This flag is set when the entity has been sent over at least one time + +typedef enum + { + SOLID_NOT, // no interaction with other objects + SOLID_TRIGGER, // only touch when inside, after moving + SOLID_BBOX, // touch on edge + SOLID_BSP // bsp clip, touch on edge + } solid_t; + +//=============================================================== + +typedef struct gentity_s gentity_t; +typedef struct gclient_s gclient_t; + +#ifndef GAME_INCLUDE + +// the server needs to know enough information to handle collision and snapshot generation + +struct gentity_s + { + entityState_t s; // communicated by server to clients + struct playerState_s *client; + qboolean inuse; + qboolean linked; // qfalse if not in any good cluster + int linkcount; + + int svFlags; // SVF_NOCLIENT, SVF_BROADCAST, etc + + qboolean bmodel; // if false, assume an explicit mins / maxs bounding box + // only set by gi.SetBrushModel + vec3_t mins, maxs; + int contents; // CONTENTS_TRIGGER, CONTENTS_SOLID, CONTENTS_BODY, etc + // a non-solid entity should set to 0 + + vec3_t absmin, absmax; // derived from mins/maxs and origin + rotation + + float radius; // radius of object + vec3_t centroid; // centroid, to be used with radius + int areanum; // areanum needs to be seen inside the game as well + + // currentOrigin will be used for all collision detection and world linking. + // it will not necessarily be the same as the trajectory evaluation for the current + // time, because each entity must be moved one at a time after time is advanced + // to avoid simultanious collision issues + vec3_t currentOrigin; + vec3_t currentAngles; + + // when a trace call is made and passEntityNum != ENTITYNUM_NONE, + // an ent will be excluded from testing if: + // ent->s.number == passEntityNum (don't interact with self) + // ent->s.ownerNum = passEntityNum (don't interact with your own missiles) + // entity[ent->s.ownerNum].ownerNum = passEntityNum (don't interact with other missiles from owner) + int ownerNum; + //gentity_t *owner; // objects never interact with their owners, to + // prevent player missiles from immediately + // colliding with their owner + + solid_t solid; + // the game dll can add anything it wants after + // this point in the structure + }; + +#endif // GAME_INCLUDE + +//=============================================================== + +// +// functions provided by the main engine +// + +// added for BOTLIB +#include "botlib.h" +//typedef struct bot_input_s bot_input_t; +//typedef struct bot_entitystate_s bot_entitystate_t; +//typedef struct client_s client_t; +//typedef struct usercmd_s usercmd_t; +// end BOTLIB additions + +typedef struct + { + //============== general services ================== + + // print message on the local console + void (*Printf)( const char *fmt, ... ); + void (*DPrintf)( const char *fmt, ... ); + void (*WPrintf)( const char *fmt, ... ); + void (*WDPrintf)( const char *fmt, ... ); + void (*DebugPrintf)( const char *fmt, ... ); + + + void (*LocalizeFilePath)(const char* path, char* localizedPath); + + // abort the game + void (*Error)( int errorLevel, const char *fmt, ... ); + + // get current time for profiling reasons this should NOT be used for any game related tasks, + // because it is not journaled + int (*Milliseconds)( void ); + + // managed memory allocation + void *(*Malloc)( int size ); + void (*Free)( void *block ); + + // console variable interaction + cvar_t *(*cvar)( const char *var_name, const char *value, int flags ); + cvar_t *(*cvar_get)( const char *var_name); + void (*cvar_set)( const char *var_name, const char *value ); + // this section added for botlib compatibility + void (*Cvar_VariableStringBuffer)( const char *var_name, char *buffer, int bufsize ); + void (*Cvar_Register)( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); + float (*Cvar_VariableValue)( const char *var_name ); + void (*Cvar_Update)( vmCvar_t *vmCvar ); + int (*Cvar_VariableIntegerValue)( const char *var_name ); + // end cvar botlib additions + + // ClientCommand and ServerCommand parameter access + int (*argc)( void ); + char *(*argv)( int n ); + const char *(*args)(void); + void (*AddCommand)( const char *cmd ); + + // the returned buffer may be part of a larger pak file, or a discrete file from anywhere in the search path + // a -1 return means the file does not exist NULL can be passed for buf to just determine existance + int (*FS_ReadFile)( const char *name, void **buf, qboolean quiet ); + qboolean (*FS_Exists)( const char *filename ); + void (*FS_FreeFile)( void *buf ); + void (*FS_WriteFile)( const char *qpath, const void *buffer, int size ); + fileHandle_t (*FS_FOpenFileWrite)( const char *qpath ); + fileHandle_t (*FS_FOpenFileAppend)( const char *filename ); + char** (*FS_ListFiles)( const char *path, const char *extension, int *numfiles ); + + char * (*FS_PrepFileWrite)( const char *filename ); + int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); + int (*FS_Read)( void *buffer, int len, fileHandle_t f ); + void (*FS_FCloseFile)( fileHandle_t f ); + int (*FS_FTell)( fileHandle_t f ); + int (*FS_FSeek)( fileHandle_t f, long offset, int origin ); + //int (*FS_filelength)( fileHandle_t f); + void (*FS_Flush)( fileHandle_t f ); + void (*FS_DeleteFile)(const char *filename); + int (*FS_GetFileList)( const char *path, const char *extension, char *listbuf, int bufsize ); // added for botlib + + const char * (*GetArchiveFileName)( const char *gameName, const char *filename, const char *extension, qboolean tempFile ); + // add commands to the console as if they were typed in for map changing, etc + void (*SendConsoleCommand)( const char *text ); + void (*DebugGraph)( float value, int color ); + + //=========== server specific functionality ============= + + // SendServerCommand reliably sends a command string to be interpreted by the given + // client. If ent is NULL, it will be sent to all clients + void (*SendServerCommand)( int clientnum, const char *fmt, ... ); + + int (*GetNumFreeReliableServerCommands) ( int clientNum ); + + // config strings hold all the index strings, the lightstyles, and misc data like the cdtrack. + // All of the current configstrings are sent to clients when they connect, and + // changes are sent to all connected clients. + void (*setConfigstring)( int index, const char *val ); + char * (*getConfigstring)( int index ); + + void (*setUserinfo)( int index, const char *val ); + void (*getUserinfo)( int index, char *buffer, int bufferSize ); + + // sets mins and maxs based on the brushmodel name + void (*SetBrushModel)( gentity_t *ent, const char *name ); + + // collision detection + void (*trace)( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, qboolean cylinder ); + void (*fulltrace)( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask, qboolean cylinder ); + int (*pointcontents)( const vec3_t point, int passEntityNum ); + int (*pointbrushnum)( const vec3_t point, int passEntityNum ); + qboolean (*inPVS)( const vec3_t p1, const vec3_t p2 ); + qboolean (*inPVSIgnorePortals)( const vec3_t p1, const vec3_t p2 ); + void (*AdjustAreaPortalState)( gentity_t *ent, qboolean open ); + qboolean (*AreasConnected)( int area1, int area2 ); + + int (*GetLightingGroup)( const char *group_name ); + + void (*SetDynamicLight)( int dynamic_light, float intensity ); + void (*SetDynamicLightDefault)( int dynamic_light, float intensity ); + + void (*SetWindDirection)( vec3_t direction ); + void (*SetWindIntensity)( float wind_intensity ); + + void (*SetWeatherInfo)( weather_t type, int intensity ); + + void (*SetTimeScale)( float time_scale ); + // an entity will never be sent to a client or used for collision + // if it is not passed to linkentity. If the size, position, or + // solidity changes, it must be relinked. + void (*linkentity)( gentity_t *ent ); + void (*unlinkentity)( gentity_t *ent ); // call before removing an interactive entity + + // EntitiesInBox will perform exact checking to bmodels, as well as bounding box + // intersection tests to other models + int (*AreaEntities)( const vec3_t mins, const vec3_t maxs, int *list, int maxcount, qboolean fullTrace ); + void (*ClipToEntity)( trace_t *trace, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int entityNum, int contentmask ); + + int (*objectivenameindex)( const char* name ); + int (*archetypeindex)(const char* name); + int (*imageindex)( const char *name ); + int (*failedcondition)( const char *name ); + int (*itemindex)( const char *name ); + int (*soundindex)( const char *name ); + int (*modelindex)( const char *name ); + + void (*SetLightStyle)( int i, const char *data ); + + const char *(*GameDir)( void ); + + // + // MODEL UTILITY FUNCTIONS + // + qboolean (*IsModel)( int index ); + void (*setmodel)( gentity_t *ent, const char *name ); + void (*setviewmodel)( gentity_t *ent, const char *name ); + + // DEF SPECIFIC STUFF + int (*NumAnims)( int modelindex ); + int (*NumSkins)( int modelindex ); + int (*NumSurfaces)( int modelindex ); + int (*NumTags)( int modelindex ); + int (*NumMorphs)( int modelindex ); + qboolean (*InitCommands)( int modelindex, tiki_cmd_t * tiki_cmd ); + void (*CalculateBounds)( int modelindex, float scale, vec3_t mins, vec3_t maxs ); + + void (*TIKI_CacheAnim)( const char * path ); + + // ANIM SPECIFIC STUFF + const char * (*Anim_NameForNum)( int modelindex, int animnum ); + int (*Anim_NumForName)( int modelindex, const char * name ); + int (*Anim_Random)( int modelindex, const char * name ); + int (*Anim_NumFrames)( int modelindex, int animnum ); + float (*Anim_Time)( int modelindex, int animnum ); + void (*Anim_Delta)( int modelindex, int animnum, vec3_t delta ); + void (*Anim_AbsoluteDelta)( int modelindex, int animnum, vec3_t delta ); + int (*Anim_Flags)( int modelindex, int animnum ); + qboolean (*Anim_HasCommands) ( int modelindex, int animnum ); + + // FRAME SPECIFIC STUFF + qboolean (*Frame_Commands)( int modelindex, int animnum, int framenum, tiki_cmd_t * tiki_cmd ); + void (*Frame_Delta)( int modelindex, int animnum, int framenum, vec3_t delta ); + float (*Frame_Time)( int modelindex, int animnum, int framenum ); + void (*Frame_Bounds)( int modelindex, int animnum, int framenum, float scale, vec3_t mins, vec3_t maxs ); + + // SURFACE SPECIFIC STUFF + int (*Surface_NameToNum)( int modelindex, const char * name ); + const char * (*Surface_NumToName)( int modelindex, int num ); + int (*Surface_Flags)( int modelindex, int num ); + int (*Surface_NumSkins)( int modelindex, int num ); + + // Morph specific stuff + int (*Morph_NumForName)( int modelindex, const char * name ); + const char * (*Morph_NameForNum)( int modelindex, int num ); + + dtikimorphtarget_t *(*GetExpression)( int modelindex, const char *expression_name, int *number_of_morph_targets ); + + // TAG SPECIFIC STUFF + int (*Tag_NumForName)( int modelindex, const char * name ); + const char * (*Tag_NameForNum)( int modelindex, int num ); + orientation_t (*Tag_Orientation)( int modelindex, int anim, int frame, int num, float scale, int *bone_tag, vec4_t *bone_quat ); + orientation_t (*Tag_OrientationEx)( int modelindex, int anim, int frame, int num, float scale, int *bone_tag, vec4_t *bone_quat, + int crossblend_anim, int crossblend_frame, float crossblend_lerp, qboolean uselegs, qboolean usetorso, int torso_anim, int torso_frame, + int torso_crossblend_anim, int torso_crossblend_frame, float torso_crossblend_lerp ); + int (*Bone_GetParentNum)( int modelindex, int bonenum ); + + qboolean (*Alias_Add)( int modelindex, const char * alias, const char * name, const char *parameters ); + const char * (*Alias_FindRandom)( int modelindex, const char * alias ); + const char * (*Alias_Find)( int modelindex, const char * alias ); + void (*Alias_Dump)( int modelindex ); + void (*Alias_Clear)( int modelindex ); + const char * (*Alias_FindDialog)( int modelindex, const char * alias, int random, int entity_number ); + const char* (*Alias_FindSpecificAnim)(int modelindex, const char *name ); + qboolean (*Alias_CheckLoopAnim)(int modelindex, const char *name ); + void *(*Alias_GetList)( int modelindex ); + void (*Alias_UpdateDialog)( int model_index, const char *alias, int number_of_times_played, byte been_played_this_loop, int last_time_played ); + void (*Alias_AddActorDialog)( int model_index, const char *alias, int actor_number, int number_of_times_played, byte been_played_this_loop, int last_time_played ); + + + const char * (*NameForNum)( int modelindex ); + + // GLOBAL ALIAS SYSTEM + qboolean (*GlobalAlias_Add)( const char * alias, const char * name, const char *parameters ); + const char * (*GlobalAlias_FindRandom)( const char * alias ); + const char * (*GlobalAlias_Find)( const char * alias ); + void (*GlobalAlias_Dump)( void ); + void (*GlobalAlias_Clear)( void ); + + qboolean (*isClientActive)( const gentity_t *ent ); + + void (*centerprintf)( const gentity_t *ent, CenterPrintImportance importance, const char *fmt, ...); + void (*locationprintf)( const gentity_t *ent, int x, int y, const char *fmt, ...); + + // Sound + void (*Sound)( vec3_t *org, int entnum, int channel, const char *sound_name, float volume, float attenuation, float pitch_modifier, qboolean onlySendToSameEntity ); + void (*StopSound)( int entnum, int channel ); + float (*SoundLength) ( const char *path ); + char *(*GetNextMorphTarget)( const char *name, int *index, int *number_of_amplitudes, float *amplitude ); + + + unsigned short (*CalcCRC)(const unsigned char *start, int count); + + debugline_t **DebugLines; + int *numDebugLines; + + void (*LocateGameData)( gentity_t *gEnts, int numGEntities, int sizeofGEntity_t, + playerState_t *clients, int sizeofGameClient ); + + void (*SetFarPlane)( int farplane, qboolean farplane_cull ); + void (*TikiReload)( const char * modelstr ); + void (*TikiLoadFromTS)( const char * path, const char * tikidata); + void* (*ToolServerGetData)( ); + void (*SetSkyPortal)( qboolean skyportal ); + + void (*WidgetPrintf)( const char *widgetName, const char *fmt, ... ); + + void (*ProcessLoadingScreen)( const char *loadingStatus ); + + // MISSION OBJECTIVE SPECIFIC STUFF + + const char* (*MObjective_GetDescription)( const char* objectivename ); + void (*MObjective_SetDescription)( const char* objectivename , const char* desc ); + + qboolean (*MObjective_GetShowObjective)( const char* objectivename ); + void (*MObjective_SetShowObjective)( const char* objectivename , qboolean show ); + + qboolean (*MObjective_GetObjectiveComplete)( const char* objectivename ); + void (*MObjective_SetObjectiveComplete)( const char* objectivename , qboolean complete ); + + qboolean (*MObjective_GetObjectiveFailed)( const char* objectivename ); + void (*MObjective_SetObjectiveFailed)( const char* objectivename , qboolean failed ); + + const char* (*MObjective_GetNameFromIndex)( unsigned int index ); + int (*MObjective_GetIndexFromName) (const char* name ); + void (*MObjective_NewObjective)( const char* name ); + void (*MObjective_ClearObjectiveList) (void); + void (*MObjective_ParseObjectiveFile) (const char* levelname ); + void (*MObjective_Update)( const char* objectivesName ); + int (*MObjective_GetNumObjectives)(void); + int (*MObjective_GetNumActiveObjectives)(void); + int (*MObjective_GetNumCompleteObjectives)(void); + int (*MObjective_GetNumFailedObjectives)(void); + int (*MObjective_GetNumIncompleteObjectives)(void); + + + + // MISSION INFORMATION SPECIFIC STUFF + const char* (*MI_GetShader)( const char* informationname ); + void (*MI_SetShader)( const char* informationname , const char* shader ); + + const char* (*MI_GetInformationData)( const char* informationname ); + void (*MI_SetInformationData)( const char* informationname , const char* data ); + + const char* (*MI_GetNameFromIndex)( unsigned int index ); + int (*MI_GetIndexFromName)( const char* name ); + void (*MI_NewInformation)( const char* name ); + void (*MI_ClearInformationList)( void ); + + void (*MI_SetShowInformation) (const char* informationname , qboolean show ); + qboolean (*MI_GetShowInformation) (const char* informationname ); + + void (*SR_InitializeStringResource)( void ); + void (*SR_UninitializeStringResource)( void ); + void (*SR_LoadLevelStrings)(const char* levelName); + + // View mode stuff + unsigned int (*GetViewModeMask)( const char *viewModeName ); + unsigned int (*GetViewModeClassMask)( const char *className ); + qboolean (*GetViewModeSendInMode)( unsigned int viewModeMask ); + qboolean (*GetViewModeSendNotInMode)( unsigned int viewModeMask ); + qboolean (*GetViewModeScreenBlend)( unsigned int viewModeMask, vec3_t color, float *alpha, qboolean *additive ); + + void (*GetLevelDefs)( const char *name, const char **environmentName, const char **levelName, const char **sublevelName ); + qboolean (*areSublevels)( const char *oldMapName, const char *newMapName ); + + const char * (*SurfaceTypeToName)( unsigned int surfaceType ); + + + //////// + // AAS STUFF + //////// + // swiped from aas_export_t + //----------------------------------- + // be_aas_entity.h + //----------------------------------- + void (*AAS_EntityInfo)(int entnum, struct aas_entityinfo_s *info); + //----------------------------------- + // be_aas_main.h + //----------------------------------- + int (*AAS_Initialized)(void); + void (*AAS_PresenceTypeBoundingBox)(int presencetype, vec3_t mins, vec3_t maxs); + float (*AAS_Time)(void); + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + int (*AAS_PointAreaNum)(vec3_t point); + int (*AAS_PointReachabilityAreaIndex)( vec3_t point ); + int (*AAS_TraceAreas)(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); + int (*AAS_BBoxAreas)(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); + int (*AAS_AreaInfo)( int areanum, struct aas_areainfo_s *info ); + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + int (*AAS_PointContents)(vec3_t point); + int (*AAS_NextBSPEntity)(int ent); + int (*AAS_ValueForBSPEpairKey)(int ent, char *key, char *value, int size); + int (*AAS_VectorForBSPEpairKey)(int ent, char *key, vec3_t v); + int (*AAS_FloatForBSPEpairKey)(int ent, char *key, float *value); + int (*AAS_IntForBSPEpairKey)(int ent, char *key, int *value); + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + int (*AAS_AreaReachability)(int areanum); + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + int (*AAS_AreaTravelTimeToGoalArea)(int areanum, vec3_t origin, int goalareanum, int travelflags); + int (*AAS_EnableRoutingArea)(int areanum, int enable); + int (*AAS_PredictRoute)(struct aas_predictroute_s *route, int areanum, vec3_t origin, + int goalareanum, int travelflags, int maxareas, int maxtime, + int stopevent, int stopcontents, int stoptfl, int stopareanum); + //-------------------------------------------- + // be_aas_altroute.c + //-------------------------------------------- + int (*AAS_AlternativeRouteGoals)(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + struct aas_altroutegoal_s *altroutegoals, int maxaltroutegoals, + int type); + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + int (*AAS_Swimming)(vec3_t origin); + int (*AAS_PredictClientMovement)(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize); + + //////// + // BOTLIB client Elementary Actions (EA) + //////// + //ClientCommand elementary actions + void (*EA_Command)(int client, const char *command ); + void (*EA_Say)(int client, char *str); + void (*EA_SayTeam)(int client, char *str); + // + void (*EA_Action)(int client, int action); + void (*EA_Gesture)(int client); + void (*EA_Talk)(int client); + void (*EA_ToggleFireState)(int client); + void (*EA_Attack)(int client, int primarydangerous, int altdangerous); + void (*EA_Use)(int client); + void (*EA_Respawn)(int client); + void (*EA_MoveUp)(int client); + void (*EA_MoveDown)(int client); + void (*EA_MoveForward)(int client); + void (*EA_MoveBack)(int client); + void (*EA_MoveLeft)(int client); + void (*EA_MoveRight)(int client); + void (*EA_Crouch)(int client); + + void (*EA_SelectWeapon)(int client, int weapon); + void (*EA_Jump)(int client); + void (*EA_DelayedJump)(int client); + void (*EA_Move)(int client, vec3_t dir, float speed); + void (*EA_View)(int client, vec3_t viewangles); + //send regular input to the server + void (*EA_EndRegular)(int client, float thinktime); + void (*EA_GetInput)(int client, float thinktime, bot_input_t *input); + void (*EA_ResetInput)(int client); + + //////// + // BOTLIB AI stuff + //////// + //----------------------------------- + // be_ai_char.h + //----------------------------------- + int (*BotLoadCharacter)(char *charfile, float skill); + void (*BotFreeCharacter)(int character); + float (*Characteristic_Float)(int character, int index); + float (*Characteristic_BFloat)(int character, int index, float min, float max); + int (*Characteristic_Integer)(int character, int index); + int (*Characteristic_BInteger)(int character, int index, int min, int max); + void (*Characteristic_String)(int character, int index, char *buf, int size); + //----------------------------------- + // be_ai_chat.h + //----------------------------------- + int (*BotAllocChatState)(void); + void (*BotFreeChatState)(int handle); + void (*BotQueueConsoleMessage)(int chatstate, int type, char *message); + void (*BotRemoveConsoleMessage)(int chatstate, int handle); + int (*BotNextConsoleMessage)(int chatstate, struct bot_consolemessage_s *cm); + int (*BotNumConsoleMessages)(int chatstate); + void (*BotInitialChat)(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotNumInitialChats)(int chatstate, char *type); + int (*BotReplyChat)(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotChatLength)(int chatstate); + void (*BotEnterChat)(int chatstate, int client, int sendto); + void (*BotGetChatMessage)(int chatstate, char *buf, int size); + int (*StringContains)(char *str1, char *str2, int casesensitive); + int (*BotFindMatch)(char *str, struct bot_match_s *match, unsigned long int context); + void (*BotMatchVariable)(struct bot_match_s *match, int variable, char *buf, int size); + void (*UnifyWhiteSpaces)(char *string); + void (*BotReplaceSynonyms)(char *string, unsigned long int context); + int (*BotLoadChatFile)(int chatstate, char *chatfile, char *chatname); + void (*BotSetChatGender)(int chatstate, int gender); + void (*BotSetChatName)(int chatstate, char *name, int client); + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + void (*BotResetGoalState)(int goalstate); + void (*BotResetAvoidGoals)(int goalstate); + void (*BotRemoveFromAvoidGoals)(int goalstate, int number); + void (*BotPushGoal)(int goalstate, struct bot_goal_s *goal); + void (*BotPopGoal)(int goalstate); + void (*BotEmptyGoalStack)(int goalstate); + void (*BotDumpAvoidGoals)(int goalstate); + void (*BotDumpGoalStack)(int goalstate); + void (*BotGoalName)(int number, char *name, int size); + int (*BotGetTopGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotGetSecondGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotChooseLTGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags); + int (*BotChooseNBGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags, + struct bot_goal_s *ltg, float maxtime); + int (*BotTouchingGoal)(vec3_t origin, struct bot_goal_s *goal); + int (*BotItemGoalInVisButNotVisible)(int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal); + int (*BotGetLevelItemGoal)(int index, char *classname, struct bot_goal_s *goal); + int (*BotGetNextCampSpotGoal)(int num, struct bot_goal_s *goal); + int (*BotGetMapLocationGoal)(char *name, struct bot_goal_s *goal); + float (*BotAvoidGoalTime)(int goalstate, int number); + void (*BotSetAvoidGoalTime)(int goalstate, int number, float avoidtime); + void (*BotInitLevelItems)(void); + void (*BotUpdateEntityItems)(void); + int (*BotLoadItemWeights)(int goalstate, char *filename); + void (*BotFreeItemWeights)(int goalstate); + void (*BotInterbreedGoalFuzzyLogic)(int parent1, int parent2, int child); + void (*BotSaveGoalFuzzyLogic)(int goalstate, char *filename); + void (*BotMutateGoalFuzzyLogic)(int goalstate, float range); + int (*BotAllocGoalState)(int client); + void (*BotFreeGoalState)(int handle); + //----------------------------------- + // be_ai_move.h + //----------------------------------- + void (*BotResetMoveState)(int movestate); + void (*BotMoveToGoal)(struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags); + int (*BotMoveInDirection)(int movestate, vec3_t dir, float speed, int type); + void (*BotResetAvoidReach)(int movestate); + void (*BotResetLastAvoidReach)(int movestate); + int (*BotReachabilityArea)(vec3_t origin, int testground); + int (*BotMovementViewTarget)(int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target); + int (*BotPredictVisiblePosition)(vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target); + int (*BotAllocMoveState)(void); + void (*BotFreeMoveState)(int handle); + void (*BotInitMoveState)(int handle, struct bot_initmove_s *initmove); + void (*BotAddAvoidSpot)(int movestate, vec3_t origin, float radius, int type); + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + int (*BotChooseBestFightWeapon)(int weaponstate, int *inventory); + void (*BotGetWeaponInfo)(int weaponstate, int weapon, struct weaponinfo_s *weaponinfo); + int (*BotLoadWeaponWeights)(int weaponstate, char *filename); + int (*BotAllocWeaponState)(void); + void (*BotFreeWeaponState)(int weaponstate); + void (*BotResetWeaponState)(int weaponstate); + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + int (*GeneticParentsAndChildSelection)(int numranks, float *ranks, int *parent1, int *parent2, int *child); + + + //////// + // BOTLIB STUFF + //////// + // swiped from botlib_import_t, but cleaned up to make sense + //print messages from the bot library + void (*Print)(int type, char *fmt, ...); +// void (*Printf)( const char *fmt, ... ); + + //trace a bbox through the world +// void (*Trace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); + //trace a bbox against a specific entity +// void (*EntityTrace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask); + //retrieve the contents at the given point + int (*PointContents)(vec3_t point); + //check if the point is in potential visible sight +// int (*inPVS)(vec3_t p1, vec3_t p2); + //retrieve the BSP entity data lump + char *(*BSPEntityData)(void); + // + void (*BSPModelMinsMaxsOrigin)(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); + //send a bot client command + void (*BotClientCommand)(int client, const char *command); // was char *, changed for c++ compilation + //memory allocation +// void *(*GetMemory)(int size); // allocate from Zone +// void (*FreeMemory)(void *ptr); // free memory from Zone + int (*AvailableMemory)(void); // available Zone memory + void *(*HunkAlloc)(int size); // allocate from hunk + //file system access + int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); +// int (*FS_Read)( void *buffer, int len, fileHandle_t f ); +// int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); +// void (*FS_FCloseFile)( fileHandle_t f ); + int (*FS_Seek)( fileHandle_t f, long offset, int origin ); + //debug visualisation stuff + int (*DebugLineCreate)(void); + void (*DebugLineDelete)(int line); + void (*DebugLineShow)(int line, vec3_t start, vec3_t end, int color); + // + int (*DebugPolygonCreate)(int color, int numPoints, vec3_t *points); + void (*DebugPolygonDelete)(int id); + +// added for botlib import + void (*DropClient)( int clientNum, const char *reason ); // maps to SV_GameDropClient + + void (*SV_GetServerinfo)( char *buffer, int bufferSize ) ; + int (*BotAllocateClient)(void); // maps to SV_BotAllocateClient + int (*BotGetSnapshotEntity)( int client, int ent ); // maps to SV_BotGetSnapshotEntity + int (*BotGetConsoleMessage)( int client, char *buf, int size ); // maps to SV_BotGetConsoleMessage + + //Area Awareness System functions +// aas_export_t aas; + //Elementary Action functions +// ea_export_t ea; + //AI functions +// ai_export_t ai; + //setup the bot library, returns BLERR_ + int (*BotLibSetup)(void); + //shutdown the bot library, returns BLERR_ + int (*BotLibShutdown)(void); + //sets a library variable returns BLERR_ + int (*BotLibVarSet)(char *var_name, char *value); + //gets a library variable returns BLERR_ + int (*BotLibVarGet)(char *var_name, char *value, int size); + + //sets a C-like define returns BLERR_ + int (*PC_AddGlobalDefine)(char *string); + int (*PC_LoadSourceHandle)(const char *filename); + int (*PC_FreeSourceHandle)(int handle); +// int (*PC_ReadTokenHandle)(int handle, pc_token_t *pc_token); + int (*PC_SourceFileAndLine)(int handle, char *filename, int *line); + + //start a frame in the bot library + int (*BotLibStartFrame)(float time); + //load a new map in the bot library + int (*BotLibLoadMap)(const char *mapname); + //entity updates + int (*BotLibUpdateEntity)(int ent, bot_entitystate_t *state); + //just for testing + int (*Test)(int parm0, char *parm1, vec3_t parm2, vec3_t parm3); + void (*BotUserCommand)(int cl, usercmd_t *cmd); // maps to SV_ClientThink(), gonna need a wrapper to go from clientnum to *client_t on exec side + +// end botlib additions + } game_import_t; + +// +// functions exported by the game subsystem +// +typedef struct { + int apiversion; + + // the init function will only be called when a game starts, + // not each time a level is loaded. Persistant data for clients + // and the server can be allocated in init + void (*Init) ( int startTime, int randomSeed); + void (*Shutdown) (void); + void (*Cleanup) ( qboolean restart ); + + // each new level entered will cause a call to SpawnEntities + void (*SpawnEntities) (const char *mapname, const char *entstring, int levelTime); + void (*PostLoad)( void ); + void (*PostSublevelLoad)( const char *spawnPosName ); + + // return NULL if the client is allowed to connect, otherwise return + // a text string with the reason for denial + const char *(*ClientConnect)( int clientNum, qboolean firstTime, qboolean isBot, qboolean checkPassword ); + + void (*ClientBegin)( gentity_t *ent, const usercmd_t *cmd ); + void (*ClientUserinfoChanged)( gentity_t *ent, const char *userinfo ); + void (*ClientDisconnect)( gentity_t *ent ); + void (*ClientCommand)( gentity_t *ent ); + void (*ClientThink)( gentity_t *ent, usercmd_t *cmd ); + + void (*PrepFrame)( void ); + void (*RunFrame)( int levelTime, int frameTime ); + qboolean (*SendEntity)( gentity_t *clientEntity, gentity_t *entityToSend ); + void (*UpdateEntityStateForClient)( gentity_t *clientEntity, entityState_t *state ); + void (*UpdatePlayerStateForClient)( gentity_t *clientEntity, playerState_t *state ); + void (*ExtraEntitiesToSend)( gentity_t *clientEntity, int *numExtraEntities, int *extraEntities ); + int (*GetEntityCurrentAnimFrame)( int entityNum, int bodyPart ); + + // ConsoleCommand will be called when a command has been issued + // that is not recognized as a builtin function. + // The game can issue gi.argc() / gi.argv() commands to get the command + // and parameters. Return qfalse if the game doesn't recognize it as a command. + qboolean (*ConsoleCommand)( void ); + + // Read/Write Persistant is for storing persistant cross level information + // about the world state and the clients. + // WriteGame is called every time a level is exited. + // ReadGame is called every time a level is entered. + void (*WritePersistant)( const char *filename, qboolean sublevelTransition ); + qboolean (*ReadPersistant)( const char *filename, qboolean sublevelTransition ); + + // ReadLevel is called after the default map information has been + // loaded with SpawnEntities, so any stored client spawn spots will + // be used when the clients reconnect. + void (*WriteLevel)( const char *filename, qboolean autosave ); + qboolean (*ReadLevel)( const char *filename ); + qboolean (*LevelArchiveValid)( const char *filename ); + + qboolean (*inMultiplayerGame)( void ); + qboolean (*isDefined)(const char *propname); + const char * (*getDefine)(const char *propname); + + //////// + // + // BOTLIB unfortunate botlib graffiti, the following functions are required + // for the executable to start a bot + // + //////// + int (*BotAIStartFrame)(int time); // executable hook to bot AI code loop + void (*AddBot_f)(void); // used for sv_addbot_f + + + + int (*GetTotalGameFrames)(void); + // + // global variables shared between game and server + // + + // The gentities array is allocated in the game dll so it + // can vary in size from one game to another. + // + // The size will be fixed when ge->Init() is called + // the server can't just use pointer arithmetic on gentities, because the + // server's sizeof(struct gentity_s) doesn't equal gentitySize + struct gentity_s *gentities; + int gentitySize; + int num_entities; // current number, <= max_entities + int max_entities; + + const char *error_message; +} game_export_t; + +game_export_t *GetGameApi (game_import_t *import); + +int BotAISetup( int restart ); +int BotAIShutdown( int restart ); +int BotAILoadMap( int restart ); +void G_InitBots(qboolean restart); +int BotAIShutdownClient(int client, qboolean restart); + +#endif // __G_PUBLIC_H__ diff --git a/dlls/game/g_spawn.cpp b/dlls/game/g_spawn.cpp new file mode 100644 index 0000000..f4c30d8 --- /dev/null +++ b/dlls/game/g_spawn.cpp @@ -0,0 +1,451 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/g_spawn.cpp $ +// $Revision:: 8 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:43a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "class.h" +#include "entity.h" +#include "g_spawn.h" +#include "navigate.h" +#include "player.h" +#include "gravpath.h" +#include "object.h" + +CLASS_DECLARATION( Class, SpawnArgs, NULL ) +{ + { NULL, NULL } +}; + +SpawnArgs::SpawnArgs() +{ +} + +SpawnArgs::SpawnArgs( SpawnArgs &otherlist ) +{ + int num; + int i; + + num = otherlist.NumArgs(); + keyList.Resize( num ); + valueList.Resize( num ); + for( i = 1; i <= num; i++ ) + { + keyList.AddObject( otherlist.keyList.ObjectAt( i ) ); + valueList.AddObject( otherlist.valueList.ObjectAt( i ) ); + } +} + +void SpawnArgs::Clear( void ) +{ + keyList.FreeObjectList(); + valueList.FreeObjectList(); +} + +/* +==================== +Parse + +Parses spawnflags out of the given string, returning the new position. +Clears out any previous args. +==================== +*/ +const char *SpawnArgs::Parse( const char *data ) +{ + str keyname; + const char *com_token; + + Clear(); + + // parse the opening brace + com_token = COM_Parse( &data ); + if ( !data ) + { + return NULL; + } + + if ( com_token[ 0 ] != '{' ) + { + gi.Error( ERR_DROP, "SpawnArgs::Parse : found %s when expecting {", com_token ); + } + + // go through all the dictionary pairs + while( 1 ) + { + // parse key + com_token = COM_Parse( &data ); + if ( com_token[ 0 ] == '}' ) + { + break; + } + + if ( !data ) + { + gi.Error( ERR_DROP, "SpawnArgs::Parse : EOF without closing brace" ); + } + + keyname = com_token; + + // parse value + com_token = COM_Parse( &data ); + if ( !data ) + { + gi.Error( ERR_DROP, "SpawnArgs::Parse : EOF without closing brace" ); + } + + if ( com_token[ 0 ] == '}' ) + { + gi.Error( ERR_DROP, "SpawnArgs::Parse : closing brace without data" ); + } + + // keynames with a leading underscore are used for utility comments, + // and are immediately discarded by the game + if ( keyname[ 0 ] == '_' ) + { + continue; + } + + setArg( keyname.c_str(), com_token ); + } + + return data; +} + +const char *SpawnArgs::getArg( const char *key, const char *defaultValue ) +{ + int i; + int num; + + num = keyList.NumObjects(); + for( i = 1; i <= num; i++ ) + { + if ( keyList.ObjectAt( i ) == key ) + { + return valueList.ObjectAt( i ); + } + } + + return defaultValue; +} + +void SpawnArgs::setArg( const char *key, const char *value ) +{ + int i; + int num; + + num = keyList.NumObjects(); + for( i = 1; i <= num; i++ ) + { + if ( keyList.ObjectAt( i ) == key ) + { + valueList.ObjectAt( i ) = value; + return; + } + } + + keyList.AddObject( str( key ) ); + valueList.AddObject( str( value ) ); +} + +void SpawnArgs::operator=( SpawnArgs &otherlist ) +{ + int num; + int i; + + Clear(); + + num = otherlist.NumArgs(); + keyList.Resize( num ); + valueList.Resize( num ); + + for( i = 1; i <= num; i++ ) + { + keyList.AddObject( otherlist.keyList.ObjectAt( i ) ); + valueList.AddObject( otherlist.valueList.ObjectAt( i ) ); + } +} + +int SpawnArgs::NumArgs( void ) +{ + return keyList.NumObjects(); +} + +const char *SpawnArgs::getKey( int index ) +{ + return keyList.ObjectAt( index + 1 ); +} + +const char *SpawnArgs::getValue( int index ) +{ + return valueList.ObjectAt( index + 1 ); +} + +void SpawnArgs::Archive( Archiver &arc ) +{ + Class::Archive( arc ); + + keyList.Archive( arc ); + valueList.Archive( arc ); +} + +/* +=============== +getClass + +Finds the spawn function for the entity and returns ClassDef * +=============== +*/ + +ClassDef *SpawnArgs::getClassDef( qboolean *tikiWasStatic ) +{ + const char *classname; + ClassDef *cls = NULL; + + classname = getArg( "classname" ); + + if ( tikiWasStatic ) + { + *tikiWasStatic = false; + } + + // + // check normal spawn functions + // see if the class name is stored within the model + // + if ( classname ) + { + // + // explicitly inhibit lights + // + if ( !strcmpi( classname, "light" ) ) + { + // + // HACK HACK HACK + // hack to suppress a warning message + // + if ( tikiWasStatic ) + { + *tikiWasStatic = true; + } + return NULL; + } + + cls = getClassForID( classname ); + if ( !cls ) + { + cls = getClass( classname ); + } + } + + if ( !cls ) + { + const char *model; + + // + // get Object in case we cannot find an alternative + // + cls = &Object::ClassInfo; + model = getArg( "model" ); + if ( model ) + { + tiki_cmd_t cmds; + int modelindex; + int i; + + // + // get handle to def file + // + if ( ( strlen( model ) >= 3 ) && ( !strcmpi( &model[ strlen(model) - 3 ], "tik" ) ) ) + { + modelindex = modelIndex( model ); + + if ( modelindex == -1 ) + return NULL; + + if ( gi.IsModel( modelindex ) ) + { + const char * s; + + s = getArg( "make_static" ); + if ( s && atoi( s ) ) + { + // + // if make_static then we don't want to spawn + // + if ( tikiWasStatic ) + { + *tikiWasStatic = true; + } + + return NULL; + } + + if ( gi.InitCommands( modelindex, &cmds ) ) + { + for( i = 0; i < cmds.num_cmds; i++ ) + { + if ( !strcmpi( cmds.cmds[ i ].args[ 0 ], "classname" ) ) + { + cls = getClass( cmds.cmds[ i ].args[ 1 ] ); + break; + } + } + if ( i == cmds.num_cmds ) + { + if ( developer->integer == 2 ) + gi.WDPrintf( "Classname %s used, but 'classname' was not found in Initialization commands, using Object.\n", classname ); + } + } + else + gi.WDPrintf( "Classname %s used, but TIKI had no Initialization commands, using Object.\n", classname ); + } + else + gi.WDPrintf( "Classname %s used, but TIKI was not valid, using Object.\n", classname ); + } + else + gi.WDPrintf( "Classname %s used, but model was not a TIKI, using Object.\n", classname ); + } + else + { + gi.WDPrintf( "Classname %s' used, but no model was set, using Object.\n", classname ); + } + } + + return cls; +} + +/* +=============== +Spawn + +Finds the spawn function for the entity and calls it. +Returns pointer to Entity +=============== +*/ + +Entity *SpawnArgs::Spawn( void ) +{ + str classname; + ClassDef *cls; + Entity *obj; + Event *ev; + int i; + qboolean tikiWasStatic; // used to determine if entity was intentionally suppressed + + classname = getArg( "classname", "Unspecified" ); + cls = getClassDef( &tikiWasStatic ); + if ( !cls ) + { + if ( !tikiWasStatic ) + { + gi.DPrintf( "%s doesn't have a spawn function\n", classname.c_str() ); + } + + return NULL; + } + + obj = ( Entity * )cls->newInstance(); + + // post spawnarg events + for( i = 0; i < NumArgs(); i++ ) + { + // if it is the "script" key, execute the script commands individually + if ( !Q_stricmp( getKey( i ), "script" ) ) + { + char *ptr; + char * token; + + ptr = const_cast< char * >( getValue( i ) ); + while ( 1 ) + { + token = COM_ParseExt( &ptr, true ); + if ( !token[ 0 ] ) + break; + if ( strchr( token, ':' ) ) + { + gi.WDPrintf( "Label %s imbedded inside editor script for %s.\n", token, classname.c_str() ); + } + else + { + ev = new Event( token ); + while ( 1 ) + { + token = COM_ParseExt( &ptr, false ); + if ( !token[ 0 ] ) + break; + ev->AddToken( token ); + } + + obj->PostEvent( ev, EV_SPAWNARG ); + } + } + } + else + { + ev = new Event( getKey( i ) ); + ev->AddToken( getValue( i ) ); + + if ( Q_stricmp( getKey( i ), "model" ) == 0 ) + obj->PostEvent( ev, EV_PRIORITY_SPAWNARG ); + else + obj->PostEvent( ev, EV_SPAWNARG ); + } + } + + if ( !obj ) + { + gi.WDPrintf( "%s failed on newInstance\n", classname.c_str() ); + return NULL; + } + + return obj; +} + +/* +============== +G_InitClientPersistant + +This is only called when the game first initializes in single player, +but is called after each death and level change in deathmatch +============== +*/ +void G_InitClientPersistant( gclient_t *client ) +{ + memset( &client->pers, 0, sizeof( client->pers ) ); +} + +ClassDef *FindClass( const char *name, qboolean *isModel ) +{ + ClassDef *cls; + + *isModel = false; + + // first lets see if it is a registered class name + cls = getClass( name ); + if ( !cls ) + { + SpawnArgs args; + + // if that didn't work lets try to resolve it as a model + args.setArg( "model", name ); + + cls = args.getClassDef(); + if ( cls ) + { + *isModel = true; + } + } + return cls; +} diff --git a/dlls/game/g_spawn.h b/dlls/game/g_spawn.h new file mode 100644 index 0000000..7a55ea5 --- /dev/null +++ b/dlls/game/g_spawn.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/g_spawn.h $ +// $Revision:: 2 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __G_SPAWN_H__ +#define __G_SPAWN_H__ + +#include "entity.h" + +// spawnflags +// these are set with checkboxes on each entity in the map editor +#define SPAWNFLAG_NOT_EASY 0x00000100 +#define SPAWNFLAG_NOT_MEDIUM 0x00000200 +#define SPAWNFLAG_NOT_HARD 0x00000400 +#define SPAWNFLAG_NOT_DEATHMATCH 0x00000800 +#define SPAWNFLAG_DEVELOPMENT 0x00002000 +#define SPAWNFLAG_DETAIL 0x00004000 + +class SpawnArgs : public Class + { + private: + Container keyList; + Container valueList; + + public: + CLASS_PROTOTYPE( SpawnArgs ); + + SpawnArgs(); + SpawnArgs( SpawnArgs &arglist ); + + void Clear( void ); + + const char *Parse( const char *data ); + const char *getArg( const char *key, const char *defaultValue = NULL ); + void setArg( const char *key, const char *value ); + + int NumArgs( void ); + const char *getKey( int index ); + const char *getValue( int index ); + void operator=( SpawnArgs &a ); + + ClassDef *getClassDef( qboolean *tikiWasStatic = NULL ); + Entity *Spawn( void ); + + virtual void Archive( Archiver &arc ); + }; + +void G_InitClientPersistant( gclient_t *client ); +ClassDef *FindClass( const char *name, qboolean *isModel ); + +#endif diff --git a/dlls/game/g_utils.cpp b/dlls/game/g_utils.cpp new file mode 100644 index 0000000..05916e8 --- /dev/null +++ b/dlls/game/g_utils.cpp @@ -0,0 +1,2499 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/g_utils.cpp $ +// $Revision:: 84 $ +// $Author:: Steven $ +// $Date:: 4/16/03 6:29p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "g_utils.h" +#include "ctype.h" +#include "worldspawn.h" +#include "scriptmaster.h" +#include "player.h" +#include "PlayerStart.h" +#include "mp_manager.hpp" +#include + +char means_of_death_strings[ MOD_TOTAL_NUMBER ][ 32 ] = +{ + "none", + "drown", + "suicide", + "crush", + "crush_every_frame", + "telefrag", + "lava", + "slime", + "falling", + "last_self_inflicted", + "explosion", + "explodewall", + "electric", + "electricwater", + "thrownobject", + "beam", + "rocket", + "impact", + "gas", + "gas_blockable", + "acid", + "sword", + "plasma", + "plasmabeam", + "plasmashotgun", + "sting", + "sting2", + "sling", + "bullet", + "fast_bullet", + "vehicle", + "fire", + "fire_blockable", + "vortex", + "lifedrain", + "flashbang", + "poo_explosion", + "axe", + "chainsword", + "on_fire", + "firesword", + "electricsword", + "circleofprotection", + "radiation", + "lightsword", + "gib", + "impale", + "uppercut", + "poison", + "eat", + "redemption", + "stasis", + + // Added for EF + "phaser", + "vaporize", + "comp_rifle", + "vaporize_comp", + "imod_primary", + "imod_secondary", + "small_explosion", + "tetryon", + "disruptor", + "vaporize_disruptor", + "vaporize_photon", + "sniper", + "melee", + "alien_melee", + "klingon_melee", + "turret", + + // Powerups/runes + + "deathQuad", + "empathyShield", + "armorPiercing" +}; + +char soundtype_strings[ SOUNDTYPE_TOTAL_NUMBER ][ 32 ] = +{ + // General Sound Types + "none", + "general", + "explosion", + "weaponfire", + "alert", + "footsteps_walk", + "footsteps_run", + "fall", + + // Context Dialog Sound Types + "dialog_context_spotted_enemy", + "dialog_context_injured", + "dialog_context_in_combat", + "dialog_context_weapon_useless", + "dialog_context_investigating" +}; + +char context_strings[CONTEXT_TOTAL_NUMBER][32] = +{ + "spotted_enemy", + "injured", + "in_combat", + "weapon_useless", + "investigating" +}; + +int Soundtype_string_to_int ( const str &soundtype_string ) +{ + for (int i = 0 ; i < SOUNDTYPE_TOTAL_NUMBER ; i++ ) + { + if ( !soundtype_string.icmp( soundtype_strings[ i ] ) ) + return i; + } + + gi.WDPrintf( "Unknown soundtype - %s\n", soundtype_string.c_str() ); + return -1; +} + +int Context_string_to_int ( const str &context_string ) +{ + for (int i = 0 ; i < CONTEXT_TOTAL_NUMBER ; i++ ) + { + if ( !context_string.icmp( context_strings[ i ] ) ) + return i; + } + + gi.WDPrintf( "Unknown context - %s\n", context_string.c_str() ); + return -1; +} + +int MOD_NameToNum( const str &meansOfDeath ) +{ + int i; + + for ( i = 0 ; i < MOD_TOTAL_NUMBER ; i++ ) + { + if ( !meansOfDeath.icmp( means_of_death_strings[ i ] ) ) + return i; + } + + gi.WDPrintf( "Unknown means of death - %s\n", meansOfDeath.c_str() ); + return -1; +} + +const char *MOD_NumToName( int meansOfDeath ) +{ + if ( ( meansOfDeath > MOD_TOTAL_NUMBER ) || ( meansOfDeath < 0 ) ) + { + gi.WDPrintf( "Unknown means of death num - %d\n", meansOfDeath ); + return ""; + } + + return means_of_death_strings[ meansOfDeath ]; +} + +qboolean MOD_matches( int incoming_damage, int damage_type ) +{ + if ( damage_type == -1 ) + { + return true; + } + + // special case the sword + if ( damage_type == MOD_SWORD ) + { + if ( + ( incoming_damage == MOD_SWORD ) || + ( incoming_damage == MOD_ELECTRICSWORD ) || + ( incoming_damage == MOD_LIGHTSWORD ) || + ( incoming_damage == MOD_FIRESWORD ) + ) + { + return true; + } + } + else if ( damage_type == incoming_damage ) + { + return true; + } + + return false; +} + +/* +============ +G_TouchTriggers + +============ +*/ +void G_TouchTriggers( Entity *ent ) +{ + int i; + int num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + Event *ev; + + // dead things don't activate triggers! + if ( ( ent->client || ( ent->edict->svflags & SVF_MONSTER ) ) && ( ent->health <= 0.0f ) ) + { + return; + } + + // Get a list of g_entity_t's that this entity touched + num = gi.AreaEntities( ent->absmin, ent->absmax, touch, MAX_GENTITIES, qfalse ); + + Container &lastTouchedList = ent->GetLastTouchedList(); + Container newTouchedList ; + Container newLastTouchedList ; + int numTouchedEntities = lastTouchedList.NumObjects(); + int touchedEntityIdx = 1 ; + + // Make a new list of entities that are valid touch targets + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + if ( !hit->inuse || ( hit->entity == ent ) || ( hit->solid != SOLID_TRIGGER ) ) continue; + + assert( hit->entity ); + newTouchedList.AddObject( hit->entity ); + } + + // If there are entities in our lastTouchedList that we are no longer touching, notify them + for ( touchedEntityIdx=1; touchedEntityIdx <= numTouchedEntities; ++touchedEntityIdx ) + { + Entity *touchedEntity = lastTouchedList.ObjectAt( touchedEntityIdx ); + if (!touchedEntity) continue ; + if ( !newTouchedList.ObjectInList( touchedEntity ) ) + { + ev = new Event ( EV_LostContact ); // call me! + ev->AddEntity( ent ); + touchedEntity->ProcessEvent( ev ); + } + else + { + ev = new Event ( EV_Touch ); + ev->AddEntity( ent ); + touchedEntity->ProcessEvent( ev ); + newTouchedList.RemoveObject( touchedEntity ); + newLastTouchedList.AddObject( touchedEntity ); + } + } + + + // For each new object we're touching, notify it, and add it to our last touched list. + lastTouchedList.ClearObjectList(); + numTouchedEntities = newTouchedList.NumObjects(); + for ( touchedEntityIdx = 1; touchedEntityIdx <= numTouchedEntities; ++touchedEntityIdx ) + { + Entity *touchedEntity = newTouchedList.ObjectAt( touchedEntityIdx ); + lastTouchedList.AddObject( touchedEntity ); + + // This is for legacy reasons + ev = new Event( EV_Touch ); + ev->AddEntity( ent ); + touchedEntity->ProcessEvent( ev ); + + ev = new Event( EV_Contact ); + ev->AddEntity( ent ); + touchedEntity->ProcessEvent( ev ); + } + + numTouchedEntities = newLastTouchedList.NumObjects(); + for ( touchedEntityIdx = 1; touchedEntityIdx <= numTouchedEntities; ++touchedEntityIdx ) + { + lastTouchedList.AddObject( newLastTouchedList.ObjectAt( touchedEntityIdx ) ); + } +} + +void G_TouchTeleporters( Entity *ent ) +{ + int i; + int num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + Event *ev; + + // dead things don't activate triggers! + + if ( ( ent->client || ( ent->edict->svflags & SVF_MONSTER ) ) && ( ent->health <= 0.0f ) ) + { + return; + } + + // Get a list of g_entity_t's that this entity touched + + num = gi.AreaEntities( ent->absmin, ent->absmax, touch, MAX_GENTITIES, qfalse ); + + // Touch all in the list that are teleporters + + for( i = 0 ; i < num ; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + + if ( !hit->inuse || ( hit->entity == ent ) || ( hit->solid != SOLID_TRIGGER ) ) + continue; + + if ( hit->entity && hit->entity->isSubclassOf( Teleporter ) ) + { + ev = new Event( EV_Touch ); + ev->AddEntity( ent ); + hit->entity->ProcessEvent( ev ); + } + } +} + + +/* +============ +G_TouchSolids + +Call after linking a new trigger in during gameplay +to force all entities it covers to immediately touch it +============ +*/ +void G_TouchSolids( Entity *ent ) +{ + int i; + int num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + Event *ev; + + num = gi.AreaEntities( ent->absmin, ent->absmax, touch, MAX_GENTITIES, qfalse ); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + if ( !hit->inuse ) + { + continue; + } + + assert( hit->entity ); + + //FIXME + // should we post the events so that we don't have to worry about any entities going away + ev = new Event( EV_Touch ); + ev->AddEntity( ent ); + hit->entity->ProcessEvent( ev ); + } +} + +void G_ShowTrace( const trace_t *trace, gentity_t *passent, const char *reason, Vector &start, Vector &end ) +{ + str text; + str pass; + str hit; + Vector delta; + float dist; + + assert( reason ); + assert( trace ); + + if ( passent ) + { + pass = va( "'%s'(%d)", passent->entname, passent->s.number ); + } + else + { + pass = "NULL"; + } + + if ( trace->ent ) + { + hit = va( "'%s'(%d)", trace->ent->entname, trace->ent->s.number ); + } + else + { + hit = "NULL"; + } + + delta = end - start; + dist = delta.length(); + + text = va( "%0.2f : Pass %s Frac %f Hit %s len %f: '%s'\n", + level.time, pass.c_str(), trace->fraction, hit.c_str(), dist, reason ? reason : "" ); + + if ( sv_traceinfo->integer == 3 ) + { + gi.DebugPrintf( text.c_str() ); + } + else + { + gi.DPrintf( "%s", text.c_str() ); + } +} + +void G_CalcBoundsOfMove( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, Vector *minbounds, + Vector *maxbounds ) +{ + Vector bmin; + Vector bmax; + + ClearBounds( bmin, bmax ); + AddPointToBounds( ( Vector )start, bmin, bmax ); + AddPointToBounds( ( Vector )end, bmin, bmax ); + bmin += mins; + bmax += maxs; + + if ( minbounds ) + { + *minbounds = bmin; + } + + if ( maxbounds ) + { + *maxbounds = bmax; + } +} + +trace_t G_FullTrace( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, gentity_t *passent, int contentmask, + qboolean cylinder, const char *reason ) +{ + return G_Trace( start, mins, maxs, end, passent, contentmask, cylinder, reason, true ); +} + +trace_t G_FullTrace( const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, Entity *passent, + int contentmask, qboolean cylinder, const char *reason ) +{ + return G_Trace( start, mins, maxs, end, passent, contentmask, cylinder, reason, true ); +} + +trace_t G_Trace( vec3_t start, const vec3_t mins, const vec3_t maxs, vec3_t end, gentity_t *passent, int contentmask, + qboolean cylinder, const char *reason, qboolean fulltrace ) +{ + int entnum; + trace_t trace; + + if ( passent ) + { + entnum = passent->s.number; + } + else + { + entnum = ENTITYNUM_NONE; + } + + if ( fulltrace ) + gi.fulltrace( &trace, start, mins, maxs, end, entnum, contentmask, cylinder ); + else + gi.trace( &trace, start, mins, maxs, end, entnum, contentmask, cylinder ); + + if ( trace.entityNum == ENTITYNUM_NONE ) + { + trace.ent = NULL; + } + else + { + trace.ent = &g_entities[ trace.entityNum ]; + } + + if ( sv_traceinfo->integer > 1 ) + { + Vector start_vector = start; + Vector end_vector = end; + + G_ShowTrace( &trace, passent, reason, start_vector, end_vector ); + } + sv_numtraces++; + + if ( sv_drawtrace->integer ) + { + G_DebugLine( Vector( start ), Vector( end ), 1.0f, 1.0f, 0.0f, 1.0f ); + } + + return trace; +} + +trace_t G_Trace( const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, const Entity *passent, + int contentmask, qboolean cylinder, const char *reason, qboolean fulltrace ) +{ + gentity_t *ent; + int entnum; + trace_t trace; + + assert( reason ); + + if ( passent == NULL ) + { + ent = NULL; + entnum = ENTITYNUM_NONE; + } + else + { + ent = passent->edict; + entnum = ent->s.number; + } + + if ( fulltrace ) + gi.fulltrace( &trace, ( Vector )start, ( Vector )mins, ( Vector )maxs, ( Vector )end, entnum, contentmask, cylinder ); + else + gi.trace( &trace, ( Vector )start, ( Vector )mins, ( Vector )maxs, ( Vector )end, entnum, contentmask, cylinder ); + + if ( trace.entityNum == ENTITYNUM_NONE ) + { + trace.ent = NULL; + } + else + { + trace.ent = &g_entities[ trace.entityNum ]; + } + + if ( sv_traceinfo->integer > 1 ) + { + Vector start_vector = start; + Vector end_vector = end; + G_ShowTrace( &trace, ent, reason, start_vector, end_vector ); + } + + sv_numtraces++; + + if ( sv_drawtrace->integer ) + { + G_DebugLine( start, end, 1.0f, 1.0f, 0.0f, 1.0f ); + } + + return trace; +} + +void G_TraceEntities( const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, + Container*victimlist, int contentmask ) +{ + trace_t trace; + vec3_t boxmins; + vec3_t boxmaxs; + int num; + int touchlist[MAX_GENTITIES]; + gentity_t *touch; + int i; + + + // Find the bounding box + + for ( i=0 ; i<3 ; i++ ) + { + if ( end[i] > start[i] ) + { + boxmins[i] = start[i] + mins[i] - 1.0f; + boxmaxs[i] = end[i] + maxs[i] + 1.0f; + } + else + { + boxmins[i] = end[i] + mins[i] - 1.0f; + boxmaxs[i] = start[i] + maxs[i] + 1.0f; + } + } + + // Find the list of entites + + num = gi.AreaEntities( boxmins, boxmaxs, touchlist, MAX_GENTITIES, qfalse ); + + for ( i=0 ; isolid == SOLID_NOT) + continue; + if (touch->solid == SOLID_TRIGGER) + continue; + + gi.ClipToEntity( &trace, ( Vector )start, ( Vector )mins, ( Vector )maxs, ( Vector )end, touchlist[i], contentmask ); + + if ( trace.entityNum == touchlist[i] ) + victimlist->AddObject( touch->entity ); + } +} + +/* +================ +PlayersRangeFromSpot + +Returns the distance to the nearest player from the given spot +================ +*/ +float PlayersRangeFromSpot( const Entity *spot ) +{ + Entity *player; + float bestplayerdistance; + Vector v; + int n; + float playerdistance; + + bestplayerdistance = 9999999.0f; + for( n = 0; n < maxclients->integer; n++ ) + { + if ( !g_entities[ n ].inuse || !g_entities[ n ].entity ) + { + continue; + } + + player = g_entities[ n ].entity; + if ( player->health <= 0.0f ) + { + continue; + } + + v = spot->origin - player->origin; + playerdistance = v.length(); + + if ( playerdistance < bestplayerdistance ) + { + bestplayerdistance = playerdistance; + } + } + + return bestplayerdistance; +} + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point, but NOT the two points closest +to other players +================ +*/ +Entity *SelectRandomDeathmatchSpawnPoint( const str &spawnpoint_type ) +{ + Entity *spot, *spot1, *spot2; + int count = 0; + int selection; + float range, range1, range2; + + spot = NULL; + range1 = range2 = 99999; + spot1 = spot2 = NULL; + + for( spot = G_FindClass( spot, spawnpoint_type ); spot ; spot = G_FindClass( spot, spawnpoint_type ) ) + { + count++; + range = PlayersRangeFromSpot( spot ); + if ( range < range1 ) + { + range1 = range; + spot1 = spot; + } + else if (range < range2) + { + range2 = range; + spot2 = spot; + } + } + + if ( !count ) + { + return NULL; + } + + if ( count <= 2 ) + { + spot1 = spot2 = NULL; + } + else + { + count -= 2; + } + + selection = rand() % count; + + spot = NULL; + do + { + spot = G_FindClass( spot, spawnpoint_type ); + + // if there are no more, break out + if ( !spot ) + break; + + if ( ( spot == spot1 ) || ( spot == spot2 ) ) + { + selection++; + } + } + while( selection-- ); + + return spot; +} + +/* +================ +SelectFarthestDeathmatchSpawnPoint + +================ +*/ +Entity *SelectFarthestDeathmatchSpawnPoint( void ) +{ + Entity *bestspot; + float bestdistance; + float bestplayerdistance; + Entity *spot; + + spot = NULL; + bestspot = NULL; + bestdistance = 0; + for( spot = G_FindClass( spot, "info_player_deathmatch" ); spot ; spot = G_FindClass( spot, "info_player_deathmatch" ) ) + { + bestplayerdistance = PlayersRangeFromSpot( spot ); + if ( bestplayerdistance > bestdistance ) + { + bestspot = spot; + bestdistance = bestplayerdistance; + } + } + + if ( bestspot ) + { + return bestspot; + } + + // if there is a player just spawned on each and every start spot + // we have no choice to turn one into a telefrag meltdown + spot = G_FindClass( NULL, "info_player_deathmatch" ); + + return spot; +} + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +void SelectSpawnPoint( Vector &org, Vector &ang, str &thread ) +{ + Entity *spot = NULL; + + // find a single player start spot + + while( ( spot = G_FindClass( spot, "info_player_start" ) ) != NULL ) + { + if ( level.spawnpoint.icmp( spot->TargetName() ) == 0 ) + { + break; + } + } + + if ( !spot && !level.spawnpoint.length() ) + { + // there wasn't a spawnpoint without a target, so use any + spot = G_FindClass( NULL, "info_player_start" ); + } + + if ( !spot ) + { + gi.Error( ERR_DROP, "No player spawn position named '%s'. Can't spawn player.\n", level.spawnpoint.c_str() ); + } + + org = spot->origin; + ang = spot->angles; + // + // see if we have a thread + // + if ( spot->isSubclassOf( PlayerStart ) ) + { + thread = ( ( PlayerStart * )spot )->getThread(); + } +} + +/* +============= +M_CheckBottom + +Returns false if any part of the bottom of the entity is off an edge that +is not a staircase. + +============= +*/ +//int c_yes, c_no; + +qboolean M_CheckBottom( Entity *ent ) +{ + Vector mins, maxs, start, stop; + trace_t trace; + int x, y; + float mid, bottom; + + mins = ent->origin + ( ent->mins * 0.5f ); + maxs = ent->origin + ( ent->maxs * 0.5f ); + + // if all of the points under the corners are solid world, don't bother + // with the tougher checks + // the corners must be within 16 of the midpoint + start[ 2 ] = mins[ 2 ] - 1.0f; + + for( x = 0; x <= 1; x++ ) + { + for( y = 0; y <= 1; y++ ) + { + start[ 0 ] = x ? maxs[ 0 ] : mins[ 0 ]; + start[ 1 ] = y ? maxs[ 1 ] : mins[ 1 ]; + if ( gi.pointcontents( start, 0 ) != CONTENTS_SOLID ) + { + goto realcheck; + } + } + } + + //c_yes++; + return true; // we got out easy + +realcheck: + + //c_no++; + + // + // check it for real... + // + start[ 2 ] = mins[ 2 ]; + + // the midpoint must be within 16 of the bottom + start[ 0 ] = stop[ 0 ] = ( mins[ 0 ] + maxs[ 0 ] ) * 0.5f; + start[ 1 ] = stop[ 1 ] = ( mins[ 1 ] + maxs[ 1 ] ) * 0.5f; + stop[ 2 ] = start[ 2 ] - 3.0f * STEPSIZE;//2 * STEPSIZE; + + trace = G_Trace( start, vec_zero, vec_zero, stop, ent, MASK_MONSTERSOLID, false, "M_CheckBottom 1" ); + + if ( trace.fraction == 1.0f ) + { + return false; + } + + mid = bottom = trace.endpos[ 2 ]; + + // the corners must be within 16 of the midpoint + /* + for( x = 0; x <= 1; x++ ) + { + for( y = 0; y <= 1; y++ ) + { + start[ 0 ] = stop[ 0 ] = ( x ? maxs[ 0 ] : mins[ 0 ] ); + start[ 1 ] = stop[ 1 ] = ( y ? maxs[ 1 ] : mins[ 1 ] ); + + trace = G_Trace( start, vec_zero, vec_zero, stop, ent, MASK_MONSTERSOLID, false, "M_CheckBottom 2" ); + + if ( ( trace.fraction != 1.0f ) && ( trace.endpos[ 2 ] > bottom ) ) + { + bottom = trace.endpos[ 2 ]; + } + + if ( ( trace.fraction == 1.0f ) || ( ( mid - trace.endpos[ 2 ] ) > STEPSIZE ) ) + { + return false; + } + } + } + */ + //c_yes++; + return true; +} + +Entity * G_FindClass( const Entity * ent, const char *classname ) +{ + int entnum; + gentity_t *from; + + if ( ent ) + { + entnum = ent->entnum; + } + else + { + entnum = -1; + } + + for ( from = &g_entities[ entnum + 1 ]; from < &g_entities[ globals.num_entities ] ; from++ ) + { + if ( !from->inuse ) + { + continue; + } + if ( !Q_stricmp ( from->entity->getClassID(), classname ) ) + { + return from->entity; + } + } + + return NULL; +} + +Entity * G_FindTarget( Entity * ent, const char *name ) +{ + Entity *next; + + if ( name && name[ 0 ] ) + { + if ( name[ 0 ] == '$' ) + name++; + + next = world->GetNextEntity( str( name ), ent ); + if ( next ) + { + return next; + } + } + + return NULL; +} + +Entity *G_NextEntity( const Entity *ent ) +{ + gentity_t *from; + + if ( !g_entities ) + { + return NULL; + } + + if ( !ent ) + { + from = g_entities; + } + else + { + from = ent->edict + 1; + } + + if ( !from ) + { + return NULL; + } + + for ( ; from < &g_entities[ globals.num_entities ] ; from++ ) + { + if ( !from->inuse || !from->entity ) + { + continue; + } + + return from->entity; + } + + return NULL; +} + +// +// QuakeEd only writes a single float for angles (bad idea), so up and down are +// just constant angles. +// +Vector G_GetMovedir( float angle ) +{ + if ( angle == -1.0f ) + { + return Vector( 0.0f, 0.0f, 1.0f ); + } + else if ( angle == -2.0f ) + { + return Vector( 0.0f, 0.0f, -1.0f ); + } + + angle *= ( M_PI * 2.0f / 360.0f ); + return Vector( cos( angle ), sin( angle ), 0.0f ); +} + +/* +================= +KillBox + +Kills all entities that would touch the proposed new positioning +of ent. Ent should be unlinked before calling this! +================= +*/ +qboolean KillBox( Entity *ent ) +{ + int i; + int num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + Vector min; + Vector max; + int fail; + + fail = 0; + + min = ent->origin + ent->mins; + max = ent->origin + ent->maxs; + + num = gi.AreaEntities( min, max, touch, MAX_GENTITIES, qfalse ); + + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + + if ( !hit->inuse || ( hit->entity == ent ) || !hit->entity || ( hit->entity == world ) || ( !hit->entity->edict->solid ) ) + { + continue; + } + + hit->entity->Damage( ent, ent, hit->entity->health + 100000.0f, ent->origin, vec_zero, vec_zero, + 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG ); + + // + // if we didn't kill it, fail + // + if ( ( hit->entity->getSolidType() != SOLID_NOT ) && ( hit->entity->health > 0.0f ) ) + { + fail++; + } + } + + // + // all clear + // + return !fail; +} + +qboolean IsNumeric( const char *str ) +{ + int len; + int i; + qboolean dot; + + if ( *str == '-' ) + { + str++; + } + + dot = false; + len = strlen( str ); + for( i = 0; i < len; i++ ) + { + if ( !isdigit( str[ i ] ) ) + { + if ( ( str[ i ] == '.' ) && !dot ) + { + dot = true; + continue; + } + return false; + } + } + + return true; +} + +/* +================= +findradius + +Returns entities that have origins within a spherical area + +findradius (org, radius) +================= +*/ +Entity *findradius( const Entity *startent, const Vector &org, float rad ) +{ + Vector eorg; + gentity_t *from; + float r2, distance; + + if ( !startent ) + { + from = active_edicts.next; + } + else + { + from = startent->edict->next; + } + + assert( from ); + if ( !from ) + { + return NULL; + } + + assert( ( from == &active_edicts ) || ( from->inuse ) ); + + // square the radius so that we don't have to do a square root + r2 = rad * rad; + + for( ; from != &active_edicts; from = from->next ) + { + assert( from->inuse ); + assert( from->entity ); + + eorg = org - from->entity->centroid; + + // dot product returns length squared + distance = eorg * eorg; + + if ( distance <= r2 ) + { + return from->entity; + } + else + { + // subtract the object's own radius from this distance + distance -= from->radius2; + if ( distance <= r2 ) + { + return from->entity; + } + } + } + + return NULL; +} + +/* +================= +findclientinradius + +Returns clients that have origins within a spherical area + +findclientinradius (org, radius) +================= +*/ +Entity *findclientsinradius( const Entity *startent, const Vector &org, float rad ) +{ + Vector eorg; + gentity_t *ed; + float r2; + int i; + + // square the radius so that we don't have to do a square root + r2 = rad * rad; + + if ( !startent ) + { + i = 0; + } + else + { + i = startent->entnum + 1; + } + + for( ; i < game.maxclients; i++ ) + { + ed = &g_entities[ i ]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + eorg = org - ed->entity->centroid; + + // dot product returns length squared + if ( ( eorg * eorg ) <= r2 ) + { + return ed->entity; + } + } + + return NULL; +} + +Vector G_CalculateImpulse( const Vector &start, const Vector &end, float speed, float gravity ) +{ + float traveltime, vertical_speed; + Vector dir, xydir, velocity; + + dir = end - start; + xydir = dir; + xydir.z = 0; + traveltime = xydir.length() / speed; + vertical_speed = ( dir.z / traveltime ) + ( 0.5f * gravity * sv_currentGravity->value * traveltime ); + xydir.normalize(); + + velocity = speed * xydir; + velocity.z = vertical_speed; + return velocity; +} + +Vector G_PredictPosition( const Vector &start, const Vector &target, const Vector &targetvelocity, float speed ) +{ + Vector projected; + float traveltime; + Vector dir, xydir; + + dir = target - start; + xydir = dir; + xydir.z = 0; + traveltime = xydir.length() / speed; + projected = target + ( targetvelocity * traveltime ); + + return projected; +} + +char *ClientTeam( const gentity_t *ent ) +{ + static char value[512]; + + value[0] = 0; + + if (!ent->client) + return value; + + if ( multiplayerManager.checkFlag( MP_FLAG_MODELTEAMS ) ) + COM_StripExtension( Info_ValueForKey( ent->client->pers.userinfo, "model" ), value ); + else if ( multiplayerManager.checkFlag( MP_FLAG_SKINTEAMS ) ) + COM_StripExtension( Info_ValueForKey( ent->client->pers.userinfo, "skin" ), value ); + + return( value ); +} + +qboolean OnSameTeam( const Entity *ent1, const Entity *ent2 ) +{ + char ent1Team [512]; + char ent2Team [512]; + + if ( !multiplayerManager.checkFlag( MP_FLAG_MODELTEAMS | MP_FLAG_SKINTEAMS ) ) + return false; + + strcpy (ent1Team, ClientTeam (ent1->edict)); + strcpy (ent2Team, ClientTeam (ent2->edict)); + + if ( !strcmp( ent1Team, ent2Team ) ) + return true; + + return false; +} + +/* +============== +G_LoadAndExecScript + +Like the man says... +============== +*/ +qboolean G_LoadAndExecScript( const char *filename, const char *label, qboolean quiet ) +{ + CThread *pThread; + + if ( gi.FS_ReadFile( filename, NULL, quiet ) != -1 ) + { + pThread = Director.CreateThread( label ); + if ( pThread ) + { + // start right away + pThread->Start(); + return true; + } + else + { + gi.WDPrintf( "G_LoadAndExecScript : %s could not create thread.\n", filename ); + return false; + } + } + + return false; +} + +CThread *ExecuteThread( const str &thread_name, qboolean start, Entity *currentEnt ) +{ + if ( thread_name.length() ) + { + CThread *pThread; + + pThread = Director.CreateThread( thread_name.c_str() ); + if ( pThread ) + { + if ( currentEnt ) + pThread->SetCurrentEntity(currentEnt); + + if ( start ) + { + // start right away + pThread->Start(); + } + } + else + { + gi.WDPrintf( "StartThread::unable to go to %s\n", thread_name.c_str() ); + return NULL; + } + + return pThread; + } + + return NULL; +} + +/* +============== +G_ArchiveEdict +============== +*/ +void G_ArchiveEdict( Archiver &arc, gentity_t *edict ) +{ + int i; + str tempStr; + + assert( edict ); + + // + // this is written funny because it is used for both saving and loading + // + + if ( edict->client ) + { + arc.ArchiveRaw( edict->client, sizeof( *edict->client ) ); + } + + // Don't archive int number; + + arc.ArchiveInteger( &edict->s.instanceNumber ); + + arc.ArchiveInteger( &edict->s.eType ); + arc.ArchiveInteger( &edict->s.eFlags ); + + // Don't archive + //trajectory_t pos; + //trajectory_t apos; + + arc.ArchiveVec3( edict->s.netorigin ); + arc.ArchiveVec3( edict->s.origin ); + arc.ArchiveVec3( edict->s.origin2 ); + arc.ArchiveVec3( edict->s.netangles ); + arc.ArchiveVec3( edict->s.angles ); + + arc.ArchiveVec3( edict->s.viewangles ); + + arc.ArchiveUnsigned( &edict->s.constantLight ); + + if ( arc.Saving() ) + { + if ( edict->s.loopSound ) + tempStr = gi.getConfigstring( CS_SOUNDS + edict->s.loopSound ); + else + tempStr = ""; + + arc.ArchiveString( &tempStr ); + } + else + { + arc.ArchiveString( &tempStr ); + + if ( tempStr.length() ) + edict->s.loopSound = gi.soundindex( tempStr.c_str() ); + else + edict->s.loopSound = 0; + } + + arc.ArchiveFloat( &edict->s.loopSoundVolume ); + arc.ArchiveFloat( &edict->s.loopSoundMinDist ); + + arc.ArchiveInteger( &edict->s.parent ); + arc.ArchiveInteger( &edict->s.tag_num ); + arc.ArchiveBoolean( &edict->s.attach_use_angles ); + arc.ArchiveVec3( edict->s.attach_offset ); + arc.ArchiveVec3( edict->s.attach_angles_offset ); + + arc.ArchiveInteger( &edict->s.modelindex ); + arc.ArchiveInteger( &edict->s.viewmodelindex ); + //arc.ArchiveInteger( &edict->s.worldmodelindex ); + arc.ArchiveInteger( &edict->s.skinNum ); + + if ( arc.Saving() ) + { + if ( edict->s.customShader ) + tempStr = gi.getConfigstring( CS_IMAGES + edict->s.customShader ); + else + tempStr = ""; + + arc.ArchiveString( &tempStr ); + } + else + { + arc.ArchiveString( &tempStr ); + + if ( tempStr.length() ) + edict->s.customShader = gi.imageindex( tempStr.c_str() ); + else + edict->s.customShader = 0; + } + + if ( arc.Saving() ) + { + if ( edict->s.customEmitter ) + tempStr = gi.getConfigstring( CS_IMAGES + edict->s.customEmitter ); + else + tempStr = ""; + + arc.ArchiveString( &tempStr ); + } + else + { + arc.ArchiveString( &tempStr ); + + if ( tempStr.length() ) + edict->s.customEmitter = gi.imageindex( tempStr.c_str() ); + else + edict->s.customEmitter = 0; + } + + arc.ArchiveFloat( &edict->s.animationRate ); + arc.ArchiveInteger( &edict->s.anim ); + arc.ArchiveInteger( &edict->s.frame ); + + arc.ArchiveInteger( &edict->s.crossblend_time ); + + arc.ArchiveInteger( &edict->s.torso_anim ); + arc.ArchiveInteger( &edict->s.torso_frame ); + arc.ArchiveInteger( &edict->s.torso_crossblend_time ); + + for( i = 0; i < NUM_BONE_CONTROLLERS; i++ ) + { + arc.ArchiveInteger( &edict->s.bone_tag[ i ] ); + arc.ArchiveVec3( edict->s.bone_angles[ i ] ); + arc.ArchiveVec4( edict->s.bone_quat[ i ] ); + } + + arc.ArchiveRaw( &edict->s.morph_controllers, sizeof( edict->s.morph_controllers ) ); + + arc.ArchiveRaw( &edict->s.surfaces, sizeof( edict->s.surfaces ) ); + + arc.ArchiveInteger( &edict->s.clientNum ); + arc.ArchiveInteger( &edict->s.groundEntityNum ); + arc.ArchiveInteger( &edict->s.solid ); + + arc.ArchiveFloat( &edict->s.scale ); + arc.ArchiveFloat( &edict->s.alpha ); + arc.ArchiveInteger( &edict->s.renderfx ); + + arc.ArchiveUnsigned( &edict->s.affectingViewModes ); + + arc.ArchiveInteger( &edict->s.archeTypeIndex ); + arc.ArchiveInteger( &edict->s.missionObjective ); + + arc.ArchiveInteger( &edict->s.infoIcon ); + + arc.ArchiveRaw( &edict->s.effectsAnims, sizeof( edict->s.effectsAnims ) ); + + arc.ArchiveInteger( &edict->s.bindparent ); + + arc.ArchiveVec4( edict->s.quat ); + arc.ArchiveRaw( &edict->s.mat, sizeof( edict->s.mat ) ); + + arc.ArchiveInteger( &edict->svflags ); + + arc.ArchiveVec3( edict->mins ); + arc.ArchiveVec3( edict->maxs ); + arc.ArchiveInteger( &edict->contents ); + arc.ArchiveVec3( edict->absmin ); + arc.ArchiveVec3( edict->absmax ); + arc.ArchiveFloat( &edict->radius ); + + if ( arc.Loading() ) + { + edict->radius2 = edict->radius * edict->radius; + } + + arc.ArchiveVec3( edict->centroid ); + + arc.ArchiveVec3( edict->currentOrigin ); + arc.ArchiveVec3( edict->currentAngles ); + + arc.ArchiveInteger( &edict->ownerNum ); + ArchiveEnum( edict->solid, solid_t ); + arc.ArchiveFloat( &edict->freetime ); + arc.ArchiveFloat( &edict->spawntime ); + + tempStr = str( edict->entname ); + arc.ArchiveString( &tempStr ); + strncpy( edict->entname, tempStr.c_str(), sizeof( edict->entname ) - 1 ); + + arc.ArchiveInteger( &edict->clipmask ); + + if ( arc.Loading() ) + { + gi.linkentity( edict ); + } +} + +/* +========================================================================= + +model / sound configstring indexes + +========================================================================= +*/ + +/* +======================= +G_FindConfigstringIndex +======================= +*/ +int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ) +{ + int i; + char *s; + + if ( !name || !name[0] ) + { + return 0; + } + + for ( i=1 ; is.pos.trBase ); + ent->s.pos.trType = TR_STATIONARY; + ent->s.pos.trTime = 0; + ent->s.pos.trDuration = 0; + VectorClear( ent->s.pos.trDelta ); + + VectorCopy( org, ent->currentOrigin ); + VectorCopy( org, ent->s.origin ); +} + +/* +=============== +G_SetConstantLight + +Sets the encoded constant light parameter for entities +=============== +*/ +void G_SetConstantLight( unsigned int * constantlight, const float * red, const float * green, const float * blue, + const float * radius, const int * lightStyle ) +{ + int ir, ig, ib, iradius; + + if ( !constantlight ) + return; + + ir = (*constantlight) & 255; + ig = ( (*constantlight) >> 8 ) & 255; + ib = ( (*constantlight) >> 16 ) & 255; + iradius = ( (*constantlight) >> 24 ) & 255; + + if ( red ) + { + ir = *red * 255; + if ( ir > 255 ) + ir = 255; + } + + if ( green ) + { + ig = *green * 255; + if ( ig > 255 ) + ig = 255; + } + + if ( blue ) + { + ib = *blue * 255; + if ( ib > 255 ) + ib = 255; + } + + if ( radius ) + { + iradius = *radius / CONSTANTLIGHT_RADIUS_SCALE; + if ( iradius > 255 ) + iradius = 255; + } + + if ( lightStyle ) + { + ir = *lightStyle; + if ( ir > 255 ) + ir = 255; + } + *constantlight = ( ir ) + ( ig << 8 ) + ( ib << 16 ) + ( iradius << 24 ); +} + +// +// caching commands +// +int modelIndex( const char *mdl ) +{ + str name; + + assert( mdl ); + + if ( !mdl ) + { + return 0; + } + + // Prepend 'models/' to make things easier + //if ( !strchr( mdl, '*' ) && !strchr( mdl, '\\' ) && !strchr( mdl, '/' ) ) + if ( ( strlen( mdl ) > 0 ) && !strchr( mdl, '*' ) && strnicmp( "models/", mdl, 7 ) && strstr( mdl, ".tik" ) ) + { + name = "models/"; + } + + name += mdl; + + return gi.modelindex( name.c_str() ); +} + +void CacheResource( const char * stuff, Entity * ent ) +{ + str real_stuff; + + if ( !stuff || !ent ) + return; + + if ( !strchr( stuff, '.' ) ) + { + // must be a global alias + stuff = gi.GlobalAlias_FindRandom( stuff ); + if ( !stuff ) + { + return; + } + } + + real_stuff = stuff; + real_stuff.tolower(); + + if ( strstr( real_stuff.c_str(), ".wav" ) ) + { + gi.soundindex( real_stuff.c_str() ); + } + else if ( strstr( real_stuff.c_str(), ".mp3" ) ) + { + gi.soundindex( real_stuff.c_str() ); + } + else if ( strstr( real_stuff.c_str(), ".tik" ) ) + { + int index; + + index = modelIndex( real_stuff.c_str() ); + + if ( index > 0 ) + { + if ( !ent ) + { + ent = new Entity; + + ent->ProcessInitCommands( index, true ); + + delete ent; + ent = NULL; + } + else + { + ent->ProcessInitCommands( index, true ); + } + } + } + else if ( strstr( real_stuff.c_str(), ".spr" ) ) + { + gi.modelindex( real_stuff.c_str() ); + } + else if ( strstr( real_stuff.c_str(), ".ska" ) ) + { + gi.TIKI_CacheAnim( real_stuff.c_str() ); + } + else if ( strstr( real_stuff.c_str(), ".st" ) ) + { + if ( strncmp( real_stuff.c_str(), "ai/", 3 ) == 0 ) + { + CacheStatemap( stuff, ( Condition * )Actor::Conditions ); + } + } + else if ( strstr( real_stuff.c_str(), ".img" ) ) + { + real_stuff.CapLength( real_stuff.length() - 4 ); + gi.imageindex( real_stuff.c_str() ); + } + else if ( strstr( real_stuff.c_str(), ".itm" ) ) + { + real_stuff.CapLength( real_stuff.length() - 4 ); + gi.itemindex( real_stuff.c_str() ); + } + else if ( strstr( real_stuff.c_str(), ".arc" ) ) + { + real_stuff.CapLength( real_stuff.length() - 4 ); + gi.archetypeindex( real_stuff.c_str() ); + } +} + +void G_CacheStateMachineAnims( Entity *ent, const char *stateMachineName ) +{ + Container animNames; + const char *animName; + StateMap *stateMap; + int i; + + // Cache the statemap + + if ( ent->isSubclassOf( Player ) ) + stateMap = GetStatemap( stateMachineName, ( Condition * )Player::Conditions, NULL, false, true ); + else if ( ent->isSubclassOf( Actor ) ) + stateMap = GetStatemap( stateMachineName, ( Condition * )Actor::Conditions, NULL, false, true ); + else + return; + + // Get all of the anims in the statemachine + + stateMap->GetAllAnims( &animNames ); + + // Loop through all of the anims in this statemachine and cache them all + + for ( i = 1 ; i <= animNames.NumObjects() ; i++ ) + { + animName = animNames.ObjectAt( i ); + + // This forces the caching of every anim that matches this anim name + + gi.Anim_Random( ent->edict->s.modelindex, animName ); + } +} + +void ChangeMusic( const char *current, const char *fallback, qboolean force ) +{ + int j; + gentity_t *other; + + if ( current || fallback ) + { + for( j = 0; j < game.maxclients; j++ ) + { + other = &g_entities[ j ]; + if ( other->inuse && other->client ) + { + Player *client; + + client = ( Player * )other->entity; + client->ChangeMusic( current, fallback, force ); + } + } + if ( current && fallback ) + { + gi.DPrintf( "music set to %s with fallback %s\n", current, fallback ); + } + } +} + +void ChangeMusicVolume( float volume, float fade_time ) +{ + int j; + gentity_t *other; + + for( j = 0; j < game.maxclients; j++ ) + { + other = &g_entities[ j ]; + if ( other->inuse && other->client ) + { + Player *client; + + client = ( Player * )other->entity; + client->ChangeMusicVolume( volume, fade_time ); + } + } + gi.DPrintf( "music volume set to %.2f, fade time %.2f\n", volume, fade_time ); +} + +void RestoreMusicVolume( float fade_time ) +{ + int j; + gentity_t *other; + + for( j = 0; j < game.maxclients; j++ ) + { + other = &g_entities[ j ]; + if ( other->inuse && other->client ) + { + Player *client; + + client = ( Player * )other->entity; + client->RestoreMusicVolume( fade_time ); + } + } +} + +//================================================================ +// Name: G_AllowMusicDucking +// Class: +// +// Description: Tells each player whether or not music ducking is allowed +// +// Parameters: bool allowMusicDucking - whether or not music ducking is allowed +// +// Returns: none +//================================================================ + +void G_AllowMusicDucking( bool allowMusicDucking ) +{ + int j; + gentity_t *other; + + for( j = 0; j < game.maxclients; j++ ) + { + other = &g_entities[ j ]; + + if ( other->inuse && other->client ) + { + Player *player; + + player = ( Player * )other->entity; + player->allowMusicDucking( allowMusicDucking ); + } + } +} + +//================================================================ +// Name: G_AllowActionMusic +// Class: +// +// Description: Tells each player whether or not action music is allowed +// +// Parameters: bool allowActionMusic - whether or not action music is allowed +// +// Returns: none +//================================================================ + +void G_AllowActionMusic( bool allowActionMusic ) +{ + int j; + gentity_t *other; + + for( j = 0; j < game.maxclients; j++ ) + { + other = &g_entities[ j ]; + + if ( other->inuse && other->client ) + { + Player *player; + + player = ( Player * )other->entity; + player->allowActionMusic( allowActionMusic ); + } + } +} + + +void ChangeSoundtrack( const char * soundtrack ) +{ + level.saved_soundtrack = level.current_soundtrack; + level.current_soundtrack = soundtrack; + + gi.setConfigstring( CS_SOUNDTRACK, soundtrack ); + gi.DPrintf( "soundtrack switched to %s.\n", soundtrack ); +} + +void RestoreSoundtrack( void ) +{ + if ( level.saved_soundtrack.length() ) + { + level.current_soundtrack = level.saved_soundtrack; + level.saved_soundtrack = ""; + gi.setConfigstring( CS_SOUNDTRACK, level.current_soundtrack.c_str() ); + gi.DPrintf( "soundtrack restored %s.\n", level.current_soundtrack.c_str() ); + } +} + +void G_BroadcastSound( Entity *soundent, const Vector &origin, float radius, int soundType ) +{ + Sentient *ent; + Vector delta; + Event *ev; + str name; + float r2; + float dist2; + int i; + int n; + +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + int count; + + count = 0; +#endif + +****************************************************************************/ + + + //Putting in the mechanisims for a SoundType within Broadcast sound + //However it won't actually do anything yet. + assert( soundent ); + if ( soundent && !( soundent->flags & FL_NOTARGET ) ) + { + r2 = radius * radius; + n = SentientList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ent = SentientList.ObjectAt( i ); + if ( ( ent == soundent ) || ent->deadflag ) + { + continue; + } + + if ( ent->isSubclassOf( Actor ) ) + { + delta = origin - ent->centroid; + + // dot product returns length squared + dist2 = delta * delta; + if ( + ( dist2 <= r2 ) && + ( + ( soundent->edict->areanum == ent->edict->areanum ) || + ( gi.AreasConnected( soundent->edict->areanum, ent->edict->areanum ) ) + ) + ) + + { + ev = new Event( EV_HeardSound ); + ev->AddEntity( soundent ); + ev->AddVector( origin ); + ev->AddInteger( soundType ); + ent->PostEvent( ev, 0.0f ); + +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + count++; +#endif + +****************************************************************************/ + + } + + } + } + +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + gi.DPrintf( "Broadcast event %s to %d entities\n", ev->getName(), count ); +#endif + +****************************************************************************/ + + } +} + +void G_BroadcastAlert( Entity *soundent, const Vector &origin, const Vector &enemy, float radius ) +{ + Sentient *ent; + Vector delta; + Event *ev; + str name; + float r2; + float dist2; + int i; + int n; +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + int count; + + count = 0; +#endif + +****************************************************************************/ + + + //int soundType = SOUNDTYPE_ALERT; + + assert( soundent ); + if ( soundent && !( soundent->flags & FL_NOTARGET ) ) + { + r2 = radius * radius; + n = SentientList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ent = SentientList.ObjectAt( i ); + if ( ( ent == soundent ) || ent->deadflag ) + { + continue; + } + + if ( ent->isSubclassOf( Actor ) ) + { + delta = origin - ent->centroid; + + // dot product returns length squared + dist2 = delta * delta; + if ( + ( dist2 <= r2 ) && + ( + ( soundent->edict->areanum == ent->edict->areanum ) || + ( gi.AreasConnected( soundent->edict->areanum, ent->edict->areanum ) ) + ) + ) + + { + ev = new Event( EV_HeardSound ); + ev->AddEntity( soundent ); + ev->AddVector( enemy ); + ent->PostEvent( ev, 0.0f ); + +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + count++; +#endif + +****************************************************************************/ + + } + } + } + +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + gi.DPrintf( "Broadcast event %s to %d entities\n", ev->getName(), count ); +#endif + +****************************************************************************/ + + } +} + +void CloneEntity( Entity * dest, const Entity * src ) +{ + int i, num; + + dest->setModel( src->model ); + // don't process our init commands + //dest->CancelEventsOfType( EV_ProcessInitCommands ); + dest->setOrigin( src->origin ); + dest->setAngles( src->angles ); + dest->setScale( src->edict->s.scale ); + dest->setAlpha( src->edict->s.alpha ); + dest->health = src->health; + // copy the surfaces + memcpy( dest->edict->s.surfaces, src->edict->s.surfaces, sizeof( src->edict->s.surfaces ) ); + dest->edict->s.constantLight = src->edict->s.constantLight; + //dest->edict->s.eFlags = src->edict->s.eFlags; + dest->edict->s.renderfx = src->edict->s.renderfx; + dest->edict->s.anim = src->edict->s.anim; + dest->edict->s.frame = src->edict->s.frame; + + if ( src->bind_info ) + { + num = src->bind_info->numchildren; + for( i = 0; ( i < MAX_MODEL_CHILDREN ) && num; i++ ) + { + Entity * clone; + Entity * child; + + // duplicate the children + if ( !src->bind_info->children[ i ] ) + { + continue; + } + child = G_GetEntity( src->bind_info->children[ i ] ); + if ( child ) + { + clone = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + CloneEntity( clone, child ); + clone->attach( dest->entnum, child->edict->s.tag_num ); + } + num--; + } + } + dest->ProcessPendingEvents(); +} + +weaponhand_t WeaponHandNameToNum( const str &side ) +{ + if ( !side.length() ) + { + gi.DPrintf( "WeaponHandNameToNum : Weapon hand not specified\n" ); + return WEAPON_ERROR; + } + + if ( !side.icmp( "righthand" ) || !side.icmp( "right" ) ) + { + return WEAPON_RIGHT; + } + else if ( !side.icmp( "lefthand" ) || !side.icmp( "left" ) ) + { + return WEAPON_LEFT; + } + else if ( !side.icmp( "dualhand" ) || !side.icmp( "dual" ) ) + { + return WEAPON_DUAL; + } + else + { + return (weaponhand_t)atoi( side ); + } +} + +const char *WeaponHandNumToName( weaponhand_t hand ) +{ + switch( hand ) + { + case WEAPON_RIGHT: + return "righthand"; + case WEAPON_LEFT: + return "lefthand"; + case WEAPON_DUAL: + return "dualhand"; + case WEAPON_ANY: + return "anyhand"; + default: + return "Invalid Hand"; + } +} + +firemode_t WeaponModeNameToNum( const str &mode ) +{ + if ( !mode.length() ) + { + gi.DPrintf( "WeaponModeNameToNum : Weapon mode not specified\n" ); + return FIRE_ERROR; + } + + if ( !mode.icmp( "primary" ) ) + { + return FIRE_MODE1; + } + + if ( !mode.icmp( "alternate" ) ) + { + return FIRE_MODE2; + } + + if ( !mode.icmp( "MODE1" ) || !mode.icmp( "FIRE_MODE1" ) ) + { + return FIRE_MODE1; + } + + if ( !mode.icmp( "MODE2" ) || !mode.icmp( "FIRE_MODE2" ) ) + { + return FIRE_MODE2; + } + + if ( !mode.icmp( "MODE3" ) || !mode.icmp( "FIRE_MODE3" ) ) + { + return FIRE_MODE3; + } + + return (firemode_t)atoi( mode ); +} + +void G_DebugTargets( Entity *e, const str &from ) +{ + gi.DPrintf( "DEBUGTARGETS:%s ", from.c_str() ); + + if ( e->TargetName() && strlen( e->TargetName() ) ) + { + gi.DPrintf( "Targetname=\"%s\"\n", e->TargetName() ); + } + else + { + gi.DPrintf( "Targetname=\"None\"\n" ); + } + + if ( e->Target() && strlen( e->Target() ) ) + { + gi.DPrintf( "Target=\"%s\"\n", e->Target() ); + } + else + { + gi.DPrintf( "Target=\"None\"\n" ); + } +} + +void G_DebugDamage( float damage, Entity *victim, Entity *attacker, Entity *inflictor ) +{ + gi.DPrintf( "Victim:%s Attacker:%s Inflictor:%s Damage:%f\n", victim->getClassname(), attacker->getClassname(), inflictor->getClassname(), damage ); +} + +void G_FadeOut( float delaytime ) +{ + // Make sure we are not already faded or fading out + + if ( ( level.m_fade_type == fadeout ) && ( level.m_fade_time_start > 0.0f ) ) + { + float alpha; + + alpha = 1.0f - ( level.m_fade_time / level.m_fade_time_start ); + + if ( alpha > 0.0f ) + return; + } + + // Fade the screen out + level.m_fade_color = Vector( 0.0f, 0.0f, 0.0f ); + level.m_fade_alpha = 1.0f; + level.m_fade_time = delaytime; + level.m_fade_time_start = delaytime; + level.m_fade_type = fadeout; + level.m_fade_style = alphablend; +} + +void G_AutoFadeIn( void ) +{ + level.m_fade_time_start = 1; + level.m_fade_time = 1; + level.m_fade_color[0] = 0; + level.m_fade_color[1] = 0; + level.m_fade_color[2] = 0; + level.m_fade_alpha = 1; + level.m_fade_type = fadein; + level.m_fade_style = alphablend; +} + +void G_ClearFade( void ) +{ + level.m_fade_time = -1; + level.m_fade_type = fadein; +} + +void G_FadeSound( float delaytime ) +{ + float time; + + // Fade the screen out + time = delaytime * 1000.0f; + gi.SendServerCommand( NULL, va( "fadesound %0.2f", time ) ); +} + +// +// restarts the game after delaytime +// +void G_RestartLevelWithDelay( float delaytime ) +{ + int i; + + if ( multiplayerManager.inMultiplayer() ) + return; + + if ( level.died_already ) + return; + + level.died_already = true; + + // Restart the level soon + for( i = 0; i < game.maxclients; i++ ) + { + if ( g_entities[ i ].inuse ) + { + if ( g_entities[ i ].entity ) + { + g_entities[ i ].entity->PostEvent( EV_Player_Respawn, delaytime ); + } + } + } +} + +void G_MissionFailed( const str& reason ) +{ + str playerDeathThread; + + + // Make sure we haven't already failed the mission + + if ( level.mission_failed ) + return; + + // Make the music system play the failure music for this level + + ChangeMusic( "failure", "normal", true ); + + // Set our failure reason in the config string + gi.failedcondition( reason.c_str() ); + + playerDeathThread = level.getPlayerDeathThread(); + + if ( ( strnicmp( reason.c_str(), "PlayerKilled", strlen( "PlayerKilled" ) ) == 0 ) && ( playerDeathThread.length() ) ) + { + ExecuteThread( playerDeathThread, true, NULL ); + } + else + { + G_FinishMissionFailed(); + } +} + +void G_FinishMissionFailed( void ) +{ + Entity* entity; + Player* player; + + // Get the player + + entity = g_entities[ 0 ].entity; + + if ( !entity->isSubclassOf( Player ) ) + return; + + player = (Player *)entity; + + // Fade everything out + G_FadeOut( 1.0f ); + G_FadeSound( 1.0f ); + + // Fail the mission + + player->setMissionFailed(); + + level.mission_failed = true; + level.playerfrozen = true; + + // Get out of letterbox mode if we are in it + + if ( level.m_letterbox_dir == letterbox_in ) + { + level.m_letterbox_time_start = 0.5f; + level.m_letterbox_dir = letterbox_out; + + level.m_letterbox_time = 0.5f; + level.m_letterbox_fraction = 1.0f/8.0f; + } +} + +void G_StartCinematic( void ) +{ + Entity *entity; + + level.cinematic = true; + gi.cvar_set( "sv_cinematic", "1" ); + + entity = g_entities[ 0 ].entity; + + if ( entity->isSubclassOf( Player ) ) + { + Player *player = (Player *)entity; + + player->cinematicStarted(); + } +} + +void G_StopCinematic( void ) +{ + Entity *entity; + + // clear out the skip thread + world->skipthread = ""; + level.cinematic = false; + gi.cvar_set( "sv_cinematic", "0" ); + + entity = g_entities[ 0 ].entity; + + if ( entity->isSubclassOf( Player ) ) + { + Player *player = (Player *)entity; + + player->cinematicStopped(); + } +} + +int G_NumClients( void ) +{ + int i,count=0; + for( i = 0; i < maxclients->integer; i++ ) + { + gentity_t *ent = g_entities + i; + if ( !ent->inuse || !ent->client || !ent->entity ) + { + continue; + } + count++; + } + return count; +} + +//---------------------------------------------------------------- +// Name: G_DrawXYBox +// Class: None +// +// Description: draws a box in the xy plane +// +// Parameters: +// Vector center -- the point about which the box is centered +// float width -- the width of the box +// float r, g, b -- the color values for the box +// float alpha -- the amount of alpha applied to the box +// +// Returns: None +//---------------------------------------------------------------- +void G_DrawXYBox(const Vector ¢er, float width, float r, float g, float b, float alpha ) +{ + float squareSize=width/2.0f; + Vector topLeftPoint = center + Vector(-squareSize, -squareSize, 0); + Vector topRightPoint = center + Vector(+squareSize, -squareSize, 0); + Vector bottomRightPoint = center + Vector(+squareSize, +squareSize, 0); + Vector bottomLeftPoint = center + Vector(-squareSize, +squareSize, 0); + G_DebugLine( topLeftPoint, topRightPoint, r, g, b, alpha ); + G_DebugLine( topRightPoint, bottomRightPoint, r, g, b, alpha ); + G_DebugLine( bottomRightPoint, bottomLeftPoint, r, g, b, alpha ); + G_DebugLine( bottomLeftPoint, topLeftPoint, r, g, b, alpha ); +} + +float G_GetDatabaseFloat( const str &prefix, const str &objectName, const str &varName ) +{ + GameplayManager *gpm; + str gameplayObjectName; + + // Get the gameplay manager + + gpm = GameplayManager::getTheGameplayManager(); + + if ( !gpm ) + return 0.0f; + + // Build the object name + + gameplayObjectName = prefix + objectName; + + return gpm->getFloatValue( gameplayObjectName, varName ); +} + +str G_GetDatabaseString( const str &prefix, const str &objectName, const str &varName ) +{ + GameplayManager *gpm; + str gameplayObjectName; + + // Get the gameplay manager + + gpm = GameplayManager::getTheGameplayManager(); + + if ( !gpm ) + return 0.0f; + + // Build the object name + + gameplayObjectName = prefix + objectName; + + return gpm->getStringValue( gameplayObjectName, varName ); +} diff --git a/dlls/game/g_utils.h b/dlls/game/g_utils.h new file mode 100644 index 0000000..d1cbfa5 --- /dev/null +++ b/dlls/game/g_utils.h @@ -0,0 +1,314 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/g_utils.h $ +// $Revision:: 24 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __G_UTILS_H__ +#define __G_UTILS_H__ + +class Archiver; + +void G_ArchiveEdict( Archiver &arc, gentity_t *edict ); + +#include "entity.h" + +int G_FindConfigstringIndex( const char *name, int start, int max, qboolean create ); + +void G_AllocDebugLines( void ); +void G_DeAllocDebugLines( void ); + +void G_TouchTriggers (Entity *ent); +void G_TouchTeleporters (Entity *ent); +void G_TouchSolids (Entity *ent); + +Entity *G_FindClass( const Entity *ent, const char *classname ); +//Entity *G_NextEntity( const Entity *ent ); + +void G_CalcBoundsOfMove( const Vector &start, const Vector &end, const Vector &mins, const Vector &maxs, Vector *minbounds, Vector *maxbounds ); + +void G_ShowTrace( const trace_t *trace, gentity_t *passent, const char *reason, Vector &start, Vector &end ); +trace_t G_Trace( const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, const Entity *passent, int contentmask, qboolean cylindrical, const char *reason, qboolean fulltrace = false ); +trace_t G_Trace( vec3_t start, const vec3_t mins, const vec3_t maxs, vec3_t end, gentity_t *passent, int contentmask, qboolean cylindrical, const char *reason, qboolean fulltrace = false ); +trace_t G_FullTrace( const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, Entity *passent, int contentmask, qboolean cylindrical, const char *reason ); +trace_t G_FullTrace( vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, gentity_t *passent, int contentmask, qboolean cylindrical, const char *reason ); +void G_TraceEntities( const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, Container*victimlist, int contentmask ); + +void SelectSpawnPoint( Vector &org, Vector &angles, str &thread ); + +Entity *G_FindTarget( Entity *ent, const char *name ); +Entity *G_NextEntity( const Entity *ent ); + +qboolean M_CheckBottom( Entity *ent ); + +Vector G_GetMovedir( float angle ); +qboolean KillBox( Entity *ent ); +qboolean IsNumeric( const char *str ); + +Entity *findradius( const Entity *startent, const Vector &org, float rad ); +Entity *findclientsinradius( const Entity *startent, const Vector &org, float rad ); + +Vector G_CalculateImpulse( const Vector &start, const Vector &end, float speed, float gravity ); +Vector G_PredictPosition( const Vector &start, const Vector &target, const Vector &targetvelocity, float speed ); + +qboolean G_LoadAndExecScript( const char *filename, const char *label = NULL, qboolean quiet = qfalse ); +CThread *ExecuteThread( const str &thread_name, qboolean start = true, Entity *currentEnt = NULL ); + +int MOD_NameToNum( const str &meansOfDeath ); +const char *MOD_NumToName( int meansOfDeath ); +qboolean MOD_matches( int incoming_damage, int damage_type ); + +int Soundtype_string_to_int( const str &soundtype_string ); +int Context_string_to_int( const str& context_string ); + +void G_MissionFailed( const str& reason ); +void G_FinishMissionFailed( void ); +void G_FadeOut( float delaytime ); +void G_FadeSound( float delaytime ); +void G_RestartLevelWithDelay( float delaytime ); +void G_AutoFadeIn( void ); +void G_ClearFade( void ); +void G_StartCinematic( void ); +void G_StopCinematic( void ); +int G_NumClients( void ); + +void G_DrawXYBox( const Vector ¢er, float width, float red, float green, float blue, float alpha ); + +// copy the entity exactly including all its children +void CloneEntity( Entity * dest, const Entity * src ); + +qboolean OnSameTeam( const Entity *ent1, const Entity *ent2 ); + +// +// caching commands +// +void CacheResource( const char * stuff, Entity * ent = NULL ); +void G_CacheStateMachineAnims( Entity *ent, const char *stateMachineName ); +int modelIndex( const char *mdl ); + +void G_SetTrajectory( gentity_t *ent, const vec3_t org ); +void G_SetConstantLight + ( + unsigned int * constantlight, + const float * red, + const float * green, + const float * blue, + const float * radius, + const int * lightstyle = NULL + ); + +void ChangeMusic + ( + const char *current, + const char *fallback, + qboolean force + ); + +void ChangeMusicVolume + ( + float volume, + float fade_time + ); + +void RestoreMusicVolume + ( + float fade_time + ); + +void G_AllowMusicDucking( bool allowMusicDucking ); +void G_AllowActionMusic( bool allowActionMusic ); + +void ChangeSoundtrack + ( + const char * soundtrack + ); + +void RestoreSoundtrack + ( + void + ); + +void G_BroadcastSound( Entity *ent, const Vector &origin, float radius = SOUND_RADIUS, int soundType = SOUNDTYPE_GENERAL); +void G_BroadcastAlert( Entity *ent, const Vector &orignin, const Vector &enemy, float radius = SOUND_RADIUS ); + +//================================================================== +// +// Inline functions +// +//================================================================== + +/* +================= +G_GetEntity + +Takes an index to an entity and returns pointer to it. +================= +*/ + +inline Entity *G_GetEntity + ( + int entnum + ) + + { + if ( ( entnum < 0 ) || ( entnum >= globals.max_entities ) ) + { + gi.Error( ERR_DROP, "G_GetEntity: %d out of valid range.", entnum ); + } + + return ( Entity * )g_entities[ entnum ].entity; + } + +/* +================= +G_Random + +Returns a number from 0<= num < 1 + +random() +================= +*/ + +inline float G_Random + ( + void + ) + + { + return ( ( float )( rand() & 0x7fff ) ) / ( ( float )0x8000 ); + } + +/* +================= +G_Random + +Returns a number from 0 <= num < n + +random() +================= +*/ + +inline float G_Random + ( + float n + ) + + { + return G_Random() * n; + } + +/* +================= +G_CRandom + +Returns a number from -1 <= num < 1 + +crandom() +================= +*/ + +inline float G_CRandom + ( + void + ) + + { + return G_Random( 2 ) - 1.0f; + } + +/* +================= +G_CRandom + +Returns a number from -n <= num < n + +crandom() +================= +*/ + +inline float G_CRandom + ( + float n + ) + + { + return G_CRandom() * n; + } + +/* +================= +G_FixSlashes + +Converts all backslashes in a string to forward slashes. +Used to make filenames consistant. +================= +*/ + +inline str G_FixSlashes + ( + const char *filename + ) + + { + int i; + int len; + str text; + + if ( filename ) + { + // Convert all forward slashes to back slashes + text = filename; + len = text.length(); + for( i = 0; i < len; i++ ) + { + if ( text[ i ] == '\\' ) + { + text[ i ] = '/'; + } + } + } + + return text; + } + +typedef enum + { + WEAPON_RIGHT, + WEAPON_LEFT, + WEAPON_DUAL, + WEAPON_ANY, + WEAPON_ERROR + } weaponhand_t; + +#define NUM_ACTIVE_WEAPONS WEAPON_ANY + +typedef enum + { + FIRE_MODE1, + FIRE_MODE2, + FIRE_MODE3, + MAX_FIREMODES, + FIRE_ERROR + } firemode_t; + +firemode_t WeaponModeNameToNum( const str &mode ); +const char *WeaponHandNumToName( weaponhand_t hand ); +weaponhand_t WeaponHandNameToNum( const str &side ); +void G_DebugTargets( Entity *e, const str &from ); +void G_DebugDamage( float damage, Entity *victim, Entity *attacker, Entity *inflictor ); + +float G_GetDatabaseFloat( const str &prefix, const str &objectName, const str &varName ); +str G_GetDatabaseString( const str &prefix, const str &objectName, const str &varName ); + +#endif /* g_utils.h */ diff --git a/dlls/game/game.cpp b/dlls/game/game.cpp new file mode 100644 index 0000000..bf5ff6c --- /dev/null +++ b/dlls/game/game.cpp @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/game.cpp $ +// $Revision:: 4 $ +// $Date:: 10/08/02 8:27p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "game.h" + +Game game; + +CLASS_DECLARATION( Class, Game, NULL ) +{ + { NULL, NULL } +}; + +void Game::Init( void ) +{ + clients = NULL; + + autosaved = false; + + maxentities = 0; + maxclients = 0; +} + +void Game::Archive( Archiver &arc ) +{ + int i; + + Class::Archive( arc ); + + if ( arc.Loading() ) + { + G_AllocGameData(); + } + + for( i = 0; i < maxclients; i++ ) + { + arc.ArchiveRaw( &clients[ i ], sizeof( gclient_t ) ); + } + + arc.ArchiveBoolean( &autosaved ); + + arc.ArchiveInteger( &maxentities ); + arc.ArchiveInteger( &maxclients ); +} diff --git a/dlls/game/game.def b/dlls/game/game.def new file mode 100644 index 0000000..4461243 --- /dev/null +++ b/dlls/game/game.def @@ -0,0 +1,2 @@ +EXPORTS + GetGameAPI diff --git a/dlls/game/game.dsp b/dlls/game/game.dsp new file mode 100644 index 0000000..f78aedb --- /dev/null +++ b/dlls/game/game.dsp @@ -0,0 +1,4459 @@ +# Microsoft Developer Studio Project File - Name="game" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=game - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "game.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "game.mak" CFG="game - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "game - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "game - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "game - Win32 Demo Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "game - Win32 VTune" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "game - Win32 Intel Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "game - Win32 Release CDROM" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/EF2/game", FYHAAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "game - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "./Release" +# PROP Intermediate_Dir "./Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /Zi /O2 /I "..\..\Shared" /I "..\..\DLLs" /I "..\..\Executable" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /D "MISSIONPACK" /D "ENABLE_ALTROUTING" /D "MSVC_BUILD" /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /dll /map /machine:I386 /out:"../../Executable/Release/gamex86.dll" +# SUBTRACT LINK32 /debug + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "../Debug" +# PROP Intermediate_Dir "./Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "..\..\Shared" /I "..\..\DLLs" /I "..\..\Executable" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /D "MISSIONPACK" /D "ENABLE_ALTROUTING" /D "MSVC_BUILD" /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo /o"../../Executable/Debug/game.bsc" +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /dll /pdb:"Debug/gamex86.pdb" /debug /machine:I386 /out:"../../Executable/Debug/gamex86.dll" /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "game___Win32_Demo_Release" +# PROP BASE Intermediate_Dir "game___Win32_Demo_Release" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Demo_Release" +# PROP Intermediate_Dir "Demo_Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /Zi /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /FR /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /Zi /O2 /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /D "NDEBUG" /D "WIN32" /D "PRE_RELEASE_DEMO" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib winmm.lib /nologo /dll /map /machine:I386 /out:"../Release/gamex86.dll" +# SUBTRACT BASE LINK32 /debug +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /dll /map /machine:I386 /out:"../Demo_Release/gamex86.dll" +# SUBTRACT LINK32 /debug + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "game___Win32_VTune" +# PROP BASE Intermediate_Dir "game___Win32_VTune" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "VTune" +# PROP Intermediate_Dir "VTune" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "..\..\Shared" /I "..\..\DLLs" /I "..\..\Executable" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /FR /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W4 /Gm /GX /Zi /O2 /I "..\..\Shared" /I "..\..\DLLs" /I "..\..\Executable" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /D "MISSIONPACK" /D "ENABLE_ALTROUTING" /D "MSVC_BUILD" /FR /YX /FD /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib winmm.lib /nologo /dll /debug /machine:I386 /out:"../../Executable/Debug/gamex86.dll" /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /dll /debug /machine:I386 /out:"../../Executable/VTune/gamex86.dll" /pdbtype:sept + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "game___Win32_Intel_Release" +# PROP BASE Intermediate_Dir "game___Win32_Intel_Release" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "./IntelRelease" +# PROP Intermediate_Dir "./IntelRelease" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /Zi /O2 /I "..\..\Shared" /I "..\..\DLLs" /I "..\..\Executable" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /FR /FD /c +# SUBTRACT BASE CPP /YX +# ADD CPP /nologo /MT /W3 /GX /Zi /O2 /I "..\..\Shared" /I "..\..\DLLs" /I "..\..\Executable" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /D "_USE_INTEL_COMPILER" /D "MISSIONPACK" /D "ENABLE_ALTROUTING" /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib winmm.lib /nologo /dll /map /machine:I386 /out:"../../Executable/Release/gamex86.dll" +# SUBTRACT BASE LINK32 /debug +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /dll /map /machine:I386 /out:"../../Executable/IntelRelease/gamex86.dll" +# SUBTRACT LINK32 /debug + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "game___Win32_Release_CDROM0" +# PROP BASE Intermediate_Dir "game___Win32_Release_CDROM0" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /Zi /O2 /I "..\..\Shared" /I "..\..\DLLs" /I "..\..\Executable" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /D "MISSIONPACK" /D "ENABLE_ALTROUTING" /D "MSVC_BUILD" /FR /FD /c +# SUBTRACT BASE CPP /YX +# ADD CPP /nologo /MT /W3 /GX /Zi /O2 /I "..\..\Shared" /I "..\..\DLLs" /I "..\..\Executable" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /D "MISSIONPACK" /D "ENABLE_ALTROUTING" /D "MSVC_BUILD" /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib winmm.lib /nologo /dll /map /machine:I386 /out:"../../Executable/Release/gamex86.dll" +# SUBTRACT BASE LINK32 /debug +# ADD LINK32 kernel32.lib user32.lib winmm.lib /nologo /dll /map /machine:I386 /out:"../../Executable/Release/gamex86.dll" +# SUBTRACT LINK32 /debug + +!ENDIF + +# Begin Target + +# Name "game - Win32 Release" +# Name "game - Win32 Debug" +# Name "game - Win32 Demo Release" +# Name "game - Win32 VTune" +# Name "game - Win32 Intel Release" +# Name "game - Win32 Release CDROM" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Group "Behaviors" + +# PROP Default_Filter ".cpp" +# Begin Source File + +SOURCE=.\changePosture.cpp +# End Source File +# Begin Source File + +SOURCE=.\closeInOnEnemy.cpp +# End Source File +# Begin Source File + +SOURCE=.\closeInOnEnemyWhileFiringWeapon.cpp +# End Source File +# Begin Source File + +SOURCE=.\closeInOnPlayer.cpp +# End Source File +# Begin Source File + +SOURCE=.\corridorCombatWithRangedWeapon.cpp +# End Source File +# Begin Source File + +SOURCE=.\coverCombatWithRangedWeapon.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\doAttack.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\generalCombatWithMeleeWeapon.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\generalCombatWithRangedWeapon.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gotoCurrentHelperNode.cpp +# End Source File +# Begin Source File + +SOURCE=.\gotoHelperNode.cpp +# End Source File +# Begin Source File + +SOURCE=.\gotoHelperNodeEX.cpp +# End Source File +# Begin Source File + +SOURCE=.\gotoHelperNodeNearestEnemy.cpp +# End Source File +# Begin Source File + +SOURCE=.\healGroupMember.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\holdPosition.cpp +# End Source File +# Begin Source File + +SOURCE=.\MoveRandomDirection.cpp +# End Source File +# Begin Source File + +SOURCE=.\patrol.cpp +# End Source File +# Begin Source File + +SOURCE=.\PlayAnim.cpp +# End Source File +# Begin Source File + +SOURCE=.\rangedCombatWithWeapon.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\rotateToEntity.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\selectBestWeapon.cpp +# End Source File +# Begin Source File + +SOURCE=.\snipeEnemy.cpp +# End Source File +# Begin Source File + +SOURCE=.\stationaryFireCombat.cpp +# End Source File +# Begin Source File + +SOURCE=.\stationaryFireCombatEX.cpp +# End Source File +# Begin Source File + +SOURCE=.\suppressionFireCombat.cpp +# End Source File +# Begin Source File + +SOURCE=.\talk.cpp +# End Source File +# Begin Source File + +SOURCE=.\teleportToEntity.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\teleportToPosition.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\torsoAimAndFireWeapon.cpp +# End Source File +# Begin Source File + +SOURCE=.\useAlarm.cpp +# End Source File +# Begin Source File + +SOURCE=.\watchEntity.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\watchEntityEX.cpp +# End Source File +# Begin Source File + +SOURCE=.\work.cpp +# End Source File +# End Group +# Begin Group "Multiplayer" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\mp_awardsystem.cpp +# End Source File +# Begin Source File + +SOURCE=.\mp_manager.cpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modeBase.cpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modeCtf.cpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modeDm.cpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modeTeamBase.cpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modeTeamDm.cpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modifiers.cpp +# End Source File +# Begin Source File + +SOURCE=.\mp_team.cpp +# End Source File +# End Group +# Begin Group "botlib source" + +# PROP Default_Filter ".cpp" +# Begin Source File + +SOURCE=.\ai_chat.cpp +# End Source File +# Begin Source File + +SOURCE=.\ai_cmd.cpp +# End Source File +# Begin Source File + +SOURCE=.\ai_dmnet.cpp +# End Source File +# Begin Source File + +SOURCE=.\ai_dmq3.cpp +# End Source File +# Begin Source File + +SOURCE=.\ai_main.cpp +# End Source File +# Begin Source File + +SOURCE=.\ai_team.cpp +# End Source File +# Begin Source File + +SOURCE=.\ai_vcmd.cpp +# End Source File +# Begin Source File + +SOURCE=.\be_ea.h +# End Source File +# Begin Source File + +SOURCE=.\g_bot.cpp +# End Source File +# End Group +# Begin Source File + +SOURCE=.\_pch_cpp.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yc"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yc"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yc"_pch_cpp.h" +# ADD CPP /Yc"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yc"_pch_cpp.h" +# ADD CPP /Yc"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\actor.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\actor_combatsubsystem.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\actor_enemymanager.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\actor_headwatcher.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\actor_locomotion.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\actor_posturecontroller.cpp +# End Source File +# Begin Source File + +SOURCE=.\actor_sensoryperception.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\actorgamecomponents.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\actorstrategies.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\actorutil.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ammo.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\animate.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\archive.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\armor.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\beam.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\behavior.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\behaviors_general.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\behaviors_specific.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\bg_misc.c + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX /Yc + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\bg_pmove.c + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX /Yc + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\body.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\bspline.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\camera.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\CameraPath.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\characterstate.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\CinematicArmature.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\class.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\compiler.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\DamageModification.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\debuglines.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\decals.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\dispenser.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\doors.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\earthquake.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\entity.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\equipment.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\explosion.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\FollowPath.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\FollowPathToEntity.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\FollowPathToPoint.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_main.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_phys.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_spawn.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\g_utils.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\game.def +# End Source File +# Begin Source File + +SOURCE=.\gamecmds.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gamecvars.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Shared\qcommon\gameplaydatabase.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Shared\qcommon\gameplayformulamanager.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Shared\qcommon\gameplaymanager.cpp +# End Source File +# Begin Source File + +SOURCE=.\gamescript.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gibs.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\globalcmd.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\GoDirectlyToPoint.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\goo.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\gravpath.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\groupcoordinator.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\health.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\helper_node.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\interpreter.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\inventoryitem.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\ipfilter.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\item.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\level.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\lexer.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\light.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\listener.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\misc.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\mover.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\nature.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\navigate.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\object.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Shared\qcommon\output.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\path.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\player.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\player_combat.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\player_util.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\playerheuristics.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\PlayerStart.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\portal.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\powerups.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\program.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\puzzleobject.cpp +# End Source File +# Begin Source File + +SOURCE=.\q_math.c + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\q_mathsys.c + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\q_shared.c + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\RageAI.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\script.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scriptmaster.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scriptslave.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\scriptvariable.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\sentient.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\shrapnelbomb.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\soundman.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\spawners.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\specialfx.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\stationaryvehicle.cpp +# End Source File +# Begin Source File + +SOURCE=.\steering.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\str.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\teammateroster.cpp +# End Source File +# Begin Source File + +SOURCE=.\trigger.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\UseData.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\vehicle.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\viewthing.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\waypoints.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\weapon.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\WeaponDualWield.cpp +# End Source File +# Begin Source File + +SOURCE=.\weaputils.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Executable\win32\win_bounds.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# SUBTRACT BASE CPP /YX +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\worldspawn.cpp + +!IF "$(CFG)" == "game - Win32 Release" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Debug" + +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Demo Release" + +!ELSEIF "$(CFG)" == "game - Win32 VTune" + +!ELSEIF "$(CFG)" == "game - Win32 Intel Release" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ELSEIF "$(CFG)" == "game - Win32 Release CDROM" + +# ADD BASE CPP /Yu"_pch_cpp.h" +# ADD CPP /Yu"_pch_cpp.h" + +!ENDIF + +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Group "BehaviorHeaders" + +# PROP Default_Filter ".hpp" +# Begin Source File + +SOURCE=.\changePosture.hpp +# End Source File +# Begin Source File + +SOURCE=.\closeInOnEnemy.hpp +# End Source File +# Begin Source File + +SOURCE=.\closeInOnEnemyWhileFiringWeapon.hpp +# End Source File +# Begin Source File + +SOURCE=.\closeInOnPlayer.hpp +# End Source File +# Begin Source File + +SOURCE=.\corridorCombatWithRangedWeapon.hpp +# End Source File +# Begin Source File + +SOURCE=.\coverCombatWithRangedWeapon.hpp +# End Source File +# Begin Source File + +SOURCE=.\doAttack.hpp +# End Source File +# Begin Source File + +SOURCE=.\generalCombatWithMeleeWeapon.hpp +# End Source File +# Begin Source File + +SOURCE=.\generalCombatWithRangedWeapon.hpp +# End Source File +# Begin Source File + +SOURCE=.\gotoCurrentHelperNode.hpp +# End Source File +# Begin Source File + +SOURCE=.\gotoHelperNode.hpp +# End Source File +# Begin Source File + +SOURCE=.\gotoHelperNodeEX.hpp +# End Source File +# Begin Source File + +SOURCE=.\gotoHelperNodeNearestEnemy.hpp +# End Source File +# Begin Source File + +SOURCE=.\healGroupMember.hpp +# End Source File +# Begin Source File + +SOURCE=.\holdPosition.hpp +# End Source File +# Begin Source File + +SOURCE=.\MoveRandomDirection.hpp +# End Source File +# Begin Source File + +SOURCE=.\patrol.hpp +# End Source File +# Begin Source File + +SOURCE=.\PlayAnim.hpp +# End Source File +# Begin Source File + +SOURCE=.\rangedCombatWithWeapon.hpp +# End Source File +# Begin Source File + +SOURCE=.\rotateToEntity.hpp +# End Source File +# Begin Source File + +SOURCE=.\selectBestWeapon.hpp +# End Source File +# Begin Source File + +SOURCE=.\snipeEnemy.hpp +# End Source File +# Begin Source File + +SOURCE=.\stationaryFireCombat.hpp +# End Source File +# Begin Source File + +SOURCE=.\stationaryFireCombatEX.hpp +# End Source File +# Begin Source File + +SOURCE=.\suppressionFireCombat.hpp +# End Source File +# Begin Source File + +SOURCE=.\talk.hpp +# End Source File +# Begin Source File + +SOURCE=.\teleportToEntity.hpp +# End Source File +# Begin Source File + +SOURCE=.\teleportToPosition.hpp +# End Source File +# Begin Source File + +SOURCE=.\torsoAimAndFireWeapon.hpp +# End Source File +# Begin Source File + +SOURCE=.\useAlarm.hpp +# End Source File +# Begin Source File + +SOURCE=.\watchEntity.hpp +# End Source File +# Begin Source File + +SOURCE=.\watchEntityEX.hpp +# End Source File +# Begin Source File + +SOURCE=.\work.hpp +# End Source File +# End Group +# Begin Group "Multiplayer Headers" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\mp_awardsystem.hpp +# End Source File +# Begin Source File + +SOURCE=.\mp_manager.hpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modeBase.hpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modeCtf.hpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modeDm.hpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modeTeamBase.hpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modeTeamDm.hpp +# End Source File +# Begin Source File + +SOURCE=.\mp_modifiers.hpp +# End Source File +# Begin Source File + +SOURCE=.\mp_shared.hpp +# End Source File +# Begin Source File + +SOURCE=.\mp_team.hpp +# End Source File +# End Group +# Begin Group "botlib header files" + +# PROP Default_Filter ".h" +# Begin Source File + +SOURCE=.\ai_chat.h +# End Source File +# Begin Source File + +SOURCE=.\ai_cmd.h +# End Source File +# Begin Source File + +SOURCE=.\ai_dmnet.h +# End Source File +# Begin Source File + +SOURCE=.\ai_dmq3.h +# End Source File +# Begin Source File + +SOURCE=.\ai_main.h +# End Source File +# Begin Source File + +SOURCE=.\ai_team.h +# End Source File +# Begin Source File + +SOURCE=.\ai_vcmd.h +# End Source File +# Begin Source File + +SOURCE=.\be_aas.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_char.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_chat.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_gen.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_goal.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_move.h +# End Source File +# Begin Source File + +SOURCE=.\be_ai_weap.h +# End Source File +# Begin Source File + +SOURCE=.\botlib.h +# End Source File +# Begin Source File + +SOURCE=.\botmenudef.h +# End Source File +# Begin Source File + +SOURCE=.\chars.h +# End Source File +# Begin Source File + +SOURCE=.\inv.h +# End Source File +# Begin Source File + +SOURCE=.\match.h +# End Source File +# Begin Source File + +SOURCE=.\syn.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\_pch_cpp.h +# End Source File +# Begin Source File + +SOURCE=.\actor.h +# End Source File +# Begin Source File + +SOURCE=.\actor_combatsubsystem.h +# End Source File +# Begin Source File + +SOURCE=.\actor_enemymanager.h +# End Source File +# Begin Source File + +SOURCE=.\actor_headwatcher.h +# End Source File +# Begin Source File + +SOURCE=.\actor_locomotion.h +# End Source File +# Begin Source File + +SOURCE=.\actor_posturecontroller.hpp +# End Source File +# Begin Source File + +SOURCE=.\actor_sensoryperception.h +# End Source File +# Begin Source File + +SOURCE=.\actorgamecomponents.h +# End Source File +# Begin Source File + +SOURCE=.\actorincludes.h +# End Source File +# Begin Source File + +SOURCE=.\actorstrategies.h +# End Source File +# Begin Source File + +SOURCE=.\actorutil.h +# End Source File +# Begin Source File + +SOURCE=.\ammo.h +# End Source File +# Begin Source File + +SOURCE=.\animate.h +# End Source File +# Begin Source File + +SOURCE=.\archive.h +# End Source File +# Begin Source File + +SOURCE=.\armor.h +# End Source File +# Begin Source File + +SOURCE=.\beam.h +# End Source File +# Begin Source File + +SOURCE=.\behavior.h +# End Source File +# Begin Source File + +SOURCE=.\behaviors.h +# End Source File +# Begin Source File + +SOURCE=.\behaviors_general.h +# End Source File +# Begin Source File + +SOURCE=.\behaviors_specific.h +# End Source File +# Begin Source File + +SOURCE=.\bg_local.h +# End Source File +# Begin Source File + +SOURCE=.\bg_public.h +# End Source File +# Begin Source File + +SOURCE=.\bit_vector.h +# End Source File +# Begin Source File + +SOURCE=.\body.h +# End Source File +# Begin Source File + +SOURCE=.\bspline.h +# End Source File +# Begin Source File + +SOURCE=.\camera.h +# End Source File +# Begin Source File + +SOURCE=.\CameraPath.h +# End Source File +# Begin Source File + +SOURCE=.\characterstate.h +# End Source File +# Begin Source File + +SOURCE=.\CinematicArmature.h +# End Source File +# Begin Source File + +SOURCE=.\class.h +# End Source File +# Begin Source File + +SOURCE=.\compiler.h +# End Source File +# Begin Source File + +SOURCE=.\container.h +# End Source File +# Begin Source File + +SOURCE=.\DamageModification.hpp +# End Source File +# Begin Source File + +SOURCE=.\debuglines.h +# End Source File +# Begin Source File + +SOURCE=.\decals.h +# End Source File +# Begin Source File + +SOURCE=.\dispenser.hpp +# End Source File +# Begin Source File + +SOURCE=.\doors.h +# End Source File +# Begin Source File + +SOURCE=.\earthquake.h +# End Source File +# Begin Source File + +SOURCE=.\entity.h +# End Source File +# Begin Source File + +SOURCE=.\equipment.h +# End Source File +# Begin Source File + +SOURCE=.\explosion.h +# End Source File +# Begin Source File + +SOURCE=.\FollowPath.h +# End Source File +# Begin Source File + +SOURCE=.\FollowPathToEntity.h +# End Source File +# Begin Source File + +SOURCE=.\FollowPathToPoint.h +# End Source File +# Begin Source File + +SOURCE=.\g_local.h +# End Source File +# Begin Source File + +SOURCE=.\g_main.h +# End Source File +# Begin Source File + +SOURCE=.\g_phys.h +# End Source File +# Begin Source File + +SOURCE=.\g_public.h +# End Source File +# Begin Source File + +SOURCE=.\g_spawn.h +# End Source File +# Begin Source File + +SOURCE=.\g_utils.h +# End Source File +# Begin Source File + +SOURCE=.\game.h +# End Source File +# Begin Source File + +SOURCE=.\gamecmds.h +# End Source File +# Begin Source File + +SOURCE=.\gamecvars.h +# End Source File +# Begin Source File + +SOURCE=..\..\Shared\qcommon\gameplaydatabase.h +# End Source File +# Begin Source File + +SOURCE=..\..\Shared\qcommon\gameplayformulamanager.h +# End Source File +# Begin Source File + +SOURCE=..\..\Shared\qcommon\gameplaymanager.h +# End Source File +# Begin Source File + +SOURCE=.\gamescript.h +# End Source File +# Begin Source File + +SOURCE=.\gibs.h +# End Source File +# Begin Source File + +SOURCE=.\globalcmd.h +# End Source File +# Begin Source File + +SOURCE=.\GoDirectlyToPoint.h +# End Source File +# Begin Source File + +SOURCE=.\goo.h +# End Source File +# Begin Source File + +SOURCE=.\gravpath.h +# End Source File +# Begin Source File + +SOURCE=.\groupcoordinator.hpp +# End Source File +# Begin Source File + +SOURCE=.\health.h +# End Source File +# Begin Source File + +SOURCE=.\helper_node.h +# End Source File +# Begin Source File + +SOURCE=.\interpreter.h +# End Source File +# Begin Source File + +SOURCE=.\inventoryitem.h +# End Source File +# Begin Source File + +SOURCE=.\ipfilter.h +# End Source File +# Begin Source File + +SOURCE=.\item.h +# End Source File +# Begin Source File + +SOURCE=.\level.h +# End Source File +# Begin Source File + +SOURCE=.\lexer.h +# End Source File +# Begin Source File + +SOURCE=.\light.h +# End Source File +# Begin Source File + +SOURCE=.\Linklist.h +# End Source File +# Begin Source File + +SOURCE=.\listener.h +# End Source File +# Begin Source File + +SOURCE=.\misc.h +# End Source File +# Begin Source File + +SOURCE=.\mover.h +# End Source File +# Begin Source File + +SOURCE=.\nature.h +# End Source File +# Begin Source File + +SOURCE=.\navigate.h +# End Source File +# Begin Source File + +SOURCE=.\object.h +# End Source File +# Begin Source File + +SOURCE=..\..\Shared\qcommon\output.h +# End Source File +# Begin Source File + +SOURCE=.\path.h +# End Source File +# Begin Source File + +SOURCE=.\player.h +# End Source File +# Begin Source File + +SOURCE=.\playerheuristics.h +# End Source File +# Begin Source File + +SOURCE=.\PlayerStart.h +# End Source File +# Begin Source File + +SOURCE=.\portal.h +# End Source File +# Begin Source File + +SOURCE=.\powerups.h +# End Source File +# Begin Source File + +SOURCE=.\program.h +# End Source File +# Begin Source File + +SOURCE=.\puzzleobject.hpp +# End Source File +# Begin Source File + +SOURCE=.\q_shared.h +# End Source File +# Begin Source File + +SOURCE=..\..\Shared\qcommon\quaternion.h +# End Source File +# Begin Source File + +SOURCE=.\queue.h +# End Source File +# Begin Source File + +SOURCE=.\RageAI.h +# End Source File +# Begin Source File + +SOURCE=.\script.h +# End Source File +# Begin Source File + +SOURCE=.\scriptmaster.h +# End Source File +# Begin Source File + +SOURCE=.\scriptslave.h +# End Source File +# Begin Source File + +SOURCE=.\scriptvariable.h +# End Source File +# Begin Source File + +SOURCE=.\sentient.h +# End Source File +# Begin Source File + +SOURCE=.\shrapnelbomb.h +# End Source File +# Begin Source File + +SOURCE=.\soundman.h +# End Source File +# Begin Source File + +SOURCE=.\spawners.h +# End Source File +# Begin Source File + +SOURCE=.\specialfx.h +# End Source File +# Begin Source File + +SOURCE=.\stack.h +# End Source File +# Begin Source File + +SOURCE=.\stationaryvehicle.hpp +# End Source File +# Begin Source File + +SOURCE=.\steering.h +# End Source File +# Begin Source File + +SOURCE=.\str.h +# End Source File +# Begin Source File + +SOURCE=.\surfaceflags.h +# End Source File +# Begin Source File + +SOURCE=.\teammateroster.hpp +# End Source File +# Begin Source File + +SOURCE=.\trigger.h +# End Source File +# Begin Source File + +SOURCE=.\umap.h +# End Source File +# Begin Source File + +SOURCE=.\UseData.h +# End Source File +# Begin Source File + +SOURCE=.\vector.h +# End Source File +# Begin Source File + +SOURCE=.\vehicle.h +# End Source File +# Begin Source File + +SOURCE=.\viewthing.h +# End Source File +# Begin Source File + +SOURCE=.\waypoints.h +# End Source File +# Begin Source File + +SOURCE=.\weapon.h +# End Source File +# Begin Source File + +SOURCE=.\WeaponDualWield.h +# End Source File +# Begin Source File + +SOURCE=.\weaputils.h +# End Source File +# Begin Source File + +SOURCE=.\worldspawn.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/dlls/game/game.h b/dlls/game/game.h new file mode 100644 index 0000000..898da65 --- /dev/null +++ b/dlls/game/game.h @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/game.h $ +// $Revision:: 3 $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __GAME_H__ +#define __GAME_H__ + +#include "g_local.h" +#include "class.h" + +// +// this structure is left intact through an entire game +// it should be initialized at dll load time, and read/written to +// the server.ssv file for savegames +// + +class Game : public Class + { + public: + CLASS_PROTOTYPE( Game ); + + gclient_t *clients; // [maxclients] + qboolean autosaved; + + // store latched cvars here that we want to get at often + int maxclients; + int maxentities; + + Game() { Init(); } + + void Init( void ); + virtual void Archive( Archiver &arc ); + }; + +extern Game game; + +#endif /* !__GAME_H__ */ diff --git a/dlls/game/game.plg b/dlls/game/game.plg new file mode 100644 index 0000000..b805720 --- /dev/null +++ b/dlls/game/game.plg @@ -0,0 +1,551 @@ + + +
+

Build Log

+

+--------------------Configuration: game - Win32 Release-------------------- +

+

Command Lines

+Creating temporary file "C:\DOCUME~1\steven\LOCALS~1\Temp\RSP5D6.tmp" with contents +[ +/nologo /MT /W3 /GX /Zi /O2 /I "..\..\Shared" /I "..\..\DLLs" /I "..\..\Executable" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /D "MISSIONPACK" /D "ENABLE_ALTROUTING" /D "MSVC_BUILD" /FR"./Release/" /Fo"./Release/" /Fd"./Release/" /FD /c +"C:\projects\ef2\dlls\game\changePosture.cpp" +"C:\projects\ef2\dlls\game\closeInOnEnemy.cpp" +"C:\projects\ef2\dlls\game\closeInOnEnemyWhileFiringWeapon.cpp" +"C:\projects\ef2\dlls\game\closeInOnPlayer.cpp" +"C:\projects\ef2\dlls\game\corridorCombatWithRangedWeapon.cpp" +"C:\projects\ef2\dlls\game\coverCombatWithRangedWeapon.cpp" +"C:\projects\ef2\dlls\game\doAttack.cpp" +"C:\projects\ef2\dlls\game\generalCombatWithMeleeWeapon.cpp" +"C:\projects\ef2\dlls\game\generalCombatWithRangedWeapon.cpp" +"C:\projects\ef2\dlls\game\gotoCurrentHelperNode.cpp" +"C:\projects\ef2\dlls\game\gotoHelperNode.cpp" +"C:\projects\ef2\dlls\game\gotoHelperNodeEX.cpp" +"C:\projects\ef2\dlls\game\gotoHelperNodeNearestEnemy.cpp" +"C:\projects\ef2\dlls\game\healGroupMember.cpp" +"C:\projects\ef2\dlls\game\holdPosition.cpp" +"C:\projects\ef2\dlls\game\MoveRandomDirection.cpp" +"C:\projects\ef2\dlls\game\patrol.cpp" +"C:\projects\ef2\dlls\game\PlayAnim.cpp" +"C:\projects\ef2\dlls\game\rangedCombatWithWeapon.cpp" +"C:\projects\ef2\dlls\game\rotateToEntity.cpp" +"C:\projects\ef2\dlls\game\selectBestWeapon.cpp" +"C:\projects\ef2\dlls\game\snipeEnemy.cpp" +"C:\projects\ef2\dlls\game\stationaryFireCombat.cpp" +"C:\projects\ef2\dlls\game\stationaryFireCombatEX.cpp" +"C:\projects\ef2\dlls\game\suppressionFireCombat.cpp" +"C:\projects\ef2\dlls\game\talk.cpp" +"C:\projects\ef2\dlls\game\teleportToEntity.cpp" +"C:\projects\ef2\dlls\game\teleportToPosition.cpp" +"C:\projects\ef2\dlls\game\torsoAimAndFireWeapon.cpp" +"C:\projects\ef2\dlls\game\useAlarm.cpp" +"C:\projects\ef2\dlls\game\watchEntity.cpp" +"C:\projects\ef2\dlls\game\watchEntityEX.cpp" +"C:\projects\ef2\dlls\game\work.cpp" +"C:\projects\ef2\dlls\game\mp_awardsystem.cpp" +"C:\projects\ef2\dlls\game\mp_manager.cpp" +"C:\projects\ef2\dlls\game\mp_modeBase.cpp" +"C:\projects\ef2\dlls\game\mp_modeCtf.cpp" +"C:\projects\ef2\dlls\game\mp_modeDm.cpp" +"C:\projects\ef2\dlls\game\mp_modeTeamBase.cpp" +"C:\projects\ef2\dlls\game\mp_modeTeamDm.cpp" +"C:\projects\ef2\dlls\game\mp_modifiers.cpp" +"C:\projects\ef2\dlls\game\mp_team.cpp" +"C:\projects\ef2\dlls\game\ai_chat.cpp" +"C:\projects\ef2\dlls\game\ai_cmd.cpp" +"C:\projects\ef2\dlls\game\ai_dmnet.cpp" +"C:\projects\ef2\dlls\game\ai_dmq3.cpp" +"C:\projects\ef2\dlls\game\ai_main.cpp" +"C:\projects\ef2\dlls\game\ai_team.cpp" +"C:\projects\ef2\dlls\game\ai_vcmd.cpp" +"C:\projects\ef2\dlls\game\g_bot.cpp" +"C:\projects\ef2\dlls\game\actor_posturecontroller.cpp" +"C:\projects\ef2\dlls\game\bg_misc.c" +"C:\projects\ef2\dlls\game\bg_pmove.c" +"C:\projects\ef2\dlls\game\class.cpp" +"C:\projects\ef2\Shared\qcommon\gameplaydatabase.cpp" +"C:\projects\ef2\Shared\qcommon\gameplayformulamanager.cpp" +"C:\projects\ef2\Shared\qcommon\gameplaymanager.cpp" +"C:\projects\ef2\dlls\game\listener.cpp" +"C:\projects\ef2\Shared\qcommon\output.cpp" +"C:\projects\ef2\dlls\game\puzzleobject.cpp" +"C:\projects\ef2\dlls\game\q_math.c" +"C:\projects\ef2\dlls\game\q_mathsys.c" +"C:\projects\ef2\dlls\game\q_shared.c" +"C:\projects\ef2\dlls\game\stationaryvehicle.cpp" +"C:\projects\ef2\dlls\game\str.cpp" +"C:\projects\ef2\dlls\game\teammateroster.cpp" +"C:\projects\ef2\dlls\game\WeaponDualWield.cpp" +"C:\projects\ef2\Executable\win32\win_bounds.cpp" +] +Creating command line "snCl.exe @C:\DOCUME~1\steven\LOCALS~1\Temp\RSP5D6.tmp" +Creating temporary file "C:\DOCUME~1\steven\LOCALS~1\Temp\RSP5D7.tmp" with contents +[ +/nologo /MT /W3 /GX /Zi /O2 /I "..\..\Shared" /I "..\..\DLLs" /I "..\..\Executable" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /D "MISSIONPACK" /D "ENABLE_ALTROUTING" /D "MSVC_BUILD" /FR"./Release/" /Fp"./Release/game.pch" /Yu"_pch_cpp.h" /Fo"./Release/" /Fd"./Release/" /FD /c +"C:\projects\ef2\dlls\game\actor.cpp" +"C:\projects\ef2\dlls\game\actor_combatsubsystem.cpp" +"C:\projects\ef2\dlls\game\actor_enemymanager.cpp" +"C:\projects\ef2\dlls\game\actor_headwatcher.cpp" +"C:\projects\ef2\dlls\game\actor_locomotion.cpp" +"C:\projects\ef2\dlls\game\actor_sensoryperception.cpp" +"C:\projects\ef2\dlls\game\actorgamecomponents.cpp" +"C:\projects\ef2\dlls\game\actorstrategies.cpp" +"C:\projects\ef2\dlls\game\actorutil.cpp" +"C:\projects\ef2\dlls\game\ammo.cpp" +"C:\projects\ef2\dlls\game\animate.cpp" +"C:\projects\ef2\dlls\game\archive.cpp" +"C:\projects\ef2\dlls\game\armor.cpp" +"C:\projects\ef2\dlls\game\beam.cpp" +"C:\projects\ef2\dlls\game\behavior.cpp" +"C:\projects\ef2\dlls\game\behaviors_general.cpp" +"C:\projects\ef2\dlls\game\behaviors_specific.cpp" +"C:\projects\ef2\dlls\game\body.cpp" +"C:\projects\ef2\dlls\game\bspline.cpp" +"C:\projects\ef2\dlls\game\camera.cpp" +"C:\projects\ef2\dlls\game\CameraPath.cpp" +"C:\projects\ef2\dlls\game\characterstate.cpp" +"C:\projects\ef2\dlls\game\CinematicArmature.cpp" +"C:\projects\ef2\dlls\game\compiler.cpp" +"C:\projects\ef2\dlls\game\DamageModification.cpp" +"C:\projects\ef2\dlls\game\debuglines.cpp" +"C:\projects\ef2\dlls\game\decals.cpp" +"C:\projects\ef2\dlls\game\dispenser.cpp" +"C:\projects\ef2\dlls\game\doors.cpp" +"C:\projects\ef2\dlls\game\earthquake.cpp" +"C:\projects\ef2\dlls\game\entity.cpp" +"C:\projects\ef2\dlls\game\equipment.cpp" +"C:\projects\ef2\dlls\game\explosion.cpp" +"C:\projects\ef2\dlls\game\FollowPath.cpp" +"C:\projects\ef2\dlls\game\FollowPathToEntity.cpp" +"C:\projects\ef2\dlls\game\FollowPathToPoint.cpp" +"C:\projects\ef2\dlls\game\g_main.cpp" +"C:\projects\ef2\dlls\game\g_phys.cpp" +"C:\projects\ef2\dlls\game\g_spawn.cpp" +"C:\projects\ef2\dlls\game\g_utils.cpp" +"C:\projects\ef2\dlls\game\game.cpp" +"C:\projects\ef2\dlls\game\gamecmds.cpp" +"C:\projects\ef2\dlls\game\gamecvars.cpp" +"C:\projects\ef2\dlls\game\gamescript.cpp" +"C:\projects\ef2\dlls\game\gibs.cpp" +"C:\projects\ef2\dlls\game\globalcmd.cpp" +"C:\projects\ef2\dlls\game\GoDirectlyToPoint.cpp" +"C:\projects\ef2\dlls\game\goo.cpp" +"C:\projects\ef2\dlls\game\gravpath.cpp" +"C:\projects\ef2\dlls\game\groupcoordinator.cpp" +"C:\projects\ef2\dlls\game\health.cpp" +"C:\projects\ef2\dlls\game\helper_node.cpp" +"C:\projects\ef2\dlls\game\interpreter.cpp" +"C:\projects\ef2\dlls\game\inventoryitem.cpp" +"C:\projects\ef2\dlls\game\ipfilter.cpp" +"C:\projects\ef2\dlls\game\item.cpp" +"C:\projects\ef2\dlls\game\level.cpp" +"C:\projects\ef2\dlls\game\lexer.cpp" +"C:\projects\ef2\dlls\game\light.cpp" +"C:\projects\ef2\dlls\game\misc.cpp" +"C:\projects\ef2\dlls\game\mover.cpp" +"C:\projects\ef2\dlls\game\nature.cpp" +"C:\projects\ef2\dlls\game\navigate.cpp" +"C:\projects\ef2\dlls\game\object.cpp" +"C:\projects\ef2\dlls\game\path.cpp" +"C:\projects\ef2\dlls\game\player.cpp" +"C:\projects\ef2\dlls\game\player_combat.cpp" +"C:\projects\ef2\dlls\game\player_util.cpp" +"C:\projects\ef2\dlls\game\playerheuristics.cpp" +"C:\projects\ef2\dlls\game\PlayerStart.cpp" +"C:\projects\ef2\dlls\game\portal.cpp" +"C:\projects\ef2\dlls\game\powerups.cpp" +"C:\projects\ef2\dlls\game\program.cpp" +"C:\projects\ef2\dlls\game\RageAI.cpp" +"C:\projects\ef2\dlls\game\script.cpp" +"C:\projects\ef2\dlls\game\scriptmaster.cpp" +"C:\projects\ef2\dlls\game\scriptslave.cpp" +"C:\projects\ef2\dlls\game\scriptvariable.cpp" +"C:\projects\ef2\dlls\game\sentient.cpp" +"C:\projects\ef2\dlls\game\shrapnelbomb.cpp" +"C:\projects\ef2\dlls\game\soundman.cpp" +"C:\projects\ef2\dlls\game\spawners.cpp" +"C:\projects\ef2\dlls\game\specialfx.cpp" +"C:\projects\ef2\dlls\game\steering.cpp" +"C:\projects\ef2\dlls\game\trigger.cpp" +"C:\projects\ef2\dlls\game\UseData.cpp" +"C:\projects\ef2\dlls\game\vehicle.cpp" +"C:\projects\ef2\dlls\game\viewthing.cpp" +"C:\projects\ef2\dlls\game\waypoints.cpp" +"C:\projects\ef2\dlls\game\weapon.cpp" +"C:\projects\ef2\dlls\game\weaputils.cpp" +"C:\projects\ef2\dlls\game\worldspawn.cpp" +] +Creating command line "snCl.exe @C:\DOCUME~1\steven\LOCALS~1\Temp\RSP5D7.tmp" +Creating temporary file "C:\DOCUME~1\steven\LOCALS~1\Temp\RSP5D8.tmp" with contents +[ +/nologo /MT /W3 /GX /Zi /O2 /I "..\..\Shared" /I "..\..\DLLs" /I "..\..\Executable" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FGAME_EXPORTS" /D "GAME_DLL" /D "MISSIONPACK" /D "ENABLE_ALTROUTING" /D "MSVC_BUILD" /FR"./Release/" /Fp"./Release/game.pch" /Yc"_pch_cpp.h" /Fo"./Release/" /Fd"./Release/" /FD /c +"C:\projects\ef2\dlls\game\_pch_cpp.cpp" +] +Creating command line "snCl.exe @C:\DOCUME~1\steven\LOCALS~1\Temp\RSP5D8.tmp" +Creating temporary file "C:\DOCUME~1\steven\LOCALS~1\Temp\RSP5D9.tmp" with contents +[ +kernel32.lib user32.lib winmm.lib /nologo /dll /incremental:no /pdb:"./Release/gamex86.pdb" /map:"./Release/gamex86.map" /machine:I386 /def:".\game.def" /out:"../../Executable/Release/gamex86.dll" /implib:"./Release/gamex86.lib" +.\Release\changePosture.obj +.\Release\closeInOnEnemy.obj +.\Release\closeInOnEnemyWhileFiringWeapon.obj +.\Release\closeInOnPlayer.obj +.\Release\corridorCombatWithRangedWeapon.obj +.\Release\coverCombatWithRangedWeapon.obj +.\Release\doAttack.obj +.\Release\generalCombatWithMeleeWeapon.obj +.\Release\generalCombatWithRangedWeapon.obj +.\Release\gotoCurrentHelperNode.obj +.\Release\gotoHelperNode.obj +.\Release\gotoHelperNodeEX.obj +.\Release\gotoHelperNodeNearestEnemy.obj +.\Release\healGroupMember.obj +.\Release\holdPosition.obj +.\Release\MoveRandomDirection.obj +.\Release\patrol.obj +.\Release\PlayAnim.obj +.\Release\rangedCombatWithWeapon.obj +.\Release\rotateToEntity.obj +.\Release\selectBestWeapon.obj +.\Release\snipeEnemy.obj +.\Release\stationaryFireCombat.obj +.\Release\stationaryFireCombatEX.obj +.\Release\suppressionFireCombat.obj +.\Release\talk.obj +.\Release\teleportToEntity.obj +.\Release\teleportToPosition.obj +.\Release\torsoAimAndFireWeapon.obj +.\Release\useAlarm.obj +.\Release\watchEntity.obj +.\Release\watchEntityEX.obj +.\Release\work.obj +.\Release\mp_awardsystem.obj +.\Release\mp_manager.obj +.\Release\mp_modeBase.obj +.\Release\mp_modeCtf.obj +.\Release\mp_modeDm.obj +.\Release\mp_modeTeamBase.obj +.\Release\mp_modeTeamDm.obj +.\Release\mp_modifiers.obj +.\Release\mp_team.obj +.\Release\ai_chat.obj +.\Release\ai_cmd.obj +.\Release\ai_dmnet.obj +.\Release\ai_dmq3.obj +.\Release\ai_main.obj +.\Release\ai_team.obj +.\Release\ai_vcmd.obj +.\Release\g_bot.obj +.\Release\_pch_cpp.obj +.\Release\actor.obj +.\Release\actor_combatsubsystem.obj +.\Release\actor_enemymanager.obj +.\Release\actor_headwatcher.obj +.\Release\actor_locomotion.obj +.\Release\actor_posturecontroller.obj +.\Release\actor_sensoryperception.obj +.\Release\actorgamecomponents.obj +.\Release\actorstrategies.obj +.\Release\actorutil.obj +.\Release\ammo.obj +.\Release\animate.obj +.\Release\archive.obj +.\Release\armor.obj +.\Release\beam.obj +.\Release\behavior.obj +.\Release\behaviors_general.obj +.\Release\behaviors_specific.obj +.\Release\bg_misc.obj +.\Release\bg_pmove.obj +.\Release\body.obj +.\Release\bspline.obj +.\Release\camera.obj +.\Release\CameraPath.obj +.\Release\characterstate.obj +.\Release\CinematicArmature.obj +.\Release\class.obj +.\Release\compiler.obj +.\Release\DamageModification.obj +.\Release\debuglines.obj +.\Release\decals.obj +.\Release\dispenser.obj +.\Release\doors.obj +.\Release\earthquake.obj +.\Release\entity.obj +.\Release\equipment.obj +.\Release\explosion.obj +.\Release\FollowPath.obj +.\Release\FollowPathToEntity.obj +.\Release\FollowPathToPoint.obj +.\Release\g_main.obj +.\Release\g_phys.obj +.\Release\g_spawn.obj +.\Release\g_utils.obj +.\Release\game.obj +.\Release\gamecmds.obj +.\Release\gamecvars.obj +.\Release\gameplaydatabase.obj +.\Release\gameplayformulamanager.obj +.\Release\gameplaymanager.obj +.\Release\gamescript.obj +.\Release\gibs.obj +.\Release\globalcmd.obj +.\Release\GoDirectlyToPoint.obj +.\Release\goo.obj +.\Release\gravpath.obj +.\Release\groupcoordinator.obj +.\Release\health.obj +.\Release\helper_node.obj +.\Release\interpreter.obj +.\Release\inventoryitem.obj +.\Release\ipfilter.obj +.\Release\item.obj +.\Release\level.obj +.\Release\lexer.obj +.\Release\light.obj +.\Release\listener.obj +.\Release\misc.obj +.\Release\mover.obj +.\Release\nature.obj +.\Release\navigate.obj +.\Release\object.obj +.\Release\output.obj +.\Release\path.obj +.\Release\player.obj +.\Release\player_combat.obj +.\Release\player_util.obj +.\Release\playerheuristics.obj +.\Release\PlayerStart.obj +.\Release\portal.obj +.\Release\powerups.obj +.\Release\program.obj +.\Release\puzzleobject.obj +.\Release\q_math.obj +.\Release\q_mathsys.obj +.\Release\q_shared.obj +.\Release\RageAI.obj +.\Release\script.obj +.\Release\scriptmaster.obj +.\Release\scriptslave.obj +.\Release\scriptvariable.obj +.\Release\sentient.obj +.\Release\shrapnelbomb.obj +.\Release\soundman.obj +.\Release\spawners.obj +.\Release\specialfx.obj +.\Release\stationaryvehicle.obj +.\Release\steering.obj +.\Release\str.obj +.\Release\teammateroster.obj +.\Release\trigger.obj +.\Release\UseData.obj +.\Release\vehicle.obj +.\Release\viewthing.obj +.\Release\waypoints.obj +.\Release\weapon.obj +.\Release\WeaponDualWield.obj +.\Release\weaputils.obj +.\Release\win_bounds.obj +.\Release\worldspawn.obj +] +Creating command line "snLink.exe @C:\DOCUME~1\steven\LOCALS~1\Temp\RSP5D9.tmp" +

Output Window

+Compiling... +snCL -- Detected win32 build...passing to cl.exe +_pch_cpp.cpp +Compiling... +snCL -- Detected win32 build...passing to cl.exe +changePosture.cpp +closeInOnEnemy.cpp +closeInOnEnemyWhileFiringWeapon.cpp +closeInOnPlayer.cpp +corridorCombatWithRangedWeapon.cpp +coverCombatWithRangedWeapon.cpp +doAttack.cpp +generalCombatWithMeleeWeapon.cpp +generalCombatWithRangedWeapon.cpp +gotoCurrentHelperNode.cpp +gotoHelperNode.cpp +gotoHelperNodeEX.cpp +gotoHelperNodeNearestEnemy.cpp +healGroupMember.cpp +holdPosition.cpp +MoveRandomDirection.cpp +patrol.cpp +PlayAnim.cpp +rangedCombatWithWeapon.cpp +rotateToEntity.cpp +Generating Code... +Compiling... +selectBestWeapon.cpp +snipeEnemy.cpp +stationaryFireCombat.cpp +stationaryFireCombatEX.cpp +suppressionFireCombat.cpp +talk.cpp +teleportToEntity.cpp +teleportToPosition.cpp +torsoAimAndFireWeapon.cpp +useAlarm.cpp +watchEntity.cpp +watchEntityEX.cpp +work.cpp +mp_awardsystem.cpp +mp_manager.cpp +mp_modeBase.cpp +mp_modeCtf.cpp +mp_modeDm.cpp +mp_modeTeamBase.cpp +mp_modeTeamDm.cpp +Generating Code... +Compiling... +mp_modifiers.cpp +mp_team.cpp +ai_chat.cpp +ai_cmd.cpp +ai_dmnet.cpp +ai_dmq3.cpp +ai_main.cpp +ai_team.cpp +ai_vcmd.cpp +g_bot.cpp +actor_posturecontroller.cpp +Generating Code... +Compiling... +bg_misc.c +bg_pmove.c +Generating Code... +Compiling... +class.cpp +gameplaydatabase.cpp +gameplayformulamanager.cpp +gameplaymanager.cpp +listener.cpp +output.cpp +puzzleobject.cpp +Generating Code... +Compiling... +q_math.c +q_mathsys.c +q_shared.c +Generating Code... +Compiling... +stationaryvehicle.cpp +str.cpp +teammateroster.cpp +WeaponDualWield.cpp +win_bounds.cpp +Generating Code... +Compiling... +snCL -- Detected win32 build...passing to cl.exe +actor.cpp +actor_combatsubsystem.cpp +actor_enemymanager.cpp +actor_headwatcher.cpp +actor_locomotion.cpp +actor_sensoryperception.cpp +actorgamecomponents.cpp +actorstrategies.cpp +actorutil.cpp +ammo.cpp +animate.cpp +archive.cpp +armor.cpp +beam.cpp +behavior.cpp +behaviors_general.cpp +behaviors_specific.cpp +body.cpp +bspline.cpp +camera.cpp +Generating Code... +Compiling... +CameraPath.cpp +characterstate.cpp +CinematicArmature.cpp +compiler.cpp +DamageModification.cpp +debuglines.cpp +decals.cpp +dispenser.cpp +doors.cpp +earthquake.cpp +entity.cpp +equipment.cpp +explosion.cpp +FollowPath.cpp +FollowPathToEntity.cpp +FollowPathToPoint.cpp +g_main.cpp +g_phys.cpp +g_spawn.cpp +g_utils.cpp +Generating Code... +Compiling... +game.cpp +gamecmds.cpp +gamecvars.cpp +gamescript.cpp +gibs.cpp +globalcmd.cpp +GoDirectlyToPoint.cpp +goo.cpp +gravpath.cpp +groupcoordinator.cpp +health.cpp +helper_node.cpp +interpreter.cpp +inventoryitem.cpp +ipfilter.cpp +item.cpp +level.cpp +lexer.cpp +light.cpp +misc.cpp +Generating Code... +Compiling... +mover.cpp +nature.cpp +navigate.cpp +object.cpp +path.cpp +player.cpp +player_combat.cpp +player_util.cpp +playerheuristics.cpp +PlayerStart.cpp +portal.cpp +powerups.cpp +program.cpp +RageAI.cpp +script.cpp +scriptmaster.cpp +scriptslave.cpp +scriptvariable.cpp +sentient.cpp +shrapnelbomb.cpp +Generating Code... +Compiling... +soundman.cpp +spawners.cpp +specialfx.cpp +steering.cpp +trigger.cpp +UseData.cpp +vehicle.cpp +viewthing.cpp +waypoints.cpp +weapon.cpp +weaputils.cpp +worldspawn.cpp +Generating Code... +Linking... +snCL -- Detected win32 build...passing to link.exe + Creating library ./Release/gamex86.lib and object ./Release/gamex86.exp + + + +

Results

+gamex86.dll - 0 error(s), 0 warning(s) +
+ + diff --git a/dlls/game/game.vcxproj b/dlls/game/game.vcxproj new file mode 100644 index 0000000..ccded48 --- /dev/null +++ b/dlls/game/game.vcxproj @@ -0,0 +1,1578 @@ + + + + + Debug + Win32 + + + Demo Release + Win32 + + + Intel Release + Win32 + + + Release CDROM + Win32 + + + Release + Win32 + + + VTune + Win32 + + + + "$/EF2/game", FYHAAAAA + . + {AD2B91AF-BE5E-4D64-952C-5F061241380D} + + + + DynamicLibrary + v110 + false + MultiByte + + + DynamicLibrary + v110 + false + MultiByte + + + DynamicLibrary + v110 + false + MultiByte + + + DynamicLibrary + v110 + false + MultiByte + + + DynamicLibrary + v110 + false + MultiByte + + + DynamicLibrary + v110 + false + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .\VTune\ + .\VTune\ + false + + + .\./Release\ + .\./Release\ + false + + + .\./IntelRelease\ + .\./IntelRelease\ + false + + + .\../Debug\ + .\./Debug\ + true + + + .\Release\ + .\Release\ + false + + + .\Demo_Release\ + .\Demo_Release\ + false + + + + MultiThreadedDebug + Default + true + true + MaxSpeed + true + Level4 + true + ..\..\Shared;..\..\DLLs;..\..\Executable;%(AdditionalIncludeDirectories) + _DEBUG;WIN32;_WINDOWS;_USRDLL;FGAME_EXPORTS;GAME_DLL;MISSIONPACK;ENABLE_ALTROUTING;MSVC_BUILD;%(PreprocessorDefinitions) + .\VTune\ + true + .\VTune\game.pch + .\VTune\ + .\VTune\ + + + true + _DEBUG;%(PreprocessorDefinitions) + .\VTune\game.tlb + true + Win32 + + + 0x0409 + _DEBUG;%(PreprocessorDefinitions) + + + true + .\VTune\game.bsc + + + true + true + true + Console + ../../Executable/VTune/gamex86.dll + .\VTune\gamex86.lib + winmm.lib;%(AdditionalDependencies) + .\game.def + + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\..\Shared;..\..\DLLs;..\..\Executable;%(AdditionalIncludeDirectories) + NDEBUG;WIN32;_WINDOWS;_USRDLL;FGAME_EXPORTS;GAME_DLL;MISSIONPACK;ENABLE_ALTROUTING;MSVC_BUILD;%(PreprocessorDefinitions) + .\./Release\ + true + .\./Release\game.pch + .\./Release\ + .\./Release\ + + + true + NDEBUG;%(PreprocessorDefinitions) + .\./Release\game.tlb + true + Win32 + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\./Release\game.bsc + + + true + true + false + Console + ../../Executable/Release/gamex86.dll + .\./Release\gamex86.lib + winmm.lib;%(AdditionalDependencies) + .\game.def + + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\..\Shared;..\..\DLLs;..\..\Executable;%(AdditionalIncludeDirectories) + NDEBUG;WIN32;_WINDOWS;_USRDLL;FGAME_EXPORTS;GAME_DLL;_USE_INTEL_COMPILER;MISSIONPACK;ENABLE_ALTROUTING;%(PreprocessorDefinitions) + .\./IntelRelease\ + true + .\./IntelRelease\game.pch + .\./IntelRelease\ + .\./IntelRelease\ + + + true + NDEBUG;%(PreprocessorDefinitions) + .\./IntelRelease\game.tlb + true + Win32 + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\./IntelRelease\game.bsc + + + true + true + false + Console + ../../Executable/IntelRelease/gamex86.dll + .\./IntelRelease\gamex86.lib + winmm.lib;%(AdditionalDependencies) + .\game.def + + + + + MultiThreadedDebug + Default + false + Disabled + true + Level4 + true + EditAndContinue + ..\..\Shared;..\..\DLLs;..\..\Executable;%(AdditionalIncludeDirectories) + _DEBUG;WIN32;_WINDOWS;_USRDLL;FGAME_EXPORTS;GAME_DLL;MISSIONPACK;ENABLE_ALTROUTING;MSVC_BUILD;%(PreprocessorDefinitions) + .\./Debug\ + true + .\./Debug\game.pch + .\./Debug\ + .\./Debug\ + EnableFastChecks + + + true + _DEBUG;%(PreprocessorDefinitions) + .\../Debug\game.tlb + true + Win32 + + + 0x0409 + _DEBUG;%(PreprocessorDefinitions) + + + true + ../../Executable/Debug/game.bsc + + + true + true + true + Console + ../../Executable/Debug/gamex86.dll + .\../Debug\gamex86.lib + winmm.lib;%(AdditionalDependencies) + .\game.def + + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + ..\..\Shared;..\..\DLLs;..\..\Executable;%(AdditionalIncludeDirectories) + NDEBUG;WIN32;_WINDOWS;_USRDLL;FGAME_EXPORTS;GAME_DLL;MISSIONPACK;ENABLE_ALTROUTING;MSVC_BUILD;%(PreprocessorDefinitions) + .\Release\ + true + .\Release\game.pch + .\Release\ + .\Release\ + + + true + NDEBUG;%(PreprocessorDefinitions) + .\Release\game.tlb + true + Win32 + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Release\game.bsc + + + true + true + false + Console + ../../Executable/Release/gamex86.dll + .\Release\gamex86.lib + winmm.lib;%(AdditionalDependencies) + .\game.def + + + + + MultiThreaded + Default + true + true + MaxSpeed + true + Level3 + _WINDOWS;_USRDLL;FGAME_EXPORTS;GAME_DLL;NDEBUG;WIN32;PRE_RELEASE_DEMO;%(PreprocessorDefinitions) + .\Demo_Release\ + true + .\Demo_Release\game.pch + .\Demo_Release\ + .\Demo_Release\ + + + true + NDEBUG;%(PreprocessorDefinitions) + .\Demo_Release\game.tlb + true + Win32 + + + 0x0409 + NDEBUG;%(PreprocessorDefinitions) + + + true + .\Demo_Release\game.bsc + + + true + true + false + Console + ../Demo_Release/gamex86.dll + .\Demo_Release\gamex86.lib + winmm.lib;%(AdditionalDependencies) + .\game.def + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + _pch_cpp.h + Create + _pch_cpp.h + Create + _pch_cpp.h + Create + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + Use + _pch_cpp.h + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dlls/game/game.vcxproj.filters b/dlls/game/game.vcxproj.filters new file mode 100644 index 0000000..69df4f1 --- /dev/null +++ b/dlls/game/game.vcxproj.filters @@ -0,0 +1,1080 @@ + + + + + {e7763a6c-a325-482d-a687-821e8e9caf46} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {383df40f-6936-4401-920d-15582f424beb} + .cpp + + + {77272985-cb9d-4076-affe-054bdfa05b92} + + + {1a684e54-782a-461d-a7b0-31dad3a045ce} + .cpp + + + {3d35ea2d-d111-4732-a28d-bf38a78e5ce8} + h;hpp;hxx;hm;inl + + + {e7b5c813-38ca-43af-8e4c-109934ff7c85} + .hpp + + + {53b3de9d-760b-4f0d-9456-e71b1c53c8a1} + + + {7c27a92d-2c38-4aa5-8dc7-81c1e9554f11} + .h + + + {8f997000-941d-4ac3-b785-97aa1cf41ff3} + ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe + + + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Behaviors + + + Source Files\Multiplayer + + + Source Files\Multiplayer + + + Source Files\Multiplayer + + + Source Files\Multiplayer + + + Source Files\Multiplayer + + + Source Files\Multiplayer + + + Source Files\Multiplayer + + + Source Files\Multiplayer + + + Source Files\Multiplayer + + + Source Files\botlib source + + + Source Files\botlib source + + + Source Files\botlib source + + + Source Files\botlib source + + + Source Files\botlib source + + + Source Files\botlib source + + + Source Files\botlib source + + + Source Files\botlib source + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files\botlib source + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\BehaviorHeaders + + + Header Files\Multiplayer Headers + + + Header Files\Multiplayer Headers + + + Header Files\Multiplayer Headers + + + Header Files\Multiplayer Headers + + + Header Files\Multiplayer Headers + + + Header Files\Multiplayer Headers + + + Header Files\Multiplayer Headers + + + Header Files\Multiplayer Headers + + + Header Files\Multiplayer Headers + + + Header Files\Multiplayer Headers + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files\botlib header files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/dlls/game/gamecmds.cpp b/dlls/game/gamecmds.cpp new file mode 100644 index 0000000..8da17d3 --- /dev/null +++ b/dlls/game/gamecmds.cpp @@ -0,0 +1,1362 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/gamecmds.cpp $ +// $Revision:: 54 $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "gamecmds.h" +#include "camera.h" +#include "viewthing.h" +#include "soundman.h" +#include "navigate.h" +#include "mp_manager.hpp" +#include "CinematicArmature.h" +#include +#include "botmenudef.h" + +typedef struct +{ + const char *command; + qboolean ( *func )( const gentity_t *ent ); + qboolean allclients; +} consolecmd_t; + +consolecmd_t G_ConsoleCmds[] = +{ + // command name function available in multiplayer? + { "vtaunt", G_VTaunt, true }, +// { "vsay_team", G_VTaunt, true }, +// { "vosay_team", G_VTaunt, true }, +// { "vtell", G_VTaunt, true }, + { "vsay", G_SayCmd, true }, + { "vosay", G_SayCmd, true }, + { "tell", G_TellCmd, true }, + { "vtell", G_TellCmd, true }, + { "say", G_SayCmd, true }, + { "taunt", G_TauntCmd, true }, + { "vosay_team", G_TeamSayCmd, true }, + { "vsay_team", G_TeamSayCmd, true }, + { "tsay", G_TeamSayCmd, true }, + { "say_team", G_TeamSayCmd, true }, // added for BOTLIB + { "eventlist", G_EventListCmd, false }, + { "pendingevents", G_PendingEventsCmd, false }, + { "eventhelp", G_EventHelpCmd, false }, + { "dumpevents", G_DumpEventsCmd, false }, + { "classevents", G_ClassEventsCmd, false }, + { "dumpclassevents", G_DumpClassEventsCmd, false }, + { "dumpallclasses", G_DumpAllClassesCmd, false }, + { "classlist", G_ClassListCmd, false }, + { "classtree", G_ClassTreeCmd, false }, + { "cam", G_CameraCmd, false }, + { "snd", G_SoundCmd, false }, + { "cin", G_CinematicCmd, false }, +// { "showvar", G_ShowVarCmd, false }, + { "script", G_ScriptCmd, false }, + { "clientrunthread", G_ClientRunThreadCmd, false }, + { "clientsetvar", G_ClientSetVarCmd, false }, + { "levelvars", G_LevelVarsCmd, false }, + { "gamevars", G_GameVarsCmd, false }, + { "loc", G_LocCmd, false }, + { "warp", G_WarpCmd, false }, + { "mask", G_MaskCmd, false }, + { "setgameplayfloat", G_SetGameplayFloatCmd, false }, + { "setgameplaystring", G_SetGameplayStringCmd, false }, + { "purchaseSkill", G_PurchaseSkillCmd, false }, + { "swapItem", G_SwapItemCmd, false }, + { "dropItem", G_DropItemCmd, false }, + { "dialogrunthread", G_DialogRunThread, false }, + { NULL, NULL, NULL } +}; + +void G_InitConsoleCommands( void ) +{ + consolecmd_t *cmds; + + // + // the game server will interpret these commands, which will be automatically + // forwarded to the server after they are not recognized locally + // + gi.AddCommand( "give" ); + gi.AddCommand( "god" ); + gi.AddCommand( "notarget" ); + gi.AddCommand( "noclip" ); + gi.AddCommand( "kill" ); + gi.AddCommand( "script" ); + + for( cmds = G_ConsoleCmds; cmds->command != NULL; cmds++ ) + { + gi.AddCommand( cmds->command ); + } +} + +qboolean G_ConsoleCommand( void ) +{ + gentity_t *ent; + qboolean result; + + result = false; + try + { + if ( dedicated->integer ) + { + const char *cmd; + + cmd = gi.argv( 0 ); + + if ( stricmp( cmd, "say" ) == 0 ) + { + G_Say( NULL, false, false ); + result = true; + } + } + + if ( !result ) + { + ent = &g_entities[ 0 ]; + result = G_ProcessClientCommand( ent ); + } + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } + + return result; +} + +void G_ClientCommand( gentity_t *ent ) +{ + try + { + if ( ent && !G_ProcessClientCommand( ent ) ) + { + // anything that doesn't match a command will be a chat + G_Say( ent, false, true ); + } + } + + catch( const char *error ) + { + G_ExitWithError( error ); + } +} + +qboolean G_ProcessClientCommand( gentity_t *ent ) +{ + const char *cmd; + consolecmd_t *cmds; + int i; + int n; + Event *ev; + + cmd = gi.argv( 0 ); + + if ( !ent || !ent->client || !ent->entity || !ent->inuse ) + { + // not fully in game yet + return false; + } + + for( cmds = G_ConsoleCmds; cmds->command != NULL; cmds++ ) + { + // if we have multiple clients and this command isn't allowed by multiple clients, skip it + if ( ( game.maxclients > 1 ) && ( !cmds->allclients ) && !sv_cheats->integer ) + { + continue; + } + + if ( !Q_stricmp( cmd, cmds->command ) ) + { + return cmds->func( ent ); + } + } + + if ( Event::Exists( cmd ) ) + { + ev = new Event( cmd ); + ev->SetSource( EV_FROM_CONSOLE ); + ev->SetConsoleEdict( ent ); + + n = gi.argc(); + for( i = 1; i < n; i++ ) + { + ev->AddToken( gi.argv( i ) ); + } + + if ( !Q_stricmpn( cmd, "ai_", 2 ) ) + { + return thePathManager.ProcessEvent( ev ); + } + else if ( !Q_stricmpn( cmd, "view", 4 ) ) + { + return Viewmodel.ProcessEvent( ev ); + } + else + { + if( ent && ent->entity ) + return ent->entity->ProcessEvent( ev ); + } + } + + return false; +} + +/* +================== +Cmd_Say_f +================== +*/ +void G_Say( const gentity_t *ent, bool team, qboolean arg0 ) +{ + str text; + const char *p; + + if ( gi.argc() < 2 && !arg0 ) + { + return; + } + + if ( arg0 ) + { + text = gi.argv( 0 ); + text += " "; + text += gi.args(); + } + else + { + p = gi.args(); + + if ( *p == '"' ) + { + p++; + text = p; + text[ text.length() - 1 ] = 0; + } + else + { + text = p; + } + } + + if ( ent && ent->entity && ent->entity->isSubclassOf( Player ) ) + { + multiplayerManager.say( (Player *)ent->entity, text, team ); + } + else + { + multiplayerManager.say( NULL, text, false ); + } +} + +qboolean G_CameraCmd( const gentity_t *ent ) +{ + Event *ev; + const char *cmd; + int i; + int n; + + n = gi.argc(); + if ( !n ) + { + gi.WPrintf( "Usage: cam [command] [arg 1]...[arg n]\n" ); + return true; + } + + cmd = gi.argv( 1 ); + if ( Event::Exists( cmd ) ) + { + ev = new Event( cmd ); + ev->SetSource( EV_FROM_CONSOLE ); + ev->SetConsoleEdict( NULL ); + + for( i = 2; i < n; i++ ) + { + ev->AddToken( gi.argv( i ) ); + } + + CameraMan.ProcessEvent( ev ); + } + else + { + gi.WPrintf( "Unknown camera command '%s'.\n", cmd ); + } + + return true; +} + +//=============================================================== +// Name: G_CinematicCmd +// Class: +// +// Description: Takes a cinematic command from the console, rips +// off the first argument and looks up the second +// as a cinematic armature event. If found, sends +// it off to the armature along with the rest of the +// event tokens. +// +// Parameters: gentity_t* -- entity sending the event +// +// Returns: qboolean -- true if processed. +// +//=============================================================== +qboolean G_CinematicCmd( const gentity_t *ent ) +{ + Event *ev ; + const char *cmd ; + int n = gi.argc(); + + if ( !n ) + { + gi.WPrintf( "Usage: cin ... \n"); + return true ; + } + + cmd = gi.argv( 1 ); + if ( Event::Exists( cmd ) ) + { + ev = new Event( cmd ); + ev->SetSource( EV_FROM_CONSOLE ); + ev->SetConsoleEdict( NULL ); + + for (int i = 2; i < n; i++ ) + { + ev->AddToken( gi.argv( i ) ); + } + + theCinematicArmature.ProcessEvent( ev ); + } + else + { + gi.WPrintf( "Unknown cinematic armature command '%s'.\n", cmd ); + } + + return true ; +} + +qboolean G_SoundCmd( const gentity_t *ent ) +{ + Event *ev; + const char *cmd; + int i; + int n; + + n = gi.argc(); + if ( !n ) + { + gi.WPrintf( "Usage: snd [command] [arg 1]...[arg n]\n" ); + return true; + } + + cmd = gi.argv( 1 ); + if ( Event::Exists( cmd ) ) + { + ev = new Event( cmd ); + ev->SetSource( EV_FROM_CONSOLE ); + ev->SetConsoleEdict( NULL ); + + for( i = 2; i < n; i++ ) + { + ev->AddToken( gi.argv( i ) ); + } + + SoundMan.ProcessEvent( ev ); + } + else + { + gi.WPrintf( "Unknown sound command '%s'.\n", cmd ); + } + + return true; +} + +char *ClientName(int client, char *name, int size); +qboolean G_VTaunt( const gentity_t *ent ) +{ +// NOTE: vtant, vsay, vosay are q3's specific hotkey chat mechanisms. we don't have these so this is a stub +// function right now. +/* + gentity_t *who; + int i; + + if (!ent->client) { + return qfalse; + } + + + Player *player = (Player *)ent->entity; + Player *enemy = multiplayerManager.getLastKilledByPlayer(player); + + // insult someone who just killed you + if (ent->enemy && ent->enemy->client && ent->enemy->client->lastkilled_client == ent->s.number) { + // i am a dead corpse + if (!(ent->enemy->r.svFlags & SVF_BOT)) { + G_Voice( ent, ent->enemy, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); + } + if (!(ent->r.svFlags & SVF_BOT)) { + G_Voice( ent, ent, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); + } + ent->enemy = NULL; + return; + } + // insult someone you just killed + if (ent->client->lastkilled_client >= 0 && ent->client->lastkilled_client != ent->s.number) { + who = g_entities + ent->client->lastkilled_client; + if (who->client) { + // who is the person I just killed + if (who->client->lasthurt_mod == MOD_GAUNTLET) { + if (!(who->r.svFlags & SVF_BOT)) { + G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); // and I killed them with a gauntlet + } + if (!(ent->r.svFlags & SVF_BOT)) { + G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); + } + } else { + if (!(who->r.svFlags & SVF_BOT)) { + G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); // and I killed them with something else + } + if (!(ent->r.svFlags & SVF_BOT)) { + G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); + } + } + ent->client->lastkilled_client = -1; + return; + } + } + + if (gametype >= GT_TEAM) { + // praise a team mate who just got a reward + for(i = 0; i < MAX_CLIENTS; i++) { + who = g_entities + i; + if (who->client && who != ent && who->client->sess.sessionTeam == ent->client->sess.sessionTeam) { + if (who->client->rewardTime > level.time) { + if (!(who->r.svFlags & SVF_BOT)) { + G_Voice( ent, who, SAY_TELL, VOICECHAT_PRAISE, qfalse ); + } + if (!(ent->r.svFlags & SVF_BOT)) { + G_Voice( ent, ent, SAY_TELL, VOICECHAT_PRAISE, qfalse ); + } + return; + } + } + } + } +*/ + // just say something +/* + cmd = "vchat"; + } + + trap_SendServerCommand( other-g_entities, va("%s %d %d %d %s", cmd, voiceonly, ent->s.number, color, id)); +*/ + +// trap_SendServerCommand( other-g_entities, va("%s %d %d %d %s", cmd, voiceonly, ent->s.number, color, id)); +/* + GameplayManager + gi.SendServerCommand(NULL,va("%s %d %d %d %s", "taunt", voiceonly, ent->s.number, color, id)); + // G_Say( ent,false, va("%s","taunt") ); // VOICECHAT_TAUNT)); // NULL, SAY_ALL, VOICECHAT_TAUNT, qfalse ); + char bogoname[1024]; + + gi.SendServerCommand(ent->s.clientNum,va("hudsay \"%s: %s \"",ClientName(ent->s.clientNum,bogoname,1024),"taunt")); // ClientName(ent->s.clientNum,bogoname,1024), +*/ + + return true; +} + +qboolean G_TellCmd( const gentity_t *ent ) +{ + str text; + const char *p; + int i; + int entnum; + + if ( gi.argc() < 3 ) + { + return true; + } + + entnum = atoi( gi.argv( 1 ) ); + + for ( i = 2 ; i < gi.argc() ; i++ ) + { + p = gi.argv( i ); + + if ( *p == '"' ) + { + p++; + text += p; + text[ text.length() - 1 ] = 0; + } + else + { + text += p; + } + + } + + if ( ent->entity && ent->entity->isSubclassOf( Player ) ) + { + multiplayerManager.tell( (Player *)ent->entity, text, entnum ); + } + + return true; +} + +qboolean G_SayCmd( const gentity_t *ent ) +{ + G_Say( ent, false, false ); + + return true; +} + +qboolean G_TeamSayCmd( const gentity_t *ent ) +{ + G_Say( ent, true, false ); + + return true; +} + +qboolean G_TauntCmd( const gentity_t *ent ) +{ + str tauntName; + + if ( gi.argc() < 2 ) + { + return true; + } + + tauntName = "taunt"; + tauntName += gi.argv( 1 ); + + if ( ent->entity && ent->entity->isSubclassOf( Player ) ) + { + Player *player = (Player *)ent->entity; + + if ( multiplayerManager.inMultiplayer() && !multiplayerManager.isPlayerSpectator( player ) ) + { + //ent->entity->Sound( tauntName, CHAN_TAUNT, DEFAULT_VOL, LEVEL_WIDE_MIN_DIST ); + ent->entity->Sound( tauntName, CHAN_TAUNT, DEFAULT_VOL, 250.0f ); + } + } + + return true; +} + +qboolean G_LocCmd( const gentity_t *ent ) +{ + if ( ent ) + { + gi.Printf( "Origin = ( %f, %f, %f ) Angles = ( %f, %f, %f )\n", + ent->currentOrigin[0], ent->currentOrigin[1], ent->currentOrigin[2], + ent->currentAngles[0], ent->currentAngles[1], ent->currentAngles[0] ); + } + + return true; +} + +qboolean G_WarpCmd( const gentity_t *ent ) +{ + Vector pos; + + if ( sv_cheats->integer == 0 ) + { + return true; + } + + if ( ent ) + { + Entity *entity; + + if ( ent->entity ) + { + // Get the new position + + if ( gi.argc() == 2 ) + { + pos = Vector(gi.argv( 1 )); + } + else if ( gi.argc() == 4 ) + { + pos[ 0 ] = atof( gi.argv( 1 ) ); + pos[ 1 ] = atof( gi.argv( 2 ) ); + pos[ 2 ] = atof( gi.argv( 3 ) ); + } + else + { + gi.Printf( "Incorrect parms\n" ); + return false; + } + + // Move the entity to the new position + + entity = ent->entity; + entity->setOrigin( pos ); + } + } + + return true; +} + +qboolean G_MaskCmd( const gentity_t *ent ) +{ + Vector pos; + + // Check parms + + if ( gi.argc() != 2 ) + { + gi.Printf( "Incorrect parms\n" ); + return true; + } + + if ( ent ) + { + Entity *entity; + + if ( ent->entity ) + { + entity = ent->entity; + + // Get and set new mask + + if ( stricmp( gi.argv( 1 ), "monster" ) == 0 ) + { + entity->edict->clipmask = MASK_MONSTERSOLID; + } + else if ( stricmp( gi.argv( 1 ), "player" ) == 0 ) + { + entity->edict->clipmask = MASK_PLAYERSOLID; + } + else + { + gi.Printf( "Unknown mask name - %s\n", gi.argv( 1 ) ); + } + } + } + + return true; +} + +qboolean G_EventListCmd( const gentity_t *ent ) +{ + const char *mask; + + mask = NULL; + if ( gi.argc() > 1 ) + { + mask = gi.argv( 1 ); + } + + Event::ListCommands( mask ); + + return true; +} + +qboolean G_PendingEventsCmd( const gentity_t *ent ) +{ + const char *mask; + + mask = NULL; + if ( gi.argc() > 1 ) + { + mask = gi.argv( 1 ); + } + + Event::PendingEvents( mask ); + + return true; +} + +qboolean G_EventHelpCmd( const gentity_t *ent ) +{ + const char *mask; + + mask = NULL; + if ( gi.argc() > 1 ) + { + mask = gi.argv( 1 ); + } + + Event::ListDocumentation( mask, false ); + + return true; +} + +qboolean G_DumpEventsCmd( const gentity_t *ent ) +{ + const char *mask; + + mask = NULL; + if ( gi.argc() > 1 ) + { + mask = gi.argv( 1 ); + } + + Event::ListDocumentation( mask, true ); + + return true; +} + +qboolean G_ClassEventsCmd( const gentity_t *ent ) +{ + const char *className; + + className = NULL; + if ( gi.argc() < 2 ) + { + gi.WPrintf( "Usage: classevents [className]\n" ); + className = gi.argv( 1 ); + } + else + { + className = gi.argv( 1 ); + ClassEvents( className ); + } + return true; +} + +qboolean G_DumpClassEventsCmd( const gentity_t *ent ) +{ + const char *className; + + className = NULL; + if ( gi.argc() < 2 ) + { + gi.WPrintf( "Usage: dumpclassevents [className]\n" ); + className = gi.argv( 1 ); + } + else + { + className = gi.argv( 1 ); + ClassEvents( className, true ); + } + return true; +} + +qboolean G_DumpAllClassesCmd( const gentity_t *ent ) +{ + const char *tmpstr = NULL; + const char *filename = NULL; + int typeFlag = EVENT_ALL; + int outputFlag = OUTPUT_ALL; + + if ( gi.argc() > 1 ) + { + tmpstr = gi.argv( 1 ); + if ( !strcmp("tiki", tmpstr ) ) + typeFlag = EVENT_TIKI_ONLY; + if ( !strcmp("script", tmpstr ) ) + typeFlag = EVENT_SCRIPT_ONLY; + } + if ( gi.argc() > 2 ) + { + tmpstr = gi.argv( 2 ); + if ( !strcmp("html", tmpstr ) ) + outputFlag = OUTPUT_HTML; + if ( !strcmp("cmdmap", tmpstr ) ) + outputFlag = OUTPUT_CMD; + } + if ( gi.argc() > 3 ) + { + filename = gi.argv( 3 ); + } + + DumpAllClasses(typeFlag, outputFlag, filename); + + return true; +} + +qboolean G_ClassListCmd( const gentity_t *ent ) +{ + listAllClasses(); + + return true; +} + +qboolean G_ClassTreeCmd( const gentity_t *ent ) +{ + if ( gi.argc() > 1 ) + { + listInheritanceOrder( gi.argv( 1 ) ); + } + else + { + gi.SendServerCommand( ent - g_entities, "print \"Syntax: classtree [classname].\n\"" ); + } + + return true; +} + +/* +qboolean G_ShowVarCmd( gentity_t *ent ) +{ + ScriptVariable *var; + + var = Director.GetExistingVariable( gi.argv( 1 ) ); + if ( var ) + { + gi.SendServerCommand( ent - g_entities, "print \"%s = '%s'\n\"", gi.argv( 1 ), var->stringValue() ); + } + else + { + gi.SendServerCommand( ent - g_entities, "print \"Variable '%s' does not exist.\"", gi.argv( 1 ) ); + } + + return true; +} */ + +qboolean G_ScriptCmd( const gentity_t *ent ) +{ + int i, argc; + const char *argv[ 32 ]; + char args[ 32 ][ 64 ]; + + argc = 0; + for( i = 1; i < gi.argc(); i++ ) + { + if ( argc < 32 ) + { + strncpy( args[ argc ], gi.argv( i ), 64 ); + argv[ argc ] = args[ argc ]; + argc++; + } + } + if ( argc > 0 ) + { + level.consoleThread->ProcessCommand( argc, argv ); + } + + return true; +} + +qboolean G_ClientRunThreadCmd( const gentity_t *ent ) +{ + str threadName; + CThread *thread; + + // Get the thread name + + if ( !gi.argc() ) + return true; + + threadName = gi.argv( 1 ); + + + // Check to make sure player is allowed to run this thread + + // Need to do this part + + // Run the thread + + if ( !threadName.length() ) + return true; + + thread = Director.CreateThread( threadName ); + + if ( thread ) + thread->DelayedStart( 0.0f ); + + return true; +} + +qboolean G_ClientSetVarCmd( const gentity_t *ent ) +{ + str varName; + str value; + + + if ( gi.argc() != 3 ) + return true; + + // Get the variable name + + varName = gi.argv( 1 ); + + // Get the variable value + + value = gi.argv( 2 ); + + // Check to make sure player is allowed to set this variable + + // Need to do this part + + // Set the variable + + if ( varName.length() && value.length() ) + { + levelVars.SetVariable( varName, value ); + } + + return true; +} + +//=============================================================== +// Name: G_SendCommandToAllPlayers +// Class: None +// +// Description: Sends the specified command to all connected +// clients (players). +// +// Parameters: const char* -- the command to send +// +// Returns: qboolean -- qtrue if successfully sent. +// +//=============================================================== +qboolean G_SendCommandToAllPlayers( const char *command ) +{ + bool retVal = true ; + + for( unsigned int clientIdx = 0; clientIdx < maxclients->integer; ++clientIdx ) + { + gentity_t *ent = g_entities + clientIdx ; + if ( !ent->inuse || !ent->client || !ent->entity ) continue; + + if ( !G_SendCommandToPlayer( ent, command ) ) retVal = false ; + } + + return retVal ; +} + + +//=============================================================== +// Name: G_SendCommandToPlayer +// Class: +// +// Description: Sends the specified command to the specified player. +// +// Parameters: Player* -- the player to send it to +// str -- the command +// +// Returns: None +// +//=============================================================== +qboolean G_SendCommandToPlayer( const gentity_t *ent, const char *command ) +{ + assert( ent ); + Entity *entity = ent->entity ; + + assert( entity ); + assert( entity->isSubclassOf( Player ) ); + Player *player = ( Player* )entity ; + + str builtCommand("stufftext \""); + builtCommand += command ; + builtCommand += "\"\n"; + gi.SendServerCommand( player->edict - g_entities, builtCommand.c_str() ); + + return true ; +} + + +//=============================================================== +// Name: G_EnableWidgetOfPlayer +// Class: +// +// Description: Enables (or disables) the specified widget of +// the specified player. +// +// Parameters: const gentity_t* -- the player gentity_t to send this command to. +// const char* -- the name of the widget to enable/disable +// bool -- true means enable +// +// Returns: None +// +//=============================================================== +qboolean G_EnableWidgetOfPlayer( const gentity_t *ent, const char *widgetName, qboolean enableFlag ) +{ + assert( widgetName ); + + str command("globalwidgetcommand "); + command += widgetName ; + command += ( enableFlag ) ? " enable" : " disable" ; + + return G_SendCommandToPlayer( ent, command.c_str()); +} + + +//=============================================================== +// Name: G_SetWidgetTextOfPlayer +// Class: +// +// Description: Sets the widget text of the widget for the speicifed player +// +// Parameters: const gentity_t* -- the player gentity_t to send this command to. +// const char* -- the name of the widget to set the text on +// const char* -- text the put on the widget +// +// Returns: None +// +//=============================================================== +qboolean G_SetWidgetTextOfPlayer( const gentity_t *ent, const char *widgetName, const char *widgetText ) +{ + assert( widgetName ); + char tmpstr[4096]; + if ( strlen(widgetText) > 4095 ) + assert(0); + + strcpy(tmpstr, widgetText); + + int i; + for ( i=0; iNumVariables(); i++ ) + { + var = list->GetVariable( i ); + gi.Printf( "%s = %s\n", var->getName(), var->stringValue() ); + } + gi.Printf( "%d variables\n", list->NumVariables() ); +} + +qboolean G_LevelVarsCmd( const gentity_t *ent ) +{ + gi.Printf( "Level Variables\n" ); + PrintVariableList( &levelVars ); + + return true; +} + +qboolean G_GameVarsCmd( const gentity_t *ent ) +{ + gi.Printf( "Game Variables\n" ); + PrintVariableList( &gameVars ); + + return true; +} + +//-------------------------------------------------------------- +// +// Name: G_SetGameplayFloatCmd +// Class: None +// +// Description: Sets properties on the gameplay database. +// +// Parameters: const gentity_t *ent -- Entity, not used +// +// Returns: qboolean +// +//-------------------------------------------------------------- +qboolean G_SetGameplayFloatCmd( const gentity_t *ent ) +{ + str objname; + str propname; + str create = "0"; + float value = 1.0f; + + // Check for not enough args + if ( gi.argc() < 4 ) + return qfalse; + + objname = gi.argv( 1 ); + propname = gi.argv( 2 ); + value = (double)atof(gi.argv( 3 )); + if ( gi.argc() > 4 ) + create = gi.argv( 4 ); + + if ( create == "0" ) + GameplayManager::getTheGameplayManager()->setFloatValue(objname, propname, value); + else + GameplayManager::getTheGameplayManager()->setFloatValue(objname, propname, value, true); + + gi.Printf("Gameplay Modified -- %s's %s changed to %g\n", objname.c_str(), propname.c_str(), value); + + return qtrue; +} + + +//-------------------------------------------------------------- +// +// Name: G_SetGameplayStringCmd +// Class: None +// +// Description: Sets properties on the gameplay database. +// +// Parameters: const gentity_t *ent -- Entity, not used +// +// Returns: qboolean +// +//-------------------------------------------------------------- +qboolean G_SetGameplayStringCmd( const gentity_t *ent ) +{ + str objname; + str propname; + str valuestr; + str create = "0"; + + // Check for not enough args + if ( gi.argc() < 4 ) + return qfalse; + + objname = gi.argv( 1 ); + propname = gi.argv( 2 ); + valuestr = gi.argv( 3 ); + if ( gi.argc() > 4 ) + create = gi.argv( 4 ); + + if ( create == "0" ) + GameplayManager::getTheGameplayManager()->setStringValue(objname, propname, valuestr); + else + GameplayManager::getTheGameplayManager()->setStringValue(objname, propname, valuestr, true); + + gi.Printf("Gameplay Modified -- %s's %s changed to %s\n", objname.c_str(), propname.c_str(), valuestr.c_str()); + + return qtrue; +} + + +//=============================================================== +// Name: G_PurchaseSkillCmd +// Class: None +// +// Description: Purchases a skill for the current player. This +// goes into the gameplay database and looks for a +// CurrentPlayer object who has a skillPoints property. +// +// If its found, we attempt to increment the value +// of the skill specified in the command's first argument. +// We also decrement by one the number of available skillPoints. +// +// Parameters: gentity_t -- the entity issuing the command. Not used. +// +// Returns: qboolean -- true if the command was executed. +// +//=============================================================== +qboolean G_PurchaseSkillCmd( const gentity_t *ent ) +{ + str propname ; + + if ( gi.argc() < 1 ) return qfalse ; + + propname = gi.argv( 1 ); + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm ) return false ; + + float skillPoints = gpm->getFloatValue( "CurrentPlayer", "SkillPoints" ); + if ( skillPoints > 0.0f ) + { + str objName("CurrentPlayer."); + objName += propname ; + float skillValue = gpm->getFloatValue( objName, "value" ); + if ( skillValue < gpm->getFloatValue( objName, "max" ) ) + { + gpm->setFloatValue( objName, "value", skillValue + 1.0f ); + gpm->setFloatValue( "CurrentPlayer", "SkillPoints", skillPoints - 1.0f ); + + if ( ent->entity->isSubclassOf( Player ) ) + { + Player *player = (Player*)ent->entity; + player->skillChanged( objName ); + } + } + } + + return qtrue ; +} + + +//=============================================================== +// Name: G_SwapItemCmd +// Class: None +// +// Description: Swaps an inventory item with the currently held item +// of that type. This is fairly specific at this point +// to use the Gameplay Database in a particular way. +// +// The command takes a single string, which defines an +// inventory slot object. We retrieve the name of the +// item in that inventory slot. If it isn't found, there +// isn't an item there and nothing happens. +// +// If it is found, we determine if the player already has +// an item of that type in their local inventory (hands). +// This is done by again checking the database. If they +// do have a weapon of that type, we swap this one with +// that. Otherwise we just give him this weapon. +// +// Parameters: gentity_t -- the entity issuing the command. This +// is the player who issued the command. +// +// Returns: qboolean -- true if the command was executed. +// +//=============================================================== +qboolean G_SwapItemCmd( const gentity_t *ent ) +{ + if ( gi.argc() < 1 ) return qfalse ; + if ( !ent->entity->isSubclassOf( Player ) ) return qfalse ; + + Player *player = (Player*)ent->entity ; + str objectName = gi.argv( 1 ); + str propertyName = gi.argv( 2 ); + str heldItem ; + weaponhand_t hand = WEAPON_RIGHT; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm ) return false ; + + str itemName = gpm->getStringValue( objectName, propertyName ); + str tikiName = gpm->getStringValue( itemName, "model" ); + str itemType = gpm->getStringValue( itemName, "class" ); + str playerItem("CurrentPlayer." + itemType); + + if ( !gpm->hasObject(playerItem) ) + { + gi.WPrintf( "Warning: Unknown item type %s for item %s", itemType.c_str(), itemName.c_str() ); + return qfalse ; + } + + // Get the currently held item, and replace it with the item we clicked on + heldItem = gpm->getStringValue( playerItem, "name" ); + gpm->setStringValue( playerItem, "name", itemName ); + + // Ranged weapons are left hand + if ( itemType == "Ranged" ) + hand = WEAPON_LEFT ; + + // Remove the currently held item from the player's inventory + player->takeItem( heldItem.c_str() ); + + // Give the player the new item we clicked on + player->giveItem( tikiName ); + + // Use the weapon we clicked on if we picked a weapon of the same type + // that we have currently equipped. Otherwise, we just swap out + // the slots + Weapon *weap = 0; + weap = player->GetActiveWeapon(WEAPON_RIGHT); + if ( !weap ) + weap = player->GetActiveWeapon(WEAPON_LEFT); + if ( weap ) + { + str tmpstr = gpm->getStringValue(weap->getArchetype(), "class"); + if ( tmpstr == itemType ) + player->useWeapon( itemType, hand ); + } + + // Put the held item in inventory slot the clicked item was in. + gpm->setStringValue( objectName, propertyName, heldItem ); + + return qtrue ; +} + +//-------------------------------------------------------------- +// +// Name: G_DropItemCmd +// Class: None +// +// Description: Drops an item that is clicked on from the inventory +// +// Parameters: gentity_t -- the entity issuing the command. This +// is the player who issued the command. +// +// Returns: qboolean -- true if the command was executed. +// +//-------------------------------------------------------------- +qboolean G_DropItemCmd( const gentity_t *ent ) +{ + if ( gi.argc() < 1 ) return false ; + if ( !ent->entity->isSubclassOf( Player ) ) return false ; + + Player *player = (Player*)ent->entity ; + str objectName = gi.argv( 1 ); + str propertyName = gi.argv( 2 ); + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm ) return false ; + + str itemName = gpm->getStringValue( objectName, propertyName ); + if ( itemName == "Empty" ) + return false; + + str tikiName = gpm->getStringValue( itemName, "model" ); + str itemType = gpm->getStringValue( itemName, "class" ); + str playerItem("CurrentPlayer." + itemType); + + if ( !gpm->hasObject(playerItem) ) + { + gi.WPrintf( "Warning: Unknown item type %s for item %s", itemType.c_str(), itemName.c_str() ); + return false ; + } + + // Empty the slot + gpm->setStringValue( objectName, propertyName, "Empty" ); + + // Give the player the item to drop + //Item *givenItem = player->giveItem(tikiName); + + Weapon *dropweap; + ClassDef *cls; + cls = getClass( tikiName.c_str() ); + if ( !cls ) + { + SpawnArgs args; + args.setArg( "model", tikiName.c_str() ); + cls = args.getClassDef(); + if ( !cls ) + return false; + } + dropweap = ( Weapon * )cls->newInstance(); + dropweap->setModel( tikiName.c_str() ); + dropweap->ProcessPendingEvents(); + dropweap->SetOwner(player); + dropweap->hideModel(); + dropweap->setAttached(true); + dropweap->Drop(); + + return true ; +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +qboolean G_DialogRunThread( const gentity_t *ent ) +{ + str threadName; + + // clear out the current dialog actor + if(ent->entity->isSubclassOf(Player)) + { + Player* player = (Player*)ent->entity; + player->clearBranchDialogActor(); + } + + return G_ClientRunThreadCmd( ent ); +} \ No newline at end of file diff --git a/dlls/game/gamecmds.h b/dlls/game/gamecmds.h new file mode 100644 index 0000000..e340080 --- /dev/null +++ b/dlls/game/gamecmds.h @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/gamecmds.h $ +// $Revision:: 24 $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __GAMECMDS_H__ +#define __GAMECMDS_H__ + +#include "g_local.h" + +extern "C" void G_ClientCommand( gentity_t *ent ); +extern "C" qboolean G_ConsoleCommand( void ); + +void G_InitConsoleCommands( void ); + +qboolean G_ProcessClientCommand( gentity_t *ent ); + +void G_Say( const gentity_t *ent, bool team, qboolean arg0 ); +qboolean G_CameraCmd( const gentity_t *ent ); +qboolean G_SoundCmd( const gentity_t *ent ); +qboolean G_CinematicCmd( const gentity_t *ent ); +qboolean G_VTaunt( const gentity_t *ent ); // BOTLIB +qboolean G_SayCmd( const gentity_t *ent ); +qboolean G_TellCmd( const gentity_t *ent ); +qboolean G_TauntCmd( const gentity_t *ent ); +qboolean G_TeamSayCmd( const gentity_t *ent ); +qboolean G_LocCmd( const gentity_t *ent ); +qboolean G_WarpCmd( const gentity_t *ent ); +qboolean G_MaskCmd( const gentity_t *ent ); +qboolean G_EventListCmd( const gentity_t *ent ); +qboolean G_PendingEventsCmd( const gentity_t *ent ); +qboolean G_EventHelpCmd( const gentity_t *ent ); +qboolean G_DumpEventsCmd( const gentity_t *ent ); +qboolean G_ClassEventsCmd( const gentity_t *ent ); +qboolean G_DumpClassEventsCmd( const gentity_t *ent ); +qboolean G_DumpAllClassesCmd( const gentity_t *ent ); +qboolean G_ClassListCmd( const gentity_t *ent ); +qboolean G_ClassTreeCmd( const gentity_t *ent ); +qboolean G_ShowVarCmd( const gentity_t *ent ); +qboolean G_RestartCmd( const gentity_t *ent ); +qboolean G_ScriptCmd( const gentity_t *ent ); +qboolean G_ClientRunThreadCmd( const gentity_t *ent ); +qboolean G_ClientSetVarCmd( const gentity_t *ent ); +qboolean G_LevelVarsCmd( const gentity_t *ent ); +qboolean G_GameVarsCmd( const gentity_t *ent ); +qboolean G_SendCommandToAllPlayers( const char *command ); +qboolean G_SendCommandToPlayer( const gentity_t *ent, const char *command ); +qboolean G_EnableWidgetOfPlayer( const gentity_t *ent, const char *widgetName, qboolean enableFlag ); +qboolean G_SetWidgetTextOfPlayer( const gentity_t *ent, const char *widgetName, const char *widgetText ); +qboolean G_SetGameplayFloatCmd( const gentity_t *ent ); +qboolean G_SetGameplayStringCmd( const gentity_t *ent ); +qboolean G_PurchaseSkillCmd( const gentity_t *ent ); +qboolean G_SwapItemCmd( const gentity_t *ent ); +qboolean G_DropItemCmd( const gentity_t *ent ); +qboolean G_DialogRunThread( const gentity_t *ent ); + +#endif /* !__GAMECMDS_H__ */ diff --git a/dlls/game/gamecvars.cpp b/dlls/game/gamecvars.cpp new file mode 100644 index 0000000..ca1cbaf --- /dev/null +++ b/dlls/game/gamecvars.cpp @@ -0,0 +1,315 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/gamecvars.cpp $ +// $Revision:: 94 $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Definitions for any cvars used by the game. +// + +#include "_pch_cpp.h" +#include "gamecvars.h" + +cvar_t *developer; +cvar_t *skill; +cvar_t *password; +cvar_t *g_needpass; +cvar_t *filterban; +//cvar_t *flood_msgs; +//cvar_t *flood_persecond; +//cvar_t *flood_waitdelay; +cvar_t *maxclients; +cvar_t *maxentities; +cvar_t *nomonsters; +cvar_t *precache; +cvar_t *dedicated; +cvar_t *detail; +cvar_t *com_blood; +cvar_t *whereami; + +cvar_t *bosshealth; +cvar_t *bossname; + +cvar_t *sv_maxvelocity; +cvar_t *sv_rollspeed; +cvar_t *sv_rollangle; +cvar_t *sv_cheats; +cvar_t *sv_showbboxes; +cvar_t *sv_showcameras; +cvar_t *sv_showentnums; +cvar_t *sv_showactnums; +cvar_t *sv_showsplines; +cvar_t *sv_waterfriction; +cvar_t *sv_traceinfo; +cvar_t *sv_drawtrace; +cvar_t *sv_fps; +cvar_t *sv_cinematic; +//cvar_t *sv_maplist; +cvar_t *sv_nextmap; +cvar_t *sv_showdamagecolors; +cvar_t *sv_showdamageshake; + +cvar_t *sv_currentGravity; + +// New server cvars +cvar_t *sv_maxspeed; +cvar_t *sv_stopspeed; +cvar_t *sv_friction; +cvar_t *sv_gravity; +cvar_t *sv_waterspeed; +cvar_t *sv_wateraccelerate; +cvar_t *sv_groundtracelength; +cvar_t *sv_jumpvelocity; +cvar_t *sv_crouchjumpvelocity; +cvar_t *sv_edgefriction; +cvar_t *sv_airmaxspeed; +cvar_t *sv_airaccelerate; +cvar_t *sv_crouchspeed; +cvar_t *sv_noclipspeed; +cvar_t *sv_accelerate; +cvar_t *sv_defaultviewheight; +cvar_t *sv_instantjump; +cvar_t *sv_cancrouch; +cvar_t *sv_useanimmovespeed; +cvar_t *sv_defaultFov; +cvar_t *sv_strafeJumpingAllowed; + +cvar_t *csys_posx; +cvar_t *csys_posy; +cvar_t *csys_posz; +cvar_t *csys_x; +cvar_t *csys_y; +cvar_t *csys_z; +cvar_t *csys_draw; + +cvar_t *g_showmem; +cvar_t *g_timeents; +cvar_t *g_showaxis; +cvar_t *g_showgravpath; +cvar_t *g_showplayerstate; +cvar_t *g_showplayeranim; +cvar_t *g_showplayerweapon; +cvar_t *g_legswingspeed; +cvar_t *g_intermissiontime; +cvar_t *g_endintermission; +cvar_t *g_legclampangle; +cvar_t *g_legclamptolerance; +cvar_t *g_legtolerance; +cvar_t *g_numdebuglines; +cvar_t *g_playermodel; +cvar_t *g_statefile; +cvar_t *g_showbullettrace; +cvar_t *g_showactortrace; +cvar_t *g_showactorpath; +cvar_t *s_debugmusic; +cvar_t *g_showautoaim; +cvar_t *g_debugtargets; +cvar_t *g_debugdamage; +cvar_t *g_logstats; +cvar_t *g_gametype; +cvar_t *g_rankedserver; +cvar_t *g_showaccuracymod; +cvar_t *g_aimviewangles; +cvar_t *g_debug; +cvar_t *g_armoradaptionlimit; + +cvar_t *g_allowActionMusic; + +cvar_t *g_secretCount; + +cvar_t *scr_maxerrors; +cvar_t *scr_printfunccalls; +cvar_t *scr_printeventcalls; + +cvar_t *ai_numactive; + +cvar_t *ai_showfailure; + +cvar_t *mp_gametype; +cvar_t *mp_flags; +cvar_t *mp_pointlimit; +cvar_t *mp_timelimit; +cvar_t *mp_itemRespawnMultiplier; +cvar_t *mp_weaponRespawnMultiplier; +cvar_t *mp_powerupRespawnMultiplier; +cvar_t *mp_knockbackMultiplier; +cvar_t *mp_damageMultiplier; +cvar_t *mp_respawnInvincibilityTime; +cvar_t *mp_warmUpTime; +cvar_t *mp_minPlayers; +cvar_t *mp_bigGunMode; +cvar_t *mp_respawnTime; +cvar_t *mp_intermissionTime; +cvar_t *mp_bombTime; +cvar_t *mp_maxVotes; +cvar_t *mp_useMapList; +cvar_t *mp_mapList; +cvar_t *mp_currentPosInMapList; +cvar_t *mp_skipWeaponReloads; +cvar_t *mp_minTauntTime; + +cvar_t *sv_showinfo; +cvar_t *sv_showinfodist; + +void CVAR_Init( void ) +{ + + developer = gi.cvar( "developer", "0", CVAR_CHEAT ); + precache = gi.cvar( "sv_precache", "1", 0 ); + dedicated = gi.cvar( "dedicated", "0", CVAR_LATCH ); + skill = gi.cvar( "skill", "1", CVAR_SERVERINFO|CVAR_LATCH ); +#ifdef DEDICATED + maxclients = gi.cvar( "sv_maxclients", "16", CVAR_SERVERINFO | CVAR_LATCH ); + sv_maxspeed = gi.cvar( "sv_maxspeed", "400", 0 ); +#else + maxclients = gi.cvar( "sv_maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH ); + sv_maxspeed = gi.cvar( "sv_maxspeed", "350", 0 ); +#endif + maxentities = gi.cvar( "maxentities", "1024", CVAR_LATCH ); + password = gi.cvar( "password", "", CVAR_USERINFO ); + g_needpass = gi.cvar( "g_needpass", "0", CVAR_SERVERINFO ); + filterban = gi.cvar( "filterban", "1", 0 ); + nomonsters = gi.cvar( "nomonsters", "0", CVAR_SERVERINFO ); + // flood_msgs = gi.cvar( "flood_msgs", "4", 0 ); + // flood_persecond = gi.cvar( "flood_persecond", "4", 0 ); + // flood_waitdelay = gi.cvar( "flood_waitdelay", "10", 0 ); + detail = gi.cvar( "detail", "1", CVAR_ARCHIVE ); + com_blood = gi.cvar( "com_blood", "1", CVAR_USERINFO|CVAR_SERVERINFO|CVAR_ARCHIVE ); + whereami = gi.cvar( "whereami", "0", 0 ); + + bosshealth = gi.cvar( "bosshealth", "0", 0 ); + bossname = gi.cvar( "bossname", "", 0 ); + + sv_rollspeed = gi.cvar( "sv_rollspeed", "200", 0 ); + sv_showdamageshake = gi.cvar( "sv_showdamageshake", "1", 0 ); + sv_rollangle = gi.cvar( "sv_rollangle", "2", 0 ); + sv_maxvelocity = gi.cvar( "sv_maxvelocity", "2000", 0 ); + sv_gravity = gi.cvar( "sv_gravity", "800", 0 ); + sv_currentGravity = gi.cvar( "sv_currentGravity", "800", 0 ); + sv_traceinfo = gi.cvar( "sv_traceinfo", "0", 0 ); + sv_drawtrace = gi.cvar( "sv_drawtrace", "0", 0 ); + sv_showbboxes = gi.cvar( "sv_showbboxes", "0", CVAR_CHEAT ); + sv_showcameras = gi.cvar( "sv_showcameras", "0", 0 ); + sv_showsplines = gi.cvar( "sv_showsplines", "0", 0 ); + sv_showentnums = gi.cvar( "sv_showentnums", "0", CVAR_CHEAT ); + sv_showactnums = gi.cvar( "sv_showactnums", "0", 0 ); + sv_showdamagecolors = gi.cvar( "sv_showdamagecolors", "1", 0 ); + sv_friction = gi.cvar( "sv_friction", "6", CVAR_SERVERINFO ); + sv_stopspeed = gi.cvar( "sv_stopspeed", "50", CVAR_SERVERINFO ); + sv_waterfriction = gi.cvar( "sv_waterfriction", "1", CVAR_SERVERINFO ); + sv_waterspeed = gi.cvar( "sv_waterspeed", "300", CVAR_SERVERINFO ); + sv_cheats = gi.cvar( "cheats", "0", CVAR_SERVERINFO|CVAR_LATCH ); + sv_fps = gi.cvar( "sv_fps", "20", CVAR_SERVERINFO ); + sv_cinematic = gi.cvar( "sv_cinematic", "0", CVAR_SERVERINFO|CVAR_ROM ); + //sv_maplist = gi.cvar( "sv_maplist", "", CVAR_ARCHIVE ); + sv_nextmap = gi.cvar( "nextmap", "", 0 ); + sv_wateraccelerate = gi.cvar( "sv_wateraccelerate", "6", 0 ); + sv_groundtracelength = gi.cvar( "sv_groundtracelength", "0.25", 0 ); + sv_jumpvelocity = gi.cvar( "sv_jumpvelocity", "350", 0 ); + sv_crouchjumpvelocity = gi.cvar( "sv_crouchjumpvelocity", "45", 0 ); + sv_edgefriction = gi.cvar( "sv_edgefriction", "8", 0 ); + sv_airmaxspeed = gi.cvar( "sv_airmaxspeed", "0", 0 ); + sv_airaccelerate = gi.cvar( "sv_airaccelerate", "2", 0 ); + sv_crouchspeed = gi.cvar( "sv_crouchspeed", "0", 0 ); + sv_noclipspeed = gi.cvar( "sv_noclipspeed", "400", 0 ); + sv_accelerate = gi.cvar( "sv_accelerate", "10", 0 ); + sv_defaultviewheight = gi.cvar( "sv_defaultviewheight", "85", 0 ); + sv_instantjump = gi.cvar( "sv_instantjump", "1", CVAR_ROM); + sv_useanimmovespeed = gi.cvar( "sv_useanimmovespeed", "0", CVAR_ROM); + sv_cancrouch = gi.cvar( "sv_cancrouch", "1", CVAR_ROM); + sv_defaultFov = gi.cvar( "sv_defaultFov", "90", 0 ); + sv_strafeJumpingAllowed = gi.cvar( "sv_strafeJumpingAllowed", "1", 0 ); + + + g_showmem = gi.cvar( "g_showmem", "0", 0 ); + g_timeents = gi.cvar( "g_timeents", "0", 0 ); + g_showaxis = gi.cvar( "g_showaxis", "0", 0 ); + g_showgravpath = gi.cvar( "g_drawgravpath", "0", 0 ); + g_showplayerstate = gi.cvar( "g_showplayerstate", "0", 0 ); + g_showplayeranim = gi.cvar( "g_showplayeranim", "0", 0 ); + g_showplayerweapon = gi.cvar( "g_showplayerweapon", "0", 0 ); + g_showbullettrace = gi.cvar( "g_showbullettrace", "0", 0 ); + g_showactortrace = gi.cvar( "g_showactortrace", "0", 0 ); + g_showactorpath = gi.cvar( "g_showactorpath", "0", 0 ); + g_showaccuracymod = gi.cvar( "g_showaccuracymod", "0", 0 ); + g_aimviewangles = gi.cvar( "g_aimviewangles", "0", 0 ); + + g_legswingspeed = gi.cvar( "g_legswingspeed", "200", 0 ); + g_intermissiontime = gi.cvar( "g_intermissiontime", "10", 0 ); + g_endintermission = gi.cvar( "g_endintermission", "0", 0 ); + g_legclampangle = gi.cvar( "g_legclampangle", "90", 0 ); + g_legclamptolerance = gi.cvar( "g_legclamptolerance", "50", 0 ); + g_legtolerance = gi.cvar( "g_legtolerance", "40", 0 ); + + g_numdebuglines = gi.cvar( "g_numdebuglines", "4096", CVAR_LATCH ); + g_playermodel = gi.cvar( "g_playermodel", "char/mainchar", 0 ); + g_statefile = gi.cvar( "g_statefile", "char/mainchar", 0 ); + g_showautoaim = gi.cvar( "g_showautoaim", "0", 0 ); + g_debugtargets = gi.cvar( "g_debugtargets", "0", 0 ); + g_debugdamage = gi.cvar( "g_debugdamage", "0", 0 ); + g_logstats = gi.cvar( "g_logstats", "0", 0 ); + g_gametype = gi.cvar( "g_gametype", "0", CVAR_SERVERINFO|CVAR_LATCH ); + g_rankedserver = gi.cvar( "g_rankedserver", "0", 0 ); + + g_allowActionMusic = gi.cvar( "g_allowActionMusic", "1", 0 ); + g_armoradaptionlimit = gi.cvar( "g_armoradaptionlimit", "300" , 0 ); + + g_secretCount = gi.cvar( "g_secretCount", "0" , CVAR_ARCHIVE | CVAR_ROM ); + //g_secretCount = gi.cvar( "g_secretCount", "0" , CVAR_ARCHIVE ); + + csys_posx = gi.cvar( "csys_posx", "0", 0 ); + csys_posy = gi.cvar( "csys_posy", "0", 0 ); + csys_posz = gi.cvar( "csys_posz", "0", 0 ); + csys_x = gi.cvar( "csys_x", "0", 0 ); + csys_y = gi.cvar( "csys_y", "0", 0 ); + csys_z = gi.cvar( "csys_z", "0", 0 ); + csys_draw = gi.cvar( "csys_draw", "0", 0 ); + + s_debugmusic = gi.cvar( "s_debugmusic", "0", 0 ); + + g_debug = gi.cvar( "g_debug", "0", 0 ); + + scr_maxerrors = gi.cvar( "scr_maxerrors", "10", 0 ); + scr_printfunccalls = gi.cvar( "scr_printfunccalls", "0", 0 ); + scr_printeventcalls = gi.cvar( "scr_printeventcalls", "0", 0 ); + + ai_numactive = gi.cvar( "ai_numactive", "0", 0 ); + + ai_showfailure = gi.cvar( "ai_showfailure", "0", 0 ); + + mp_gametype = gi.cvar( "mp_gametype", "0", CVAR_SERVERINFO ); + mp_flags = gi.cvar( "mp_flags", "0", CVAR_SERVERINFO ); + mp_pointlimit = gi.cvar( "mp_pointlimit", "0", CVAR_SERVERINFO ); + mp_timelimit = gi.cvar( "mp_timelimit", "0", CVAR_SERVERINFO ); + mp_itemRespawnMultiplier = gi.cvar( "mp_itemRespawnMultiplier", "1.0", CVAR_SERVERINFO ); + mp_weaponRespawnMultiplier = gi.cvar( "mp_weaponRespawnMultiplier", "1.0", CVAR_SERVERINFO ); + mp_powerupRespawnMultiplier = gi.cvar( "mp_powerupRespawnMultiplier", "1.0", CVAR_SERVERINFO ); + mp_knockbackMultiplier = gi.cvar( "mp_knockbackMultiplier", "1.0", CVAR_SERVERINFO ); + mp_damageMultiplier = gi.cvar( "mp_damageMultiplier", "1.0", CVAR_SERVERINFO ); + mp_respawnInvincibilityTime = gi.cvar( "mp_respawnInvincibilityTime", "4", CVAR_SERVERINFO ); + mp_warmUpTime = gi.cvar( "mp_warmUpTime", "10", CVAR_SERVERINFO ); + mp_minPlayers = gi.cvar( "mp_minPlayers", "0", CVAR_SERVERINFO ); + mp_bigGunMode = gi.cvar( "mp_bigGunMode", "0", CVAR_SERVERINFO ); + mp_respawnTime = gi.cvar( "mp_respawnTime", "-1", CVAR_SERVERINFO ); + mp_intermissionTime = gi.cvar( "mp_intermissionTime", "30", 0 ); + + mp_bombTime = gi.cvar( "mp_bombTime", "30", 0 ); + mp_maxVotes = gi.cvar( "mp_maxVotes", "3", 0 ); + mp_skipWeaponReloads = gi.cvar( "mp_skipWeaponReloads", "0", 0 ); + mp_minTauntTime = gi.cvar( "mp_minTauntTime", "4", CVAR_ARCHIVE ); + + mp_useMapList = gi.cvar( "mp_useMapList", "0", 0 ); + mp_mapList = gi.cvar( "mp_mapList", "", CVAR_ARCHIVE ); + mp_currentPosInMapList = gi.cvar( "mp_currentPosInMapList", "0", 0 ); + + sv_showinfo = gi.cvar( "sv_showinfo", "0", 0 ); + sv_showinfodist = gi.cvar( "sv_showinfodist", "256", 0 ); +} diff --git a/dlls/game/gamecvars.h b/dlls/game/gamecvars.h new file mode 100644 index 0000000..9ec92e0 --- /dev/null +++ b/dlls/game/gamecvars.h @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/gamecvars.h $ +// $Revision:: 62 $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __GAMECVARS_H__ +#define __GAMECVARS_H__ + +#include "g_local.h" + +extern cvar_t *developer; +extern cvar_t *skill; +extern cvar_t *password; +extern cvar_t *g_needpass; +extern cvar_t *filterban; +//extern cvar_t *flood_msgs; +//extern cvar_t *flood_persecond; +//extern cvar_t *flood_waitdelay; +extern cvar_t *maxclients; +extern cvar_t *maxentities; +extern cvar_t *nomonsters; +extern cvar_t *precache; +extern cvar_t *dedicated; +extern cvar_t *detail; +extern cvar_t *com_blood; +extern cvar_t *whereami; +extern cvar_t *bosshealth; +extern cvar_t *bossname; + +extern cvar_t *sv_maxvelocity; +extern cvar_t *sv_rollspeed; +extern cvar_t *sv_showdamageshake; +extern cvar_t *sv_rollangle; +extern cvar_t *sv_cheats; +extern cvar_t *sv_showbboxes; +extern cvar_t *sv_showcameras; +extern cvar_t *sv_showsplines; +extern cvar_t *sv_showentnums; +extern cvar_t *sv_showactnums; +extern cvar_t *sv_showdamagecolors ; +extern cvar_t *sv_waterfriction; +extern cvar_t *sv_traceinfo; +extern cvar_t *sv_drawtrace; +extern cvar_t *sv_fps; +extern cvar_t *sv_cinematic; +//extern cvar_t *sv_maplist; +extern cvar_t *sv_nextmap; + +extern cvar_t *sv_currentGravity; + +// New server cvars +extern cvar_t *sv_maxspeed; +extern cvar_t *sv_stopspeed; +extern cvar_t *sv_friction; +extern cvar_t *sv_gravity; +extern cvar_t *sv_waterspeed; +extern cvar_t *sv_wateraccelerate; +extern cvar_t *sv_groundtracelength; +extern cvar_t *sv_jumpvelocity; +extern cvar_t *sv_crouchjumpvelocity; +extern cvar_t *sv_edgefriction; +extern cvar_t *sv_airmaxspeed; +extern cvar_t *sv_airaccelerate; +extern cvar_t *sv_crouchspeed; +extern cvar_t *sv_noclipspeed; +extern cvar_t *sv_accelerate; +extern cvar_t *sv_defaultviewheight; +extern cvar_t *sv_instantjump; +extern cvar_t *sv_cancrouch; +extern cvar_t *sv_useanimmovespeed; +extern cvar_t *sv_defaultFov; +extern cvar_t *sv_strafeJumpingAllowed; + +extern cvar_t *csys_posx; +extern cvar_t *csys_posy; +extern cvar_t *csys_posz; +extern cvar_t *csys_x; +extern cvar_t *csys_y; +extern cvar_t *csys_z; +extern cvar_t *csys_draw; + +extern cvar_t *g_showmem; +extern cvar_t *g_timeents; +extern cvar_t *g_showaxis; +extern cvar_t *g_showgravpath; +extern cvar_t *g_showplayerstate; +extern cvar_t *g_showplayeranim; +extern cvar_t *g_showplayerweapon; +extern cvar_t *g_showbullettrace; +extern cvar_t *g_showactortrace; +extern cvar_t *g_showactorpath; +extern cvar_t *g_legswingspeed; +extern cvar_t *g_intermissiontime; +extern cvar_t *g_endintermission; +extern cvar_t *g_legclampangle; +extern cvar_t *g_legclamptolerance; +extern cvar_t *g_legtolerance; +extern cvar_t *g_numdebuglines; +extern cvar_t *g_playermodel; +extern cvar_t *g_statefile; +extern cvar_t *g_showautoaim; +extern cvar_t *g_debugtargets; +extern cvar_t *g_debugdamage; +extern cvar_t *s_debugmusic; +extern cvar_t *g_logstats; +extern cvar_t *g_gametype; +extern cvar_t *g_showaccuracymod; +extern cvar_t *g_aimviewangles; +extern cvar_t *g_debug; + +extern cvar_t *g_secretCount; + +//extern cvar_t *g_allowActionMusic; + +extern cvar_t *g_allowActionMusic; + +extern cvar_t *scr_maxerrors; +extern cvar_t *scr_printfunccalls; +extern cvar_t *scr_printeventcalls; + +extern cvar_t *ai_numactive; + +extern cvar_t *mp_gametype; +extern cvar_t *mp_flags; +extern cvar_t *mp_pointlimit; +extern cvar_t *mp_timelimit; +extern cvar_t *mp_itemRespawnMultiplier; +extern cvar_t *mp_weaponRespawnMultiplier; +extern cvar_t *mp_powerupRespawnMultiplier; +extern cvar_t *mp_knockbackMultiplier; +extern cvar_t *mp_damageMultiplier; +extern cvar_t *mp_respawnInvincibilityTime; +extern cvar_t *mp_warmUpTime; +extern cvar_t *mp_minPlayers; +extern cvar_t *mp_bigGunMode; +extern cvar_t *mp_respawnTime; +extern cvar_t *mp_intermissionTime; +extern cvar_t *mp_bombTime; +extern cvar_t *mp_maxVotes; +extern cvar_t *mp_skipWeaponReloads; +extern cvar_t *mp_minTauntTime; + +extern cvar_t *mp_useMapList; +extern cvar_t *mp_mapList; +extern cvar_t *mp_currentPosInMapList; + +extern cvar_t *sv_showinfo; +extern cvar_t *sv_showinfodist; + +void CVAR_Init( void ); + +#endif /* !__GAMECVARS_H__ */ diff --git a/dlls/game/gamescript.cpp b/dlls/game/gamescript.cpp new file mode 100644 index 0000000..823ed84 --- /dev/null +++ b/dlls/game/gamescript.cpp @@ -0,0 +1,447 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/gamescript.cpp $ +// $Revision:: 5 $ +// $Author:: Steven $ +// $Date:: 10/08/02 1:37p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Subclass of script that preprocesses labels +// + +#include "_pch_cpp.h" +#include "script.h" +#include "gamescript.h" + +ScriptLibrarian ScriptLib; + +CLASS_DECLARATION( Class, GameScriptMarker, NULL ) +{ + { NULL, NULL } +}; + +CLASS_DECLARATION( Class, ScriptLibrarian, NULL ) +{ + { NULL, NULL } +}; + +ScriptLibrarian::~ScriptLibrarian() +{ + int i; + int num; + + num = scripts.NumObjects(); + for( i = 1; i <= num; i++ ) + { + delete scripts.ObjectAt( i ); + } +} + +void ScriptLibrarian::CloseScripts( void ) +{ + int i; + int num; + GameScript *scr; + + // Clear out the game and dialog scripts + SetGameScript( "" ); + SetDialogScript( "" ); + + num = scripts.NumObjects(); + for( i = num; i > 0; i-- ) + { + scr = scripts.ObjectAt( i ); + scripts.RemoveObjectAt( i ); + delete scr; + } +} + +void ScriptLibrarian::SetDialogScript( const str &scriptname ) +{ + dialog_script = scriptname; +} + +void ScriptLibrarian::SetGameScript( const str &scriptname ) +{ + game_script = scriptname; +} + +const char *ScriptLibrarian::GetGameScript( void ) +{ + return game_script.c_str(); +} + +GameScript *ScriptLibrarian::FindScript( const char *name ) +{ + int i; + int num; + GameScript *scr; + str n; + + // Convert all forward slashes to back slashes + n = G_FixSlashes( name ); + + num = scripts.NumObjects(); + for( i = 1; i <= num; i++ ) + { + scr = scripts.ObjectAt( i ); + + if ( scr->Filename() == n ) + { + return scr; + } + } + + return NULL; +} + +GameScript *ScriptLibrarian::GetScript( const char *name ) +{ + GameScript *scr; + str n; + + n = G_FixSlashes( name ); + if ( !n.length() ) + { + return NULL; + } + + + // see if we have an absolute path specified, + // if we don't use the same path as the map file + if ( !strchr( n.c_str(), '/' ) ) + { + int i; + str mapname; + + mapname = "maps/"; + mapname += level.mapname; + for( i = mapname.length() - 1; i >= 0; i-- ) + { + if ( mapname[ i ] == '/' ) + { + // skip back over the '/' + i++; + mapname[ i ] = 0; + break; + } + } + mapname += n; + // copy it back into the write string + n = mapname; + } + + scr = FindScript( n.c_str() ); + if ( !scr && ( gi.FS_ReadFile( n.c_str(), NULL, false ) != -1 ) ) + { + scr = new GameScript(); + scr->LoadFile( n.c_str() ); + scripts.AddObject( scr ); + } + + return scr; +} + +qboolean ScriptLibrarian::Goto( GameScript *scr, const char *name ) +{ + const char *p; + GameScript *s; + str n; + + p = strstr( name, "::" ); + if ( !p ) + { + return scr->Goto( name ); + } + else + { + n = str( name, 0, p - name ); + if ( n == str( "dialog" ) ) + { + n = dialog_script; + } + s = GetScript( n.c_str() ); + if ( !s ) + { + return false; + } + + p += 2; + if ( s->labelExists( p ) ) + { + scr->SetSourceScript( s ); + return scr->Goto( p ); + } + } + + return false; +} + +qboolean ScriptLibrarian::labelExists( GameScript *scr, const char *name ) +{ + const char *p; + GameScript *s; + str n; + + // if we got passed a NULL than that means just run the script so of course it exists + if ( !name ) + { + return true; + } + + p = strstr( name, "::" ); + if ( !p ) + { + return scr->labelExists( name ); + } + else + { + n = str( name, 0, p - name ); + if ( n == str( "dialog" ) ) + { + n = dialog_script; + } + s = GetScript( n.c_str() ); + if ( !s ) + { + return false; + } + + p += 2; + return s->labelExists( p ); + } +} + +CLASS_DECLARATION( Script, GameScript, NULL ) +{ + { NULL, NULL } +}; + +GameScript::GameScript() +{ + sourcescript = this; + labelList = NULL; + crc = 0; +} + +GameScript::GameScript( GameScript *scr ) +{ + crc = 0; + labelList = NULL; + SetSourceScript( scr ); +} + +GameScript::~GameScript() +{ + Close(); +} + +void GameScript::Close( void ) +{ + FreeLabels(); + Script::Close(); + sourcescript = this; + crc = 0; +} + +void GameScript::SetSourceScript( GameScript *scr ) +{ + if ( scr != this ) + { + Close(); + + sourcescript = scr->sourcescript; + crc = sourcescript->crc; + Parse( scr->buffer, scr->length, scr->Filename() ); + } +} + +void GameScript::FreeLabels( void ) +{ + int i; + int num; + + if ( labelList ) + { + num = labelList->NumObjects(); + for( i = 1; i <= num; i++ ) + { + delete labelList->ObjectAt( i ); + } + + delete labelList; + } + + labelList = NULL; +} + +void GameScript::LoadFile( const char *name ) +{ + str n; + + // Convert all forward slashes to back slashes + n = G_FixSlashes( name ); + + sourcescript = this; + Script::LoadFile( n.c_str() ); + FindLabels(); + + crc = gi.CalcCRC( (const unsigned char *)buffer, length ); +} + +void GameScript::FindLabels( void ) +{ + scriptmarker_t mark; + const char *tok; + script_label_t *label; + int len; + + FreeLabels(); + + labelList = new Container; + + MarkPosition( &mark ); + + Reset(); + + while( TokenAvailable( true ) ) + { + tok = GetToken( true ); + // see if it is a label + if ( tok ) + { + len = strlen( tok ); + if ( ( len > 1 ) && ( tok[ len - 1 ] == ':' ) ) + { + if ( !labelExists( tok ) ) + { + label = new script_label_t; + MarkPosition( &label->pos ); + label->labelname = tok; + labelList->AddObject( label ); + } + else + { + warning( "FindLabels", "Duplicate labels %s\n", tok ); + } + } + } + } + + RestorePosition( &mark ); +} + +qboolean GameScript::labelExists( const char *name ) +{ + str labelname; + script_label_t *label; + int i; + int num; + + // if we got passed a NULL than that means just run the script so of course it exists + if ( !name ) + { + return true; + } + + if ( !sourcescript->labelList ) + { + return false; + } + + labelname = name; + if ( !labelname.length() ) + { + return false; + } + + if ( labelname[ labelname.length() - 1 ] != ':' ) + { + labelname += ":"; + } + + num = sourcescript->labelList->NumObjects(); + for( i = 1; i <= num; i++ ) + { + label = sourcescript->labelList->ObjectAt( i ); + if ( labelname == label->labelname ) + { + return true; + } + } + + return false; +} + +qboolean GameScript::Goto( const char *name ) +{ + str labelname; + script_label_t *label; + int i; + int num; + + if ( !sourcescript->labelList ) + { + return false; + } + + labelname = name; + if ( !labelname.length() ) + { + return false; + } + + if ( labelname[ labelname.length() - 1 ] != ':' ) + { + labelname += ":"; + } + + num = sourcescript->labelList->NumObjects(); + for( i = 1; i <= num; i++ ) + { + label = sourcescript->labelList->ObjectAt( i ); + if ( labelname == label->labelname ) + { + RestorePosition( &label->pos ); + return true; + } + } + + return false; +} + +void GameScript::Mark( GameScriptMarker *mark ) +{ + assert( mark ); + assert( sourcescript ); + + mark->filename = sourcescript->Filename(); + MarkPosition( &mark->scriptmarker ); +} + +void GameScript::Restore( GameScriptMarker *mark ) +{ + // If we change this function, we must update the unarchive function as well + GameScript *scr; + + assert( mark ); + + scr = ScriptLib.FindScript( mark->filename.c_str() ); + if ( scr ) + { + SetSourceScript( scr ); + } + else + { + LoadFile( mark->filename.c_str() ); + } + + RestorePosition( &mark->scriptmarker ); +} diff --git a/dlls/game/gamescript.h b/dlls/game/gamescript.h new file mode 100644 index 0000000..8bfe9d3 --- /dev/null +++ b/dlls/game/gamescript.h @@ -0,0 +1,193 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/gamescript.h $ +// $Revision:: 4 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Subclass of script that preprocesses labels +// + +#ifndef __GAMESCRIPT_H__ +#define __GAMESCRIPT_H__ + +#include "class.h" +#include "script.h" + +typedef struct + { + scriptmarker_t pos; + str labelname; + } script_label_t; + +class GameScript; + +class GameScriptMarker : public Class + { + public: + CLASS_PROTOTYPE( GameScriptMarker ); + + str filename; + scriptmarker_t scriptmarker; + virtual void Archive( Archiver &arc ); + }; + +inline void GameScriptMarker::Archive + ( + Archiver &arc + ) + + { + // Game scripts are unique in that we don't call our superclass to archive it's data. + // Instead, we only read enough info to then initialize the script ourselves. + arc.ArchiveString( &filename ); + arc.ArchiveBoolean( &scriptmarker.tokenready ); + arc.ArchiveInteger( &scriptmarker.offset ); + arc.ArchiveInteger( &scriptmarker.line ); + arc.ArchiveRaw( scriptmarker.token, sizeof( scriptmarker.token ) ); + } + +class GameScript : public Script + { + protected: + Container *labelList; + GameScript *sourcescript; + unsigned crc; + + public: + CLASS_PROTOTYPE( GameScript ); + + GameScript(); + GameScript( GameScript *scr ); + ~GameScript(); + void Close( void ); + void SetSourceScript( GameScript *scr ); + void LoadFile( const char *filename ); + + void Mark( GameScriptMarker *mark ); + void Restore( GameScriptMarker *mark ); + + void FreeLabels( void ); + void FindLabels( void ); + qboolean labelExists( const char *name ); + qboolean Goto( const char *name ); + virtual void Archive( Archiver &arc ); + }; + +class ScriptLibrarian : public Class + { + protected: + Container scripts; + str dialog_script; + str game_script; + + public: + CLASS_PROTOTYPE( ScriptLibrarian ); + + ~ScriptLibrarian(); + + void CloseScripts( void ); + void SetDialogScript( const str &scriptname ); + void SetGameScript( const str &scriptname ); + const char *GetGameScript( void ); + GameScript *FindScript( const char *name ); + GameScript *GetScript( const char *name ); + qboolean Goto( GameScript *scr, const char *name ); + qboolean labelExists( GameScript *scr, const char *name ); + virtual void Archive( Archiver &arc ); + }; + +inline void ScriptLibrarian::Archive + ( + Archiver &arc + ) + { + GameScript * scr; + int i, num; + + Class::Archive( arc ); + + if ( arc.Loading() ) + { + scripts.FreeObjectList(); + } + else + { + num = scripts.NumObjects(); + } + arc.ArchiveInteger( &num ); + + for ( i = 1; i <= num; i++ ) + { + if ( arc.Saving() ) + { + scr = scripts.ObjectAt( i ); + } + else + { + scr = new GameScript; + } + arc.ArchiveObject( scr ); + if ( arc.Loading() ) + { + scripts.AddObject( scr ); + } + } + arc.ArchiveString( &dialog_script ); + arc.ArchiveString( &game_script ); + } + +extern ScriptLibrarian ScriptLib; + +inline void GameScript::Archive + ( + Archiver &arc + ) + + { + // Game scripts are unique in that we don't call our superclass to archive it's data. + // Instead, we only read enough info to then initialize the script ourselves. + GameScriptMarker mark; + + if ( arc.Saving() ) + { + arc.ArchiveUnsigned( &crc ); + Mark( &mark ); + arc.ArchiveObject( &mark ); + } + else + { + unsigned filecrc; + GameScript *scr; + + arc.ArchiveUnsigned( &filecrc ); + arc.ArchiveObject( &mark ); + scr = ScriptLib.FindScript( mark.filename.c_str() ); + if ( scr ) + { + SetSourceScript( scr ); + } + else + { + LoadFile( mark.filename.c_str() ); + } + + // Error out if CRCs have changed + if ( filecrc != crc ) + { + gi.Error( ERR_DROP, "File '%s' has changed from when this savegame was written. Load cancelled.\n", filename.c_str() ); + } + + RestorePosition( &mark.scriptmarker ); + } + } + +#endif diff --git a/dlls/game/generalCombatWithMeleeWeapon.cpp b/dlls/game/generalCombatWithMeleeWeapon.cpp new file mode 100644 index 0000000..07f4677 --- /dev/null +++ b/dlls/game/generalCombatWithMeleeWeapon.cpp @@ -0,0 +1,957 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/generalCombatWithMeleeWeapon.cpp $ +// $Revision:: 16 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// GeneralCombatWithMeleeWeapon Implementation +// -- GCWMW will run the actor towards its enemy until it is within melee range. +// it will work to keep the actor rotated to face its enemy and play the +// specified attack animations. There is also rudimentary support for changing +// "posture" which will allow the actor to duck and lean out of the way. Additionally +// there is very minimal support for blocking... Though this will really need to +// be fleshed out for it to work. +// +// PARAMETERS: +// Primary Torso Anim ( the Idle Animation ) -- Required +// Attack Anim -- Required +// MaxDistanceToEngage -- Optional +// MeleeDistance -- Optional +// Rush Anim -- Optional +// Allow Rush Failure -- Optional +// Rotation Anim -- Optional +// Strafe Chance -- Optional +// Block Chance -- Optional +// Attack Chance -- Optional +// Posture Change Chance -- Optional +// +// ANIMATIONS: +// Primary Torso Animation ( Idle ) : Parameter +// Attack Animation : Parameter +// Rotation Animation : Parameter -- Defaults to "rotate" +// Rush Animation : Parameter -- Defaults to "run" +// "strafe_left" : TIKI Requirement -- If Strafing +// "strafe_right" : TIKI Requirement -- If Strafing +// "roll_left" : TIKI Requirement -- If Strafing +// "roll_right" : TIKI Requirement -- If Strafing +// "block" : TIKI Requirement -- If Blocking +// "lean_left_dual_stand" : TIKI Requirement -- If Changing Posture +// "lean_right_dual_stand" : TIKI Requirement -- If Changing Posture +// "lean_left_dual_stand_end" : TIKI Requirement -- If Changing Posture +// "lean_right_dual_stand_end" : TIKI Requirement -- If Changing Posture +// "lean_left_dual_stand_start" : TIKI Requirement -- If Changing Posture +// "lean_right_dual_stand_start" : TIKI Requirement -- If Changing Posture +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "generalCombatWithMeleeWeapon.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, GeneralCombatWithMeleeWeapon, NULL ) + { + { &EV_Behavior_Args, &GeneralCombatWithMeleeWeapon::SetArgs }, + { &EV_Behavior_AnimDone, &GeneralCombatWithMeleeWeapon::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: GeneralCombatWithMeleeWeapon() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GeneralCombatWithMeleeWeapon::GeneralCombatWithMeleeWeapon() +{ + _primaryTorsoAnim = "idle"; + _attackAnim = "idle"; + _rotationAnim = "rotate"; + _rushAnim = "run"; + _maxDistanceToEngage = 0.0f; + _meleeDistance = 128.0f; + _strafeChance = 0.50f; + _blockChance = 0.50f; + _attackChance = 0.85f; + _postureChangeChance = 0.0f; + _allowRushFailure = false; +} + +//-------------------------------------------------------------- +// Name: ~GeneralCombatWithMeleeWeapon() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GeneralCombatWithMeleeWeapon::~GeneralCombatWithMeleeWeapon() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::SetArgs ( Event *ev) +{ + // Required Parameters + _primaryTorsoAnim = ev->GetString( 1 ); + _attackAnim = ev->GetString( 2 ); + + // Optional Parameters + if ( ev->NumArgs() > 2 ) + _maxDistanceToEngage = ev->GetFloat( 3 ); + + if ( ev->NumArgs() > 3 ) + _meleeDistance = ev->GetFloat( 4 ); + + if ( ev->NumArgs() > 4 ) + _rushAnim = ev->GetString( 5 ); + + if ( ev->NumArgs() > 5 ) + _allowRushFailure = ev->GetBoolean( 6 ); + + if ( ev->NumArgs() > 6 ) + _rotationAnim = ev->GetString( 7 ); + + if ( ev->NumArgs() > 7 ) + _strafeChance = ev->GetFloat( 8 ); + + if ( ev->NumArgs() > 8 ) + _blockChance = ev->GetFloat( 9 ); + + if ( ev->NumArgs() > 9 ) + _attackChance = ev->GetFloat( 10 ); + + if ( ev->NumArgs() > 10 ) + _postureChangeChance = ev->GetFloat( 11 ); + + +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::AnimDone( Event *ev ) +{ + switch ( _state ) + { + case GENERAL_COMBAT_MELEE_STRAFE: + _strafeComponent.AnimDone( ev ); + _nextRushAttemptTime = 0.0f; + break; + + case GENERAL_COMBAT_MELEE_ATTACK: + _self->SetAnim( _primaryTorsoAnim ); + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; + _nextRushAttemptTime = 0.0f; + break; + + /* + case GENERAL_COMBAT_MELEE_CHANGE_POSTURE: + switch ( _self->movementSubsystem->getPostureState() ) + { + case POSTURE_TRANSITION_STAND_TO_LEAN_LEFT_DUAL: + _self->movementSubsystem->setPostureState( POSTURE_LEAN_LEFT_DUAL ); + _self->SetAnim( "lean_left_dual_stand" , EV_Actor_NotifyBehavior ); + break; + + case POSTURE_TRANSITION_STAND_TO_LEAN_RIGHT_DUAL: + _self->movementSubsystem->setPostureState( POSTURE_LEAN_RIGHT_DUAL ); + _self->SetAnim( "lean_right_dual_stand" , EV_Actor_NotifyBehavior ); + break; + + case POSTURE_LEAN_LEFT_DUAL: + _self->movementSubsystem->setPostureState( POSTURE_TRANSITION_LEAN_LEFT_DUAL_TO_STAND ); + _self->SetAnim( "lean_left_dual_stand_end" , EV_Actor_NotifyBehavior ); + break; + + case POSTURE_LEAN_RIGHT_DUAL: + _self->movementSubsystem->setPostureState( POSTURE_TRANSITION_LEAN_RIGHT_DUAL_TO_STAND ); + _self->SetAnim( "lean_right_dual_stand_end" , EV_Actor_NotifyBehavior ); + break; + + case POSTURE_TRANSITION_LEAN_LEFT_DUAL_TO_STAND: + _self->movementSubsystem->setPostureState( POSTURE_STAND ); + setTorsoAnim( *_self ); + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; + _nextPostureChangeTime = level.time + G_Random ( 2.0f ) + 0.5f; + break; + + case POSTURE_TRANSITION_LEAN_RIGHT_DUAL_TO_STAND: + _self->movementSubsystem->setPostureState( POSTURE_STAND ); + setTorsoAnim( *_self );; + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; + _nextPostureChangeTime = level.time + G_Random ( 2.0f ) + 0.5f; + break; + } + + break; + */ + + } +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::Begin( Actor &self ) +{ + init( self ); + + setUpRotate( self ); + setHeadWatchTarget( self ); +} + + + +//-------------------------------------------------------------- +// Name: init() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::init( Actor &self ) +{ + _self = &self; + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; + + _nextRotateTime = 0.0f; + _nextStrafeAttemptTime = 0.0f; + _nextRushAttemptTime = 0.0f; + _exitHoldTime = 0.0f; + _exitBlockTime = 0.0f; + _nextPostureChangeTime = 0.0f; +} + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithMeleeWeapon::Evaluate ( Actor &self ) +{ + Entity *currentEnemy; + + // Make sure our rotate and headwatch targets are up to date + if ( _state != GENERAL_COMBAT_MELEE_ATTACK && _state != GENERAL_COMBAT_MELEE_CHANGE_POSTURE ) + setUpRotate( self ); + + faceEnemy( self ); + setHeadWatchTarget( self ); + + + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if (currentEnemy && _maxDistanceToEngage && !self.WithinDistance( currentEnemy, _maxDistanceToEngage)) + { + self.enemyManager->ClearCurrentEnemy(); + SetFailureReason( "My Current Enemy is farther away than my _maxDistanceToEngage\n" ); + return BEHAVIOR_FAILED; // I may have an enemy, but he's too far away. Ignore him. + } + + if ( _state == GENERAL_COMBAT_MELEE_FAILED ) + return BEHAVIOR_FAILED; + + if ( currentEnemy && !self.WithinDistance( currentEnemy , _meleeDistance ) && level.time > _nextRushAttemptTime ) + { + setupRushEnemy( self ); + _state = GENERAL_COMBAT_MELEE_RUSH_ENEMY; + } + + switch ( _state ) + { + case GENERAL_COMBAT_MELEE_SELECT_STATE: + //setTorsoAnim( self ); // Breaks the initial attack anim + selectState( self ); + break; + + case GENERAL_COMBAT_MELEE_RUSH_ENEMY: + rushEnemy( self ); + break; + + case GENERAL_COMBAT_MELEE_STRAFE: + setTorsoAnim( self ); + strafe(self); + setTorsoAnim( self ); + break; + + case GENERAL_COMBAT_MELEE_ATTACK: + attack( self ); + break; + + case GENERAL_COMBAT_MELEE_BLOCK: + block( self ); + break; + + case GENERAL_COMBAT_MELEE_CHANGE_POSTURE: + changePosture(self); + break; + + case GENERAL_COMBAT_MELEE_HOLD: + setTorsoAnim( self ); + hold( self ); + break; + + case GENERAL_COMBAT_MELEE_FAILED: + return BEHAVIOR_FAILED; + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::End ( Actor &self ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setUpRotate +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Sets up the Rotate Component Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::setUpRotate( Actor &self ) +{ + Entity *currentEnemy; + currentEnemy = NULL; + + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + return; + + _rotate.SetEntity( currentEnemy ); +} + + + +//-------------------------------------------------------------- +// Name: faceEnemy +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Evaluates the Rotate Component Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::faceEnemy( Actor &self ) +{ + BehaviorReturnCode_t rotateResult; + + //We set the _nextRotateTime after we get a successul rotate + //evaluation... This is give our headwatch a chance to kick + //into action... If we just always spin to face the enemy + //we'll never see the headwatch. + if ( level.time < _nextRotateTime ) + return; + + _rotate.Begin( self ); + if ( _state == GENERAL_COMBAT_MELEE_ATTACK ) + _rotate.SetAnim( _rotationAnim ); + + rotateResult = _rotate.Evaluate( self ); + + if ( rotateResult == BEHAVIOR_SUCCESS ) + _nextRotateTime = level.time + .75f; +} + + + +//-------------------------------------------------------------- +// Name: setTorsoAnim() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Sets the animation for the actor's torso +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::setTorsoAnim( Actor &self ) +{ + Event *torsoAnimEvent; + torsoAnimEvent = new Event( EV_Torso_Anim_Done ); + + self.SetAnim( _primaryTorsoAnim , torsoAnimEvent , torso ); + +} + + + +//-------------------------------------------------------------- +// Name: setHeadWatchTarget() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Sets the headwatch target for the actor's +// headwatcher object +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::setHeadWatchTarget( Actor &self ) +{ + Entity *currentEnemy; + currentEnemy = NULL; + + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + return; + + self.SetHeadWatchTarget( currentEnemy ); +} + + + +//-------------------------------------------------------------- +// Name: setupStrafe() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Sets ourselves up to execute the STRAFE state +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: true or false +//-------------------------------------------------------------- +bool GeneralCombatWithMeleeWeapon::setupStrafe( Actor &self ) +{ + _strafeComponent.SetMode( Strafe::STRAFE_RANDOM ); + _strafeComponent.Begin( self ); + + return true; +} + + + +//-------------------------------------------------------------- +// Name: strafe() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Evaluation State for STRAFE +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::strafe( Actor &self ) +{ + BehaviorReturnCode_t result; + + result = _strafeComponent.Evaluate( self ); + + if ( result == BEHAVIOR_FAILED ) + strafeFailed( self ); + else if ( result == BEHAVIOR_SUCCESS ) + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; +} + + + +//-------------------------------------------------------------- +// Name: setupStrafeFailed() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Failure Handler for _setupStrafe() +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::strafeFailed( Actor &self ) +{ + _nextStrafeAttemptTime = level.time + G_Random( 1.0 ) + 3.0f; + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; +} + + + +//-------------------------------------------------------------- +// Name: setupRushEnemy +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Sets up the GotoEntity Component Behavior +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool GeneralCombatWithMeleeWeapon::setupRushEnemy( Actor &self ) +{ + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( !currentEnemy ) + return false; + + _nextRushAttemptTime = level.time + G_Random( 0.25 ) + 1.25f; + _rush.SetAnim ( _rushAnim ); + _rush.SetEntity( self, currentEnemy ); + _rush.SetDistance ( 96.0f ); + _rush.Begin( self ); + + + return true; +} + + + +//-------------------------------------------------------------- +// Name: setupRushEnemyFailed() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Failure Handler for _setupRushEnemy +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::setupRushEnemyFailed ( Actor &self ) +{ + if ( !_allowRushFailure ) + { + SetFailureReason( "RushToEnemy Failed" ); + _state = GENERAL_COMBAT_MELEE_FAILED; + return; + } + + _nextRushAttemptTime = level.time + G_Random( 0.25 ) + 1.25f; + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; +} + + + +//-------------------------------------------------------------- +// Name: rushEnemy() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Evaluates the GotoEntity Component Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::rushEnemy ( Actor &self ) +{ + BehaviorReturnCode_t rushResult; + + rushResult = _rush.Evaluate( self ); + + Entity* currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + if ( self.WithinDistance( currentEnemy , _meleeDistance ) ) + rushResult = BEHAVIOR_SUCCESS; + + if ( rushResult == BEHAVIOR_SUCCESS ) + { + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; + return; + } + + if ( rushResult == BEHAVIOR_FAILED ) + setupRushEnemyFailed( self ); + +} + + + +//-------------------------------------------------------------- +// Name: selectState() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Selects the state for the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::selectState( Actor &self ) +{ + float chance; + Entity* currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + // If we don't have an enemy, then let's ask our enemy manager + // to try and find one + if ( !currentEnemy ) + self.enemyManager->FindHighestHateEnemy(); + + // If we still don't have an enemy, then we need to hold + if ( !currentEnemy ) + { + if ( !setupHold( self )) + return; + + _state = GENERAL_COMBAT_MELEE_HOLD; + return; + } + + chance = G_Random(); + if ( self.checkincomingmeleeattack() && chance < _blockChance ) + { + _state = GENERAL_COMBAT_MELEE_BLOCK; + + if ( !setupBlock( self ) ) + setupBlockFailed( self ); + return; + } + + chance = G_Random(); + if ( currentEnemy && self.enemyManager->InEnemyLineOfFire() && chance < _strafeChance && self.WithinDistance( currentEnemy , _meleeDistance ) ) + { + _state = GENERAL_COMBAT_MELEE_STRAFE; + + if ( !setupStrafe( self ) ) + strafeFailed(self); + + return; + } + + + + + // + // The Change Posture stuff is commented out because it looks really weak right now + // I need to keep it here though, so that when the animations look better or I figure out + // how to better utilize them it will be easy to re-enable + // + //if ( currentEnemy && chance < _postureChangeChance && level.time > _nextPostureChangeTime && self.WithinDistance( currentEnemy , _meleeDistance ) ) + // { + // _state = GENERAL_COMBAT_MELEE_CHANGE_POSTURE; + // + // if ( !_setupChangePosture(self) ) + // _setupChangePostureFailed(self); + // + // return; + // } + + chance = G_Random(); + if ( currentEnemy && chance < _attackChance && self.WithinDistance( currentEnemy , _meleeDistance ) ) + { + _state = GENERAL_COMBAT_MELEE_ATTACK; + + if (!setupAttack( self )) + setupAttackFailed( self ); + + return; + } + + setupHold( self ); + _state = GENERAL_COMBAT_MELEE_HOLD; + +} + + + +//-------------------------------------------------------------- +// Name: setupAttack +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Sets up the behavior to enter the Attack state +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool GeneralCombatWithMeleeWeapon::setupAttack( Actor &self ) +{ + self.animate->ClearTorsoAnim(); + self.SetAnim( _attackAnim , EV_Actor_NotifyBehavior ); + return true; +} + + + +//-------------------------------------------------------------- +// Name: setupAttackFailed() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Failure Handler for _setupAttack() +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::setupAttackFailed( Actor &self ) +{ + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; +} + + + +//-------------------------------------------------------------- +// Name: attack() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Does Nothing right now, here in case need for +// for expansion, and to keep with precedent +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::attack( Actor &self ) +{ + // The transition back to SELECT_STATE is + // handled by AnimDone +} + + + +//-------------------------------------------------------------- +// Name: setupHold() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Set ourselves up to execute the HOLD state +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: true or false +//-------------------------------------------------------------- +bool GeneralCombatWithMeleeWeapon::setupHold( Actor &self ) +{ + self.SetAnim( _primaryTorsoAnim ); + _exitHoldTime = level.time + G_Random ( 0.15f ) + 0.25f; + + return true; +} + + + +//-------------------------------------------------------------- +// Name: hold() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Evaluation State for HOLD +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::hold( Actor &self ) + { + if ( level.time > _exitHoldTime ) + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; + } + + + +//-------------------------------------------------------------- +// Name: setupHoldFailed() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Failure Handler for _setupHold +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::setupHoldFailed( Actor &self ) +{ + //if we get here, we got a real bad problem + self.SetAnim( _primaryTorsoAnim ); + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; +} + + + +//-------------------------------------------------------------- +// Name: setupBlock() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Sets the behavior up to enter the Block State +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +bool GeneralCombatWithMeleeWeapon::setupBlock( Actor &self ) +{ + self.SetAnim( "block" ); + _exitBlockTime = level.time + G_Random ( 1.00f ) + 0.25f; + return true; +} + + + +//-------------------------------------------------------------- +// Name: _setupBlockFailed() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Failure Handler for _setupBlock() +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::setupBlockFailed( Actor &self ) +{ + //if we get here, we got a real bad problem + self.SetAnim( _primaryTorsoAnim ); + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; +} + + + +//-------------------------------------------------------------- +// Name: block() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Main Function for the Block state +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::block( Actor &self ) +{ + //if ( !self.checkincomingmeleeattack() ) // Old condition, do we need this anymore? + if ( level.time > _exitBlockTime ) + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; +} + + + +//-------------------------------------------------------------- +// Name: setupChangePosture() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Sets ourselves up to execute the CHANGE_POSTURE state +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: true or false +//-------------------------------------------------------------- +bool GeneralCombatWithMeleeWeapon::setupChangePosture( Actor &self ) +{ + float chance; + chance = G_Random(); + + /* + if ( chance < .5 ) + { + _postureTransitionAnim = "lean_left_dual_stand_start"; + _nextPostureState = POSTURE_TRANSITION_STAND_TO_LEAN_LEFT_DUAL; + } + else + { + _postureTransitionAnim = "lean_right_dual_stand_start"; + _nextPostureState = POSTURE_TRANSITION_STAND_TO_LEAN_RIGHT_DUAL; + } + + self.SetAnim( _postureTransitionAnim , EV_Actor_NotifyBehavior ); + self.movementSubsystem->setPostureState( (PostureStates_t)_nextPostureState ); +*/ + return true; +} + + + +//-------------------------------------------------------------- +// Name: changePosture() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Evaluation State for CHANGE_POSTURE state +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::changePosture( Actor &self ) +{ + // Transitions handled in AnimDone +} + + + +//-------------------------------------------------------------- +// Name: _setupChangePostureFailed() +// Class: GeneralCombatWithMeleeWeapon +// +// Description: Failure Handler for _setupChangePosture() +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithMeleeWeapon::setupChangePostureFailed( Actor &self ) +{ + //if we get here, we got a real bad problem + self.SetAnim( _primaryTorsoAnim ); + _state = GENERAL_COMBAT_MELEE_SELECT_STATE; +} + diff --git a/dlls/game/generalCombatWithMeleeWeapon.hpp b/dlls/game/generalCombatWithMeleeWeapon.hpp new file mode 100644 index 0000000..ed65d6a --- /dev/null +++ b/dlls/game/generalCombatWithMeleeWeapon.hpp @@ -0,0 +1,198 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/generalCombatWithMeleeWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: Bschofield $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// GeneralCombatWithMeleeWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class GeneralCombatWithMeleeWeapon; + +#ifndef __GENERALCOMBAT_WITH_MELEEWEAPON_HPP__ +#define __GENERALCOMBAT_WITH_MELEEWEAPON_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: GeneralCombatWithMeleeWeapon +// Base Class: Behavior +// +// Description: Allows the Actor to do general combat +// with a melee weapon ( best used on open areas ) +// +// Method of Use: Statemachine or another behavior +//-------------------------------------------------------------- +class GeneralCombatWithMeleeWeapon : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + GENERAL_COMBAT_MELEE_SELECT_STATE, + GENERAL_COMBAT_MELEE_RUSH_ENEMY, + GENERAL_COMBAT_MELEE_STRAFE, + GENERAL_COMBAT_MELEE_ATTACK, + GENERAL_COMBAT_MELEE_BLOCK, + GENERAL_COMBAT_MELEE_CHANGE_POSTURE, + GENERAL_COMBAT_MELEE_HOLD, + GENERAL_COMBAT_MELEE_FAILED + } generalMeleeCombatStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _primaryTorsoAnim; + str _attackAnim; + str _rotationAnim; + str _rushAnim; + float _maxDistanceToEngage; + float _meleeDistance; + float _strafeChance; + float _blockChance; + float _attackChance; + float _postureChangeChance; + bool _allowRushFailure; + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void init ( Actor &self ); + void selectState ( Actor &self ); + void setUpRotate ( Actor &self ); + void setTorsoAnim ( Actor &self ); + void setHeadWatchTarget ( Actor &self ); + + bool setupStrafe ( Actor &self ); + void strafeFailed ( Actor &self ); + void strafe ( Actor &self ); + + bool setupRushEnemy ( Actor &self ); + void setupRushEnemyFailed ( Actor &self ); + void rushEnemy ( Actor &self ); + + bool setupHold ( Actor &self ); + void setupHoldFailed ( Actor &self ); + void hold ( Actor &self ); + + bool setupAttack ( Actor &self ); + void setupAttackFailed ( Actor &self ); + void attack ( Actor &self ); + + bool setupBlock ( Actor &self ); + void setupBlockFailed ( Actor &self ); + void block ( Actor &self ); + + bool setupChangePosture ( Actor &self ); + void setupChangePostureFailed ( Actor &self ); + void changePosture ( Actor &self ); + + void faceEnemy ( Actor &self ); + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( GeneralCombatWithMeleeWeapon ); + + GeneralCombatWithMeleeWeapon(); + ~GeneralCombatWithMeleeWeapon(); + + void SetArgs( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End ( Actor &self ); + + virtual void Archive ( Archiver &arc ); + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + str _blockAttack; + str _postureTransitionAnim; + + unsigned int _state; + unsigned int _nextPostureState; + + float _nextRotateTime; + float _nextStrafeAttemptTime; + float _nextRushAttemptTime; + float _nextPostureChangeTime; + float _exitHoldTime; + float _exitBlockTime; + + Actor* _self; + + // Components + RotateToEntity _rotate; + GotoEntity _rush; + Strafe _strafeComponent; + + }; + + +inline void GeneralCombatWithMeleeWeapon::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Parameters + arc.ArchiveString ( &_primaryTorsoAnim ); + arc.ArchiveString ( &_attackAnim ); + arc.ArchiveString ( &_rotationAnim ); + arc.ArchiveString ( &_rushAnim ); + + arc.ArchiveFloat ( &_maxDistanceToEngage ); + arc.ArchiveFloat ( &_meleeDistance ); + arc.ArchiveFloat ( &_strafeChance ); + arc.ArchiveFloat ( &_blockChance ); + arc.ArchiveFloat ( &_attackChance ); + arc.ArchiveFloat ( &_postureChangeChance ); + arc.ArchiveBool ( &_allowRushFailure ); + arc.ArchiveString ( &_blockAttack ); + arc.ArchiveString ( &_postureTransitionAnim ); + + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveUnsigned ( &_nextPostureState ); + + arc.ArchiveFloat ( &_nextRotateTime ); + arc.ArchiveFloat ( &_nextStrafeAttemptTime ); + arc.ArchiveFloat ( &_nextRushAttemptTime ); + arc.ArchiveFloat ( &_nextPostureChangeTime ); + arc.ArchiveFloat ( &_exitHoldTime ); + arc.ArchiveFloat ( &_exitBlockTime ); + + arc.ArchiveObjectPointer ( ( Class ** )&_self ); + + // Archive Components + arc.ArchiveObject ( &_rotate ); + arc.ArchiveObject ( &_rush ); + arc.ArchiveObject ( &_strafeComponent ); +} + + +#endif /* __GENERALCOMBAT_WITH_MELEEWEAPON_HPP__ */ + diff --git a/dlls/game/generalCombatWithRangedWeapon.cpp b/dlls/game/generalCombatWithRangedWeapon.cpp new file mode 100644 index 0000000..f9464d9 --- /dev/null +++ b/dlls/game/generalCombatWithRangedWeapon.cpp @@ -0,0 +1,1691 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/generalCombatWithRangedWeapon.cpp $ +// $Revision:: 17 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// GeneralCombatWithRangedWeapon Implementation +// -- GCWRW will Strafe, Duck, Roll and fire a weapon at an enemy. Also, it will +// retreat and approach based on distance from the enemy. +// +// PARAMETERS: +// +// +// ANIMATIONS: +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "generalCombatWithRangedWeapon.hpp" + +extern Event EV_PostureChanged_Completed; + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, GeneralCombatWithRangedWeapon, NULL ) + { + { &EV_Behavior_Args, &GeneralCombatWithRangedWeapon::SetArgs }, + { &EV_Behavior_AnimDone, &GeneralCombatWithRangedWeapon::AnimDone }, + { &EV_PostureChanged_Completed, &GeneralCombatWithRangedWeapon::PostureDone }, + { NULL, NULL } + }; + +//-------------------------------------------------------------- +// Name: GeneralCombatWithRangedWeapon() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GeneralCombatWithRangedWeapon::GeneralCombatWithRangedWeapon() +{ + _self = NULL; + _movementAnim = "walk"; + _torsoAnim = "idle"; + _fireAnim = "idle"; + _approachDist = 500; + _retreatDist = 250; + _strafeChance = 1.0; + _fireTimeMin = 1.0; + _fireTimeMax = 1.5; + _pauseTimeMin = 1.0; + _pauseTimeMax = 1.5; +} + +//-------------------------------------------------------------- +// Name: GeneralCombatWithRangedWeapon() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GeneralCombatWithRangedWeapon::~GeneralCombatWithRangedWeapon() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::SetArgs( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) _movementAnim = ev->GetString( 1 ); + if ( ev->NumArgs() > 1 ) _torsoAnim = ev->GetString( 2 ); + if ( ev->NumArgs() > 2 ) _fireAnim = ev->GetString( 3 ); + if ( ev->NumArgs() > 3 ) _approachDist = ev->GetFloat ( 4 ); + if ( ev->NumArgs() > 4 ) _retreatDist = ev->GetFloat ( 5 ); + if ( ev->NumArgs() > 5 ) _strafeChance = ev->GetFloat ( 6 ); + if ( ev->NumArgs() > 6 ) _fireTimeMin = ev->GetFloat ( 7 ); + if ( ev->NumArgs() > 7 ) _fireTimeMax = ev->GetFloat ( 8 ); + if ( ev->NumArgs() > 8 ) _pauseTimeMin = ev->GetFloat ( 9 ); + if ( ev->NumArgs() > 9 ) _pauseTimeMax = ev->GetFloat ( 10 ); + +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::AnimDone( Event *ev ) +{ + switch ( _state ) + { + case GC_WRW_STRAFE: + _strafeComponent.AnimDone( ev ); + break; + } +} + + +//-------------------------------------------------------------- +// Name: PostureDone() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Handles a Posture Done Event +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::PostureDone( Event *ev ) +{ + _finishedPostureTransition = true; +} + +//-------------------------------------------------------------- +// Name: Begin() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::Begin( Actor &self ) +{ + init( self ); +} + + + +//-------------------------------------------------------------- +// Name: init() +// Class: GeneralCombat +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::init( Actor &self ) +{ + _self = &self; + _finishedPostureTransition = true; + _randomAdvanceLockOut = false; + _randomRetreatLockOut = false; + _nextRotateTime = 0.0f; + _nextStrafeAttemptTime = 0.0f; + _nextPostureChange = 0.0f; + _nextClearRetreatLockOutTime = 0.0f; + _nextClearAdvanceLockOutTime = 0.0f; + _nextFireTime = 0.0f; + _nextPauseTime = 0.0f; + _randomAdvanceFailures = 0; + _randomRetreatFailures = 0; + + transitionToState(GC_WRW_STAND); + updateEnemy(); +} + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + str currentPostureState = _self->postureController->getCurrentPostureName(); + float distToEnemy = self.enemyManager->GetDistanceFromEnemy(); + + think(); + switch ( _state ) + { + //--------------------------------------------------------------------- + case GC_WRW_CHANGE_POSTURE_TO_ADVANCE: + //--------------------------------------------------------------------- + stateResult = evaluateStateChangePostureToAdvance(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GC_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GC_WRW_ADVANCE ); + break; + + //--------------------------------------------------------------------- + case GC_WRW_CHANGE_POSTURE_TO_RETREAT: + //--------------------------------------------------------------------- + stateResult = evaluateStateChangePostureToRetreat(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GC_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GC_WRW_RETREAT ); + break; + + //--------------------------------------------------------------------- + case GC_WRW_ADVANCE: + //--------------------------------------------------------------------- + if ( !checkShouldApproach( distToEnemy ) ) + { + transitionToState( GC_WRW_STAND ); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateStateAdvance(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GC_WRW_STAND ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GC_WRW_ADVANCE_FIRING ); + + break; + + //--------------------------------------------------------------------- + case GC_WRW_ADVANCE_FIRING: + //--------------------------------------------------------------------- + if ( !checkShouldApproach( distToEnemy ) ) + { + transitionToState( GC_WRW_STAND ); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateStateAdvanceFiring(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GC_WRW_STAND ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GC_WRW_ADVANCE ); + break; + + //--------------------------------------------------------------------- + case GC_WRW_RETREAT: + //--------------------------------------------------------------------- + if ( !checkShouldRetreat( distToEnemy ) ) + { + transitionToState( GC_WRW_STAND ); + _self->movementSubsystem->setMovingBackwards( false ); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateStateRetreat(); + if ( stateResult == BEHAVIOR_FAILED ) + { + transitionToState( GC_WRW_STAND ); + _self->movementSubsystem->setMovingBackwards( false ); + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + _self->movementSubsystem->setMovingBackwards( false ); + transitionToState( GC_WRW_RETREAT_FIRING ); + } + + break; + + //--------------------------------------------------------------------- + case GC_WRW_RETREAT_FIRING: + //--------------------------------------------------------------------- + if ( !checkShouldRetreat( distToEnemy ) ) + { + _self->movementSubsystem->setMovingBackwards( false ); + transitionToState( GC_WRW_STAND ); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateStateRetreatFiring(); + if ( stateResult == BEHAVIOR_FAILED ) + { + transitionToState( GC_WRW_STAND ); + _self->movementSubsystem->setMovingBackwards( false ); + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + _self->movementSubsystem->setMovingBackwards( false ); + transitionToState( GC_WRW_RETREAT ); + } + break; + + //--------------------------------------------------------------------- + case GC_WRW_STRAFE: + //--------------------------------------------------------------------- + stateResult = evaluateStateStrafe(); + + //It's perfectly understandable that strafe might + //fail, so instead of failing the behavior entirely + //we'll just change states + if ( stateResult != BEHAVIOR_EVALUATING ) + { + if ( currentPostureState == "DUCK" ) + transitionToState( GC_WRW_DUCKED ); + + if ( currentPostureState == "STAND" ) + transitionToState( GC_WRW_STAND ); + } + + break; + + //--------------------------------------------------------------------- + case GC_WRW_CHANGE_POSTURE_DUCK: + //--------------------------------------------------------------------- + stateResult = evaluateStateChangePostureDuck(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GC_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GC_WRW_DUCKED ); + break; + + //--------------------------------------------------------------------- + case GC_WRW_DUCKED: + //--------------------------------------------------------------------- + + // + // First Check if we are within our _approachDist. If + // not, then we need to close in on our enemy + // + if ( checkShouldApproach( distToEnemy ) ) + { + transitionToState( GC_WRW_CHANGE_POSTURE_TO_ADVANCE ); + return BEHAVIOR_EVALUATING; + } + + // + // Now Check if wer are within our _retreatDist. If + // we are, then we need to retreat in on our enemy + // + if ( checkShouldRetreat( distToEnemy ) ) + { + transitionToState( GC_WRW_CHANGE_POSTURE_TO_RETREAT ); + return BEHAVIOR_EVALUATING; + } + + // + // Lets check if we need to strafe + // + if ( checkShouldStrafe( distToEnemy ) ) + { + transitionToState( GC_WRW_STRAFE ); + return BEHAVIOR_EVALUATING; + } + + + // + //Now let's check if we need to change posture + // + if ( checkShouldChangePosture( distToEnemy ) ) + { + transitionToState( GC_WRW_CHANGE_POSTURE_STAND ); + return BEHAVIOR_EVALUATING; + } + + // + //Well we don't have anything else to do, so let's evaluate our state + // + stateResult = evaluateRotate(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + stateResult = evaluateStateStand(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GC_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GC_WRW_DUCKED_FIRING ); + + } + + break; + + //--------------------------------------------------------------------- + case GC_WRW_DUCKED_FIRING: + //--------------------------------------------------------------------- + stateResult = evaluateRotate(); + stateResult = evaluateStateDuckedFiring(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GC_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GC_WRW_DUCKED ); + break; + + //--------------------------------------------------------------------- + case GC_WRW_CHANGE_POSTURE_STAND: + //--------------------------------------------------------------------- + stateResult = evaluateStateChangePostureStand(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GC_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GC_WRW_STAND ); + break; + + //--------------------------------------------------------------------- + case GC_WRW_STAND: + //--------------------------------------------------------------------- + + // + // First Check if we are within our _approachDist. If + // not, then we need to close in on our enemy + // + if ( checkShouldApproach( distToEnemy ) ) + { + transitionToState( GC_WRW_CHANGE_POSTURE_TO_ADVANCE ); + return BEHAVIOR_EVALUATING; + } + + // + // Now Check if wer are within our _retreatDist. If + // we are, then we need to retreat in on our enemy + // + if ( checkShouldRetreat( distToEnemy ) ) + { + transitionToState( GC_WRW_CHANGE_POSTURE_TO_RETREAT ); + return BEHAVIOR_EVALUATING; + } + + // + // Lets check if we need to strafe + // + if ( checkShouldStrafe( distToEnemy ) ) + { + transitionToState( GC_WRW_STRAFE ); + return BEHAVIOR_EVALUATING; + } + + + // + //Now let's check if we need to change posture + // + if ( checkShouldChangePosture( distToEnemy ) ) + { + transitionToState( GC_WRW_CHANGE_POSTURE_DUCK ); + return BEHAVIOR_EVALUATING; + } + + // + //Well we don't have anything else to do, so let's evaluate our state + // + stateResult = evaluateRotate(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + stateResult = evaluateStateStand(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GC_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GC_WRW_STAND_FIRING ); + + } + + break; + + //--------------------------------------------------------------------- + case GC_WRW_STAND_FIRING: + //--------------------------------------------------------------------- + stateResult = evaluateRotate(); + stateResult = evaluateStateStandFiring(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GC_WRW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GC_WRW_STAND ); + break; + } + + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::End(Actor &self) +{ + if ( !_self ) + return; + + _self->movementSubsystem->setMovingBackwards( false ); + _fireWeapon.End(*_self); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::think() +{ + if ( level.time > _nextClearAdvanceLockOutTime && _randomAdvanceLockOut ) + _randomAdvanceLockOut = false; + + if ( level.time > _nextClearRetreatLockOutTime && _randomRetreatLockOut ) + _randomRetreatLockOut = false; +} + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::updateEnemy() +{ + + Entity *currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + SetFailureReason( "GeneralCombatWithRangedWeapon::updateEnemy -- No Enemy" ); + transitionToState( GC_WRW_FAILED ); + } + + } + + _currentEnemy = currentEnemy; + _self->SetHeadWatchTarget( _currentEnemy ); + setupRotate(); +} + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::transitionToState( generalCombatStates_t state ) +{ + + switch ( state ) + { + case GC_WRW_CHANGE_POSTURE_TO_ADVANCE: + setupStateChangePostureToAdvance(); + setInternalState( state , "GC_WRW_CHANGE_POSTURE_TO_ADVANCE" ); + break; + + case GC_WRW_CHANGE_POSTURE_TO_RETREAT: + setupStateChangePostureToRetreat(); + setInternalState( state , "GC_WRW_CHANGE_POSTURE_TO_RETREAT" ); + break; + + case GC_WRW_ADVANCE: + setupStateAdvance(); + setInternalState( state , "GC_WRW_ADVANCE" ); + break; + + case GC_WRW_ADVANCE_FIRING: + setupStateAdvanceFiring(); + setInternalState( state , "GC_WRW_ADVANCE_FIRING" ); + break; + + case GC_WRW_RETREAT: + setupStateRetreat(); + setInternalState( state , "GC_WRW_RETREAT" ); + break; + + case GC_WRW_RETREAT_FIRING: + setupStateRetreatFiring(); + setInternalState( state , "GC_WRW_RETREAT_FIRING" ); + break; + + case GC_WRW_STRAFE: + setupStateStrafe(); + setInternalState( state , "GC_WRW_STRAFE" ); + break; + + case GC_WRW_CHANGE_POSTURE_DUCK: + setupStateChangePostureDuck(); + setInternalState( state , "GC_WRW_CHANGE_POSTURE_DUCK" ); + break; + + case GC_WRW_DUCKED: + setupStateDucked(); + setInternalState( state , "GC_WRW_DUCKED" ); + break; + + case GC_WRW_DUCKED_FIRING: + setupStateDuckedFiring(); + setInternalState( state , "GC_WRW_DUCKED_FIRING" ); + break; + + case GC_WRW_CHANGE_POSTURE_STAND: + setupStateChangePostureStand(); + setInternalState( state , "GC_WRW_CHANGE_POSTURE_STAND" ); + break; + + case GC_WRW_STAND: + setupStateStand(); + setInternalState( state , "GC_WRW_STAND" ); + break; + + case GC_WRW_STAND_FIRING: + setupStateStandFiring(); + setInternalState( state , "GC_WRW_STAND_FIRING" ); + break; + + case GC_WRW_FAILED: + setInternalState( state , "GC_WRW_FAILED" ); + break; + } + +} + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setInternalState( generalCombatStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); + //gi.WDPrintf( "State: " + stateName + "\n" ); +} + +//-------------------------------------------------------------- +// Name: setTorsoAnim() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets our Torso Animation +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setTorsoAnim() +{ + _self->SetAnim( _torsoAnim , NULL , torso ); +} + + +//-------------------------------------------------------------- +// Name: setupRotate +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up Rotate Component +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupRotate() +{ + _rotate.SetEntity( _currentEnemy ); + _rotate.Begin( *_self ); +} + + +//-------------------------------------------------------------- +// Name: evaluateRotate() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates our Rotate Component +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateRotate() +{ + BehaviorReturnCode_t rotateResult; + + if ( level.time < _nextRotateTime ) + return BEHAVIOR_EVALUATING; + + rotateResult = _rotate.Evaluate( *_self ); + + if ( rotateResult == BEHAVIOR_SUCCESS ) + _nextRotateTime = level.time + .75f; + + return rotateResult; +} + +//-------------------------------------------------------------- +// Name: setupStateChangePostureToAdvance() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up the Change Posture To Advance state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateChangePostureToAdvance() +{ + bool canChange = _self->postureController->requestPosture( "STAND" , this ); + + if ( !canChange ) + { + failureStateChangePostureDuck ( "Requested Posture is unavailable" ); + return; + } + + _finishedPostureTransition = false; +} + +//-------------------------------------------------------------- +// Name: evaluateStateChangePostureToAdvance() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates the Change Posture To Advance State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateChangePostureToAdvance() +{ + //Basically waiting for our check to come true + if ( _finishedPostureTransition ) + { + _nextPostureChange = level.time + G_Random() + 2.5f; + return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_EVALUATING; + +} + +//-------------------------------------------------------------- +// Name: failureStateChangePostureToAdvance() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure Handler for the Change Posture To Advance State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateChangePostureToAdvance( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateChangePostureToRetreat() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up the Change Posture To Retreat State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateChangePostureToRetreat() +{ + bool canChange = _self->postureController->requestPosture( "STAND" , this ); + + if ( !canChange ) + { + failureStateChangePostureDuck ( "Requested Posture is unavailable" ); + return; + } + + _finishedPostureTransition = false; +} + +//-------------------------------------------------------------- +// Name: evaluateStateChangePostureToRetreat() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates the ChangePostureToRetreat State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateChangePostureToRetreat() +{ + //Basically waiting for our check to come true + if ( _finishedPostureTransition ) + { + _nextPostureChange = level.time + G_Random() + 2.5f; + return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateChangePostureToRetreat() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure Handler for the ChangePostureToRetreat State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateChangePostureToRetreat( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateAdvance() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up the Advance State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateAdvance() +{ + Vector selfToEnemy; + float dist; + + dist = G_Random ( 64.0f ) + 96.0f; + + if ( !_currentEnemy ) + { + updateEnemy(); + return; + } + + // Lets get oriented towards our enemy + selfToEnemy = _currentEnemy->origin - _self->origin; + selfToEnemy = selfToEnemy.toAngles(); + _self->setAngles( selfToEnemy ); + selfToEnemy.AngleVectors( &selfToEnemy ); + _self->movementSubsystem->setMoveDir( selfToEnemy ); + _self->movementSubsystem->setAnimDir( selfToEnemy ); + + _moveRandomDir.SetDistance( dist ); + _moveRandomDir.SetAnim( "run" ); + _moveRandomDir.SetMode(MoveRandomDirection::RANDOM_MOVE_IN_FRONT); + _moveRandomDir.Begin(*_self); + _moveRandomDir.SetMinDistance( dist * .75 ); + +} + +//-------------------------------------------------------------- +// Name: evaluateStateAdvance() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates our Advance State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateAdvance() +{ + BehaviorReturnCode_t moveResult = _moveRandomDir.Evaluate( *_self ); + + if ( moveResult == BEHAVIOR_SUCCESS ) + { + _randomAdvanceFailures = 0; + return BEHAVIOR_SUCCESS; + } + + if ( moveResult == BEHAVIOR_FAILED ) + failureStateAdvance( _moveRandomDir.GetFailureReason() ); + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAdvance() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure Handler for the Advance State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateAdvance( const str& failureReason ) +{ + str FailureReason = "_moveRandomDir returned: " + failureReason; + + _randomAdvanceFailures++; + if ( _randomAdvanceFailures > 10 ) + { + _randomAdvanceLockOut = true; + _nextClearAdvanceLockOutTime = level.time + G_Random( 2.0f ) + 1.0f; + } +} + +//-------------------------------------------------------------- +// Name: setupStateAdvanceFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up Advance Firing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateAdvanceFiring() +{ + setupStateAdvance(); + _nextFireTime = level.time + G_Random( _fireTimeMax ) + _fireTimeMin; + + if ( _self->combatSubsystem->CanAttackEnemy() ) + { + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); + } +} + +//-------------------------------------------------------------- +// Name: evaluateStateAdvanceFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates State Advance Firing +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateAdvanceFiring() +{ + BehaviorReturnCode_t result; + + if ( _self->combatSubsystem->CanAttackEnemy() ) + _fireWeapon.Evaluate( *_self ); + else + _fireWeapon.End( *_self ); + + _self->combatSubsystem->AimWeaponTag(_currentEnemy); + + result = evaluateStateAdvance(); + if ( result == BEHAVIOR_SUCCESS ) + setupStateAdvance(); + + if ( level.time > _nextFireTime ) + return BEHAVIOR_SUCCESS; + + if ( result == BEHAVIOR_FAILED ) + { + failureStateAdvance( _moveRandomDir.GetFailureReason() ); + return BEHAVIOR_FAILED; + } + + return BEHAVIOR_EVALUATING; + +} + +//-------------------------------------------------------------- +// Name: failureStateAdvanceFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure Handler for State Advance Firing +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateAdvanceFiring( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateRetreat() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up the Retreat State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateRetreat() +{ + Vector selfToEnemy; + float dist; + + dist = G_Random ( 64.0f ) + 96.0f; + + selfToEnemy = _currentEnemy->origin - _self->origin; + selfToEnemy = selfToEnemy.toAngles(); + selfToEnemy[PITCH] = 0; + selfToEnemy[ROLL] = 0; + _self->setAngles( selfToEnemy ); + selfToEnemy.AngleVectors( &selfToEnemy ); + _self->movementSubsystem->setMoveDir( selfToEnemy ); + _self->movementSubsystem->setAnimDir( selfToEnemy ); + + _moveRandomDir.SetDistance( dist ); + _moveRandomDir.SetAnim( "backpedal" ); + _moveRandomDir.SetMode(MoveRandomDirection::RANDOM_MOVE_IN_BACK); + _moveRandomDir.Begin(*_self); + _moveRandomDir.SetMinDistance( dist * .75 ); + _self->movementSubsystem->setMovingBackwards( true ); + +} + +//-------------------------------------------------------------- +// Name: evaluateStateRetreat() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates the Retreat State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateRetreat() +{ + BehaviorReturnCode_t moveResult = _moveRandomDir.Evaluate( *_self ); + + if ( moveResult == BEHAVIOR_SUCCESS ) + { + _randomAdvanceFailures = 0; + return BEHAVIOR_SUCCESS; + } + + if ( moveResult == BEHAVIOR_FAILED ) + { + failureStateRetreat( _moveRandomDir.GetFailureReason() ); + return BEHAVIOR_FAILED; + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateRetreat() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure Handler for Retreat +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateRetreat( const str& failureReason ) +{ + str FailureReason = "_moveRandomDir returned: " + failureReason; + + _randomAdvanceFailures++; + if ( _randomAdvanceFailures > 10 ) + { + _randomAdvanceLockOut = true; + _nextClearAdvanceLockOutTime = level.time + G_Random( 2.0f ) + 1.0f; + } +} + +//-------------------------------------------------------------- +// Name: setupStateRetreatFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up Retreat Firing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateRetreatFiring() +{ + setupStateRetreat(); + _nextFireTime = level.time + G_Random( _fireTimeMax ) + _fireTimeMin; + if ( _self->combatSubsystem->CanAttackEnemy() ) + { + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); + } + + _self->movementSubsystem->setMovingBackwards( true ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateRetreatFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluate Retreat Firing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateRetreatFiring() +{ + BehaviorReturnCode_t result; + + if ( _self->combatSubsystem->CanAttackEnemy() ) + _fireWeapon.Evaluate( *_self ); + else + _fireWeapon.End( *_self ); + + _self->combatSubsystem->AimWeaponTag(_currentEnemy); + + result = evaluateStateRetreat(); + if ( result == BEHAVIOR_SUCCESS ) + setupStateRetreat(); + + if ( result == BEHAVIOR_FAILED ) + { + failureStateRetreat( _moveRandomDir.GetFailureReason() ); + return BEHAVIOR_FAILED; + } + + if ( level.time > _nextFireTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateRetreatFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure handler for Retreat Firing State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateRetreatFiring( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateStrafe() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up Strafe State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateStrafe() +{ + _strafeComponent.SetMode( Strafe::STRAFE_RANDOM ); + _strafeComponent.Begin( *_self ); +} + + +//-------------------------------------------------------------- +// Name: evaluateStateStrafe() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates Strafe State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateStrafe() +{ + BehaviorReturnCode_t result; + + result = _strafeComponent.Evaluate( *_self ); + + if ( result == BEHAVIOR_FAILED ) + { + failureStateStrafe( _strafeComponent.GetFailureReason() ); + return BEHAVIOR_FAILED; + } + else if ( result == BEHAVIOR_SUCCESS ) + { + _nextStrafeAttemptTime = level.time + G_Random( 1.0 ) + 1.0f; + return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateStrafe() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure Handler for Strafe State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateStrafe( const str& failureReason ) +{ + _nextStrafeAttemptTime = level.time + G_Random( 1.0 ) + 3.0f; +} + +//-------------------------------------------------------------- +// Name: setupStateChangePostureDuck() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up Change Posture Duck State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateChangePostureDuck() +{ + bool canChange = _self->postureController->requestPosture( "DUCK" , this ); + + if ( !canChange ) + { + // If we got here, there's a good chance we don't have a posture + // state machine. Check that first. + failureStateChangePostureDuck ( "Requested Posture is unavailable" ); + return; + } + + _finishedPostureTransition = false; +} + +//-------------------------------------------------------------- +// Name: evaluateStateChangePostureDuck() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates our ChangePostureDuck state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateChangePostureDuck() +{ + //Basically waiting for our check to come true + if ( _finishedPostureTransition ) + { + _nextPostureChange = level.time + G_Random() + 2.5f; + return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateChangePostureDuck() +// Class: GeneralCombatWithRangedWeapon +// +// Description: failureHandler for ChangePostureDuck +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateChangePostureDuck( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateDucked() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up Ducked State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateDucked() +{ + _nextPauseTime = level.time + G_Random( _pauseTimeMax ) + _pauseTimeMin; + setTorsoAnim(); + _self->SetAnim( "duck" , NULL , legs ); + _fireWeapon.End(*_self); +} + +//-------------------------------------------------------------- +// Name: evaluateStateDucked() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates Duck State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateDucked() +{ + if ( level.time > _nextPauseTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateDucked() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure Handler for Ducked State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateDucked( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateDuckedFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: sets up Ducked Firing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateDuckedFiring() +{ + _nextFireTime = level.time + G_Random( _fireTimeMax ) + _fireTimeMin; + if ( _self->combatSubsystem->CanAttackEnemy() ) + { + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); + } +} + +//-------------------------------------------------------------- +// Name: evaluateStateDuckedFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates Ducked Firing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateDuckedFiring() +{ + + if ( _self->combatSubsystem->CanAttackEnemy() ) + _fireWeapon.Evaluate( *_self ); + else + _fireWeapon.End( *_self ); + + _self->combatSubsystem->AimWeaponTag(_currentEnemy); + + if ( level.time > _nextFireTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateDuckedFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure Handler for Ducked Firing State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateDuckedFiring( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateChangePostureStand() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up Change Posture Stand State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateChangePostureStand() +{ + bool canChange = _self->postureController->requestPosture( "STAND" , this ); + + if ( !canChange ) + { + // If we got here, there's a good chance we don't have a posture + // state machine. Check that first. + failureStateChangePostureDuck ( "Requested Posture is unavailable" ); + return; + } + + _finishedPostureTransition = false; +} + +//-------------------------------------------------------------- +// Name: evaluateStateChangePostureStand() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates Change Posture Stand State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateChangePostureStand() +{ + //Basically waiting for our check to come true + if ( _finishedPostureTransition ) + { + _nextPostureChange = level.time + G_Random() + 2.5f; + return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateChangePostureStand +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure Handler for Change Posture Stand +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateChangePostureStand( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateStand() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up Stand State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateStand() +{ + _nextPauseTime = level.time + G_Random( _pauseTimeMax ) + _pauseTimeMin; + setTorsoAnim(); + _self->SetAnim( _torsoAnim , NULL , legs ); + _fireWeapon.End(*_self); +} + +//-------------------------------------------------------------- +// Name: evaluateStateStand() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates Stand State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateStand() +{ + if ( level.time > _nextPauseTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateStand() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure Handler for Stand State +// +// Parameters: const str& failureHandler +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateStand( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateStandFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Sets up Stand Firing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::setupStateStandFiring() +{ + _nextFireTime = level.time + G_Random( _fireTimeMax ) + _fireTimeMin; + if ( _self->combatSubsystem->CanAttackEnemy() ) + { + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); + } +} + +//-------------------------------------------------------------- +// Name: evaluateStateStandFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Evaluates Stand Firing State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GeneralCombatWithRangedWeapon::evaluateStateStandFiring() +{ + if ( _self->combatSubsystem->CanAttackEnemy() ) + _fireWeapon.Evaluate( *_self ); + else + _fireWeapon.End( *_self ); + + _self->combatSubsystem->AimWeaponTag(_currentEnemy); + + if ( level.time > _nextFireTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateStandFiring() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Failure Handler for Stand Firing State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GeneralCombatWithRangedWeapon::failureStateStandFiring( const str& failureReason ) +{ +} + + +//-------------------------------------------------------------- +// Name: checkShouldApproach() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Checks if we should change our state to approach +// +// Parameters: float distToEnemy +// +// Returns: true or false +//-------------------------------------------------------------- +bool GeneralCombatWithRangedWeapon::checkShouldApproach( float distToEnemy ) +{ + if ( distToEnemy >= _approachDist && !_randomAdvanceLockOut ) + return true; + + return false; +} + +//-------------------------------------------------------------- +// Name: checkShouldRetreat() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Checks if we should change our state to retreat +// +// Parameters: float distToEnemy +// +// Returns: true or false +//-------------------------------------------------------------- +bool GeneralCombatWithRangedWeapon::checkShouldRetreat( float distToEnemy ) +{ + if ( distToEnemy <= _retreatDist && !_randomRetreatLockOut ) + return true; + + return false; +} + +//-------------------------------------------------------------- +// Name: checkShouldStrafe() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Checks if we should strafe +// +// Parameters: float distToEnemy +// +// Returns: true or false +//-------------------------------------------------------------- +bool GeneralCombatWithRangedWeapon::checkShouldStrafe( float distToEnemy ) +{ + // + // First Check Timer + // + if ( level.time < _nextStrafeAttemptTime ) + return false; + + + // + //Next check if we need to strafe because we're too close to the enemy and our retreats have + //have been locked. + // + if ( distToEnemy <= _retreatDist && _randomRetreatLockOut ) + return true; + + + // + //Now let's check if we need to strafe based on fire line + // + + if ( _self->enemyManager->InEnemyLineOfFire() && G_Random() < _strafeChance ) + return true; + + return false; + +} + +//-------------------------------------------------------------- +// Name: checkShouldChangePosture() +// Class: GeneralCombatWithRangedWeapon +// +// Description: Checks if we should change posture +// +// Parameters: float distToEnemy +// +// Returns: true or false +//-------------------------------------------------------------- +bool GeneralCombatWithRangedWeapon::checkShouldChangePosture( float distToEnemy ) +{ + // + // First Check Timer + // + if ( level.time < _nextPostureChange ) + return false; + + if ( G_Random() < _postureChangeChance && distToEnemy > _retreatDist ) + return true; + + return false; +} + + diff --git a/dlls/game/generalCombatWithRangedWeapon.hpp b/dlls/game/generalCombatWithRangedWeapon.hpp new file mode 100644 index 0000000..7c5f178 --- /dev/null +++ b/dlls/game/generalCombatWithRangedWeapon.hpp @@ -0,0 +1,295 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/generalCombatWithRangedWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: Bschofield $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// GeneralCombatWithMeleeWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class GeneralCombatWithRangedWeapon; + +#ifndef __GENERALCOMBAT_WITH_RANGEDWEAPON_HPP__ +#define __GENERALCOMBAT_WITH_RANGEDWEAPON_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: GeneralCombatWithRangedWeapon +// Base Class: Behavior +// +// Description: MetaBehavior for Sentient Type ( No relation to the sentient class ) +// of Actor for executing Combat with no additional information +// ( i.e. Helper Nodes ) in the near vicinity. +// We may need to subclass this behavior for additional +// individuality, like "teammate general combat" +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class GeneralCombatWithRangedWeapon : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + GC_WRW_CHANGE_POSTURE_TO_ADVANCE, + GC_WRW_CHANGE_POSTURE_TO_RETREAT, + GC_WRW_ADVANCE, + GC_WRW_ADVANCE_FIRING, + GC_WRW_RETREAT, + GC_WRW_RETREAT_FIRING, + GC_WRW_STRAFE, + GC_WRW_CHANGE_POSTURE_DUCK, + GC_WRW_DUCKED, + GC_WRW_DUCKED_FIRING, + GC_WRW_CHANGE_POSTURE_STAND, + GC_WRW_STAND, + GC_WRW_STAND_FIRING, + GC_WRW_FAILED + } generalCombatStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _movementAnim; + str _torsoAnim; + str _fireAnim; + str _preFireAnim; + str _postFireAnim; + float _approachDist; + float _retreatDist; + float _strafeChance; + float _strafeTime; + float _strafeRandomFactor; + float _postureChangeChance; + float _fireTimeMin; + float _fireTimeMax; + float _fireTimeRandomFactor; + float _pauseTimeMin; + float _pauseTimeMax; + float _pauseTimeRandomFactor; + + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( generalCombatStates_t state ); + void setInternalState ( generalCombatStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + void setTorsoAnim (); + bool checkShouldApproach ( float distToEnemy ); + bool checkShouldRetreat ( float distToEnemy ); + bool checkShouldStrafe ( float distToEnemy ); + bool checkShouldChangePosture ( float distToEnemy ); + + + void setupRotate (); + BehaviorReturnCode_t evaluateRotate (); + + void setupStateChangePostureToAdvance (); + BehaviorReturnCode_t evaluateStateChangePostureToAdvance (); + void failureStateChangePostureToAdvance ( const str& failureReason ); + + void setupStateChangePostureToRetreat (); + BehaviorReturnCode_t evaluateStateChangePostureToRetreat (); + void failureStateChangePostureToRetreat ( const str& failureReason ); + + void setupStateAdvance (); + BehaviorReturnCode_t evaluateStateAdvance (); + void failureStateAdvance ( const str& failureReason ); + + void setupStateAdvanceFiring (); + BehaviorReturnCode_t evaluateStateAdvanceFiring (); + void failureStateAdvanceFiring ( const str& failureReason ); + + void setupStateRetreat (); + BehaviorReturnCode_t evaluateStateRetreat (); + void failureStateRetreat ( const str& failureReason ); + + void setupStateRetreatFiring (); + BehaviorReturnCode_t evaluateStateRetreatFiring (); + void failureStateRetreatFiring ( const str& failureReason ); + + void setupStateStrafe (); + BehaviorReturnCode_t evaluateStateStrafe (); + void failureStateStrafe ( const str& failureReason ); + + void setupStateChangePostureDuck (); + BehaviorReturnCode_t evaluateStateChangePostureDuck (); + void failureStateChangePostureDuck ( const str& failureReason ); + + void setupStateDucked (); + BehaviorReturnCode_t evaluateStateDucked (); + void failureStateDucked ( const str& failureReason ); + + void setupStateDuckedFiring (); + BehaviorReturnCode_t evaluateStateDuckedFiring (); + void failureStateDuckedFiring ( const str& failureReason ); + + void setupStateChangePostureStand (); + BehaviorReturnCode_t evaluateStateChangePostureStand (); + void failureStateChangePostureStand ( const str& failureReason ); + + void setupStateStand (); + BehaviorReturnCode_t evaluateStateStand (); + void failureStateStand ( const str& failureReason ); + + void setupStateStandFiring (); + BehaviorReturnCode_t evaluateStateStandFiring (); + void failureStateStandFiring ( const str& failureReason ); + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( GeneralCombatWithRangedWeapon ); + + GeneralCombatWithRangedWeapon(); + ~GeneralCombatWithRangedWeapon(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void PostureDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + + void SetMovementAnim ( const str& movementAnim ) { _movementAnim = movementAnim; } + void SetTorsoAnim ( const str& torsoAnim ) { _torsoAnim = torsoAnim; } + void SetFireAnim ( const str& fireAnim ) { _fireAnim = fireAnim; } + void SetPreFireAnim ( const str& preFireAnim ) { _preFireAnim = preFireAnim; } + void SetPostFireAnim ( const str& postFireAnim ) { _postFireAnim = postFireAnim; } + void SetApproachDist ( float approachDist ) { _approachDist = approachDist; } + void SetRetreatDist ( float retreatDist ) { _retreatDist = retreatDist; } + void SetStrafeChance ( float strafeChance ) { _strafeChance = strafeChance; } + void SetStrafeTime ( float strafeTime ) { _strafeTime = strafeTime; } + void SetStrafeRandomFactor ( float strafeRandomFactor ) { _strafeRandomFactor = strafeRandomFactor; } + void SetPostureChangeChance ( float postureChangeChance ) { _postureChangeChance = postureChangeChance; } + void SetFireTimeMin ( float fireTimeMin ) { _fireTimeMin = fireTimeMin; } + void SetFireTimeMax ( float fireTimeMax ) { _fireTimeMax = fireTimeMax; } + void SetFireTimeRandomFactor ( float fireTimeRandomFactor ) { _fireTimeRandomFactor = fireTimeRandomFactor; } + void SetPauseTimeMin ( float pauseTimeMin ) { _pauseTimeMin = pauseTimeMin; } + void SetPauseTimeMax ( float pauseTimeMax ) { _pauseTimeMax = pauseTimeMax; } + void SetPauseTimeRandomFactor ( float pauseTimeRandomFactor ) { _pauseTimeRandomFactor = pauseTimeRandomFactor; } + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + RotateToEntity _rotate; + MoveRandomDirection _moveRandomDir; + FireWeapon _fireWeapon; + Strafe _strafeComponent; + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + generalCombatStates_t _state; + Actor *_self; + EntityPtr _currentEnemy; + bool _finishedPostureTransition; + bool _randomAdvanceLockOut; + bool _randomRetreatLockOut; + + float _nextRotateTime; + float _nextStrafeAttemptTime; + float _nextPostureChange; + float _nextClearRetreatLockOutTime; + float _nextClearAdvanceLockOutTime; + + int _randomAdvanceFailures; + int _randomRetreatFailures; + + float _nextFireTime; + float _nextPauseTime; + +}; + + +inline void GeneralCombatWithRangedWeapon::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + + // + // Archive Parameters + // + arc.ArchiveString( &_movementAnim ); + arc.ArchiveString( &_torsoAnim ); + arc.ArchiveString( &_fireAnim ); + arc.ArchiveString( &_preFireAnim ); + arc.ArchiveString( &_postFireAnim ); + arc.ArchiveFloat( &_approachDist ); + arc.ArchiveFloat( &_retreatDist ); + arc.ArchiveFloat( &_strafeChance ); + arc.ArchiveFloat( &_strafeTime ); + arc.ArchiveFloat( &_strafeRandomFactor ); + arc.ArchiveFloat( &_postureChangeChance ); + arc.ArchiveFloat( &_fireTimeMin ); + arc.ArchiveFloat( &_fireTimeMax ); + arc.ArchiveFloat( &_fireTimeRandomFactor ); + arc.ArchiveFloat( &_pauseTimeMin ); + arc.ArchiveFloat( &_pauseTimeMax ); + arc.ArchiveFloat( &_pauseTimeRandomFactor ); + + // + // Archive Components + // + arc.ArchiveObject( &_rotate ); + arc.ArchiveObject( &_moveRandomDir ); + arc.ArchiveObject( &_fireWeapon ); + arc.ArchiveObject( &_strafeComponent ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, generalCombatStates_t ); + arc.ArchiveObjectPointer ( ( Class ** )&_self ); + arc.ArchiveSafePointer ( &_currentEnemy ); + arc.ArchiveBool ( &_finishedPostureTransition ); + arc.ArchiveBool ( &_randomAdvanceLockOut ); + arc.ArchiveBool ( &_randomRetreatLockOut ); + arc.ArchiveFloat ( &_nextRotateTime ); + arc.ArchiveFloat ( &_nextStrafeAttemptTime ); + arc.ArchiveFloat ( &_nextPostureChange ); + arc.ArchiveFloat ( &_nextClearRetreatLockOutTime ); + arc.ArchiveFloat ( &_nextClearAdvanceLockOutTime ); + arc.ArchiveInteger ( &_randomAdvanceFailures ); + arc.ArchiveInteger ( &_randomRetreatFailures ); + arc.ArchiveFloat ( &_nextFireTime ); + arc.ArchiveFloat ( &_nextPauseTime ); + + +} + + +#endif /* __GENERALCOMBAT_WITH_RANGEDWEAPON_HPP__ */ + diff --git a/dlls/game/gibs.cpp b/dlls/game/gibs.cpp new file mode 100644 index 0000000..20ed344 --- /dev/null +++ b/dlls/game/gibs.cpp @@ -0,0 +1,272 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/gibs.cpp $ +// $Revision:: 12 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Gibs - nuff said + +#include "_pch_cpp.h" +#include "gibs.h" +#include "decals.h" +#include "mp_manager.hpp" + +Event EV_ThrowGib +( + "throwgib", + EV_DEFAULT, + "eff", + "ent velocity scale", + "Throw a gib." +); + +CLASS_DECLARATION( Entity, Gib, "gib" ) +{ + { &EV_ThrowGib, &Gib::Throw }, + { &EV_Touch, &Gib::Splat }, + { &EV_Stop, &Gib::Stop }, + { &EV_Damage, &Gib::Damage }, + + { NULL, NULL } +}; + +Gib::Gib( const str &name, qboolean blood_trail, const str &bloodtrailname, const str &bloodspurtname, + const str &bloodsplatname, float bloodsplatsize, float pitch ) +{ + animate = new Animate( this ); + + setSize(Vector(0, 0, 0), Vector(0, 0, 0)); + + if ( name.length() ) + setModel( name.c_str() ); + + setMoveType( MOVETYPE_GIB ); + setSolidType( SOLID_BBOX ); + takedamage = DAMAGE_YES; + sprayed = false; + fadesplat = true; + scale = 2.0f; + + next_bleed_time = 0; + + final_pitch = pitch; + + if ( blood_trail ) + { + // Make a blood emitter and bind it to the head + blood = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + if ( bloodtrailname.length() ) + blood->setModel( bloodtrailname.c_str() ); + + blood->setMoveType( MOVETYPE_BOUNCE ); + blood->setSolidType( SOLID_NOT ); + blood->bind( this ); + + // Save the blood spurt name + + if ( bloodspurtname.length() ) + blood_spurt_name = bloodspurtname; + + // Save the blood splat name + + if ( bloodsplatname.length() ) + blood_splat_name = bloodsplatname; + + blood_splat_size = bloodsplatsize; + } + else + { + blood = NULL; + } + + Sound( "snd_decap", CHAN_BODY, 1.0f, 300.0f ); +} + +Gib::~Gib() +{ + if ( blood ) + blood->PostEvent( EV_Remove, 0.0f ); + blood = NULL; +} + +Gib::Gib() +{ + animate = new Animate( this ); + + if ( LoadingSavegame ) + { + return; + } + + setSize(Vector(0, 0, 0), Vector(0, 0, 0)); + setModel("gib1.def"); + setMoveType( MOVETYPE_GIB ); + setSolidType( SOLID_BBOX ); + sprayed = 0; + fadesplat = true; + scale = 2.0f; +} + +void Gib::Stop( Event *ev ) +{ + //setSolidType( SOLID_NOT ); + if ( blood ) + blood->PostEvent( EV_Remove, 0.0f ); + blood = NULL; +} + +void Gib::Splat( Event *ev ) +{ + if ( multiplayerManager.inMultiplayer() ) + return; + + if ( sprayed > 3 ) + { + //setSolidType(SOLID_NOT); + return; + } + + sprayed++; + scale -= 0.2f; + + // Stop spinning / force to final pitch + + avelocity = vec_zero; + + if ( final_pitch != NO_FINAL_PITCH ) + { + angles[PITCH] = final_pitch; + setAngles( angles ); + } + + SprayBlood( origin ); + Sound( "snd_gibhit" ); +} + +void Gib::Damage( Event *ev ) +{ + Vector direction; + Entity *blood; + Vector dir; + + if ( next_bleed_time > level.time ) + return; + + direction = ev->GetVector ( 5 ); + + // Spawn a blood spurt + + if ( blood_spurt_name.length() > 0 ) + { + blood = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + blood->setModel( blood_spurt_name.c_str() ); + + dir[0] = -direction[0]; + dir[1] = -direction[1]; + dir[2] = -direction[2]; + + blood->angles = dir.toAngles(); + blood->setAngles( blood->angles ); + + blood->setOrigin( centroid ); + blood->origin.copyTo( blood->edict->s.origin2 ); + blood->setSolidType( SOLID_NOT ); + + blood->PostEvent( EV_Remove, 1.0f ); + + next_bleed_time = level.time + 0.5f; + } +} + +void Gib::SprayBlood( const Vector &start ) +{ + Vector norm; + trace_t trace; + Vector trace_end; + + trace_end = velocity; + trace_end.normalize(); + trace_end *= 1000.0f; + trace_end += start; + + trace = G_Trace( start, vec_zero, vec_zero, trace_end, this, MASK_SOLID, false, "Gib::SprayBlood" ); + + if ( HitSky( &level.impact_trace ) || ( !level.impact_trace.ent ) || ( level.impact_trace.ent->solid != SOLID_BSP ) ) + { + return; + } + + // Do a bloodsplat + if ( blood_splat_name.length() ) + { + Decal *decal = new Decal; + decal->setShader( blood_splat_name ); + decal->setOrigin( Vector( trace.endpos ) + ( Vector( level.impact_trace.plane.normal ) * 0.2f ) ); + decal->setDirection( level.impact_trace.plane.normal ); + decal->setOrientation( "random" ); + decal->setRadius( blood_splat_size + G_Random( blood_splat_size ) ); + } +} + +void Gib::ClipGibVelocity( void ) +{ + if (velocity[0] < -400.0f) + velocity[0] = -400.0f; + else if (velocity[0] > 400.0f) + velocity[0] = 400.0f; + if (velocity[1] < -400.0f) + velocity[1] = -400.0f; + else if (velocity[1] > 400.0f) + velocity[1] = 400.0f; + if (velocity[2] < 200.0f) + velocity[2] = 200.0f; // always some upwards + else if (velocity[2] > 600.0f) + velocity[2] = 600.0f; +} + +void Gib::SetVelocity( float damage ) +{ + velocity[0] = 100.0f * crandom(); + velocity[1] = 100.0f * crandom(); + velocity[2] = 200.0f + 100.0f * random(); + + avelocity = Vector( G_Random( 600.0f ), G_Random( 600.0f ), G_Random( 600.0f ) ); + + if ( ( damage < -150.0f ) && ( G_Random() > 0.95f ) ) + velocity *= 2.0f; + else if ( damage < -100.0f ) + velocity *= 1.5f; + + ClipGibVelocity(); +} + +void Gib::Throw( Event *ev ) +{ + Entity *ent; + + ent = ev->GetEntity(1); + + if ( !ent ) + return; + + setOrigin(ent->centroid); + origin.copyTo(edict->s.origin2); + SetVelocity(ev->GetFloat(2)); + edict->s.scale = ev->GetFloat(3); + PostEvent( EV_FadeOut, 10.0f + G_Random( 5.0f ) ); +} + +void CreateGibs( Entity * ent, float damage, float scale, int num, const char * modelname ) +{ + +} diff --git a/dlls/game/gibs.h b/dlls/game/gibs.h new file mode 100644 index 0000000..9753297 --- /dev/null +++ b/dlls/game/gibs.h @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/gibs.h $ +// $Revision:: 7 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Gibs - nuff said + +#ifndef __GIBS_H__ +#define __GIBS_H__ + +#include "g_local.h" +#include "mover.h" + +#define NO_FINAL_PITCH -1000 + +class Gib : public Entity + { + private: + int sprayed; + float scale; + Entity *blood; + str blood_splat_name; + float blood_splat_size; + str blood_spurt_name; + float final_pitch; + float next_bleed_time; + public: + CLASS_PROTOTYPE( Gib ); + + qboolean fadesplat; + Gib(); + ~Gib(); + Gib( const str &name, qboolean blood_trail, const str &bloodtrailname="", const str &bloodspurtname="", + const str &bloodsplatname="", float blood_splat_size = 8, float pitch=NO_FINAL_PITCH ); + void SetVelocity( float health ); + void SprayBlood( const Vector &start ); + void Throw( Event *ev ); + void Splat( Event *ev ); + void Stop( Event *ev ); + void Damage( Event *ev ); + void ClipGibVelocity( void ); + virtual void Archive( Archiver &arc ); + }; + +inline void Gib::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveBoolean( &sprayed ); + arc.ArchiveFloat( &scale ); + arc.ArchiveObjectPointer( ( Class ** )&blood ); + arc.ArchiveString( &blood_splat_name ); + arc.ArchiveFloat( &blood_splat_size ); + arc.ArchiveString( &blood_spurt_name ); + arc.ArchiveFloat( &final_pitch ); + arc.ArchiveFloat( &next_bleed_time ); + + arc.ArchiveBoolean( &fadesplat ); + } + + +void CreateGibs + ( + Entity * ent, + float damage = -50, + float scale = 1.0f, + int num = 1, + const char * modelname = NULL + ); + +extern Event EV_ThrowGib; + +#endif // gibs.h diff --git a/dlls/game/globalcmd.cpp b/dlls/game/globalcmd.cpp new file mode 100644 index 0000000..18b3078 --- /dev/null +++ b/dlls/game/globalcmd.cpp @@ -0,0 +1,4089 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/globalcmd.cpp $ +// $Revision:: 76 $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Global commands for scripts +// + +#include "_pch_cpp.h" +#include "globalcmd.h" +#include "scriptmaster.h" +#include "player.h" +#include "actor.h" +#include +#include "CinematicArmature.h" + +Event EV_ProcessCommands +( + "processCommands", + EV_CODEONLY, + NULL, + NULL, + "Not used." +); +Event EV_Script_NewOrders +( + "newOrders", + EV_CODEONLY, + NULL, + NULL, + "Inform script that it is about to get new orders." +); +Event EV_ScriptThread_Execute +( + "execute", + EV_CODEONLY, + NULL, + NULL, + "Execute the thread." +); +Event EV_ScriptThread_Callback +( + "script_callback", + EV_CODEONLY, + "ese", + "slave name other", + "Script callback." +); +Event EV_ScriptThread_ThreadCallback +( + "thread_callback", + EV_CODEONLY, + NULL, + NULL, + "Thread callback." +); +Event EV_ScriptThread_DeathCallback +( + "death_callback", + EV_CODEONLY, + NULL, + NULL, + "Death callback." +); +Event EV_ScriptThread_CreateThread +( + "thread", + EV_SCRIPTONLY, + "@is", + "threadnumber label", + "Creates a thread starting at label." +); +Event EV_ScriptThread_CreateThread2 +( + "runThreadNamed", + EV_SCRIPTONLY, + "@is", + "threadnumber label", + "Creates a thread starting at label." +); +Event EV_ScriptThread_TerminateThread +( + "terminate", + EV_SCRIPTONLY, + "i", + "thread_number", + "Terminates the specified thread or the current one if none specified." +); +Event EV_ScriptThread_ControlObject +( + "control", + EV_CODEONLY, + "e", + "object", + "Not used." +); +Event EV_ScriptThread_Goto +( + "goto", + EV_SCRIPTONLY, + "s", + "label", + "Goes to the specified label." +); +Event EV_ScriptThread_Pause +( + "pause", + EV_SCRIPTONLY, + NULL, + NULL, + "Pauses the thread." +); +Event EV_ScriptThread_Wait +( + "wait", + EV_SCRIPTONLY, + "f", + "wait_time", + "Wait for the specified amount of time." +); +Event EV_ScriptThread_WaitFor +( + "waitfor", + EV_SCRIPTONLY, + "e", + "ent", + "Wait for the specified entity." +); +Event EV_ScriptThread_WaitForThread +( + "waitForThread", + EV_SCRIPTONLY, + "i", + "thread_number", + "Wait for the specified thread." +); +Event EV_ScriptThread_WaitForDeath +( + "waitForDeath", + EV_SCRIPTONLY, + "s", + "name", + "Wait for death." +); +Event EV_ScriptThread_WaitForSound +( + "waitForSound", + EV_SCRIPTONLY, + "sf", + "sound_name delay", + "Wait for end of the named sound plus an optional delay." +); +Event EV_ScriptThread_WaitForDialog +( + "waitForDialog", + EV_SCRIPTONLY, + "eF", + "actor additional_delay", + "Wait for end of the dialog for the specified actor.\n" + "If additional_delay is specified, than the actor will\n" + "wait the dialog length plus the delay." +); +Event EV_ScriptThread_WaitDialogLength +( + "waitDialogLength", + EV_SCRIPTONLY, + "s", + "dialogName", + "Wait for the length of time of the dialog.\n" +); +Event EV_ScriptThread_WaitForAnimation +( + "waitForAnimation", + EV_SCRIPTONLY, + "esF", + "entity animName additionalDelay", + "Waits for time of the animation length plus the additional delayt specified.\n" +); +Event EV_ScriptThread_WaitForPlayer +( + "waitForPlayer", + EV_SCRIPTONLY, + NULL, + NULL, + "Wait for player to be ready." +); +Event EV_ScriptThread_CPrint +( + "cprint", + EV_SCRIPTONLY, + "s", + "string", + "Prints a string." +); +Event EV_ScriptThread_Print +( + "print", + EV_SCRIPTONLY, + "sSSSSSSSSSSSSSSS", + "string string string string string string string string string string string string string string string string", + "Prints a string." +); +Event EV_ScriptThread_PrintInt +( + "printint", + EV_SCRIPTONLY, + "i", + "integer", + "Print integer." +); +Event EV_ScriptThread_PrintFloat +( + "printfloat", + EV_SCRIPTONLY, + "f", + "float", + "Prints a float." +); +Event EV_ScriptThread_PrintVector +( + "printvector", + EV_SCRIPTONLY, + "v", + "vector", + "Prints a vector." +); +Event EV_ScriptThread_NewLine +( + "newline", + EV_SCRIPTONLY, + NULL, + NULL, + "Prints a newline." +); +Event EV_ScriptThread_Assert +( + "assert", + EV_SCRIPTONLY, + "f", + "value", + "Assert if value is 0." +); +Event EV_ScriptThread_Break +( + "break", + EV_SCRIPTONLY, + NULL, + NULL, + "Asserts so that we can break back into the debugger from script." +); +Event EV_ScriptThread_Clear +( + "clear", + EV_SCRIPTONLY, + "s", + "var_group", + "Clears the specified var group." +); +Event EV_ScriptThread_Trigger +( + "trigger", + EV_SCRIPTONLY, + "s", + "entname", + "Trigger the specified target." +); +Event EV_ScriptThread_TriggerEntity +( + "triggerentity", + EV_SCRIPTONLY, + "e", + "entityToTrigger", + "Trigger the specified entity." +); +Event EV_ScriptThread_Spawn +( + "spawn", + EV_CHEAT | EV_SCRIPTONLY, + "@esSSSSSSSS", + "entity entityname keyname1 value1 keyname2 value2 keyname3 value3 keyname4 value4", + "Spawn the specified entity." +); +Event EV_ScriptThread_Map +( + "map", + EV_SCRIPTONLY, + "s", + "map_name", + "Starts the specified map." +); +Event EV_ScriptThread_NoIntermission +( + "noIntermission", + EV_SCRIPTONLY, + NULL, + NULL, + "Makes it so no intermission happens at the end of this level." +); +Event EV_ScriptThread_DontSaveOrientation +( + "dontSaveOrientation", + EV_SCRIPTONLY, + NULL, + NULL, + "Makes it so we don't save the player's orientation across this sublevel." +); +Event EV_ScriptThread_PlayerDeathThread +( + "playerDeathThread", + EV_SCRIPTONLY, + "s", + "threadName", + "Sets a thread to run when the player dies." +); +Event EV_ScriptThread_EndPlayerDeathThread +( + "endPlayerDeathThread", + EV_SCRIPTONLY, + NULL, + NULL, + "Ends the player death thread (goes on to mission failed)." +); +Event EV_ScriptThread_GetCvar +( + "getcvar", + EV_SCRIPTONLY, + "@ss", + "string_value cvar_name", + "Returns the string value of the specified cvar." +); +Event EV_ScriptThread_GetCvarFloat +( + "getcvarfloat", + EV_SCRIPTONLY, + "@fs", + "float_value cvar_name", + "Returns the float value of the specified cvar." +); +Event EV_ScriptThread_GetCvarInt +( + "getcvarint", + EV_SCRIPTONLY, + "@fs", + "int_value cvar_name", + "Returns the integer value of the specified cvar." +); +Event EV_ScriptThread_SetCvar +( + "setcvar", + EV_SCRIPTONLY, + "ss", + "cvar_name value", + "Sets the value of the specified cvar." +); +Event EV_ScriptThread_CueCamera +( + "cuecamera", + EV_SCRIPTONLY, + "eF", + "entity switchTime", + "Cue the camera. If switchTime is specified, then the camera\n" + "will switch over that length of time." +); +Event EV_ScriptThread_CuePlayer +( + "cueplayer", + EV_SCRIPTONLY, + "F", + "switchTime", + "Go back to the normal camera. If switchTime is specified,\n" + "then the camera will switch over that length of time." +); +Event EV_ScriptThread_FreezePlayer +( + "freezeplayer", + EV_SCRIPTONLY, + NULL, + NULL, + "Freeze the player." +); +Event EV_ScriptThread_ReleasePlayer +( + "releaseplayer", + EV_SCRIPTONLY, + NULL, + NULL, + "Release the player." +); +Event EV_ScriptThread_FakePlayer +( + "fakeplayer", + EV_SCRIPTONLY, + NULL, + NULL, + "Will create a fake version of the player, hide the real one and\n" + "call the fake one 'fakeplayer'." +); +Event EV_ScriptThread_RemoveFakePlayer +( + "removefakeplayer", + EV_SCRIPTONLY, + NULL, + NULL, + "Will delete the fake version of the player, un-hide the real one and\n" + "return to the normal game" +); +Event EV_ScriptThread_KillEnt +( + "killent", + EV_CHEAT | EV_SCRIPTONLY, + "i", + "ent_num", + "Kill the specified entity." +); +Event EV_ScriptThread_KillClass +( + "killclass", + EV_CHEAT | EV_SCRIPTONLY, + "sI", + "class_name except", + "Kills everything in the specified class except for the specified entity (optional)." +); +Event EV_ScriptThread_RemoveEnt +( + "removeent", + EV_CHEAT | EV_SCRIPTONLY, + "i", + "ent_num", + "Removes the specified entity." +); +Event EV_ScriptThread_RemoveClass +( + "removeclass", + EV_CHEAT | EV_SCRIPTONLY, + "sI", + "class_name except", + "Removes everything in the specified class except for the specified entity (optional)." +); +Event EV_ScriptThread_RemoveActorsNamed +( + "removeActorsNamed", + EV_CHEAT | EV_SCRIPTONLY, + "s", + "class_name", + "remove all actors in the game that have a matching name field (NOT targetname)" +); + +// client/server flow control +Event EV_ScriptThread_StuffCommand +( + "stuffcmd", + EV_SCRIPTONLY, + "SSSSSS", + "arg1 arg2 arg3 arg4 arg5 arg6", + "Server only command." +); + +// +// world stuff +// +Event EV_ScriptThread_RegisterAlias +( + "alias", + EV_DEFAULT, + "ssSSSS", + "alias_name real_name arg1 arg2 arg3 arg4", + "Sets up an alias." +); +Event EV_ScriptThread_RegisterAliasAndCache +( + "aliascache", + EV_DEFAULT, + "ssSSSS", + "alias_name real_name arg1 arg2 arg3 arg4", + "Sets up an alias and caches the resourse." +); +Event EV_ScriptThread_SetCinematic +( + "cinematic", + EV_SCRIPTONLY, + NULL, + NULL, + "Turns on cinematic." +); +Event EV_ScriptThread_SetNonCinematic +( + "noncinematic", + EV_SCRIPTONLY, + NULL, + NULL, + "Turns off cinematic." +); +Event EV_ScriptThread_SetLevelAI +( + "level_ai", + EV_SCRIPTONLY, + "b", + "on_or_off", + "Turns on or off Level-wide AI for monsters." +); +Event EV_ScriptThread_SetSkipThread +( + "skipthread", + EV_SCRIPTONLY, + "s", + "thread_name", + "Set the thread to skip." +); + +// Precache specific +Event EV_ScriptThread_Precache_Cache +( + "cache", + EV_DEFAULT, + "s", + "resource_name", + "Cache the specified resource." +); + +// drugged up warping FOV controls +Event EV_ScriptThread_DrugView +( + "drugview", + EV_SCRIPTONLY, + "b", + "on_or_off", + "1 to turn FOV warping on, 0 to turn off." +); + +// fades for movies +Event EV_ScriptThread_FadeIn +( + "cam_fadein", + EV_SCRIPTONLY, + "fffffI", + "time color_red color_green color_blue alpha mode", + "Sets up fadein in values." +); +Event EV_ScriptThread_FadeOut +( + "cam_fadeout", + EV_SCRIPTONLY, + "fffffI", + "time color_red color_green color_blue alpha mode", + "Sets up fadeout values." +); +Event EV_ScriptThread_FadeIsActive +( + "fadeisactive", + EV_SCRIPTONLY, + "@i", + "Result", + "Returns 1 if a fade is in progress." +); +Event EV_ScriptThread_DoesEntityExist +( + "doesEntityExist", + EV_SCRIPTONLY, + "@fe", + "return_0_or_1 entity", + "Returns whether or not an entity exists." +); +Event EV_ScriptThread_GetEntity +( + "getentity", + EV_SCRIPTONLY, + "@es", + "entity entityname", + "Returns the entity with the specified targetname." +); +Event EV_ScriptThread_GetNextEntity +( + "getnextentity", + EV_SCRIPTONLY, + "@ee", + "entity currententity", + "Returns the next entity in the list with the same name as the current entity." +); + +// music command +Event EV_ScriptThread_MusicEvent +( + "music", + EV_SCRIPTONLY, + "sS", + "current fallback", + "Sets the current and fallback (optional) music moods." +); +Event EV_ScriptThread_ForceMusicEvent +( + "forcemusic", + EV_SCRIPTONLY, + "sS", + "current fallback", + "Forces the current and fallback (optional) music moods." +); +Event EV_ScriptThread_MusicVolumeEvent +( + "musicvolume", + EV_SCRIPTONLY, + "ff", + "volume fade_time", + "Sets the volume and fade time of the music." +); +Event EV_ScriptThread_RestoreMusicVolumeEvent +( + "restoremusicvolume", + EV_SCRIPTONLY, + "f", + "fade_time", + "Restores the music volume to its previous value." +); +Event EV_ScriptThread_AllowMusicDucking +( + "allowMusicDucking", + EV_SCRIPTONLY, + "B", + "boolean", + "Sets whether or not music ducking is allowed." +); +Event EV_ScriptThread_AllowActionMusic +( + "allowActionMusic", + EV_SCRIPTONLY, + "B", + "boolean", + "Sets whether or not action music is allowed or not." +); +Event EV_ScriptThread_SoundtrackEvent +( + "soundtrack", + EV_SCRIPTONLY, + "s", + "soundtrack_name", + "Changes the soundtrack." +); +Event EV_ScriptThread_RestoreSoundtrackEvent +( + "restoresoundtrack", + EV_SCRIPTONLY, + NULL, + NULL, + "Restores the soundtrack to the previous one." +); +Event EV_ScriptThread_ClearFade +( + "clearfade", + EV_SCRIPTONLY, + NULL, + NULL, + "Clear the fade from the screen" +); +Event EV_ScriptThread_Letterbox +( + "letterbox", + EV_SCRIPTONLY, + "f", + "time", + "Puts the game in letterbox mode." +); +Event EV_ScriptThread_ClearLetterbox +( + "clearletterbox", + EV_SCRIPTONLY, + "f", + "time", + "Clears letterbox mode." +); +Event EV_ScriptThread_SetDialogScript +( + "setdialogscript" , + EV_SCRIPTONLY, + "s", + "dialog_script", + "Set the script to be used when dialog:: is used" +); +Event EV_ScriptThread_CosDegrees +( + "cosdegrees", + EV_SCRIPTONLY, + "@ff", + "cosine degrees", + "Returns the cosine of the angle specified (in degrees)." +); +Event EV_ScriptThread_SinDegrees +( + "sindegrees", + EV_SCRIPTONLY, + "@ff", + "sine degrees", + "Returns the sine of the angle specified (in degrees)." +); +Event EV_ScriptThread_CosRadians +( + "cosradians", + EV_SCRIPTONLY, + "@ff", + "cosine radians", + "Returns the cosine of the angle specified (in radians)." +); +Event EV_ScriptThread_SinRadians +( + "sinradians", + EV_SCRIPTONLY, + "@ff", + "sine radians", + "Returns the sine of the angle specified (in radians)." +); +Event EV_ScriptThread_ArcTanDegrees +( + "arctandegrees", + EV_SCRIPTONLY, + "@fff", + "arctangent y x", + "Returns the angle (in degrees) described by a 2d vector pointing to (x,y)." +); +Event EV_ScriptThread_Sqrt +( + "sqrt", + EV_SCRIPTONLY, + "@ff", + "square_root x", + "Returns the sign-preserving square root of x." +); +Event EV_ScriptThread_Log +( + "log", + EV_SCRIPTONLY, + "@ff", + "log x", + "Returns the log (base 2)." +); +Event EV_ScriptThread_Exp +( + "exp", + EV_SCRIPTONLY, + "@ff", + "exp x", + "Returns the exponential." +); +Event EV_ScriptThread_RandomFloat +( + "randomfloat", + EV_SCRIPTONLY, + "@ff", + "returnval max_float", + "Sets the float to a random number." +); +Event EV_ScriptThread_RandomInteger +( + "randomint", + EV_SCRIPTONLY, + "@ii", + "returnval max_int", + "Sets the integer to a random number." +); +Event EV_ScriptThread_CameraCommand +( + "cam", + EV_SCRIPTONLY, + "sSSSSSS", + "command arg1 arg2 arg3 arg4 arg5 arg6", + "Processes a camera command." +); +Event EV_ScriptThread_SetLightStyle +( + "setlightstyle" , + EV_SCRIPTONLY, + "is", + "lightstyleindex lightstyledata", + "Set up the lightstyle with lightstyleindex to the specified data" +); +Event EV_ScriptThread_KillThreadEvent +( + "killthread", + EV_SCRIPTONLY, + "s", + "threadName", + "kills all threads starting with the specified name" +); +Event EV_ScriptThread_SetThreadNameEvent +( + "threadname", + EV_SCRIPTONLY, + "s", + "threadName", + "sets the name of the thread" +); +Event EV_ScriptThread_CenterPrint +( + "centerprint", + EV_SCRIPTONLY, + "s", + "stuffToPrint", + "prints the included message in the middle of all player's screens" +); +Event EV_ScriptThread_IsActorDead +( + "isactordead", + EV_SCRIPTONLY, + "s", + "actor_name", + "Checks if an Actor is dead or not" +); +Event EV_ScriptThread_IsActorAlive +( + "isactoralive", + EV_SCRIPTONLY, + "@fs", + "flag actor_name", + "Checks if an Actor is alive or not" +); +Event EV_ScriptThread_SendClientCommand +( + "SendClientCommand", + EV_SCRIPTONLY, + "es", + "player command", + "Sends a command across to the client" +); +Event EV_ScriptThread_GetNumFreeReliableServerCommands +( + "GetNumFreeReliableServerCommands", + EV_SCRIPTONLY, + "@fe", + "count player", + "Returns the number of reliable server command slots that are still available for use" +); +Event EV_ScriptThread_SendClientVar +( + "SendClientVar", + EV_SCRIPTONLY, + "es", + "player varName", + "Sends a variable across to the client" +); +Event EV_ScriptThread_GetCurrentEntity +( + "getcurrententity", + EV_SCRIPTONLY, + "@e", + "entity", + "Retrieves the current entity" +); +Event EV_ScriptThread_Mod +( + "mod", + EV_SCRIPTONLY, + "@fii", + "integer_remainder nominator denominator", + "Returns the integer remainder from nominator / denominator" +); +Event EV_ScriptThread_Div +( + "div", + EV_SCRIPTONLY, + "@fii", + "integer_result nominator denominator", + "Returns the integer value from nominator / denominator" +); +Event EV_ScriptThread_VectorScale +( + "vectorscale", + EV_SCRIPTONLY, + "@vvf", + "scaled_vector vector scale_value", + "Returns the vector scaled by scale" +); +Event EV_ScriptThread_VectorDot +( + "vectordot", + EV_SCRIPTONLY, + "@fvv", + "dot_product vector1 vector2", + "Returns the dot product of vector1 and vector2." +); +Event EV_ScriptThread_VectorCross +( + "vectorcross", + EV_SCRIPTONLY, + "@vvv", + "cross_vector vector1 vector2", + "Returns the cross product of vector1 and vector2." +); +Event EV_ScriptThread_VectorNormalize +( + "vectornormalize", + EV_SCRIPTONLY, + "@vv", + "normalized_vector vector", + "Returns the normalized vector." +); +Event EV_ScriptThread_VectorLength +( + "vectorlength", + EV_SCRIPTONLY, + "@fv", + "length vector", + "Returns the length of the vector." +); +Event EV_ScriptThread_VectorForward +( + "vectorforward", + EV_SCRIPTONLY, + "@vv", + "forward_vector vector", + "Returns the forward vector of the vector." +); +Event EV_ScriptThread_VectorLeft +( + "vectorleft", + EV_SCRIPTONLY, + "@vv", + "left_vector vector", + "Returns the left vector of the vector." +); +Event EV_ScriptThread_VectorUp +( + "vectorup", + EV_SCRIPTONLY, + "@vv", + "up_vector vector", + "Returns the up vector of the vector." +); +Event EV_ScriptThread_VectorGetX +( + "vectorgetx", + EV_SCRIPTONLY, + "@fv", + "x_value vector", + "Returns the x of the vector." +); +Event EV_ScriptThread_VectorGetY +( + "vectorgety", + EV_SCRIPTONLY, + "@fv", + "y_value vector", + "Returns the y of the vector." +); +Event EV_ScriptThread_VectorGetZ +( + "vectorgetz", + EV_SCRIPTONLY, + "@fv", + "z_value vector", + "Returns the z of the vector." +); +Event EV_ScriptThread_VectorSetX +( + "vectorsetx", + EV_SCRIPTONLY, + "@vvf", + "vector_result vector x_value", + "Sets the x value of a vector" +); +Event EV_ScriptThread_VectorSetY +( + "vectorsety", + EV_SCRIPTONLY, + "@vvf", + "vector_result vector y_value", + "Sets the y value of a vector" +); +Event EV_ScriptThread_VectorSetZ +( + "vectorsetz", + EV_SCRIPTONLY, + "@vvf", + "vector_result vector z_value", + "Sets the z value of a vector" +); +Event EV_ScriptThread_VectorToString +( + "vectorToString", + EV_SCRIPTONLY, + "@sv", + "string_result vector", + "Converts a vector to a string" +); +Event EV_ScriptThread_TargetOf +( + "targetof", + EV_SCRIPTONLY, + "@se", + "name entity", + "Gets the name of the entity's target" +); +Event EV_ScriptThread_Floor +( + "floor", + EV_SCRIPTONLY, + "@ff", + "return_value float_value", + "Returns the closest integer not larger than float." +); +Event EV_ScriptThread_Ceil +( + "ceil", + EV_SCRIPTONLY, + "@ff", + "return_value float_value", + "Returns the closest integer not smaller than float." +); +Event EV_ScriptThread_Round +( + "round", + EV_SCRIPTONLY, + "@ff", + "return_value float_value", + "Returns the rounded value of a float." +); +Event EV_ScriptThread_SetGameplayFloat +( + "setgameplayfloat", + EV_SCRIPTONLY, + "ssf", + "objname propname value", + "Sets a property float value on an object" +); +Event EV_ScriptThread_GetGameplayString +( + "getgameplaystring", + EV_DEFAULT, + "@sss", + "stringvalue objname propname", + "Returns the string value of the object property." +); +Event EV_ScriptThread_GetGameplayFloat +( + "getgameplayfloat", + EV_DEFAULT, + "@fss", + "floatvalue objname propname", + "Returns the float value of the object property." +); +Event EV_ScriptThread_SetGameplayString +( + "setgameplaystring", + EV_SCRIPTONLY, + "sss", + "objname propname valuestr", + "Sets a property string value on an object" +); +Event EV_ScriptThread_GetIntegerFromString +( + "getintfromstring", + EV_SCRIPTONLY, + "@fs", + "returned_int string", + "Returns the integer found in a string. Can be called with strings " + "that contain text, such as enemy12." +); +Event EV_ScriptThread_CreateCinematic +( + "createCinematic", + EV_SCRIPTONLY, + "@es", + "returnedCinematic cinematicName", + "Creates a cinematic from disk. Returns the created cinematic." + "Each call to this function creates a new cinematic, even if a" + "cinematic with this name already exists." +); +Event EV_ScriptThread_GetTime +( + "getLevelTime", + EV_SCRIPTONLY, + "@f", + "returnedFloat", + "Returns how many seconds.milliseconds the current level has been running." +); +Event EV_ScriptThread_ConnectPathnodes +( + "connectPathnodes", + EV_SCRIPTONLY, + "ss", + "node1 node2", + "Connects the 2 specified nodes." +); +Event EV_ScriptThread_DisconnectPathnodes +( + "disconnectPathnodes", + EV_SCRIPTONLY, + "ss", + "node1 node2", + "Disconnects the 2 specified nodes." +); + + +CLASS_DECLARATION( Interpreter, CThread, NULL ) +{ + { &EV_ScriptThread_Execute, &CThread::ExecuteFunc }, + { &EV_MoveDone, &CThread::ObjectMoveDone }, + { &EV_ScriptThread_Callback, &CThread::ScriptCallback }, + { &EV_ScriptThread_ThreadCallback, &CThread::ThreadCallback }, + { &EV_ScriptThread_DeathCallback, &CThread::DeathCallback }, + { &EV_ScriptThread_CreateThread, &CThread::CreateThread }, + { &EV_ScriptThread_CreateThread2, &CThread::CreateThread }, + { &EV_ScriptThread_TerminateThread, &CThread::TerminateThread }, + { &EV_ScriptThread_Pause, &CThread::EventPause }, + { &EV_ScriptThread_Wait, &CThread::EventWait }, + { &EV_ScriptThread_WaitFor, &CThread::EventWaitFor }, + { &EV_ScriptThread_WaitForThread, &CThread::EventWaitForThread }, + { &EV_ScriptThread_WaitForDeath, &CThread::EventWaitForDeath }, + { &EV_ScriptThread_WaitForSound, &CThread::EventWaitForSound }, + { &EV_ScriptThread_WaitForDialog, &CThread::EventWaitForDialog }, + { &EV_ScriptThread_WaitDialogLength, &CThread::EventWaitDialogLength }, + { &EV_ScriptThread_WaitForAnimation, &CThread::EventWaitForAnimation }, + { &EV_ScriptThread_WaitForPlayer, &CThread::EventWaitForPlayer }, + { &EV_ScriptThread_CPrint, &CThread::CPrint }, + { &EV_ScriptThread_Print, &CThread::Print }, + { &EV_ScriptThread_PrintInt, &CThread::PrintInt }, + { &EV_ScriptThread_PrintFloat, &CThread::PrintFloat }, + { &EV_ScriptThread_PrintVector, &CThread::PrintVector }, + { &EV_ScriptThread_NewLine, &CThread::NewLine }, + { &EV_ScriptThread_Assert, &CThread::Assert }, + { &EV_ScriptThread_Break, &CThread::Break }, + { &EV_ScriptThread_Trigger, &CThread::TriggerEvent }, + { &EV_ScriptThread_TriggerEntity, &CThread::TriggerEntityEvent }, + { &EV_ScriptThread_StuffCommand, &CThread::StuffCommand }, + { &EV_ScriptThread_Spawn, &CThread::Spawn }, + { &EV_ScriptThread_Precache_Cache, &CThread::CacheResourceEvent }, + { &EV_ScriptThread_RegisterAlias, &CThread::RegisterAlias }, + { &EV_ScriptThread_RegisterAliasAndCache, &CThread::RegisterAliasAndCache }, + { &EV_ScriptThread_Map, &CThread::MapEvent }, + { &EV_ScriptThread_NoIntermission, &CThread::noIntermission }, + { &EV_ScriptThread_DontSaveOrientation, &CThread::dontSaveOrientation }, + + { &EV_ScriptThread_PlayerDeathThread, &CThread::setPlayerDeathThread }, + { &EV_ScriptThread_EndPlayerDeathThread, &CThread::endPlayerDeathThread }, + + { &EV_ScriptThread_GetCvar, &CThread::GetCvarEvent }, + { &EV_ScriptThread_GetCvarFloat, &CThread::GetCvarFloatEvent }, + { &EV_ScriptThread_GetCvarInt, &CThread::GetCvarIntEvent }, + { &EV_ScriptThread_SetCvar, &CThread::SetCvarEvent }, + { &EV_ScriptThread_CueCamera, &CThread::CueCamera }, + { &EV_ScriptThread_CuePlayer, &CThread::CuePlayer }, + { &EV_ScriptThread_FreezePlayer, &CThread::FreezePlayer }, + { &EV_ScriptThread_ReleasePlayer, &CThread::ReleasePlayer }, + { &EV_ScriptThread_FadeIn, &CThread::FadeIn }, + { &EV_ScriptThread_FadeOut, &CThread::FadeOut }, + { &EV_ScriptThread_FadeIsActive, &CThread::FadeIsActive }, + { &EV_ScriptThread_ClearFade, &CThread::ClearFade }, + { &EV_ScriptThread_Letterbox, &CThread::Letterbox }, + { &EV_ScriptThread_ClearLetterbox, &CThread::ClearLetterbox }, + { &EV_ScriptThread_MusicEvent, &CThread::MusicEvent }, + { &EV_ScriptThread_ForceMusicEvent, &CThread::ForceMusicEvent }, + { &EV_ScriptThread_MusicVolumeEvent, &CThread::MusicVolumeEvent }, + { &EV_ScriptThread_RestoreMusicVolumeEvent, &CThread::RestoreMusicVolumeEvent }, + { &EV_ScriptThread_AllowMusicDucking, &CThread::allowMusicDucking }, + { &EV_ScriptThread_AllowActionMusic, &CThread::allowActionMusic }, + { &EV_ScriptThread_SoundtrackEvent, &CThread::SoundtrackEvent }, + { &EV_ScriptThread_RestoreSoundtrackEvent, &CThread::RestoreSoundtrackEvent }, + { &EV_ScriptThread_SetCinematic, &CThread::SetCinematic }, + { &EV_ScriptThread_SetNonCinematic, &CThread::SetNonCinematic }, + { &EV_ScriptThread_SetLevelAI, &CThread::SetLevelAI }, + { &EV_ScriptThread_SetSkipThread, &CThread::SetSkipThread }, + { &EV_ScriptThread_KillEnt, &CThread::KillEnt }, + { &EV_ScriptThread_RemoveEnt, &CThread::RemoveEnt }, + { &EV_ScriptThread_KillClass, &CThread::KillClass }, + { &EV_ScriptThread_RemoveClass, &CThread::RemoveClass }, + { &EV_ScriptThread_RemoveActorsNamed, &CThread::RemoveActorsNamed }, + { &EV_ScriptThread_FakePlayer, &CThread::FakePlayer }, + { &EV_ScriptThread_RemoveFakePlayer, &CThread::RemoveFakePlayer }, + { &EV_AI_RecalcPaths, &CThread::PassToPathmanager }, + { &EV_AI_CalcPath, &CThread::PassToPathmanager }, + { &EV_ScriptThread_DoesEntityExist, &CThread::doesEntityExist }, + { &EV_ScriptThread_GetEntity, &CThread::GetEntityEvent }, + { &EV_ScriptThread_GetNextEntity, &CThread::GetNextEntityEvent }, + { &EV_ScriptThread_CosDegrees, &CThread::CosDegrees }, + { &EV_ScriptThread_SinDegrees, &CThread::SinDegrees }, + { &EV_ScriptThread_CosRadians, &CThread::CosRadians }, + { &EV_ScriptThread_SinRadians, &CThread::SinRadians }, + { &EV_ScriptThread_ArcTanDegrees, &CThread::ArcTanDegrees }, + { &EV_ScriptThread_Sqrt, &CThread::ScriptSqrt }, + { &EV_ScriptThread_Log, &CThread::ScriptLog }, + { &EV_ScriptThread_Exp, &CThread::ScriptExp }, + { &EV_ScriptThread_RandomFloat, &CThread::RandomFloat }, + { &EV_ScriptThread_RandomInteger, &CThread::RandomInteger }, + { &EV_ScriptThread_CameraCommand, &CThread::CameraCommand }, + { &EV_ScriptThread_SetLightStyle, &CThread::SetLightStyle }, + { &EV_ScriptThread_KillThreadEvent, &CThread::KillThreadEvent }, + { &EV_ScriptThread_SetThreadNameEvent, &CThread::SetThreadName }, + + { &EV_SetFloatVar, &CThread::SetFloatVar }, + { &EV_SetVectorVar, &CThread::SetVectorVar }, + { &EV_SetStringVar, &CThread::SetStringVar }, + + { &EV_DoesVarExist, &CThread::doesVarExist }, + { &EV_RemoveVariable, &CThread::RemoveVariable }, + + { &EV_GetFloatVar, &CThread::GetFloatVar }, + { &EV_GetVectorVar, &CThread::GetVectorVar }, + { &EV_GetStringVar, &CThread::GetStringVar }, + + { &EV_ScriptThread_CenterPrint, &CThread::CenterPrint }, + { &EV_ScriptThread_IsActorDead, &CThread::isActorDead }, + { &EV_ScriptThread_IsActorAlive, &CThread::isActorAlive }, + { &EV_ScriptThread_SendClientCommand, &CThread::SendClientCommand }, + { &EV_ScriptThread_GetNumFreeReliableServerCommands, &CThread::GetNumFreeReliableServerCommands }, + { &EV_ScriptThread_SendClientVar, &CThread::SendClientVar }, + { &EV_ScriptThread_GetCurrentEntity, &CThread::GetCurrentEntity }, + { &EV_ScriptThread_Mod, &CThread::ModEvent }, + { &EV_ScriptThread_Div, &CThread::DivEvent }, + { &EV_ScriptThread_VectorScale, &CThread::VectorScaleEvent }, + { &EV_ScriptThread_VectorDot, &CThread::VectorDotEvent }, + { &EV_ScriptThread_VectorCross, &CThread::VectorCrossEvent }, + { &EV_ScriptThread_VectorNormalize, &CThread::VectorNormalizeEvent }, + { &EV_ScriptThread_VectorLength, &CThread::VectorLengthEvent }, + { &EV_ScriptThread_VectorSetX, &CThread::VectorSetXEvent }, + { &EV_ScriptThread_VectorSetY, &CThread::VectorSetYEvent }, + { &EV_ScriptThread_VectorSetZ, &CThread::VectorSetZEvent }, + { &EV_ScriptThread_VectorGetX, &CThread::VectorGetXEvent }, + { &EV_ScriptThread_VectorGetY, &CThread::VectorGetYEvent }, + { &EV_ScriptThread_VectorGetZ, &CThread::VectorGetZEvent }, + { &EV_ScriptThread_VectorForward, &CThread::VectorForwardEvent }, + { &EV_ScriptThread_VectorLeft, &CThread::VectorLeftEvent }, + { &EV_ScriptThread_VectorUp, &CThread::VectorUpEvent }, + { &EV_ScriptThread_VectorToString, &CThread::vectorToString }, + { &EV_ScriptThread_TargetOf, &CThread::TargetOfEvent }, + { &EV_ScriptThread_Floor, &CThread::FloorEvent }, + { &EV_ScriptThread_Ceil, &CThread::CeilEvent }, + { &EV_ScriptThread_Round, &CThread::RoundEvent }, + { &EV_ScriptThread_GetGameplayFloat, &CThread::GetGameplayFloat }, + { &EV_ScriptThread_GetGameplayString, &CThread::GetGameplayString }, + { &EV_ScriptThread_SetGameplayFloat, &CThread::SetGameplayFloat }, + { &EV_ScriptThread_SetGameplayString, &CThread::SetGameplayString }, + { &EV_ScriptThread_GetIntegerFromString, &CThread::GetIntegerFromString }, + { &EV_ScriptThread_CreateCinematic, &CThread::CreateCinematic }, + { &EV_ScriptThread_GetTime, &CThread::GetLevelTime }, + + { &EV_ScriptThread_ConnectPathnodes, &CThread::connectPathnodes }, + { &EV_ScriptThread_DisconnectPathnodes, &CThread::disconnectPathnodes }, + + { NULL, NULL } +}; + +CThread::CThread() +{ + ClearWaitFor(); + currentEntity = NULL; +} + +void CThread::ExecuteFunc( Event *ev ) +{ + ClearWaitFor(); + + Interpreter::Execute( ev ); +} + +void CThread::ClearWaitFor( void ) +{ + waitUntil = 0; + waitingFor = NULL; + waitingNumObjects = 0; + waitingForThread = NULL; + waitingForDeath = ""; + waitingForPlayer = false; +} + +qboolean CThread::WaitingFor( Entity *obj ) +{ + assert( obj ); + + return ( waitingFor && waitingFor->list.ObjectInList( obj ) ); +} + +void CThread::ObjectMoveDone( Event *ev ) +{ + Entity *obj; + + obj = ev->GetEntity( 1 ); + assert( obj ); + + if ( WaitingFor( obj ) ) + { + waitingNumObjects--; + if ( waitingNumObjects <= 0 ) + { + ClearWaitFor(); + DelayedStart( 0.0f ); + } + } +} + +void CThread::CreateThread( Event *ev ) +{ + CThread *pThread; + + pThread = Director.CreateThread( ev->GetToken( 1 ) ); + if ( pThread ) + { + // start right away + pThread->Start(); + + ev->ReturnInteger( pThread->ThreadNum() ); + } +} + +void CThread::TerminateThread( Event *ev ) +{ + int threadnum; + + threadnum = ev->GetInteger( 1 ); + Director.GetThread( threadnum ); + Director.KillThread( threadnum ); +} + +void CThread::EventPause( Event *ev ) +{ + DoMove(); + + ClearWaitFor(); + + doneProcessing = true; +} + +void CThread::EventWait( Event *ev ) +{ + DoMove(); + + ClearWaitFor(); + + waitUntil = ev->GetFloat( 1 ) + level.time; + + DelayedStart( ev->GetFloat( 1 ) ); + doneProcessing = true; +} + +void CThread::EventWaitFor( Event *ev ) +{ + Entity *ent; + const char *tname; + TargetList *tlist; + int i; + + ClearWaitFor(); + doneProcessing = true; + + ent = ev->GetEntity( 1 ); + if ( ent ) + { + tname = ent->TargetName(); + + // + // set the number of objects that belong to this targetname + // + tlist = world->GetTargetList( str( tname ) ); + waitingFor = tlist; + waitingNumObjects = tlist->list.NumObjects(); + + // + // make sure all these objects are in the update list + // + for( i = 1; i <= waitingNumObjects; i++ ) + { + ent = tlist->list.ObjectAt( i ); + + // add the object to the update list to make sure we tell it to do a move + if ( !updateList.ObjectInList( ent->entnum ) ) + { + updateList.AddObject( ent->entnum ); + } + } + + if ( waitingNumObjects <= 0 ) + { + waitingNumObjects = 1; + ev->Error( "no objects of targetname %s found.\n", tname ); + } + } + + DoMove(); +} + +void CThread::EventWaitForThread( Event *ev ) +{ + doneProcessing = true; + + ClearWaitFor(); + waitingForThread = Director.GetThread( ev->GetInteger( 1 ) ); + if ( !waitingForThread ) + { + ev->Error( "Thread %d not running", ev->GetInteger( 1 ) ); + return; + } + + DoMove(); +} + +void CThread::EventWaitForDeath( Event *ev ) +{ + doneProcessing = true; + + ClearWaitFor(); + waitingForDeath = ev->GetString(1); + if ( !waitingForDeath.length() ) + { + ev->Error( "Null name" ); + return; + } + + DoMove(); +} + +void CThread::EventWaitForSound( Event *ev ) +{ + str sound; + float delay; + + ClearWaitFor(); + + DoMove(); + + delay = 0.0f; + sound = ev->GetString( 1 ); + + delay = gi.SoundLength( sound.c_str() ); + + if ( delay < 0.0f ) + { + gi.WDPrintf( "Lip file not found for dialog %s\n", sound.c_str() ); + } + + if ( ev->NumArgs() > 1 ) + { + delay += ev->GetFloat( 2 ); + } + + DelayedStart( delay ); + doneProcessing = true; +} + +void CThread::EventWaitForDialog( Event *ev ) +{ + float delay; + Actor *act; + Entity *ent; + + ent = ev->GetEntity( 1 ); + + if ( !ent ) + { + gi.WDPrintf( "WaitForDialog: Can't find entity\n" ); + return; + } + + if ( ent->isSubclassOf( Actor ) ) + { + act = ( Actor * )ent; + + delay = act->GetDialogRemainingTime( ); + if ( ev->NumArgs() > 1 ) + { + delay += ev->GetFloat( 2 ); + } + DelayedStart( delay ); + doneProcessing = true; + } +} + +void CThread::EventWaitDialogLength( Event *ev ) +{ + str dialogName; + float delay; + char localizedDialogName[ MAX_QPATH ]; + + // Get the localized dialog name + + dialogName = ev->GetString( 1 ); + + gi.LocalizeFilePath( dialogName, localizedDialogName ); + + // Figure out the delay time (length of dialog + extra specified delay) + + delay = gi.SoundLength( localizedDialogName ); + + if ( ev->NumArgs() > 1 ) + { + delay += ev->GetFloat( 2 ); + } + + // Delay the thread + + DelayedStart( delay ); + doneProcessing = true; +} + +void CThread::EventWaitForAnimation( Event *ev ) +{ + Entity *ent; + str animName; + int animNum; + float totalTime; + float extraTime; + + // Get the entity we are refering to + + ent = ev->GetEntity( 1 ); + + if ( !ent ) + return; + + // Get the animation we are refering to + + animName = ev->GetString( 2 ); + + // Get any extra time if any + + if ( ev->NumArgs() > 2 ) + extraTime = ev->GetFloat( 3 ); + else + extraTime = 0.0f; + + // Get the anim number + + animNum = gi.Anim_NumForName( ent->edict->s.modelindex, animName.c_str() ); + + if ( animNum < 0 ) + { + gi.WDPrintf( "Waiting for animation %s, but %s doesn't have this anim!\n", animName.c_str(), ent->model.c_str() ); + return; + } + + // Get the total time of this animation + + totalTime = gi.Anim_Time( ent->edict->s.modelindex, animNum ); + + totalTime += extraTime; + + // Setup all of the wait for stuff + + ClearWaitFor(); + DoMove(); + DelayedStart( totalTime ); + + doneProcessing = true; +} + +void CThread::EventWaitForPlayer( Event *ev ) +{ + if ( !Director.PlayerReady() ) + { + doneProcessing = true; + + ClearWaitFor(); + waitingForPlayer = true; + + DoMove(); + } +} + +void CThread::CPrint( Event *ev ) +{ + gi.centerprintf( &g_entities[ 0 ], CENTERPRINT_IMPORTANCE_NORMAL, ev->GetString( 1 ) ); +} + +void CThread::Print( Event *ev ) +{ + int i; + int n; + + n = ev->NumArgs(); + for( i = 1; i <= n; i++ ) + { + gi.DPrintf( "%s", ev->GetString( i ) ); + } +} + +void CThread::PrintInt( Event *ev ) +{ + gi.DPrintf( "%d", ev->GetInteger( 1 ) ); +} + +void CThread::PrintFloat( Event *ev ) +{ + gi.DPrintf( "%.2f", ev->GetFloat( 1 ) ); +} + +void CThread::PrintVector( Event *ev ) +{ + Vector vec; + + vec = ev->GetVector( 1 ); + gi.DPrintf( "(%.2f %.2f %.2f)", vec.x, vec.y, vec.z ); +} + +void CThread::NewLine( Event *ev ) +{ + gi.DPrintf( "\n" ); +} + +void CThread::Assert( Event *ev ) +{ + assert( ev->GetFloat( 1 ) ); +} + +void CThread::Break( Event *ev ) +{ + // Break into the debugger +#ifdef _WIN32 + __asm int 3 +#else + assert( 0 ); +#endif +} + +void CThread::ScriptCallback( Event *ev ) +{ + const char *name; + Entity *other; + Entity *slave; + + if ( threadDying ) + { + return; + } + + + slave = ev->GetEntity( 1 ); + name = ev->GetString( 2 ); + other = ev->GetEntity( 3 ); + currentEntity = slave; + + if ( !Goto( name ) ) + { + ev->Error( "Label '%s' not found", name ); + } + else + { + // kill any execute events (in case our last command was "wait") + ClearWaitFor(); + + // start right away + Start(); + } +} + +void CThread::ThreadCallback( Event *ev ) +{ + CThread *thread; + + if ( threadDying ) + { + return; + } + + thread = ev->GetThread(); + if ( thread && ( thread == waitingForThread ) ) + { + ClearWaitFor(); + Start(); + } +} + +void CThread::DeathCallback( Event *ev ) +{ + if ( threadDying ) + { + return; + } + + ClearWaitFor(); + DelayedStart( 0.0f ); +} + +void CThread::DoMove( void ) +{ + int entnum; + Entity *ent; + Event *event; + int count; + int i; + + count = updateList.NumObjects(); + + for( i = 1; i <= count; i++ ) + { + entnum = ( int )updateList.ObjectAt( i ); + ent = G_GetEntity( entnum ); + if ( ent && ( ent->ValidEvent( EV_ProcessCommands ) ) ) + { + event = new Event( EV_ProcessCommands ); + event->SetThread( this ); + ent->PostEvent( event, 0.0f ); + } + else + { + // try to remove this from the update list + if ( waitingNumObjects > 0 ) + { + waitingNumObjects--; + } + } + } + + updateList.ClearObjectList(); +} + +void CThread::TriggerEvent( Event *ev ) +{ + const char *name; + Event *event; + Entity *ent; + TargetList *tlist; + int i; + int num; + + name = ev->GetString( 1 ); + + if ( !name ) + return; + + // Check for object commands + if ( name[ 0 ] == '$' ) + { + tlist = world->GetTargetList( str( name + 1 ) ); + num = tlist->list.NumObjects(); + for ( i = 1; i <= num; i++ ) + { + ent = tlist->list.ObjectAt( i ); + + assert( ent ); + + event = new Event( EV_Activate ); + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( ev->GetLineNumber() ); + event->AddEntity( world ); + ent->ProcessEvent( event ); + } + } + else if ( name[ 0 ] == '*' ) // Check for entnum commands + { + if ( !IsNumeric( &name[ 1 ] ) ) + { + RunError( "trigger: Expecting numeric value for * command, but found '%s'\n", &name[ 1 ] ); + } + else + { + ent = G_GetEntity( atoi( &name[ 1 ] ) ); + if ( ent ) + { + event = new Event( EV_Activate ); + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( ev->GetLineNumber() ); + event->AddEntity( world ); + ent->ProcessEvent( event ); + } + else + { + RunError( "trigger: Entity not found for * command\n" ); + } + } + return; + } + else + { + RunError( "trigger: Invalid entity reference '%s'.\n", name ); + } +} + +//---------------------------------------------------------------- +// Name: TriggerEntityEvent +// Class: CThread +// +// Description: Triggers the specified entity +// +// Parameters: Event *ev - contains entity to trigger +// +// Returns: none +//---------------------------------------------------------------- + +void CThread::TriggerEntityEvent( Event *ev ) +{ + Event *event; + Entity *ent; + + // Get the entity we wish to trigger + + ent = ev->GetEntity( 1 ); + + if ( !ent ) + return; + + // Trigger the entity + + event = new Event( EV_Activate ); + + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( ev->GetLineNumber() ); + event->AddEntity( world ); + + ent->ProcessEvent( event ); +} + +void CThread::CacheResourceEvent( Event * ev ) +{ + if ( !precache->integer ) + { + return; + } + + CacheResource( ev->GetString( 1 ), world ); +} + +void CThread::RegisterAlias( Event *ev ) +{ + str parameters; + int i; + + // Get the parameters for this alias command + for( i = 3; i <= ev->NumArgs(); i++ ) + { + parameters += ev->GetString( i ); + parameters += " "; + } + + gi.GlobalAlias_Add( ev->GetString( 1 ), ev->GetString( 2 ), parameters.c_str() ); +} + +void CThread::RegisterAliasAndCache( Event *ev ) +{ + RegisterAlias( ev ); + + if ( !precache->integer ) + { + return; + } + + CacheResource( ev->GetString( 2 ) ); +} + +void CThread::MapEvent( Event *ev ) +{ + G_BeginIntermission( ev->GetString( 1 ) ); + doneProcessing = true; +} + +void CThread::noIntermission( Event *ev ) +{ + level._showIntermission = false; +} + +void CThread::dontSaveOrientation( Event *ev ) +{ + level._saveOrientation = false; +} + +void CThread::setPlayerDeathThread( Event *ev ) +{ + level.setPlayerDeathThread( ev->GetString( 1 ) ); +} + +void CThread::endPlayerDeathThread( Event *ev ) +{ + G_FinishMissionFailed(); +} + +void CThread::GetCvarEvent( Event *ev ) +{ + assert( ev ); + cvar_t *cvar = gi.cvar_get( ev->GetString( 1 ) ); + ev->ReturnString( cvar ? cvar->string : "" ); +} + +void CThread::GetCvarFloatEvent( Event *ev ) +{ + assert( ev ); + cvar_t *cvar = gi.cvar_get( ev->GetString( 1 ) ); + ev->ReturnFloat( cvar ? cvar->value : 0.0f ); +} + +void CThread::GetCvarIntEvent( Event *ev ) +{ + assert( ev ); + cvar_t *cvar = gi.cvar_get( ev->GetString( 1 ) ); + ev->ReturnFloat( cvar ? cvar->integer : 0.0f ); +} + +void CThread::SetCvarEvent( Event *ev ) +{ + str name; + + name = ev->GetString( 1 ); + if ( name != "" ) + { + gi.cvar_set( name.c_str(), ev->GetString( 2 ) ); + } +} + +void CThread::CueCamera( Event *ev ) +{ + float switchTime; + Entity *ent; + + if ( ev->NumArgs() > 1 ) + { + switchTime = ev->GetFloat( 2 ); + } + else + { + switchTime = 0; + } + + ent = ev->GetEntity( 1 ); + if ( ent ) + { + SetCamera( ent, switchTime ); + } + else + { + ev->Error( "Camera named %s not found", ev->GetString( 1 ) ); + } +} + +void CThread::CuePlayer( Event *ev ) +{ + float switchTime; + + if ( ev->NumArgs() > 0 ) + { + switchTime = ev->GetFloat( 1 ); + } + else + { + switchTime = 0; + } + + SetCamera( NULL, switchTime ); +} + +void CThread::FreezePlayer( Event *ev ) +{ + level.playerfrozen = true; +} + +void CThread::ReleasePlayer( Event *ev ) +{ + level.playerfrozen = false; +} + +void CThread::FakePlayer( Event *ev ) +{ + Player *player; + + player = ( Player * )g_entities[ 0 ].entity; + if ( player && player->edict->inuse && player->edict->client ) + { + player->FakePlayer(false); + } +} + +void CThread::RemoveFakePlayer( Event *ev ) +{ + Player *player; + + player = ( Player * )g_entities[ 0 ].entity; + if ( player && player->edict->inuse && player->edict->client ) + { + player->RemoveFakePlayer(); + } +} + +void CThread::Spawn( Event *ev ) +{ + Entity *ent; + Entity *tent; + const char *name; + ClassDef *cls; + int n; + int i; + const char *targetname; + const char *value; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "Usage: spawn entityname [keyname] [value]..." ); + return; + } + + // create a new entity + SpawnArgs args; + + name = ev->GetString( 1 ); + + if ( name ) + { + cls = getClassForID( name ); + if ( !cls ) + { + cls = getClass( name ); + } + + if ( !cls ) + { + str n; + + n = name; + if ( !strstr( n.c_str(), ".tik" ) ) + { + n += ".tik"; + } + args.setArg( "model", n.c_str() ); + } + else + { + args.setArg( "classname", name ); + } + } + + if ( ev->NumArgs() > 2 ) + { + n = ev->NumArgs(); + for( i = 2; i <= n; i += 2 ) + { + args.setArg( ev->GetString( i ), ev->GetString( i + 1 ) ); + } + } + + cls = args.getClassDef(); + if ( !cls ) + { + ev->Error( "'%s' is not a valid entity name", name ); + return; + } + + // If there is a spawntarget set, then use that entity's origin and angles + targetname = args.getArg( "spawntarget" ); + + if ( targetname ) + { + tent = G_FindTarget( NULL, targetname ); + if ( tent ) + { + args.setArg( "origin", va( "%f %f %f", tent->origin[ 0 ], tent->origin[ 1 ], tent->origin[ 2 ] ) ); + args.setArg( "angle", va( "%f", tent->angles[1] ) ); + } + else + { + ev->Error( "Can't find targetname %s", targetname ); + } + } + + // + // make sure to setup spawnflags properly + // + level.spawnflags = 0; + value = args.getArg( "spawnflags" ); + if ( value ) + { + level.spawnflags = atoi( value ); + } + + ent = args.Spawn(); + + ev->ReturnEntity( ent ); +} + +//FIXME +//Move this to someplace Level class. +static float last_fraction = 1.0f/8.0f; + +void CThread::Letterbox( Event *ev ) +{ + float time; + + if ( ev->NumArgs() < 1 ) + { + warning ("CThread::Letterbox", "time parameter required!\n"); + return; + } + + last_fraction = 1.0f/8.0f; + + time = ev->GetFloat( 1 ); + + if ( ev->NumArgs() > 1 ) + { + last_fraction = ev->GetFloat( 2 ); + } + + // this is now handled server side + //gi.SendServerCommand( NULL, va( "letterbox %0.2f 1 %0.2f",time,last_fraction ) ); + level.m_letterbox_time_start = time; + level.m_letterbox_dir = letterbox_in; + + level.m_letterbox_time = time; + level.m_letterbox_fraction = last_fraction; +} + +void CThread::ClearLetterbox( Event *ev ) +{ + float time; + + time = ev->GetFloat( 1 ); + + // this is now handled server side + //gi.SendServerCommand( NULL, va( "letterbox %0.2f -1 %0.2f",time,last_fraction ) ); + level.m_letterbox_time_start = time; + level.m_letterbox_dir = letterbox_out; + + level.m_letterbox_time = time; + level.m_letterbox_fraction = last_fraction; +} + +void CThread::FadeIn( Event *ev ) +{ + level.m_fade_time_start = ev->GetFloat( 1 ); + level.m_fade_time = ev->GetFloat( 1 ); + level.m_fade_color[0] = ev->GetFloat( 2 ); + level.m_fade_color[1] = ev->GetFloat( 3 ); + level.m_fade_color[2] = ev->GetFloat( 4 ); + level.m_fade_alpha = ev->GetFloat( 5 ); + level.m_fade_type = fadein; + level.m_fade_style = alphablend; + + if ( ev->NumArgs() > 5 ) + { + level.m_fade_style = (fadestyle_t)ev->GetInteger( 6 ); + } +} + +void CThread::ClearFade( Event *ev ) +{ + // this is now handled server side + //gi.SendServerCommand( NULL, "clearfade" ); + level.m_fade_time = -1; + level.m_fade_type = fadein; +} + +void CThread::FadeOut( Event *ev ) +{ + // Make sure we are not already faded or fading out + + if ( ( level.m_fade_type == fadeout ) && ( level.m_fade_time_start > 0.0f ) ) + { + float alpha; + + alpha = 1.0f - ( level.m_fade_time / level.m_fade_time_start ); + + if ( alpha > 0.0f ) + return; + } + + level.m_fade_time_start = ev->GetFloat( 1 ); + level.m_fade_time = ev->GetFloat( 1 ); + level.m_fade_color[0] = ev->GetFloat( 2 ); + level.m_fade_color[1] = ev->GetFloat( 3 ); + level.m_fade_color[2] = ev->GetFloat( 4 ); + level.m_fade_alpha = ev->GetFloat( 5 ); + level.m_fade_type = fadeout; + level.m_fade_style = alphablend; + + if ( ev->NumArgs() > 5 ) + { + level.m_fade_style = (fadestyle_t)ev->GetInteger( 6 ); + } +} + +void CThread::FadeIsActive( Event *ev ) +{ + if ( level.m_fade_time > 0.0f ) + ev->ReturnInteger( true ); + else + ev->ReturnInteger( false ); +} + +void CThread::MusicEvent( Event *ev ) +{ + const char *current; + const char *fallback; + + current = ev->GetString( 1 ); + + fallback = NULL; + if ( ev->NumArgs() > 1 ) + { + fallback = ev->GetString( 2 ); + } + + ChangeMusic( current, fallback, false ); +} + +void CThread::MusicVolumeEvent( Event *ev ) +{ + float volume; + float fade_time; + + volume = ev->GetFloat( 1 ); + fade_time = ev->GetFloat( 2 ); + + ChangeMusicVolume( volume, fade_time ); +} + +void CThread::RestoreMusicVolumeEvent( Event *ev ) +{ + float fade_time; + + fade_time = ev->GetFloat( 1 ); + + RestoreMusicVolume( fade_time ); +} + +//---------------------------------------------------------------- +// Name: allowMusicDucking +// Class: CThread +// +// Description: Specifies whether or not music ducking is allowed +// +// Parameters: Event *ev - contains bool, specifies whether or not music ducking is allowed +// +// Returns: none +//---------------------------------------------------------------- + +void CThread::allowMusicDucking( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + G_AllowMusicDucking( ev->GetBoolean( 1 ) ); + else + G_AllowMusicDucking( true ); +} + +//---------------------------------------------------------------- +// Name: allowActionMusic +// Class: CThread +// +// Description: Specifies whether or not action music is allowed +// +// Parameters: Event *ev - contains bool, specifies whether or not action music is allowed +// +// Returns: none +//---------------------------------------------------------------- + +void CThread::allowActionMusic( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + G_AllowActionMusic( ev->GetBoolean( 1 ) ); + else + G_AllowActionMusic( true ); +} + +void CThread::ForceMusicEvent( Event *ev ) +{ + const char *current; + const char *fallback; + + current = ev->GetString( 1 ); + fallback = NULL; + if ( ev->NumArgs() > 1 ) + { + fallback = ev->GetString( 2 ); + } + + ChangeMusic( current, fallback, true ); +} + +void CThread::SoundtrackEvent( Event *ev ) +{ + ChangeSoundtrack( ev->GetString( 1 ) ); +} + +void CThread::RestoreSoundtrackEvent( Event *ev ) +{ + RestoreSoundtrack(); +} + +void CThread::SetCinematic( Event *ev ) +{ + G_StartCinematic(); +} + +void CThread::SetNonCinematic( Event *ev ) +{ + G_StopCinematic(); + //level.cinematic = false; +} + +void CThread::SetLevelAI( Event *ev ) +{ + qboolean ai = false; + + if ( ev->NumArgs() > 0 ) + ai = ev->GetBoolean( 1 ); + + if ( ai ) + level.ai_on = true; + else + level.ai_on = false; +} + +void CThread::SetSkipThread( Event *ev ) +{ + world->skipthread = ev->GetString( 1 ); +} + +void CThread::PassToPathmanager( Event *ev ) +{ + thePathManager.ProcessEvent( ev ); +} + +void CThread::StuffCommand( Event *ev ) +{ + int i; + str command; + + for( i = 1; i <= ev->NumArgs(); i++ ) + { + command += ev->GetString( i ); + command += " "; + } + + if ( command.length() ) + { + gi.SendConsoleCommand( command.c_str() ); + } +} + +void CThread::KillEnt( Event * ev ) +{ + int num; + Entity *ent; + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "No args passed in" ); + return; + } + + num = ev->GetInteger( 1 ); + if ( ( num < 0 ) || ( num >= globals.max_entities ) ) + { + ev->Error( "Value out of range. Possible values range from 0 to %d.\n", globals.max_entities ); + return; + } + + ent = G_GetEntity( num ); + ent->Damage( world, world, ent->max_health + 25.0f, vec_zero, vec_zero, vec_zero, 0, 0, 0 ); +} + +void CThread::RemoveEnt( Event * ev ) +{ + int num; + Entity *ent; + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "No args passed in" ); + return; + } + + num = ev->GetInteger( 1 ); + if ( ( num < 0 ) || ( num >= globals.max_entities ) ) + { + ev->Error( "Value out of range. Possible values range from 0 to %d.\n", globals.max_entities ); + return; + } + + ent = G_GetEntity( num ); + ent->PostEvent( Event( EV_Remove ), 0.0f ); +} + +void CThread::KillClass( Event * ev ) +{ + int except; + str classname; + gentity_t * from; + Entity *ent; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "No args passed in" ); + return; + } + + classname = ev->GetString( 1 ); + + except = 0; + if ( ev->NumArgs() == 2 ) + { + except = ev->GetInteger( 1 ); + } + + for ( from = &g_entities[ game.maxclients ]; from < &g_entities[ globals.num_entities ]; from++ ) + { + if ( !from->inuse ) + { + continue; + } + + assert( from->entity ); + + ent = from->entity; + + if ( ent->entnum == except ) + { + continue; + } + + if ( ent->inheritsFrom( classname.c_str() ) ) + { + ent->Damage( world, world, ent->max_health + 25.0f, vec_zero, vec_zero, vec_zero, 0, 0, 0 ); + } + } +} + +// RemoveActorsNamed - remove all actors in the game that have a matching name field +void CThread::RemoveActorsNamed( Event* ev ) +{ + if ( ev->NumArgs() < 1 ) + { + ev->Error( "No args passed in" ); + return; + } + + str removename = ev->GetString( 1 ); + + for( gentity_t* gent = &g_entities[ game.maxclients ]; + gent < &g_entities[ globals.num_entities ]; + gent++ ) + { + if( !gent->inuse ) continue; + assert( gent->entity ); + if( !gent->entity->inheritsFrom( "Actor" ) ) continue; + + Actor* act = static_cast< Actor* >( gent->entity ); + if( act->getName() == removename ) + { + gent->entity->PostEvent( Event( EV_Remove ), 0.0f ); + } + } +} + +void CThread::RemoveClass( Event * ev ) +{ + int except; + str classname; + gentity_t * from; + Entity *ent; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "No args passed in" ); + return; + } + + classname = ev->GetString( 1 ); + + except = 0; + if ( ev->NumArgs() == 2 ) + { + except = ev->GetInteger( 1 ); + } + + for ( from = &g_entities[ game.maxclients ]; from < &g_entities[ globals.num_entities ]; from++ ) + { + if ( !from->inuse ) + { + continue; + } + + assert( from->entity ); + + ent = from->entity; + + if ( ent->entnum == except ) + continue; + + if ( ent->inheritsFrom( classname.c_str() ) ) + { + ent->PostEvent( Event( EV_Remove ), 0.0f ); + } + } +} + +//=============================================================== +// Name: doesEntityExist +// Class: CThread +// +// Description: Determines if an entity with the specified name +// exists. The return value for the event is either +// a 1 or a 0 ( true or false ). +// +// Parameters: Event* -- the event that triggered this call. +// +// Returns: None +// +//=============================================================== + +void CThread::doesEntityExist( Event *ev ) +{ + if ( ev->IsEntityAt( 1 ) ) + ev->ReturnFloat( 1.0f ); + else + ev->ReturnFloat( 0.0f ); +} + + +void CThread::GetEntityEvent( Event *ev ) +{ + TargetList *tlist; + + str name( ev->GetString( 1 ) ); + if ( ( name.length() > 0 ) && name[ 0 ] == '*' ) + { + ev->ReturnEntity( G_GetEntity( atoi( &name[ 1 ] ) ) ); + return; + } + tlist = world->GetTargetList( name, false ); + + //FIXME + //ev->ReturnInteger( ( int )tlist ); + if ( tlist ) + { + ev->ReturnEntity( tlist->GetNextEntity( NULL ) ); + } + else + { + ev->ReturnEntity( NULL ); + } +} + +void CThread::GetNextEntityEvent( Event *ev ) +{ + TargetList *tlist; + + Entity *ent = 0; + ent = ev->GetEntity( 1 ); + + if ( !ent ) + { + ev->ReturnEntity( NULL ); + return; + } + + tlist = world->GetTargetList( ent->targetname, false ); + + if ( tlist ) + ev->ReturnEntity( tlist->GetNextEntity( ent ) ); + else + ev->ReturnEntity( NULL ); +} + +void CThread::CosDegrees( Event *ev ) +{ + float degrees = ev->GetFloat( 1 ); + float radians = DEG2RAD( degrees ); + float result = cos( radians ); + + ev->ReturnFloat( result ); +} + +void CThread::SinDegrees( Event *ev ) +{ + float degrees = ev->GetFloat( 1 ); + float radians = DEG2RAD( degrees ); + float result = sin( radians ); + + ev->ReturnFloat( result ); +} + +void CThread::CosRadians( Event *ev ) +{ + float radians = ev->GetFloat( 1 ); + float result = cos( radians ); + + ev->ReturnFloat( result ); +} + +void CThread::SinRadians( Event *ev ) +{ + float radians = ev->GetFloat( 1 ); + float result = sin( radians ); + + ev->ReturnFloat( result ); +} + +void CThread::ArcTanDegrees( Event *ev ) +{ + float y = ev->GetFloat( 1 ); + float x = ev->GetFloat( 2 ); + float radians = atan2( y, x ); + float degrees = RAD2DEG( radians ); + + ev->ReturnFloat( degrees ); +} + +void CThread::ScriptSqrt( Event *ev ) +{ + /// Perform a sign-preserving square root ( i.e. sqrt( -16 ) = -4 ) + float x = ev->GetFloat( 1 ); + if( x < 0.0f ) + { + x = static_cast( -sqrt( -x ) ); + } + else + { + x = static_cast( sqrt( x ) ); + } + ev->ReturnFloat( x ); +} + +void CThread::ScriptLog( Event *ev ) +{ + float x = ev->GetFloat( 1 ); + + ev->ReturnFloat( log( x ) ); +} + +void CThread::ScriptExp( Event *ev ) +{ + float x = ev->GetFloat( 1 ); + + ev->ReturnFloat( exp( x ) ); +} + +void CThread::RandomFloat( Event *ev ) +{ + ev->ReturnFloat( G_Random( ev->GetFloat( 1 ) ) ); +} + +void CThread::RandomInteger( Event *ev ) +{ + ev->ReturnFloat( (float)(int)G_Random( (float)ev->GetInteger( 1 ) ) ); +} + +void CThread::CameraCommand( Event * ev ) +{ + Event *e; + const char *cmd; + int i; + int n; + + if ( !ev->NumArgs() ) + { + ev->Error( "Usage: cam [command] [arg 1]...[arg n]" ); + return; + } + + cmd = ev->GetString( 1 ); + if ( Event::Exists( cmd ) ) + { + e = new Event( cmd ); + e->SetSource( EV_FROM_SCRIPT ); + e->SetThread( this ); + e->SetLineNumber( ev->GetLineNumber() ); + + n = ev->NumArgs(); + for( i = 2; i <= n; i++ ) + { + e->AddToken( ev->GetToken( i ) ); + } + + CameraMan.ProcessEvent( e ); + } + else + { + ev->Error( "Unknown camera command '%s'.\n", cmd ); + } +} + +void CThread::SetLightStyle( Event *ev ) +{ + gi.SetLightStyle( ev->GetInteger( 1 ), ev->GetString( 2 ) ); +} + +void CThread::KillThreadEvent( Event *ev ) +{ + str threadToKill; + + threadToKill = ev->GetString( 1 ); + + if ( threadToKill == threadName ) + { + gi.WDPrintf( "Trying to kill the current thread %s, this is very bad!\n", threadName.c_str() ); + return; + } + + if ( threadToKill.length() == 0 ) + { + gi.WDPrintf( "Trying to kill a thread with no name\n" ); + return; + } + + Director.KillThread( threadToKill ); +} + +void CThread::SetThreadName( Event *ev ) +{ + threadName = ev->GetString( 1 ); +} + +void CThread::GetCurrentEntity( Event *ev ) +{ + if ( currentEntity ) + ev->ReturnEntity( currentEntity ); + else + ev->ReturnEntity ( NULL ); +} + +void CThread::SetCurrentEntity( Entity *ent ) +{ + if ( ent ) + currentEntity = ent; +} + +void CThread::Parse( const char *filename ) +{ + str token; + Script script; + int eventnum; + Event *ev; + + script.LoadFile( filename ); + while( script.TokenAvailable( true ) ) + { + token = script.GetToken( true ); + if ( token == "end" ) + { + break; + } + + eventnum = Event::FindEvent( token ); + if ( !eventnum ) + { + gi.WDPrintf( "Unknown command '%s' in %s\n", token.c_str(), filename ); + script.SkipToEOL(); + } + else + { + ev = new Event( eventnum ); + while( script.TokenAvailable( false ) ) + { + ev->AddToken( script.GetToken( false ) ); + } + + ProcessEvent( ev ); + } + } +} + +void CThread::SendCommandToSlaves( const char *name, Event *ev ) +{ + Event *sendevent; + Entity *ent; + TargetList *tlist; + int i; + int num; + + if ( name && name[ 0 ] ) + { + updateList.ClearObjectList(); + + tlist = world->GetTargetList( str( name + 1 ) ); + num = tlist->list.NumObjects(); + for ( i = 1; i <= num; i++ ) + { + ent = tlist->list.ObjectAt( i ); + + assert( ent ); + + sendevent = new Event( *ev ); + + if ( !updateList.ObjectInList( ent->entnum ) ) + { + updateList.AddObject( ent->entnum ); + + // Tell the object that we're about to send it some orders + ent->ProcessEvent( EV_Script_NewOrders ); + } + + // Send the command + ent->ProcessEvent( sendevent ); + } + + if ( !num ) + { + warning( "SendCommandToSlaves", "Could not find target %s in world.\n", name ); + } + } + + // + // free up the event + // + delete ev; +} + +void CThread::ProcessCommand( int argc, const char **argv ) +{ + str command; + str name; + Event *event; + Entity *ent; + + if ( argc < 1 ) + { + return; + } + + name = argv[ 0 ]; + if ( argc > 1 ) + { + command = argv[ 1 ]; + } + + // Check for object commands + if ( name[ 0 ] == '$' ) + { + if ( Event::FindEvent( command.c_str() ) ) + { + event = new Event( command ); + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( 0 ); + event->AddTokens( argc - 2, &argv[ 2 ] ); + SendCommandToSlaves( name.c_str(), event ); + } + return; + } + + // Check for entnum commands + if ( name[ 0 ] == '*' ) + { + if ( !IsNumeric( &name[ 1 ] ) ) + { + gi.WPrintf( "Expecting numeric value for * command, but found '%s'\n", &name[ 1 ] ); + } + else if ( Event::FindEvent( command.c_str() ) ) + { + ent = G_GetEntity( atoi( &name[ 1 ] ) ); + if ( ent ) + { + event = new Event( command ); + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( 0 ); + event->AddTokens( argc - 2, &argv[ 2 ] ); + ent->ProcessEvent( event ); + } + else + { + gi.WPrintf( "Entity not found for * command\n" ); + } + } + return; + } + + // Handle global commands + if ( Event::FindEvent( name.c_str() ) ) + { + event = new Event( name ); + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( 0 ); + event->AddTokens( argc - 1, &argv[ 1 ] ); + if ( !ProcessEvent( event ) ) + { + gi.WPrintf( "Invalid global command '%s'\n", name.c_str() ); + } + } +} + +inline void CThread::Archive( Archiver &arc ) +{ + int exists; + + Interpreter::Archive( arc ); + + arc.ArchiveFloat( &waitUntil ); + + // save waiting for thread + + if ( arc.Saving() ) + { + if ( waitingForThread ) + exists = 1; + else + exists = 0; + } + + arc.ArchiveInteger( &exists ); + + if ( exists ) + arc.ArchiveObjectPointer( ( Class ** )&waitingForThread ); + else if ( arc.Loading() ) + waitingForThread = NULL; + + arc.ArchiveString( &waitingForDeath ); + arc.ArchiveBoolean( &waitingForPlayer ); + + if ( arc.Saving() ) + { + if ( waitingFor ) + exists = 1; + else + exists = 0; + } + + arc.ArchiveInteger( &exists ); + + if ( exists ) + arc.ArchiveObjectPointer( ( Class ** )&waitingFor ); + else if ( arc.Loading() ) + waitingFor = NULL; + + arc.ArchiveInteger( &waitingNumObjects ); + + arc.ArchiveSafePointer( ¤tEntity ); +} + +void CThread::SetFloatVar( Event *ev ) +{ + str var_name; + const char *real_var_name; + float value; + + var_name = ev->GetString( 1 ); + value = ev->GetFloat( 2 ); + + if ( strncmp( var_name.c_str(), "level.", 6 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + levelVars.SetVariable( real_var_name, value ); + } + else if ( strncmp( var_name.c_str(), "game.", 5 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + gameVars.SetVariable( real_var_name, value ); + } +} + +void CThread::SetVectorVar( Event *ev ) +{ + str var_name; + const char *real_var_name; + Vector value; + + var_name = ev->GetString( 1 ); + value = ev->GetVector( 2 ); + + if ( strncmp( var_name.c_str(), "level.", 6 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + levelVars.SetVariable( real_var_name, value ); + } + else if ( strncmp( var_name.c_str(), "game.", 5 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + gameVars.SetVariable( real_var_name, value ); + } +} + +void CThread::SetStringVar( Event *ev ) +{ + str var_name; + const char *real_var_name; + str value; + + var_name = ev->GetString( 1 ); + value = ev->GetString( 2 ); + + if ( strncmp( var_name.c_str(), "level.", 6 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + levelVars.SetVariable( real_var_name, value ); + } + else if ( strncmp( var_name.c_str(), "game.", 5 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + gameVars.SetVariable( real_var_name, value ); + } +} + +void CThread::RemoveVariable( Event* ev ) +{ + str var_name; + const char* real_var_name; + + var_name = ev->GetString( 1 ); + + if ( strncmp( var_name.c_str(), "level.", 6 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + levelVars.RemoveVariable( real_var_name ); + } + else if ( strncmp( var_name.c_str(), "game.", 5 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + levelVars.RemoveVariable( real_var_name ); + } +} + +void CThread::GetFloatVar( Event *ev ) +{ + str var_name; + const char *real_var_name; + ScriptVariable *var = NULL; + + var_name = ev->GetString( 1 ); + + if ( strncmp( var_name.c_str(), "level.", 6 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + var = levelVars.GetVariable( real_var_name ); + } + else if ( strncmp( var_name.c_str(), "game.", 5 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + var = gameVars.GetVariable( real_var_name ); + } + + if ( var ) + ev->ReturnFloat( var->floatValue() ); + else + { + gi.WDPrintf( "%s variable not found\n", var_name.c_str() ); + ev->ReturnFloat( 0.0f ); + } +} + +void CThread::GetVectorVar( Event *ev ) +{ + str var_name; + const char *real_var_name; + ScriptVariable *var = NULL; + + var_name = ev->GetString( 1 ); + + if ( strncmp( var_name.c_str(), "level.", 6 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + var = levelVars.GetVariable( real_var_name ); + } + else if ( strncmp( var_name.c_str(), "game.", 5 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + var = gameVars.GetVariable( real_var_name ); + } + + if ( var ) + ev->ReturnVector( var->vectorValue() ); + else + { + gi.WDPrintf( "%s variable not found\n", var_name.c_str() ); + ev->ReturnVector( vec_zero ); + } +} + +void CThread::GetStringVar( Event *ev ) +{ + str var_name; + const char *real_var_name; + ScriptVariable *var = NULL; + + var_name = ev->GetString( 1 ); + + if ( strncmp( var_name.c_str(), "level.", 6 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + var = levelVars.GetVariable( real_var_name ); + } + else if ( strncmp( var_name.c_str(), "game.", 5 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + var = gameVars.GetVariable( real_var_name ); + } + + if ( var ) + ev->ReturnString( var->stringValue() ); + else + { + gi.WDPrintf( "%s variable not found\n", var_name.c_str() ); + ev->ReturnString( "\n" ); + } +} + +void CThread::doesVarExist( Event *ev ) +{ + str var_name; + const char *real_var_name; + ScriptVariable *var = NULL; + + var_name = ev->GetString( 1 ); + + if ( strncmp( var_name.c_str(), "level.", 6 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + var = levelVars.GetVariable( real_var_name ); + } + else if ( strncmp( var_name.c_str(), "game.", 5 ) == 0 ) + { + real_var_name = var_name.c_str() + 6; + var = gameVars.GetVariable( real_var_name ); + } + + if ( var ) + ev->ReturnFloat( 1.0f ); + else + ev->ReturnFloat( 0.0f ); +} + +void CThread::CenterPrint( Event *ev ) +{ + int j; + gentity_t *other; + + for( j = 0; j < game.maxclients; j++ ) + { + other = &g_entities[ j ]; + if ( other->inuse && other->client ) + { + gi.centerprintf( other, CENTERPRINT_IMPORTANCE_NORMAL, ev->GetString( 1 ) ); + } + } +} + +void CThread::isActorDead( Event *ev ) +{ + ev->ReturnInteger( isActorDead( ev->GetString( 1 ) ) ); +} + +qboolean CThread::isActorDead( const str &actor_name ) +{ + Actor *act; + + act = GetActor( actor_name ); + + if ( !act || act->deadflag || ( act->health <= 0 ) ) + return true; + + return false; +} + +void CThread::isActorAlive( Event *ev ) +{ + ev->ReturnFloat( isActorAlive( ev->GetString( 1 ) ) ); +} + +qboolean CThread::isActorAlive( const str &actor_name ) +{ + Actor *act; + + act = GetActor( actor_name ); + + if ( !act || act->deadflag || ( act->health <= 0 ) ) + return false; + + return true; +} + +void CThread::SendClientCommand( Event *ev ) +{ + Entity *entity; + Player *player; + str builtCommand; + int i; + + + // Get the player + + entity = ev->GetEntity( 1 ); + + if ( !entity->isSubclassOf( Player ) ) + return; + + player = (Player *)entity; + + // Build the command + + builtCommand += "stufftext"; + + builtCommand += " \""; + + for ( i = 2 ; i <= ev->NumArgs() ; i++ ) + { + builtCommand += " "; + + builtCommand += ev->GetString( i ); + } + + builtCommand += "\"\n"; + + gi.SendServerCommand( player->edict - g_entities, builtCommand.c_str() ); +} + +void CThread::GetNumFreeReliableServerCommands( Event* ev ) +{ + Entity* entity; + Player* player; + int i; + + entity = ev->GetEntity( 1 ); + if( !entity->isSubclassOf( Player ) ) + { + ev->ReturnFloat( 0.0 ); + return; + } + + player = (Player*) entity; + + i = gi.GetNumFreeReliableServerCommands( player->edict - g_entities ); + ev->ReturnFloat( i ); +} + +void CThread::SendClientVar( Event *ev ) +{ + Entity *entity; + Player *player; + str builtCommand; + str varName; + str realVarName; + ScriptVariable *var = NULL; + str levelVarPrefix = "level."; + + + // Get the player + + entity = ev->GetEntity( 1 ); + + if ( !entity->isSubclassOf( Player ) ) + return; + + player = (Player *)entity; + + // Build the command + + builtCommand += "stufftext"; + + builtCommand += " \"SetClientVar "; + + // Add the variable name + + varName = ev->GetString( 2 ); + + if ( strnicmp( varName.c_str(), levelVarPrefix.c_str(), levelVarPrefix.length() ) != 0 ) + { + gi.WDPrintf( "SendClientVar can only send level vars, %s not allowed\n", varName.c_str() ); + return; + } + + realVarName = varName.c_str() + levelVarPrefix.length(); + + builtCommand += realVarName; + + // Add the variable value + + builtCommand += " "; + + var = levelVars.GetVariable( realVarName ); + + if ( var ) + builtCommand += var->stringValue(); + else + return; + + builtCommand += "\"\n"; + + // Send the command + + gi.SendServerCommand( player->edict - g_entities, builtCommand.c_str() ); +} + +//=============================================================== +// Name: ModEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_Mod event. +// Gets the integer remainder of a division. +// +// Parameters: Event* -- Argument 1 is the nominator. +// Argument 2 is the denominator. +// Return cache is the integer remainder. +// +// Returns: None +// +//=============================================================== +void CThread::ModEvent( Event *ev ) +{ + assert( ev ); + if ( ev->NumArgs() != 2 ) + { + ev->Error("Usage: variable = %s( nominator, denominator)\n", ev->getName()); + return ; + } + + int nominator = ev->GetInteger( 1 ); + int denominator = ev->GetInteger( 2 ); + int remainder = nominator % denominator ; + ev->ReturnFloat( remainder ); +} + +//=============================================================== +// Name: DivEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_Div event. +// Gets the integer result of a division. +// +// Parameters: Event* -- Argument 1 is the nominator +// Argument 2 is the denominator +// Return cache is the integer result +// +// Returns: None +// +//=============================================================== +void CThread::DivEvent( Event *ev ) +{ + assert( ev ); + if ( ev->NumArgs() != 2 ) + { + ev->Error("Usage: variable = %s( nominator, denominator )\n", ev->getName()); + return ; + } + + int nominator = ev->GetInteger( 1 ); + int denominator = ev->GetInteger( 2 ); + int result = nominator / denominator ; + + ev->ReturnFloat( result ); +} + +//=============================================================== +// Name: VectorScaleEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_VectorScaleEvent. +// Scales a vector. +// +// Parameters: Event* -- Argument 1 is the vector to scale (vector) +// Argument 2 is the amount to scale (float) +// Return cache is the scaled vector. +// +// Returns: None +// +//=============================================================== +void CThread::VectorScaleEvent( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 2 ) + { + ev->Error("Usage: vectorvariable = %s( vector, scale_value )\n", ev->getName()); + return ; + } + + Vector v = ev->GetVector( 1 ); + float scaler = ev->GetFloat( 2 ); + Vector returnVector = v * scaler ; + + ev->ReturnVector( returnVector ); +} + +//=============================================================== +// Name: VectorDotEvent +// Class: CThread +// +// Description: Handles EV_ScriptThread_VectorDotEvent. +// Gets the dot product of two vectors. +// +// Parameters: Event* -- Argument 1 is the first vector. +// Argument 2 is the second vector. +// Return cache is the dot product value. +// +// Returns: None +// +//=============================================================== +void CThread::VectorDotEvent( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 2 ) + { + ev->Error("Usage: floatvariable = %s( vector, vector )\n", ev->getName()); + return ; + } + + Vector v1 = ev->GetVector( 1 ); + Vector v2 = ev->GetVector( 2 ); + float result = Vector::Dot( v1, v2 ); + + ev->ReturnFloat( result ); +} + +//=============================================================== +// Name: VectorCrossEvent +// Class: CThread +// +// Description: Handles EV_ScriptThread_VectorCrossEvent. +// Gets the cross product of two vectors. +// +// Parameters: Event* -- Argument 1 is the first vector. +// Argument 2 is the second vector. +// Return cache is the cross product value. +// +// Returns: None +// +//=============================================================== +void CThread::VectorCrossEvent( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 2 ) + { + ev->Error("Usage: vectorvariable = %s( vector, vector )\n", ev->getName()); + return ; + } + + Vector v1 = ev->GetVector( 1 ); + Vector v2 = ev->GetVector( 2 ); + Vector result ; + result.CrossProduct( v1, v2 ); + + ev->ReturnVector( result ); +} + +//=============================================================== +// Name: VectorLengthEvent +// Class: CThread +// +// Description: Handles EV_ScriptThread_VectorLengthEvent. +// Gets the length of a vector. +// +// Parameters: Event* -- Argument 1 is the vector to get the length of. +// Return cache is the vector length. +// +// Returns: None +// +//=============================================================== +void CThread::VectorLengthEvent( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "Usage: floatvariable = %s( vector )\n", ev->getName()); + return ; + } + + Vector v1 = ev->GetVector( 1 ); + ev->ReturnFloat( v1.length() ); +} + +//=============================================================== +// Name: VectorNormalizeEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_VectorNormalizeEvent. +// Normalizes a vector. +// +// Parameters: Event* -- Argument 1 is the vector to normalize. +// Return cache is the normalized vector. +// +// Returns: None +// +//=============================================================== +void CThread::VectorNormalizeEvent( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "Usage: vectorvariable = %s( vector )\n", ev->getName()); + return ; + } + + Vector v1 = ev->GetVector( 1 ); + v1.normalize(); + ev->ReturnVector( v1 ); +} + +//=============================================================== +// Name: VectorGetXEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_VectorGetXEvent. +// Gets the x value from a vector. +// +// Parameters: Event* -- Argument 1 is the vector to get x from. +// Return cache is the x value. +// +// Returns: None +// +//=============================================================== +void CThread::VectorGetXEvent( Event *ev ) +{ + assert( ev ); + + if (ev->NumArgs() != 1 ) + { + ev->Error( "Usage: floatvariable = %s( vector )\n", ev->getName()); + return ; + } + + Vector v1 = ev->GetVector( 1 ); + ev->ReturnFloat( v1.x ) ; +} + +//=============================================================== +// Name: VectorGetYEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_VectorGetXEvent. +// Gets the y value from a vector. +// +// Parameters: Event* -- Argument 1 is the vector to get y from. +// Return cache is the y value. +// +// Returns: None +// +//=============================================================== +void CThread::VectorGetYEvent( Event *ev ) +{ + assert( ev ); + + if (ev->NumArgs() != 1 ) + { + ev->Error( "Usage: floatvariable = %s( vector )\n", ev->getName()); + return ; + } + + Vector v1 = ev->GetVector( 1 ); + ev->ReturnFloat( v1.y ) ; +} + +//=============================================================== +// Name: VectorGetZEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_VectorGetXEvent. +// Gets the z value from a vector. +// +// Parameters: Event* -- Argument 1 is the vector to get z from. +// Return cache is the z value. +// +// Returns: None +// +//=============================================================== +void CThread::VectorGetZEvent( Event *ev ) +{ + assert( ev ); + + if (ev->NumArgs() != 1 ) + { + ev->Error( "Usage: floatvariable = %s( vector )\n", ev->getName()); + return ; + } + + Vector v1 = ev->GetVector( 1 ); + ev->ReturnFloat( v1.z ) ; +} + +//=============================================================== +// Name: VectorSetXEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_VectorSetXEvent. +// Sets the x value for a vector. +// +// Parameters: Event* -- Argument 1 is the vector to set the x of. +// Argument 2 is the x value for the vector +// Return cache is the resulting vector. +// +// +// Returns: None +// +//=============================================================== +void CThread::VectorSetXEvent( Event *ev ) +{ + assert( ev ); + + if (ev->NumArgs() != 2 ) + { + ev->Error( "Usage: vectorvariable = %s( vector, x_value )\n", ev->getName()); + return ; + } + + Vector v1 = ev->GetVector( 1 ); + v1.x = ev->GetFloat( 2 ); + ev->ReturnVector( v1 ); +} + +//=============================================================== +// Name: VectorSetYEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_VectorSetYEvent. +// Sets the y value for a vector. +// +// Parameters: Event* -- Argument 1 is the vector to set the y of. +// Argument 2 is the y value for the vector +// Return cache is the resulting vector. +// +// +// Returns: None +// +//=============================================================== +void CThread::VectorSetYEvent( Event *ev ) +{ + assert( ev ); + + if (ev->NumArgs() != 2 ) + { + ev->Error( "Usage: vectorvariable = %s( vector, y_value )\n", ev->getName()); + return ; + } + + Vector v1 = ev->GetVector( 1 ); + v1.y = ev->GetFloat( 2 ); + ev->ReturnVector( v1 ); +} + +//=============================================================== +// Name: VectorSetZEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_VectorSetZEvent. +// Sets the z value for a vector. +// +// Parameters: Event* -- Argument 1 is the vector to set the z of +// Argument 2 is the z value for the vector +// Return cache is the resulting vector. +// +// Returns: None +// +//=============================================================== +void CThread::VectorSetZEvent( Event *ev ) +{ + assert( ev ); + + if (ev->NumArgs() != 2 ) + { + ev->Error( "Usage: vectorvariable = %s( vector, z_value )\n", ev->getName()); + return ; + } + + Vector v1 = ev->GetVector( 1 ); + v1.z = ev->GetFloat( 2 ); + ev->ReturnVector( v1 ); +} + +//=============================================================== +// Name: VectorForwardEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_VectorForwardEvent. +// Gets the forward vector of a vector. +// +// Parameters: Event* -- Argument 1 is the vector to get the forward of. +// Return cache is the forward vector. +// +// Returns: None +// +//=============================================================== +void CThread::VectorForwardEvent( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "Usage: floatvariable = %s( vector )\n", ev->getName()); + return ; + } + + Vector forward ; + Vector v1 = ev->GetVector( 1 ); + v1.AngleVectors( &forward ); + ev->ReturnVector( forward ); +} + +//=============================================================== +// Name: VectorLeftEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_VectorLeftEvent. +// Gets the left vector of a vector. +// +// Parameters: Event* -- Argument 1 is the vector to get the left of. +// Return cache is the left vector. +// +// Returns: None +// +//=============================================================== +void CThread::VectorLeftEvent( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "Usage: floatvariable = %s( vector )\n", ev->getName()); + return ; + } + + Vector left ; + Vector v1 = ev->GetVector( 1 ); + v1.AngleVectors( 0, &left ); + ev->ReturnVector( left ); +} + +//=============================================================== +// Name: VectorUpEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_VectorUpEvent. +// Gets the up vector of a vector. +// +// Parameters: Event* -- Argument 1 is the vector to get the up of. +// Return cache is the up vector. +// +// Returns: None +// +//=============================================================== +void CThread::VectorUpEvent( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "Usage: floatvariable = %s( vector )\n", ev->getName()); + return ; + } + + Vector up ; + Vector v1 = ev->GetVector( 1 ); + v1.AngleVectors( 0, 0, &up ); + ev->ReturnVector( up ); +} + +//=============================================================== +// Name: vectorToString +// Class: CThread +// +// Description: Converts a vector to a string +// +// Parameters: Event *ev - contains a vector +// +// Returns: None +// +//=============================================================== + +void CThread::vectorToString( Event *ev ) +{ + str stringResult; + Vector vectorToConvert; + + vectorToConvert = ev->GetVector( 1 ); + + stringResult = "\""; + stringResult += vectorToConvert.x; + stringResult += " "; + stringResult += vectorToConvert.y; + stringResult += " "; + stringResult += vectorToConvert.z; + stringResult += "\""; + + ev->ReturnString( stringResult ); +} + +//=============================================================== +// Name: TargetOfEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_TargetOfEvent. +// Gets the target of an entity. +// +// Parameters: Event* -- Argument 1 is the entity whose target you want. +// Return cache is the name of the target, +// or the empty string if no target. +// +// Returns: None +// +//=============================================================== +void CThread::TargetOfEvent( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "Usage: stringvariable = %s( entity )\n", ev->getName()); + return ; + } + + Entity *entity = ev->GetEntity( 1 ); + if ( !entity ) + { + ev->Error( "First argument of %s is not an entity!\n", ev->getName()); + return ; + } + + const char *target = entity->Target(); + ev->ReturnString( target ? target : "" ); +} + + + +//=============================================================== +// Name: FloorEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_FloorEvent, +// Floors the specified floor. +// +// Parameters: Event* -- Argument 1 is the float you want the +// floor of. +// +// Returns: None +// +//=============================================================== +void CThread::FloorEvent( Event *ev ) +{ + assert( ev ); + + float value = ev->GetFloat( 1 ); + ev->ReturnFloat( floor( value ) ); +} + +//=============================================================== +// Name: CeilEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_CeilEvent, +// Ceils the specified float. +// +// Parameters: Event* -- Argument 1 is the float you want the +// ceil of. +// +// Returns: None +// +//=============================================================== +void CThread::CeilEvent( Event *ev ) +{ + assert( ev ); + float value = ev->GetFloat( 1 ); + ev->ReturnFloat( ceil( value ) ); +} + +//=============================================================== +// Name: RoundEvent +// Class: CThread +// +// Description: Handles the EV_ScriptThread_RoundEvent. +// Rounds the specified float. +// +// Parameters: Event* -- Argument 1 is the float you want to round. +// +// Returns: None +// +//=============================================================== +void CThread::RoundEvent( Event *ev ) +{ + assert( ev ); + + float value = ev->GetFloat( 1 ); + float remainder = value - floor(value) ; + ev->ReturnFloat( (remainder >= 0.5) ? ceil( value ) : floor( value ) ); +} + +//-------------------------------------------------------------- +// +// Name: SetGameplayFloat +// Class: CThread +// +// Description: Sets properties on the gameplay database. +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void CThread::SetGameplayFloat( Event *ev ) +{ + const char *objname = 0; + const char *propname = 0; + float value = 1.0f; + + // Check for not enough args + if ( ev->NumArgs() < 3 ) + return; + + objname = ev->GetString( 1 ); + propname = ev->GetString( 2 ); + value = ev->GetFloat( 3 ); + + GameplayManager::getTheGameplayManager()->setFloatValue(objname, propname, value); +} + +//-------------------------------------------------------------- +// +// Name: GetGameplayString +// Class: CThread +// +// Description: Gets a gameplay database property string value. +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void CThread::GetGameplayString( Event *ev ) +{ + const char *objname = 0; + const char *propname = 0; + + // Check for not enough args + if ( ev->NumArgs() < 2 ) + { + ev->Error( "getgameplaystring: Wrong number of arguments. getgameplaystring objname propertyname" ); + return; + } + + objname = ev->GetString( 1 ); + propname = ev->GetString( 2 ); + + str stringvalue = GameplayManager::getTheGameplayManager()->getStringValue( objname, propname ); + ev->ReturnString( stringvalue.c_str() ); +} + +//-------------------------------------------------------------- +// +// Name: GetGameplayFloat +// Class: CThread +// +// Description: Gets a gameplay database property float value. +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void CThread::GetGameplayFloat( Event *ev ) +{ + const char *objname = 0; + const char *propname = 0; + + // Check for not enough args + if ( ev->NumArgs() < 2 ) + { + ev->Error( "getgameplayfloat: Wrong number of arguments. getgameplayfloat objname propertyname" ); + return; + } + + objname = ev->GetString( 1 ); + propname = ev->GetString( 2 ); + + float floatvalue = GameplayManager::getTheGameplayManager()->getFloatValue( objname, propname ); + ev->ReturnFloat( floatvalue ); +} + +//-------------------------------------------------------------- +// +// Name: SetGameplayString +// Class: CThread +// +// Description: Sets properties on the gameplay database. +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void CThread::SetGameplayString( Event *ev ) +{ + const char *objname = 0; + const char *propname = 0; + const char *valuestr = 0; + + // Check for not enough args + if ( ev->NumArgs() < 3 ) + return; + + objname = ev->GetString( 1 ); + propname = ev->GetString( 2 ); + valuestr = ev->GetString( 3 ); + + GameplayManager::getTheGameplayManager()->setStringValue(objname, propname, valuestr); +} + +//=============================================================== +// Name: GetIntegerFromString +// Class: CThread +// +// Description: Retreives the first integer value found in the +// event's first argument (which must be a string). +// +// The value is returned in the event's return field. +// +// Parameters: Event -- the event that triggered this call +// +// Returns: None +// +//=============================================================== +void CThread::GetIntegerFromString( Event *ev ) +{ + assert( ev ); + + const char *valueText = 0 ; + char *text = 0 ; + char buffer[256] ; + + strcpy( buffer, ev->GetString( 1 ) ); + text = buffer ; + + while ( text && *text ) + { + if ( ( *text < '0' ) || ( *text > '9') ) + { + text++ ; + continue ; + } + valueText = text ; + while ( text && *text ) + { + if ( ( *text < '0') || ( *text > '9' ) ) + { + *text = 0 ; + break ; + } + text++ ; + } + break ; + } + + int value = atoi( valueText ); + ev->ReturnInteger( value ); +} + +//=============================================================== +// Name: CreateCinematic +// Class: CThread +// +// Description: Creates a new cinematic with the specified name. +// Loads the corresponding cinematic from disk. Once +// loaded, events can be called on the cinematic directly +// to set it up and play it. +// +// Parameters: Event* -- EV_ScriptThread_CreateCinematic. +// - First argument must be string name without .cin extension. +// +// Returns: None +// +//=============================================================== +void CThread::CreateCinematic( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "script usage: entity cin = createCinematic GetString( 1 ); + Cinematic *cinematic = theCinematicArmature.createCinematic( cinematicName ); + ev->ReturnEntity( cinematic ); +} + +//=============================================================== +// Name: GetLevelTime +// Class: CThread +// +// Description: Returns the level.time to the script. +// Parameters: Event* -- EV_ScriptThread_GetTime +// Returns: none +//=============================================================== +void CThread::GetLevelTime( Event* ev ) +{ + assert( ev ); + ev->ReturnFloat( level.time ); +} + +void CThread::disconnectPathnodes( Event *ev ) +{ + thePathManager.disconnectNodes( ev->GetString( 1 ), ev->GetString( 2 ) ); +} + +void CThread::connectPathnodes( Event *ev ) +{ + thePathManager.connectNodes( ev->GetString( 1 ), ev->GetString( 2 ) ); +} diff --git a/dlls/game/globalcmd.h b/dlls/game/globalcmd.h new file mode 100644 index 0000000..8f18e8e --- /dev/null +++ b/dlls/game/globalcmd.h @@ -0,0 +1,289 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/globalcmd.h $ +// $Revision:: 42 $ +// $Date:: 4/14/03 4:33p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Global commands for scripts +// + +#ifndef __GLOBALCMD_H__ +#define __GLOBALCMD_H__ + +#include "interpreter.h" +#include "g_local.h" + +extern Event EV_ProcessCommands; +extern Event EV_ScriptThread_Execute; +extern Event EV_ScriptThread_Callback; +extern Event EV_ScriptThread_ThreadCallback; +extern Event EV_ScriptThread_DeathCallback; +extern Event EV_ScriptThread_CreateThread; +extern Event EV_ScriptThread_TerminateThread; +extern Event EV_ScriptThread_ControlObject; +extern Event EV_ScriptThread_Goto; +extern Event EV_ScriptThread_Pause; +extern Event EV_ScriptThread_Wait; +extern Event EV_ScriptThread_WaitFor; +extern Event EV_ScriptThread_WaitForThread; +extern Event EV_ScriptThread_WaitForSound; +extern Event EV_ScriptThread_Print; +extern Event EV_ScriptThread_PrintInt; +extern Event EV_ScriptThread_PrintFloat; +extern Event EV_ScriptThread_PrintVector; +extern Event EV_ScriptThread_NewLine; +extern Event EV_ScriptThread_Clear; +extern Event EV_ScriptThread_Assert; +extern Event EV_ScriptThread_Break; +extern Event EV_ScriptThread_Clear; +extern Event EV_ScriptThread_Trigger; +extern Event EV_ScriptThread_Spawn; +extern Event EV_ScriptThread_Map; +extern Event EV_ScriptThread_SetCvar; +extern Event EV_ScriptThread_CueCamera; +extern Event EV_ScriptThread_CuePlayer; +extern Event EV_ScriptThread_FreezePlayer; +extern Event EV_ScriptThread_ReleasePlayer; +extern Event EV_ScriptThread_SetCinematic; +extern Event EV_ScriptThread_SetNonCinematic; +extern Event EV_ScriptThread_SetSkipThread; +// Add from old ScriptVariable events +extern Event EV_ScriptThread_TargetOf; +extern Event EV_ScriptThread_GetCVar; +extern Event EV_ScriptThread_Mod; +extern Event EV_ScriptThread_Div; +extern Event EV_ScriptThread_Round; +extern Event EV_ScriptThread_VectorScale; +extern Event EV_ScriptThread_VectorNormalize; +extern Event EV_ScriptThread_VectorDot; +extern Event EV_ScriptThread_VectorCross; +extern Event EV_ScriptThread_VectorLength; +extern Event EV_ScriptThread_VectorX; +extern Event EV_ScriptThread_VectorY; +extern Event EV_ScriptThread_VectorZ; +extern Event EV_ScriptThread_VectorSetX; +extern Event EV_ScriptThread_VectorSetY; +extern Event EV_ScriptThread_VectorSetZ; +extern Event EV_ScriptThread_AnglesToForward; +extern Event EV_ScriptThread_AnglesToLeft; +extern Event EV_ScriptThread_AnglesToUp; +extern Event EV_ScriptThread_FloorEvent ; +extern Event EV_ScriptThread_CeilEvent ; +extern Event EV_ScriptThread_RoundEvent ; + +class CThread : public Interpreter +{ + private: + float waitUntil; + CThread *waitingForThread; + str waitingForDeath; + qboolean waitingForPlayer; + TargetList *waitingFor; + int waitingNumObjects; + EntityPtr currentEntity; + + public: + CLASS_PROTOTYPE( CThread ); + + CThread(); + + void ExecuteFunc( Event *ev ); + void ClearWaitFor( void ); + qboolean WaitingFor( Entity *obj ); + void ObjectMoveDone( Event *ev ); + void CreateThread( Event *ev ); + void TerminateThread( Event *ev ); + void EventPause( Event *ev ); + void EventWait( Event *ev ); + void EventWaitFor( Event *ev ); + void EventWaitForThread( Event *ev ); + void EventWaitForDeath( Event *ev ); + void EventWaitForSound( Event *ev ); + void EventWaitForDialog( Event *ev ); + void EventWaitDialogLength( Event *ev ); + void EventWaitForAnimation( Event *ev ); + void EventWaitForPlayer( Event *ev ); + void CPrint( Event *ev ); + void Print( Event *ev ); + void PrintInt( Event *ev ); + void PrintFloat( Event *ev ); + void PrintVector( Event *ev ); + void NewLine( Event *ev ); + void Assert( Event *ev ); + void Break( Event *ev ); + void ScriptCallback( Event *ev ); + void ThreadCallback( Event *ev ); + void DeathCallback( Event *ev ); + void DoMove( void ); + void TriggerEvent( Event *ev ); + void TriggerEntityEvent( Event *ev ); + void CacheResourceEvent( Event *ev ); + void RegisterAlias( Event *ev ); + void RegisterAliasAndCache( Event *ev ); + void MapEvent( Event *ev ); + void noIntermission( Event *ev ); + void dontSaveOrientation( Event *ev ); + void setPlayerDeathThread( Event *ev ); + void endPlayerDeathThread( Event *ev ); + void GetCvarEvent( Event *ev ); + void GetCvarFloatEvent( Event *ev ); + void GetCvarIntEvent( Event *ev ); + void SetCvarEvent( Event *ev ); + void CueCamera( Event *ev ); + void CuePlayer( Event *ev ); + void FreezePlayer( Event *ev ); + void ReleasePlayer( Event *ev ); + void FakePlayer( Event *ev ); + void RemoveFakePlayer( Event *ev ); + void Spawn( Event *ev ); + void Letterbox( Event *ev ); + void ClearLetterbox( Event *ev ); + void FadeIn( Event *ev ); + void ClearFade( Event *ev ); + void FadeOut( Event *ev ); + void FadeIsActive( Event *ev ); + void MusicEvent( Event *ev ); + void MusicVolumeEvent( Event *ev ); + void RestoreMusicVolumeEvent( Event *ev ); + void allowMusicDucking( Event *ev ); + void allowActionMusic( Event *ev ); + void ForceMusicEvent( Event *ev ); + void SoundtrackEvent( Event *ev ); + void RestoreSoundtrackEvent( Event *ev ); + void SetCinematic( Event *ev ); + void SetNonCinematic( Event *ev ); + void SetLevelAI( Event *ev ); + void SetSkipThread( Event *ev ); + void PassToPathmanager( Event *ev ); + void StuffCommand( Event *ev ); + void KillEnt( Event *ev ); + void RemoveEnt( Event *ev ); + void KillClass( Event *ev ); + void RemoveClass( Event *ev ); + void RemoveActorsNamed( Event* ev ); + void doesEntityExist( Event *ev ); + void GetEntityEvent( Event *ev ); + void GetNextEntityEvent( Event *ev ); + void CosDegrees( Event *ev ); + void SinDegrees( Event *ev ); + void CosRadians( Event *ev ); + void SinRadians( Event *ev ); + void ArcTanDegrees( Event *ev ); + void ScriptSqrt( Event *ev ); + void ScriptLog( Event *ev ); + void ScriptExp( Event *ev ); + void RandomFloat( Event *ev ); + void RandomInteger( Event *ev ); + void CameraCommand( Event *ev ); + void SetLightStyle( Event *ev ); + void KillThreadEvent( Event *ev ); + void SetThreadName( Event *ev ); + void GetCurrentEntity( Event *ev ); + void SetCurrentEntity( Entity *ent ); + + CThread *WaitingOnThread( void ); + const char *WaitingOnDeath( void ); + qboolean WaitingOnPlayer( void ); + + void DelayedStart( float delay ); + void Start( void ); + + void SendCommandToSlaves( const char *name, Event *ev ); + void Parse( const char *filename ); + void ProcessCommand( int argc, const char **argv ); + + void Archive( Archiver &arc ); + + void SetFloatVar( Event *ev ); + void SetVectorVar( Event *ev ); + void SetStringVar( Event *ev ); + + void doesVarExist( Event *ev ); + void RemoveVariable( Event* ev ); + + void GetFloatVar( Event *ev ); + void GetVectorVar( Event *ev ); + void GetStringVar( Event *ev ); + + void CenterPrint( Event *ev ); + void isActorDead( Event *ev ); + qboolean isActorDead( const str &actor_name ); + + void isActorAlive( Event *ev ); + qboolean isActorAlive( const str &actor_name ); + + void SendClientCommand( Event *ev ); + void GetNumFreeReliableServerCommands( Event* ev ); + void SendClientVar( Event *ev ); + void ModEvent( Event *ev ); + void DivEvent( Event *ev ); + void VectorScaleEvent( Event *ev ); + void VectorDotEvent( Event *ev ); + void VectorCrossEvent( Event *ev ); + void VectorNormalizeEvent( Event *ev ); + void VectorLengthEvent( Event *ev ); + void VectorSetXEvent( Event *ev ); + void VectorSetYEvent( Event *ev ); + void VectorSetZEvent( Event *ev ); + void VectorGetXEvent( Event *ev ); + void VectorGetYEvent( Event *ev ); + void VectorGetZEvent( Event *ev ); + void VectorForwardEvent( Event *ev ); + void VectorLeftEvent( Event *ev ); + void VectorUpEvent( Event *ev ); + void vectorToString( Event *ev ); + void TargetOfEvent( Event *ev ); + void FloorEvent( Event *ev ); + void CeilEvent( Event *ev ); + void RoundEvent( Event *ev ); + void GetGameplayFloat( Event *ev ); + void GetGameplayString( Event *ev ); + void SetGameplayFloat( Event *ev ); + void SetGameplayString( Event *ev ); + void GetIntegerFromString( Event *ev ); + + void CreateCinematic( Event *ev ); + void GetLevelTime( Event* ev ); + + void connectPathnodes( Event *ev ); + void disconnectPathnodes( Event *ev ); + }; + +typedef SafePtr ThreadPtr; + +inline void CThread::DelayedStart( float delay ) +{ + CancelEventsOfType( EV_ScriptThread_Execute ); + PostEvent( EV_ScriptThread_Execute, delay ); +} + +inline void CThread::Start( void ) +{ + CancelEventsOfType( EV_ScriptThread_Execute ); + ProcessEvent( EV_ScriptThread_Execute ); +} + +inline CThread *CThread::WaitingOnThread( void ) +{ + return waitingForThread; +} + +inline const char *CThread::WaitingOnDeath( void ) +{ + return waitingForDeath.c_str(); +} + +inline qboolean CThread::WaitingOnPlayer( void ) +{ + return waitingForPlayer; +} + +#endif diff --git a/dlls/game/goo.cpp b/dlls/game/goo.cpp new file mode 100644 index 0000000..d33babd --- /dev/null +++ b/dlls/game/goo.cpp @@ -0,0 +1,235 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/goo.cpp $ +// $Revision:: 7 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Goo Gun Projectile + +#include "_pch_cpp.h" +#include "goo.h" + +Event EV_GooDebris_Prethink +( + "_prethink", + EV_CODEONLY, + NULL, + NULL, + "Think function for the debris" +); + +CLASS_DECLARATION( Projectile, GooDebris, NULL ) +{ + { &EV_GooDebris_Prethink, &GooDebris::Prethink }, + { &EV_Touch, &GooDebris::Touch }, + + { NULL, NULL } +}; + +GooDebris::GooDebris() +{ + nexttouch = 0; +} + +void GooDebris::Prethink( Event *ev ) +{ + if ( ( velocity.length() < 30.0f ) && ( CurrentAnim() == gi.Anim_NumForName( edict->s.modelindex, "idle" ) ) ) + { + velocity = Vector( 0.0f, 0.0f, 0.0f ); + animate->RandomAnimate( "splat" ); + } + + if ( ( level.time - this->edict->spawntime ) > 5.0f ) + { + edict->s.scale *= 0.95f; + } + + if ( edict->s.scale < 0.1f ) + { + PostEvent( EV_Remove, 0.0f ); + } + else + { + Event *ev1 = new Event( ev ); + PostEvent( ev1, level.frametime ); + } +} + +void GooDebris::Touch( Event *ev ) +{ + Entity *other; + Entity *owner; + Vector ang; + + other = ev->GetEntity( 1 ); + + if ( other == world ) + { + vectoangles( level.impact_trace.plane.normal, ang ); + setAngles( ang ); + } + + if ( level.time < nexttouch ) + { + return; + } + + nexttouch = level.time + 0.5f; + + if ( !other || !other->isSubclassOf( Sentient ) ) + { + return; + } + + owner = G_GetEntity( this->owner ); + + if ( !owner ) + { + owner = world; + } + + if ( !other ) + { + return; + } + + other->Damage( this, owner, damage, origin, Vector( 0.0f, 0.0f, 0.0f ), Vector( 0.0f, 0.0f, 0.0f ), 0, 0, meansofdeath ); +} + +Event EV_Goo_DebrisModel +( + "goodebrismodel", + EV_TIKIONLY, + "s", + "modelname", + "Model name for the debris that is spawned on explosion" +); +Event EV_Goo_DebrisCount +( + "goodebriscount", + EV_TIKIONLY, + "i", + "count", + "Number of pieces of debris to spawn" +); + +CLASS_DECLARATION( Projectile, GooProjectile, NULL ) +{ + { &EV_Goo_DebrisModel, &GooProjectile::SetDebrisModel }, + { &EV_Goo_DebrisCount, &GooProjectile::SetDebrisCount }, + + { NULL, NULL } +}; + +GooProjectile::GooProjectile() +{ + m_debrismodel = "fx_goo_debris.tik"; + m_debriscount = 3; +} + +void GooProjectile::SetDebrisModel( Event *ev ) +{ + m_debrismodel = ev->GetString( 1 ); +} + +void GooProjectile::SetDebrisCount( Event *ev ) +{ + m_debriscount = ev->GetInteger( 1 ); +} + +void GooProjectile::Explode( Event *ev ) +{ + int i; + Entity *owner; + Entity *ignoreEnt=NULL; + + if ( ev->NumArgs() == 1 ) + ignoreEnt = ev->GetEntity( 1 ); + + // Get the owner of this projectile + owner = G_GetEntity( this->owner ); + + // If the owner's not here, make the world the owner + if ( !owner ) + owner = world; + + takedamage = DAMAGE_NO; + + // Spawn an explosion model + if ( explosionmodel.length() ) + { + // Move the projectile back off the surface a bit so we can see + // explosion effects. + Vector dir, v; + v = velocity; + v.normalize(); + dir = v; + v = origin - v * 36.0f; + setOrigin( v ); + + ExplosionAttack( v, owner, explosionmodel, dir, ignoreEnt ); + } + + CancelEventsOfType( EV_Projectile_UpdateBeam ); + + // Kill the beam + if ( m_beam ) + { + m_beam->ProcessEvent( EV_Remove ); + m_beam = NULL; + } + + // When the goo hits something, spawn debris + for ( i=0; iorigin, dir, this, m_debrismodel, 1.0f ); + + if ( !proj ) + { + warning( "GooProjectile::Explode", "Could not create debris projectile" ); + return; + } + + proj->owner = ENTITYNUM_WORLD; + proj->setSolidType( SOLID_TRIGGER ); + proj->avelocity = Vector( G_CRandom( 360.0f ), G_CRandom( 360.0f ), G_CRandom( 360.0f ) ); + proj->setSize( Vector( -16.0f, -16.0f, 0.0f ) * proj->edict->s.scale, Vector( 16.0f ,16.0f ,32.0f ) * proj->edict->s.scale ); + proj->setMoveType( MOVETYPE_TOSS ); + proj->PostEvent( EV_GooDebris_Prethink, level.frametime ); + } + + // Change to the splat + if ( level.impact_trace.ent && ( level.impact_trace.ent->solid == SOLID_BSP ) ) + { + GooDebris *p; + Vector ang; + p = new GooDebris; + vectoangles( level.impact_trace.plane.normal, ang ); + p->setAngles( ang ); + p->setModel( this->model ); + p->ProcessInitCommands( p->edict->s.modelindex ); + p->setSize( Vector( -16.0f, -16.0f, 0.0f ) * p->edict->s.scale, Vector( 16.0f, 16.0f, 32.0f ) * p->edict->s.scale ); + p->setOrigin( this->origin ); + p->velocity = Vector( 0.0f, 0.0f, 0.0f ); + p->setMoveType( MOVETYPE_FLY ); + p->setSolidType( SOLID_TRIGGER ); + p->animate->RandomAnimate( "splat" ); + p->owner = this->owner; + p->PostEvent( EV_GooDebris_Prethink, level.frametime ); // shrink out + } +} diff --git a/dlls/game/goo.h b/dlls/game/goo.h new file mode 100644 index 0000000..0e1c98a --- /dev/null +++ b/dlls/game/goo.h @@ -0,0 +1,76 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/goo.h $ +// $Revision:: 3 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Goo Gun Projectile + +#ifndef __GOO_H__ +#define __GOO_H__ + +#include "weapon.h" +#include "weaputils.h" + +class GooProjectile : public Projectile + { + private: + str m_debrismodel; + int m_debriscount; + + public: + CLASS_PROTOTYPE( GooProjectile ); + + GooProjectile(); + void Explode( Event *ev ); + void SetDebrisModel( Event *ev ); + void SetDebrisCount( Event *ev ); + void Archive( Archiver &arc ); + }; + +void GooProjectile::Archive + ( + Archiver &arc + ) + + { + Projectile::Archive( arc ); + arc.ArchiveString( &m_debrismodel ); + arc.ArchiveInteger( &m_debriscount ); + } + + +class GooDebris : public Projectile + { + private: + float nexttouch; + + public: + CLASS_PROTOTYPE( GooDebris ); + + GooDebris(); + void Touch( Event *ev ); + void Prethink( Event *ev ); + void Archive( Archiver &arc ); + }; + +void GooDebris::Archive + ( + Archiver &arc + ) + + { + Projectile::Archive( arc ); + arc.ArchiveFloat( &nexttouch ); + } + +#endif // __GOO_H__ diff --git a/dlls/game/gotoCurrentHelperNode.cpp b/dlls/game/gotoCurrentHelperNode.cpp new file mode 100644 index 0000000..68461d7 --- /dev/null +++ b/dlls/game/gotoCurrentHelperNode.cpp @@ -0,0 +1,475 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/gotoCurrentHelperNode.cpp $ +// $Revision:: 5 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CoverCombatWithRangedWeapon Implementation +// +// PARAMETERS: +// str _movementAnim -- The animation to play while moving to the node +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "gotoCurrentHelperNode.hpp" +#include + +//-------------------------------------------------------------- +// +// Init Static Vars +// +//-------------------------------------------------------------- +const float GotoCurrentHelperNode::NODE_RADIUS = 32.0f; + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, GotoCurrentHelperNode, NULL ) + { + { &EV_Behavior_Args, &GotoCurrentHelperNode::SetArgs }, + { &EV_Behavior_AnimDone, &GotoCurrentHelperNode::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: GotoCurrentHelperNode() +// Class: GotoCurrentHelperNode +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GotoCurrentHelperNode::GotoCurrentHelperNode() +{ + _movementAnim = "run"; + _faceEnemy = false; +} + +//-------------------------------------------------------------- +// Name: GotoCurrentHelperNode() +// Class: GotoCurrentHelperNode +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GotoCurrentHelperNode::~GotoCurrentHelperNode() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: GotoCurrentHelperNode +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::SetArgs( Event *ev ) +{ + _movementAnim = ev->GetString( 1 ); + if ( ev->NumArgs() > 1 ) + _faceEnemy = ev->GetBoolean( 2 ); + +} + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: GotoCurrentHelperNode +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::AnimDone( Event *ev ) +{ +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: GotoCurrentHelperNode +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::Begin( Actor &self ) +{ + init( self ); + transitionToState ( GOTO_HNODE_FIND_NODE ); +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: GotoCurrentHelperNode +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoCurrentHelperNode::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case GOTO_HNODE_FIND_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateFindNode(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GOTO_HNODE_MOVE_TO_NODE ); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GOTO_HNODE_FAILED ); + break; + + //--------------------------------------------------------------------- + case GOTO_HNODE_MOVE_TO_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateMoveToNode(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GOTO_HNODE_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GOTO_HNODE_SUCCESS ); + break; + + + //--------------------------------------------------------------------- + case GOTO_HNODE_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + + break; + + + //--------------------------------------------------------------------- + case GOTO_HNODE_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + + break; + + + } + + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: GotoCurrentHelperNode +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::End(Actor &self) +{ + if ( _faceEnemy ) + self.movementSubsystem->setFaceEnemy( false ); +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: GotoCurrentHelperNode +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::transitionToState( GotoHelperNodeStates_t state ) +{ + switch ( state ) + { + case GOTO_HNODE_FIND_NODE: + setupStateFindNode(); + setInternalState( state , "GOTO_HNODE_FIND_NODE" ); + break; + + case GOTO_HNODE_MOVE_TO_NODE: + setupStateMoveToNode(); + setInternalState( state , "GOTO_HNODE_MOVE_TO_NODE" ); + break; + + case GOTO_HNODE_SUCCESS: + setInternalState( state , "GOTO_HNODE_SUCCESS" ); + break; + + case GOTO_HNODE_FAILED: + setInternalState( state , "GOTO_HNODE_FAILED" ); + break; + } + +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: GotoCurrentHelperNode +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::setInternalState( GotoHelperNodeStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: GotoCurrentHelperNode +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::init( Actor &self ) +{ + str objname; + str movementMode; + str stateName; + GameplayManager *gpm; + + self.movementSubsystem->setFaceEnemy( _faceEnemy ); + // + // Animation Selection + // 1st We check if we have an explicit animation in the GPD for our state, if we + // do, we're going to use that. + // + // Next, we'll check for StateVar, and pull the animation from that... + // If we have nothing there, then we'll just assume the animation passed in + // is the animation we are going to try and use + + // + //First check if we're even in the GPM + gpm = GameplayManager::getTheGameplayManager(); + if ( gpm->hasObject(self.getArchetype()) ) + { + //Now we're going to check and see if we have state data + //in the GPM for our current state that has our movement + //mode in it. + objname = self.getArchetype(); + stateName = self.currentState->getName(); + objname = objname + "." + stateName; + + //If we have a MovementMode property, use its value, else use + //the default value of RUN + if ( gpm->hasProperty( objname , "MovementMode" ) ) + movementMode = gpm->getStringValue( objname , "MovementMode" ); + else + movementMode = "RUN"; + + + //Now, depending on what our movementMode is, we set a proper + //movement animation + objname = self.getArchetype(); + objname = objname + "." + self.GetAnimSet(); + + if ( movementMode == "RUN" ) + { + if ( gpm->hasProperty(objname, "Run") ) + _movementAnim = gpm->getStringValue( objname , "Run" ); + + return; + } + + if ( movementMode == "WALK" ) + { + if ( gpm->hasProperty(objname, "Walk") ) + _movementAnim = gpm->getStringValue( objname , "Walk" ); + return; + } + + } + + // We didn't find anything in the GPD, so let's check for + // state vars + str movementAnim = self.GetStateVar( _movementAnim ); + + if ( movementAnim.length() ) + _movementAnim = movementAnim; + +} + +//-------------------------------------------------------------- +// Name: think() +// Class: GotoCurrentHelperNode +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::think() +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateMoveToNode() +// Class: GotoCurrentHelperNode +// +// Description: Sets up the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::setupStateMoveToNode() +{ + _gotoPoint.SetAnim( _movementAnim ); + _gotoPoint.SetDistance( NODE_RADIUS ); + _gotoPoint.SetPoint( _node->origin ); + _gotoPoint.Begin( *GetSelf() ); +} + + +//-------------------------------------------------------------- +// Name: evaluateStateMoveToNode() +// Class: GotoCurrentHelperNode +// +// Description: Evaluates the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoCurrentHelperNode::evaluateStateMoveToNode() +{ + BehaviorReturnCode_t result = _gotoPoint.Evaluate( *GetSelf() ); + str failureReason = "GotoCurrentHelperNode::evaluateStateMoveToNode -- _gotoPoint component returned: " + _gotoPoint.GetFailureReason(); + + if ( result == BEHAVIOR_FAILED ) + failureStateMoveToNode( failureReason ); + + return result; +} + + +//-------------------------------------------------------------- +// Name: evaluateStateFindNode() +// Class: GotoCurrentHelperNode +// +// Description: Evaluates the FindNode State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- + +BehaviorReturnCode_t GotoCurrentHelperNode::evaluateStateFindNode() +{ + BehaviorReturnCode_t result = BEHAVIOR_SUCCESS; + str failureReason = "GotoCurrentHelperNode::evaluateStateFindNode -- could not find an appropriate node"; + + if ( !_node ) + { + failureStateFindNode( failureReason ); + result = BEHAVIOR_FAILED; + } + + + return result; +} + +//-------------------------------------------------------------- +// Name: setupStateFindNode() +// Class: GotoCurrentHelperNode +// +// Description: Sets up the Find State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::setupStateFindNode() +{ + _node = GetSelf()->currentHelperNode.node; + +} + + +//-------------------------------------------------------------- +// Name: failureStateMoveToNode +// Class: GotoCurrentHelperNode +// +// Description: Failure Handler for State Move To Cover +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::failureStateMoveToNode( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: failureStateFindNode +// Class: GotoCurrentHelperNode +// +// Description: Failure Handler for State Move To Cover +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GotoCurrentHelperNode::failureStateFindNode( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + diff --git a/dlls/game/gotoCurrentHelperNode.hpp b/dlls/game/gotoCurrentHelperNode.hpp new file mode 100644 index 0000000..cfd362d --- /dev/null +++ b/dlls/game/gotoCurrentHelperNode.hpp @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/gotoCurrentHelperNode.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// gotoCurrentHelperNode Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class GotoCurrentHelperNode; + +#ifndef __GOTO_CURRENT_HELPER_NODE__ +#define __GOTO_CURRENT_HELPER_NODE__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: GotoHelperNodeEX +// Base Class: Behavior +// +// Description: Moves the Actor to the currentHelper node of the +// Actor +// +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class GotoCurrentHelperNode : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + GOTO_HNODE_FIND_NODE, + GOTO_HNODE_MOVE_TO_NODE, + GOTO_HNODE_SUCCESS, + GOTO_HNODE_FAILED + } GotoHelperNodeStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _movementAnim; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( GotoHelperNodeStates_t state ); + void setInternalState ( GotoHelperNodeStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + + + void setupStateMoveToNode (); + BehaviorReturnCode_t evaluateStateMoveToNode (); + void failureStateFindNode ( const str& failureReason ); + + void setupStateFindNode (); + BehaviorReturnCode_t evaluateStateFindNode (); + void failureStateMoveToNode ( const str& failureReason ); + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( GotoCurrentHelperNode ); + + GotoCurrentHelperNode(); + ~GotoCurrentHelperNode(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + // Accessors + void SetMovementAnim ( const str &anim ); + + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoPoint _gotoPoint; + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + GotoHelperNodeStates_t _state; + HelperNodePtr _node; + bool _faceEnemy; + static const float NODE_RADIUS; + + }; + +inline void GotoCurrentHelperNode::SetMovementAnim ( const str &anim ) +{ + _movementAnim = anim; +} + + +inline void GotoCurrentHelperNode::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveString ( &_movementAnim ); + // + // Archive Components + // + arc.ArchiveObject ( &_gotoPoint ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, GotoHelperNodeStates_t ); + arc.ArchiveSafePointer ( &_node ); + arc.ArchiveBool ( &_faceEnemy ); +} + +#endif /* __GOTO_CURRENT_HELPER_NODE__ */ + diff --git a/dlls/game/gotoHelperNode.cpp b/dlls/game/gotoHelperNode.cpp new file mode 100644 index 0000000..e004345 --- /dev/null +++ b/dlls/game/gotoHelperNode.cpp @@ -0,0 +1,418 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/gotoHelperNode.cpp $ +// $Revision:: 6 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CoverCombatWithRangedWeapon Implementation +// +// PARAMETERS: +// str _nodeType -- The name of the node to move to +// str _movementAnim -- The animation to play while moving to the node +// float _maxDistance -- The maximum distance to look for a node +// +// ANIMATIONS: +// _movementAnim : PARAMETER +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "gotoHelperNode.hpp" + +//-------------------------------------------------------------- +// +// Init Static Vars +// +//-------------------------------------------------------------- +const float GotoHelperNode::NODE_RADIUS = 32.0f; + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, GotoHelperNode, NULL ) + { + { &EV_Behavior_Args, &GotoHelperNode::SetArgs }, + { &EV_Behavior_AnimDone, &GotoHelperNode::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: GotoHelperNode() +// Class: GotoHelperNode +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GotoHelperNode::GotoHelperNode() +{ + _movementAnim = ""; + _nodeType = "" ; + _maxDistance = 256; +} + +//-------------------------------------------------------------- +// Name: GotoHelperNode() +// Class: GotoHelperNode +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GotoHelperNode::~GotoHelperNode() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: GotoHelperNode +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::SetArgs( Event *ev ) +{ + _nodeType = ev->GetString( 1 ); + _movementAnim = ev->GetString( 2 ); + _maxDistance = ev->GetFloat ( 3 ); +} + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: GotoHelperNode +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::AnimDone( Event *ev ) +{ +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: GotoHelperNode +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::Begin( Actor &self ) + { + init( self ); + transitionToState ( GOTO_HNODE_FIND_NODE ); + } + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: GotoHelperNode +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoHelperNode::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case GOTO_HNODE_FIND_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateFindNode(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GOTO_HNODE_MOVE_TO_NODE ); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GOTO_HNODE_FAILED ); + break; + + //--------------------------------------------------------------------- + case GOTO_HNODE_MOVE_TO_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateMoveToNode(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GOTO_HNODE_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GOTO_HNODE_SUCCESS ); + break; + + + //--------------------------------------------------------------------- + case GOTO_HNODE_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + + break; + + + //--------------------------------------------------------------------- + case GOTO_HNODE_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + + break; + + + } + + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: GotoHelperNode +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::End(Actor &self) +{ +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: GotoHelperNode +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::transitionToState( GotoHelperNodeStates_t state ) +{ + switch ( state ) + { + case GOTO_HNODE_FIND_NODE: + setupStateFindNode(); + setInternalState( state , "GOTO_HNODE_FIND_NODE" ); + break; + + case GOTO_HNODE_MOVE_TO_NODE: + setupStateMoveToNode(); + setInternalState( state , "GOTO_HNODE_MOVE_TO_NODE" ); + break; + + case GOTO_HNODE_SUCCESS: + setInternalState( state , "GOTO_HNODE_SUCCESS" ); + break; + + case GOTO_HNODE_FAILED: + setInternalState( state , "GOTO_HNODE_FAILED" ); + break; + } + +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: GotoHelperNode +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::setInternalState( GotoHelperNodeStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: GotoHelperNode +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::init( Actor &self ) +{ + _self = &self; +} + +//-------------------------------------------------------------- +// Name: think() +// Class: GotoHelperNode +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::think() +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateMoveToNode() +// Class: GotoHelperNode +// +// Description: Sets up the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::setupStateMoveToNode() +{ + _gotoPoint.SetAnim( _movementAnim ); + _gotoPoint.SetDistance( NODE_RADIUS ); + _gotoPoint.SetPoint( _node->origin ); + _gotoPoint.Begin( *_self ); +} + + +//-------------------------------------------------------------- +// Name: evaluateStateMoveToNode() +// Class: GotoHelperNode +// +// Description: Evaluates the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoHelperNode::evaluateStateMoveToNode() +{ + BehaviorReturnCode_t result = _gotoPoint.Evaluate( *_self ); + str failureReason = "GotoHelperNode::evaluateStateMoveToNode -- _gotoPoint component returned: " + _gotoPoint.GetFailureReason(); + + if ( result == BEHAVIOR_FAILED ) + failureStateMoveToNode( failureReason ); + + return result; +} + + +//-------------------------------------------------------------- +// Name: evaluateStateFindNode() +// Class: GotoHelperNode +// +// Description: Evaluates the FindNode State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- + +BehaviorReturnCode_t GotoHelperNode::evaluateStateFindNode() +{ + if ( _node ) + return BEHAVIOR_SUCCESS; + + BehaviorReturnCode_t result = BEHAVIOR_SUCCESS; + str failureReason = "GotoHelperNode::evaluateStateFindNode -- could not find an appropriate node"; + + unsigned int NodeMask = HelperNode::GetHelperNodeMask( _nodeType ); + + _node = HelperNode::FindClosestHelperNode( *_self , NodeMask , _maxDistance ); + + if ( !_node ) + { + failureStateFindNode( failureReason ); + result = BEHAVIOR_FAILED; + } + + + return result; +} + +//-------------------------------------------------------------- +// Name: setupStateFindNode() +// Class: GotoHelperNode +// +// Description: Sets up the Find State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::setupStateFindNode() +{ +} + + +//-------------------------------------------------------------- +// Name: failureStateMoveToNode +// Class: GotoHelperNode +// +// Description: Failure Handler for State Move To Cover +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::failureStateMoveToNode( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: failureStateFindNode +// Class: GotoHelperNode +// +// Description: Failure Handler for State Move To Cover +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNode::failureStateFindNode( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + diff --git a/dlls/game/gotoHelperNode.hpp b/dlls/game/gotoHelperNode.hpp new file mode 100644 index 0000000..b12093d --- /dev/null +++ b/dlls/game/gotoHelperNode.hpp @@ -0,0 +1,158 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/coverCombatWithRangedWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CoverCombatWithRangedWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class GotoHelperNode; + +#ifndef __GOTO_HELPER_NODE___ +#define __GOTO_HELPER_NODE___ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: CoverCombatWithRangedWeapon +// Base Class: Behavior +// +// Description: +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class GotoHelperNode : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + GOTO_HNODE_FIND_NODE, + GOTO_HNODE_MOVE_TO_NODE, + GOTO_HNODE_SUCCESS, + GOTO_HNODE_FAILED + } GotoHelperNodeStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _nodeType; + str _movementAnim; + HelperNodePtr _node; + float _maxDistance; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( GotoHelperNodeStates_t state ); + void setInternalState ( GotoHelperNodeStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + + + void setupStateMoveToNode (); + BehaviorReturnCode_t evaluateStateMoveToNode (); + void failureStateFindNode ( const str& failureReason ); + + void setupStateFindNode (); + BehaviorReturnCode_t evaluateStateFindNode (); + void failureStateMoveToNode ( const str& failureReason ); + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( GotoHelperNode ); + + GotoHelperNode(); + ~GotoHelperNode(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + // Accessors + void SetNode ( HelperNode *node ); + void SetMovementAnim ( const str &anim ); + + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoPoint _gotoPoint; + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + GotoHelperNodeStates_t _state; + Actor *_self; + + static const float NODE_RADIUS; + + }; + +inline void GotoHelperNode::SetNode ( HelperNode *node ) +{ + assert ( node ); + _node = node; +} + +inline void GotoHelperNode::SetMovementAnim ( const str &anim ) +{ + _movementAnim = anim; +} + + +inline void GotoHelperNode::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveString( &_nodeType ); + arc.ArchiveString ( &_movementAnim ); + arc.ArchiveSafePointer ( &_node ); + arc.ArchiveFloat( &_maxDistance ); + + // + // Archive Components + // + arc.ArchiveObject ( &_gotoPoint ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, GotoHelperNodeStates_t ); + arc.ArchiveObjectPointer( ( Class ** )&_self ); +} + +#endif /* __GOTO_HELPER_NODE___ */ diff --git a/dlls/game/gotoHelperNodeEX.cpp b/dlls/game/gotoHelperNodeEX.cpp new file mode 100644 index 0000000..88fb216 --- /dev/null +++ b/dlls/game/gotoHelperNodeEX.cpp @@ -0,0 +1,467 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/gotoHelperNodeEX.cpp $ +// $Revision:: 3 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CoverCombatWithRangedWeapon Implementation +// +// PARAMETERS: +// str _nodeType -- The name of the node to move to +// str _movementAnim -- The animation to play while moving to the node +// float _maxDistance -- The maximum distance to look for a node +// +// ANIMATIONS: +// _movementAnim : PARAMETER +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "gotoHelperNodeEX.hpp" +#include + +//-------------------------------------------------------------- +// +// Init Static Vars +// +//-------------------------------------------------------------- +const float GotoHelperNodeEX::NODE_RADIUS = 32.0f; + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, GotoHelperNodeEX, NULL ) + { + { &EV_Behavior_Args, &GotoHelperNodeEX::SetArgs }, + { &EV_Behavior_AnimDone, &GotoHelperNodeEX::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: GotoHelperNodeEX() +// Class: GotoHelperNodeEX +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GotoHelperNodeEX::GotoHelperNodeEX() +{ + _movementAnim = ""; + _nodeType = "" ; + _maxDistance = 256; +} + +//-------------------------------------------------------------- +// Name: GotoHelperNodeEX() +// Class: GotoHelperNodeEX +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GotoHelperNodeEX::~GotoHelperNodeEX() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: GotoHelperNodeEX +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::SetArgs( Event *ev ) +{ + _nodeType = ev->GetString( 1 ); + _maxDistance = ev->GetFloat ( 2 ); +} + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: GotoHelperNodeEX +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::AnimDone( Event *ev ) +{ +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: GotoHelperNodeEX +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::Begin( Actor &self ) + { + init( self ); + transitionToState ( GOTO_HNODE_FIND_NODE ); + } + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: GotoHelperNodeEX +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoHelperNodeEX::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case GOTO_HNODE_FIND_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateFindNode(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GOTO_HNODE_MOVE_TO_NODE ); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GOTO_HNODE_FAILED ); + break; + + //--------------------------------------------------------------------- + case GOTO_HNODE_MOVE_TO_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateMoveToNode(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GOTO_HNODE_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GOTO_HNODE_SUCCESS ); + break; + + + //--------------------------------------------------------------------- + case GOTO_HNODE_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + + break; + + + //--------------------------------------------------------------------- + case GOTO_HNODE_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + + break; + + + } + + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: GotoHelperNodeEX +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::End(Actor &self) +{ +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: GotoHelperNodeEX +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::transitionToState( GotoHelperNodeStates_t state ) +{ + switch ( state ) + { + case GOTO_HNODE_FIND_NODE: + setupStateFindNode(); + setInternalState( state , "GOTO_HNODE_FIND_NODE" ); + break; + + case GOTO_HNODE_MOVE_TO_NODE: + setupStateMoveToNode(); + setInternalState( state , "GOTO_HNODE_MOVE_TO_NODE" ); + break; + + case GOTO_HNODE_SUCCESS: + setInternalState( state , "GOTO_HNODE_SUCCESS" ); + break; + + case GOTO_HNODE_FAILED: + setInternalState( state , "GOTO_HNODE_FAILED" ); + break; + } + +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: GotoHelperNodeEX +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::setInternalState( GotoHelperNodeStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: GotoHelperNodeEX +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::init( Actor &self ) +{ + str objname; + str movementMode; + str stateName; + GameplayManager *gpm; + + //First check if we're even in the GPM + gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(self.getArchetype()) ) + { + _movementAnim = "run"; + return; + } + + //Now we're going to check and see if we have state data + //in the GPM for our current state that has our movement + //mode in it. + objname = self.getArchetype(); + stateName = self.currentState->getName(); + objname = objname + "." + stateName; + + if ( gpm->hasProperty( objname , "MovementMode" ) ) + movementMode = gpm->getStringValue( objname , "MovementMode" ); + else + movementMode = "RUN"; + + + //Now, depending on what our movementMode is, we set a proper + //movement animation + objname = self.getArchetype(); + objname = objname + "." + self.GetAnimSet(); + + if ( movementMode == "RUN" ) + { + if ( gpm->hasProperty(objname, "Run") ) + _movementAnim = gpm->getStringValue( objname , "Run" ); + else + _movementAnim = "run"; + + return; + } + + if ( movementMode == "WALK" ) + { + if ( gpm->hasProperty(objname, "Walk") ) + _movementAnim = gpm->getStringValue( objname , "Walk" ); + else + _movementAnim = "walk"; + + return; + } + + _movementAnim = "run"; + +} + +//-------------------------------------------------------------- +// Name: think() +// Class: GotoHelperNodeEX +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::think() +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateMoveToNode() +// Class: GotoHelperNodeEX +// +// Description: Sets up the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::setupStateMoveToNode() +{ + _gotoPoint.SetAnim( _movementAnim ); + _gotoPoint.SetDistance( NODE_RADIUS ); + _gotoPoint.SetPoint( _node->origin ); + _gotoPoint.Begin( *GetSelf() ); +} + + +//-------------------------------------------------------------- +// Name: evaluateStateMoveToNode() +// Class: GotoHelperNodeEX +// +// Description: Evaluates the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoHelperNodeEX::evaluateStateMoveToNode() +{ + BehaviorReturnCode_t result = _gotoPoint.Evaluate( *GetSelf() ); + str failureReason = "GotoHelperNode::evaluateStateMoveToNode -- _gotoPoint component returned: " + _gotoPoint.GetFailureReason(); + + if ( result == BEHAVIOR_FAILED ) + failureStateMoveToNode( failureReason ); + + return result; +} + + +//-------------------------------------------------------------- +// Name: evaluateStateFindNode() +// Class: GotoHelperNodeEX +// +// Description: Evaluates the FindNode State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- + +BehaviorReturnCode_t GotoHelperNodeEX::evaluateStateFindNode() +{ + BehaviorReturnCode_t result = BEHAVIOR_SUCCESS; + str failureReason = "GotoHelperNode::evaluateStateFindNode -- could not find an appropriate node"; + + unsigned int NodeMask = HelperNode::GetHelperNodeMask( _nodeType ); + + _node = HelperNode::FindClosestHelperNode( *GetSelf() , NodeMask , _maxDistance ); + + if ( !_node ) + { + failureStateFindNode( failureReason ); + result = BEHAVIOR_FAILED; + } + + + return result; +} + +//-------------------------------------------------------------- +// Name: setupStateFindNode() +// Class: GotoHelperNodeEX +// +// Description: Sets up the Find State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::setupStateFindNode() +{ +} + + +//-------------------------------------------------------------- +// Name: failureStateMoveToNode +// Class: GotoHelperNodeEX +// +// Description: Failure Handler for State Move To Cover +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::failureStateMoveToNode( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: failureStateFindNode +// Class: GotoHelperNodeEX +// +// Description: Failure Handler for State Move To Cover +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeEX::failureStateFindNode( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + diff --git a/dlls/game/gotoHelperNodeEX.hpp b/dlls/game/gotoHelperNodeEX.hpp new file mode 100644 index 0000000..71f869d --- /dev/null +++ b/dlls/game/gotoHelperNodeEX.hpp @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/gotoHelperNodeEX.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// gotoHelperNodeEX Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class GotoHelperNodeEX; + +#ifndef __GOTO_HELPER_NODE_EX___ +#define __GOTO_HELPER_NODE_EX___ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: GotoHelperNodeEX +// Base Class: Behavior +// +// Description: Moves the Actor to the closest appropriate +// helper node, using data from the GPDB +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class GotoHelperNodeEX : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + GOTO_HNODE_FIND_NODE, + GOTO_HNODE_MOVE_TO_NODE, + GOTO_HNODE_SUCCESS, + GOTO_HNODE_FAILED + } GotoHelperNodeStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _nodeType; + str _movementAnim; + HelperNodePtr _node; + float _maxDistance; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( GotoHelperNodeStates_t state ); + void setInternalState ( GotoHelperNodeStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + + + void setupStateMoveToNode (); + BehaviorReturnCode_t evaluateStateMoveToNode (); + void failureStateFindNode ( const str& failureReason ); + + void setupStateFindNode (); + BehaviorReturnCode_t evaluateStateFindNode (); + void failureStateMoveToNode ( const str& failureReason ); + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( GotoHelperNodeEX ); + + GotoHelperNodeEX(); + ~GotoHelperNodeEX(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + // Accessors + void SetNode ( HelperNode *node ); + void SetMovementAnim ( const str &anim ); + + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoPoint _gotoPoint; + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + GotoHelperNodeStates_t _state; + + static const float NODE_RADIUS; + + }; + +inline void GotoHelperNodeEX::SetNode ( HelperNode *node ) +{ + assert ( node ); + _node = node; +} + +inline void GotoHelperNodeEX::SetMovementAnim ( const str &anim ) +{ + _movementAnim = anim; +} + + +inline void GotoHelperNodeEX::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveString( &_nodeType ); + arc.ArchiveString( &_movementAnim ); + arc.ArchiveSafePointer( &_node ); + arc.ArchiveFloat ( &_maxDistance ); + // + // Archive Components + // + arc.ArchiveObject ( &_gotoPoint ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, GotoHelperNodeStates_t ); +} + +#endif /* __GOTO_HELPER_NODE_EX___ */ + diff --git a/dlls/game/gotoHelperNodeNearestEnemy.cpp b/dlls/game/gotoHelperNodeNearestEnemy.cpp new file mode 100644 index 0000000..f22e23a --- /dev/null +++ b/dlls/game/gotoHelperNodeNearestEnemy.cpp @@ -0,0 +1,433 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/gotoHelperNodeNearestEnemy.cpp $ +// $Revision:: 6 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CoverCombatWithRangedWeapon Implementation +// +// PARAMETERS: +// str _movementAnim -- The animation to play while moving to the cover node +// +// ANIMATIONS: +// _movementAnim : PARAMETER +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "gotoHelperNodeNearestEnemy.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, GotoHelperNodeNearestEnemy, NULL ) + { + { &EV_Behavior_Args, &GotoHelperNodeNearestEnemy::SetArgs }, + { &EV_Behavior_AnimDone, &GotoHelperNodeNearestEnemy::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: GotoHelperNodeNearestEnemy() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GotoHelperNodeNearestEnemy::GotoHelperNodeNearestEnemy() +{ +} + +//-------------------------------------------------------------- +// Name: GotoHelperNodeNearestEnemy() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GotoHelperNodeNearestEnemy::~GotoHelperNodeNearestEnemy() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeNearestEnemy::SetArgs( Event *ev ) +{ + _nodeType = ev->GetString( 1 ); + _movementAnim = ev->GetString( 2 ); + _maxDistance = ev->GetFloat( 3 ); + +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeNearestEnemy::AnimDone( Event *ev ) +{ +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeNearestEnemy::Begin( Actor &self ) + { + init( self ); + transitionToState ( GOTO_HNODE_FIND_NODE ); + } + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoHelperNodeNearestEnemy::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case GOTO_HNODE_FIND_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateFindNode(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GOTO_HNODE_FAILED ); + else + transitionToState( GOTO_HNODE_MOVE_TO_NODE ); + + break; + + //--------------------------------------------------------------------- + case GOTO_HNODE_MOVE_TO_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateMoveToNode(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( GOTO_HNODE_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( GOTO_HNODE_SUCCESS ); + + break; + + + //--------------------------------------------------------------------- + case GOTO_HNODE_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + + break; + + + //--------------------------------------------------------------------- + case GOTO_HNODE_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + + break; + + + } + + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeNearestEnemy::End(Actor &self) +{ +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeNearestEnemy::transitionToState( GotoHelperNodeStates_t state ) +{ + switch ( state ) + { + case GOTO_HNODE_FIND_NODE: + setupStateFindNode(); + setInternalState( state , "GOTO_HNODE_FIND_NODE" ); + break; + + case GOTO_HNODE_MOVE_TO_NODE: + setupStateMoveToNode(); + setInternalState( state , "GOTO_HNODE_MOVE_TO_NODE" ); + break; + + case GOTO_HNODE_SUCCESS: + setInternalState( state , "GOTO_HNODE_SUCCESS" ); + break; + + case GOTO_HNODE_FAILED: + setInternalState( state , "GOTO_HNODE_FAILED" ); + break; + } + +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeNearestEnemy::setInternalState( GotoHelperNodeStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeNearestEnemy::init( Actor &self ) +{ + _self = &self; + updateEnemy(); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeNearestEnemy::think() +{ +} + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeNearestEnemy::updateEnemy() +{ + Entity *currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + SetFailureReason( "GotoHelperNodeNearestEnemy::updateEnemy -- No Enemy" ); + transitionToState( GOTO_HNODE_FAILED ); + return; + } + + } + + _currentEnemy = currentEnemy; +} + + +void GotoHelperNodeNearestEnemy::setupStateFindNode() +{ + unsigned int NodeMask = HelperNode::GetHelperNodeMask( _nodeType ); + + if ( !_currentEnemy ) + return; + + _node = HelperNode::FindHelperNodeClosestTo( *_self , _currentEnemy , NodeMask , _maxDistance ); + + if ( !_node ) + _node = HelperNode::FindHelperNodeClosestToWithoutPathing( *_self , _currentEnemy , NodeMask , _maxDistance ); + +} + +BehaviorReturnCode_t GotoHelperNodeNearestEnemy::evaluateStateFindNode() +{ + if ( !_node ) + { + str failureMsg = "No Node of Type: " + _nodeType + " in range"; + failureStateFindNode( failureMsg ); + return BEHAVIOR_FAILED; + } + + return BEHAVIOR_SUCCESS; +} + +void GotoHelperNodeNearestEnemy::failureStateFindNode(const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + +//-------------------------------------------------------------- +// Name: setupStateMoveToNode() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Sets up the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeNearestEnemy::setupStateMoveToNode() +{ + _gotoHNode.SetNode( _node ); + _gotoHNode.SetMovementAnim( _movementAnim ); + _gotoHNode.Begin( *_self ); +} + + +//-------------------------------------------------------------- +// Name: evaluateStateMoveToNode() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Evaluates the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t GotoHelperNodeNearestEnemy::evaluateStateMoveToNode() +{ + BehaviorReturnCode_t result = _gotoHNode.Evaluate( *_self ); + + if ( result == BEHAVIOR_FAILED ) + { + str failureReason = "GotoHelperNodeNearestEnemy::evaluateStateMoveToNode -- _gotoHNode component returned: " + _gotoHNode.GetFailureReason(); + failureStateMoveToNode( failureReason ); + } + + return result; + +} + + +//-------------------------------------------------------------- +// Name: failureStateMoveToNode +// Class: GotoHelperNodeNearestEnemy +// +// Description: Failure Handler for State Move To Cover +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void GotoHelperNodeNearestEnemy::failureStateMoveToNode( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dlls/game/gotoHelperNodeNearestEnemy.hpp b/dlls/game/gotoHelperNodeNearestEnemy.hpp new file mode 100644 index 0000000..1a7a5ee --- /dev/null +++ b/dlls/game/gotoHelperNodeNearestEnemy.hpp @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/coverCombatWithRangedWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CoverCombatWithRangedWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class GotoHelperNodeNearestEnemy; + +#ifndef __GOTO_HELPER_NODE_NEAREST_ENEMY___ +#define __GOTO_HELPER_NODE_NEAREST_ENEMY__ + +#include "behavior.h" +#include "behaviors_general.h" +#include "gotoHelperNode.hpp" + +//------------------------- CLASS ------------------------------ +// +// Name: CoverCombatWithRangedWeapon +// Base Class: Behavior +// +// Description: +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class GotoHelperNodeNearestEnemy : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + GOTO_HNODE_FIND_NODE, + GOTO_HNODE_MOVE_TO_NODE, + GOTO_HNODE_SUCCESS, + GOTO_HNODE_FAILED + } GotoHelperNodeStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _nodeType; + str _movementAnim; + float _maxDistance; + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( GotoHelperNodeStates_t state ); + void setInternalState ( GotoHelperNodeStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + + + void setupStateFindNode (); + BehaviorReturnCode_t evaluateStateFindNode (); + void failureStateFindNode ( const str& failureReason ); + + void setupStateMoveToNode (); + BehaviorReturnCode_t evaluateStateMoveToNode (); + void failureStateMoveToNode ( const str& failureReason ); + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( GotoHelperNodeNearestEnemy ); + + GotoHelperNodeNearestEnemy(); + ~GotoHelperNodeNearestEnemy(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + // Accessors + void SetNode ( HelperNode *node ); + void SetMovementAnim ( const str &anim ); + + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoHelperNode _gotoHNode; + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + GotoHelperNodeStates_t _state; + HelperNodePtr _node; + EntityPtr _currentEnemy; + + Actor *_self; + + + }; + + + +inline void GotoHelperNodeNearestEnemy::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveString ( &_nodeType ); + arc.ArchiveString ( &_movementAnim ); + arc.ArchiveFloat ( &_maxDistance ); + + // + // Archive Components + // + arc.ArchiveObject ( &_gotoHNode ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, GotoHelperNodeStates_t ); + arc.ArchiveSafePointer ( &_node ); + arc.ArchiveSafePointer ( &_currentEnemy ); + arc.ArchiveObjectPointer( ( Class ** )&_self ); +} + +#endif /* __GOTO_HELPER_NODE_NEAREST_ENEMY__ */ + diff --git a/dlls/game/gravpath.cpp b/dlls/game/gravpath.cpp new file mode 100644 index 0000000..a26a7e4 --- /dev/null +++ b/dlls/game/gravpath.cpp @@ -0,0 +1,699 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/gravpath.cpp $ +// $Revision:: 8 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Gravity path - Used for underwater currents and wells. + +#include "_pch_cpp.h" +#include "entity.h" +#include "gravpath.h" +#include "container.h" +#include "navigate.h" +#include "misc.h" +#include "player.h" + +GravPathManager gravPathManager; + +CLASS_DECLARATION( Class, GravPathManager, NULL ) +{ + { NULL,NULL } +}; + +GravPathManager::~GravPathManager() +{ + Reset(); +} + +void GravPathManager::Reset( void ) +{ + while( pathList.NumObjects() > 0 ) + { + delete ( GravPath * )pathList.ObjectAt( 1 ); + } + + pathList.FreeObjectList(); +} + +void GravPathManager::AddPath(GravPath *p) +{ + pathList.AddObject( p ); +} + +void GravPathManager::RemovePath(GravPath *p) +{ + pathList.RemoveObject( p ); +} + +void GravPathManager::DrawGravPaths( void ) +{ + int i; + int num = pathList.NumObjects(); + + for( i = 1; i <= num; i++ ) + { + GravPath *p = ( GravPath * )pathList.ObjectAt( i ); + + p->DrawPath( 1.0f, 0.0f, 0.0f ); + } +} + +Vector GravPathManager::CalculateGravityPull(Entity &ent, const Vector &pos, qboolean *force, float *max_speed) +{ + int i,num; + GravPath *p; + GravPathNode *node; + Vector point; + Vector newpoint; + Vector dir; + float bestdist = 99999; + float dist; + float speed; + float radius; + Vector velocity; + int bestpath = 0; + int entity_contents, grav_contents; + + + *force = false; + + num = pathList.NumObjects(); + if ( !num ) + { + return vec_zero; + } + + entity_contents = gi.pointcontents( ent.origin, 0 ); + + for( i = 1; i <= num; i++ ) + { + p = ( GravPath * )pathList.ObjectAt( i ); + + if ( !p ) + continue; + + // Check to see if path is active + node = p->GetNode( 1 ); + if ( !node || !node->active ) + continue; + + // Check to see if the contents are the same + grav_contents = gi.pointcontents( node->origin, 0 ); + + // If grav node is in water, make sure ent is too. + if ( ( grav_contents & CONTENTS_WATER ) && !( entity_contents & CONTENTS_WATER ) ) + continue; + + // Test to see if we are in this path's bounding box + if ( (pos.x < p->maxs.x) && (pos.y < p->maxs.y) && (pos.z < p->maxs.z) && + (pos.x > p->mins.x) && (pos.y > p->mins.y) && (pos.z > p->mins.z) ) + { + point = p->ClosestPointOnPath(pos, ent, &dist, &speed, &radius); + + // If the closest distance on the path is greater than the radius, then + // do not consider this path. + + if (dist > radius) + { + continue; + } + else if (dist < bestdist) + { + bestpath = i; + bestdist = dist; + } + } + } + + if (!bestpath) + { + return vec_zero; + } + + p = ( GravPath * )pathList.ObjectAt( bestpath ); + if ( !p ) + return velocity; + *force = p->force; + dist = p->DistanceAlongPath(pos, &speed); + newpoint = p->PointAtDistance( pos, dist + speed, ent.isSubclassOf( Player ), max_speed ); + dir = newpoint-pos; + dir.normalize(); + velocity = dir * speed; + + //velocity *= .75; + return velocity; +} + +/*****************************************************************************/ +/*QUAKED info_grav_pathnode (0 0 .5) (-16 -16 0) (16 16 32) HEADNODE FORCE PULL_UPWARDS + "radius" Radius of the effect of the pull (Default is 256) + "speed" Speed of the pull (Use negative for a repulsion) (Default is 100) + + Set HEADNODE to signify the head of the path. + Set FORCE if you want un-fightable gravity ( i.e. can't go backwards ) + Set PULL_UPWARDS if you want the gravnodes to pull you upwards also +******************************************************************************/ + +#define PULL_UPWARDS ( 1 << 2 ) + +Event EV_GravPath_Create +( + "gravpath_create", + EV_SCRIPTONLY, + NULL, + NULL, + "Create the grav path." +); +Event EV_GravPath_Activate +( + "activate", + EV_SCRIPTONLY, + NULL, + NULL, + "Activate the grav path." +); +Event EV_GravPath_Deactivate +( + "deactivate", + EV_SCRIPTONLY, + NULL, + NULL, + "Deactivate the grav path." +); +Event EV_GravPath_SetSpeed +( + "speed", + EV_DEFAULT, + "f", + "speed", + "Set the speed of the grav path." +); +Event EV_GravPath_SetMaxSpeed +( + "maxspeed", + EV_SCRIPTONLY, + "f", + "maxspeed", + "Set the max speed of the grav path." +); +Event EV_GravPath_SetRadius +( + "radius", + EV_SCRIPTONLY, + "f", + "radius", + "Set the radius of the grav path." +); + +CLASS_DECLARATION( Entity, GravPathNode, "info_grav_pathnode" ) +{ + { &EV_GravPath_Create, &GravPathNode::CreatePath }, + { &EV_GravPath_Activate, &GravPathNode::Activate }, + { &EV_GravPath_Deactivate, &GravPathNode::Deactivate }, + { &EV_GravPath_SetSpeed, &GravPathNode::SetSpeed }, + { &EV_GravPath_SetMaxSpeed, &GravPathNode::SetMaxSpeed }, + { &EV_GravPath_SetRadius, &GravPathNode::SetRadius }, + + { NULL, NULL } +}; + +GravPathNode::GravPathNode() +{ + if ( LoadingSavegame ) + { + // all data will be setup by the archive function + return; + } + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_NOT ); + hideModel(); + + speed = 100.0f; + max_speed = 200.0f; + radius = 256.0f; + headnode = spawnflags & 1; + active = true; + + // This is the head of a new path, post an event to create the path + if ( headnode ) + { + PostEvent( EV_GravPath_Create, 0.0f ); + } +} + +void GravPathNode::SetSpeed( Event *ev ) +{ + speed = ev->GetFloat( 1 ); +} + +void GravPathNode::SetMaxSpeed( Event *ev ) +{ + max_speed = ev->GetFloat( 1 ); +} + +void GravPathNode::SetRadius( Event *ev ) +{ + radius = ev->GetFloat( 1 ); +} + +float GravPathNode::Speed( void ) +{ + if ( active ) + return speed; + else + return 0; +} + +float GravPathNode::MaxSpeed( void ) +{ + return max_speed; +} + +void GravPathNode::Activate( Event *ev ) +{ + GravPathNode *node; + Entity *ent; + const char *target; + + active = true; + node = this; + // Go through the entire path and activate it + target = node->Target(); + while (target[0]) + { + ent = G_FindTarget( NULL, target ); + if ( ent ) + { + node = (GravPathNode *)ent; + assert( node ); + node->active = true; + } + else + { + gi.Error( ERR_DROP, "GravPathNode::CreatePath: target %s not found\n",target); + } + target = node->Target(); + } +} + +void GravPathNode::Deactivate(Event *ev) +{ + GravPathNode *node; + Entity *ent; + const char *target; + + active = false; + node = this; + // Go through the entire path and activate it + target = node->Target(); + while (target[0]) + { + ent = G_FindTarget( NULL, target); + if ( ent ) + { + node = (GravPathNode *)ent; + assert( node ); + node->active = false; + } + else + { + gi.Error( ERR_DROP, "GravPathNode::CreatePath: target %s not found\n",target); + } + target = node->Target(); + } +} + +void GravPathNode::CreatePath(Event *ev) +{ + const char *target; + GravPath *path = new GravPath; + GravPathNode *node; + Entity *ent; + + ClearBounds( path->mins, path->maxs ); + + // This node is the head of a path, create a new path in the path manager. + // and add it in, then add all of it's children in the path. + node = this; + path->AddNode(node); + path->force = spawnflags & 2; + + // Make the path from the targetlist. + target = node->Target(); + while (target[0]) + { + ent = G_FindTarget( NULL, target ); + if ( ent ) + { + node = (GravPathNode *)ent; + assert( node ); + path->AddNode(node); + } + else + { + gi.Error( ERR_DROP, "GravPathNode::CreatePath: target %s not found\n",target); + } + target = node->Target(); + } + + // Set the origin. + path->origin = path->mins + path->maxs; + path->origin *= 0.5f; +} + +CLASS_DECLARATION( Listener, GravPath, NULL ) +{ + { NULL, NULL } +}; + +GravPath::GravPath() +{ + pathlength = 0; + from = NULL; + to = NULL; + nextnode = 1; + force = 0; + + if ( !LoadingSavegame ) + { + gravPathManager.AddPath(this); + } +} + +GravPath::~GravPath() +{ + pathlength = 0; + from = NULL; + to = NULL; + nextnode = 1; + gravPathManager.RemovePath(this); +} + +void GravPath::Clear( void ) +{ + nextnode = 1; + pathlength = 0; + from = NULL; + to = NULL; + pathlist.FreeObjectList(); +} + +void GravPath::Reset( void ) +{ + nextnode = 1; +} + +GravPathNode *GravPath::Start( void ) +{ + return from; +} + +GravPathNode *GravPath::End( void ) +{ + return to; +} + +void GravPath::AddNode( GravPathNode *node ) +{ + int num; + Vector r,addp; + + if ( !from ) + { + from = node; + } + + to = node; + pathlist.AddObject( GravPathNodePtr( node ) ); + + num = NumNodes(); + if ( num > 1 ) + { + pathlength += ( node->origin - GetNode( num )->origin ).length(); + } + + r.setXYZ(node->Radius(),node->Radius(),node->Radius()); + addp = node->origin + r; + AddPointToBounds(addp,mins,maxs); + addp = node->origin - r; + AddPointToBounds(addp,mins,maxs); +} + +GravPathNode *GravPath::GetNode( int num ) +{ + return pathlist.ObjectAt( num ); +} + +GravPathNode *GravPath::NextNode( void ) +{ + if ( nextnode <= NumNodes() ) + { + return pathlist.ObjectAt( nextnode++ ); + } + return NULL; +} + +Vector GravPath::ClosestPointOnPath( const Vector &pos, Entity &ent, float *ret_dist, float *speed, float *radius ) +{ + GravPathNode *s; + GravPathNode *e; + int num; + int i; + float bestdist; + Vector bestpoint; + float dist; + float segmentlength; + Vector delta; + Vector p1; + Vector p2; + Vector p3; + float t; + //trace_t trace; + + num = NumNodes(); + s = GetNode( 1 ); + //trace = G_Trace( pos, ent.mins, ent.maxs, s->origin, &ent, MASK_PLAYERSOLID, false, "GravPath::ClosestPointOnPath 1" ); + bestpoint = s->origin; + delta = bestpoint - pos; + bestdist = delta.length(); + *speed = s->Speed(); + *radius = s->Radius(); + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + // check if we're closest to the endpoint + delta = e->origin - pos; + dist = delta.length(); + + if ( dist < bestdist ) + { + //trace = G_Trace( pos, ent.mins, ent.maxs, e->origin, &ent, MASK_PLAYERSOLID, false, "GravPath::ClosestPointOnPath 2" ); + bestdist = dist; + bestpoint = e->origin; + *speed = e->Speed(); + *radius = e->Radius(); + } + + // check if we're closest to the segment + p1 = e->origin - s->origin; + segmentlength = p1.length(); + p1 *= 1.0f / segmentlength; + p2 = pos - s->origin; + + t = p1 * p2; + if ( ( t > 0.0f ) && ( t < segmentlength ) ) + { + p3 = ( p1 * t ) + s->origin; + + delta = p3 - pos; + dist = delta.length(); + if ( dist < bestdist ) + { + //trace = G_Trace( pos, ent.mins, ent.maxs, p3, &ent, MASK_PLAYERSOLID, false, "GravPath::ClosestPointOnPath 3" ); + bestdist = dist; + bestpoint = p3; + *speed = (e->Speed() * t) + (s->Speed() * (1.0f - t)); + *radius = (e->Radius() * t) + (s->Radius() * (1.0f - t)); + } + } + + s = e; + } + *ret_dist = bestdist; + return bestpoint; +} + +float GravPath::DistanceAlongPath( const Vector &pos, float *speed ) +{ + GravPathNode *s; + GravPathNode *e; + int num; + int i; + float bestdist; + float dist; + float segmentlength; + Vector delta; + Vector segment; + Vector p1; + Vector p2; + Vector p3; + float t; + float pathdist; + float bestdistalongpath; + float oosl; + pathdist = 0; + + num = NumNodes(); + s = GetNode( 1 ); + delta = s->origin - pos; + bestdist = delta.length(); + bestdistalongpath = 0; + *speed = s->Speed(); + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + segment = e->origin - s->origin; + segmentlength = segment.length(); + + // check if we're closest to the endpoint + delta = e->origin - pos; + dist = delta.length(); + + if ( dist < bestdist ) + { + bestdist = dist; + bestdistalongpath = pathdist + segmentlength; + *speed = e->Speed(); + } + + // check if we're closest to the segment + oosl = ( 1.0f / segmentlength ); + p1 = segment * oosl; + p1.normalize(); + p2 = pos - s->origin; + + t = p1 * p2; + if ( ( t > 0.0f ) && ( t < segmentlength ) ) + { + p3 = ( p1 * t ) + s->origin; + + delta = p3 - pos; + dist = delta.length(); + if ( dist < bestdist ) + { + bestdist = dist; + bestdistalongpath = pathdist + t; + + t *= oosl; + *speed = (e->Speed() * t) + (s->Speed() * (1.0f - t)); + } + } + + s = e; + pathdist += segmentlength; + } + + return bestdistalongpath; +} + +Vector GravPath::PointAtDistance( const Vector &pos, float dist, qboolean is_player, float *max_speed ) +{ + GravPathNode *s; + GravPathNode *e; + int num; + int i; + Vector delta; + Vector p1; + float t; + float pathdist; + float segmentlength; + + num = NumNodes(); + s = GetNode( 1 ); + pathdist = 0; + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + delta = e->origin - s->origin; + segmentlength = delta.length(); + + if ( ( pathdist + segmentlength ) > dist ) + { + t = dist - pathdist; + + p1 = delta * ( t / segmentlength ); + // return p1 + s->origin; + + if ( e->spawnflags & PULL_UPWARDS && is_player ) + p1.z = p1.length() / 2.0f; + + *max_speed = e->MaxSpeed(); + + return p1 + pos; + } + + s = e; + pathdist += segmentlength; + } + + *max_speed = s->MaxSpeed(); + + // cap it off at start or end of path + return s->origin; +} + +void GravPath::DrawPath( float r, float g, float b ) +{ + Vector s; + Vector e; + Vector offset; + GravPathNode *node; + int num; + int i; + + num = NumNodes(); + node = GetNode( 1 ); + s = node->origin; + G_DebugBBox( s, Vector(8.0f, 8.0f, 8.0f), Vector(-8.0f, -8.0f, -8.0f), 0.0f, 1.0f, 0.0f, 1.0f ); + offset = ( Vector( r, g, b ) * 4.0f ) + Vector( 0.0f, 0.0f, 0.0f ); + offset = Vector( 0.0f, 0.0f, 0.0f ); + + for( i = 2; i <= num; i++ ) + { + node = GetNode( i ); + e = node->origin; + + G_DebugLine( s + offset, e + offset, r, g, b, 1 ); + G_DebugBBox( e, Vector( 8.0f, 8.0f, 8.0f ), Vector( -8.0f, -8.0f, -8.0f ), 0.0f, 1.0f, 0.0f, 1.0f ); + s = e; + } + + G_DebugBBox( origin,mins-origin,maxs-origin, 1.0f, 0.0f, 0.0f, 1.0f ); +} + +int GravPath::NumNodes( void ) +{ + return pathlist.NumObjects(); +} + +float GravPath::Length( void ) +{ + return pathlength; +} diff --git a/dlls/game/gravpath.h b/dlls/game/gravpath.h new file mode 100644 index 0000000..7ba389e --- /dev/null +++ b/dlls/game/gravpath.h @@ -0,0 +1,200 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/gravpath.h $ +// $Revision:: 5 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Gravity path - Used for underwater currents and wells. + +#ifndef __GRAVPATH_H__ +#define __GRAVPATH_H__ + +#include "g_local.h" +#include "class.h" +#include "container.h" + + +class GravPathNode : public Entity + { + private: + float speed; + float radius; + qboolean headnode; + float max_speed; + + public: + qboolean active; + + CLASS_PROTOTYPE(GravPathNode); + GravPathNode(); + void SetSpeed( Event *ev ); + void SetMaxSpeed( Event *ev ); + void SetRadius( Event *ev ); + void CreatePath( Event *ev ); + void Activate( Event *ev ); + void Deactivate( Event *ev ); + float Speed( void ); + float MaxSpeed( void ); + float Radius( void ) { return radius; }; + virtual void Archive( Archiver &arc ); + }; + +inline void GravPathNode::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &radius ); + arc.ArchiveBoolean( &headnode ); + arc.ArchiveFloat( &max_speed ); + + arc.ArchiveBoolean( &active ); + } + +typedef SafePtr GravPathNodePtr; + +class GravPath : public Listener + { + private: + Container pathlist; + float pathlength; + + GravPathNodePtr from; + GravPathNodePtr to; + int nextnode; + + public: + CLASS_PROTOTYPE( GravPath ); + + GravPath(); + ~GravPath(); + void Clear(void); + void Reset(void); + void AddNode(GravPathNode *node); + GravPathNode *GetNode(int num); + GravPathNode *NextNode(void); + Vector ClosestPointOnPath(const Vector &pos, Entity &ent,float *bestdist,float *speed,float *radius); + float DistanceAlongPath(const Vector &pos, float *speed); + Vector PointAtDistance( const Vector &pos, float dist, qboolean is_player, float *max_distance ); + void DrawPath(float r, float g, float b); + int NumNodes(void); + float Length(void); + GravPathNode *Start(void); + GravPathNode *End(void); + virtual void Archive( Archiver &arc ); + + Vector mins; + Vector maxs; + Vector origin; + qboolean force; + }; + +inline void GravPath::Archive + ( + Archiver &arc + ) + + { + GravPathNodePtr *tempPtr; + int i, num; + + Listener::Archive( arc ); + + if ( arc.Loading() ) + { + Reset(); + } + else + { + num = pathlist.NumObjects(); + } + arc.ArchiveInteger( &num ); + if ( arc.Loading() ) + { + pathlist.Resize( num ); + } + + for ( i = 1; i <= num; i++ ) + { + tempPtr = pathlist.AddressOfObjectAt( i ); + arc.ArchiveSafePointer( tempPtr ); + } + + arc.ArchiveFloat( &pathlength ); + arc.ArchiveSafePointer( &from ); + arc.ArchiveSafePointer( &to ); + arc.ArchiveInteger( &nextnode ); + arc.ArchiveVector( &mins ); + arc.ArchiveVector( &maxs ); + arc.ArchiveVector( &origin ); + arc.ArchiveBoolean( &force ); + } + +class GravPathManager : public Class + { + private: + Container pathList; + + public: + CLASS_PROTOTYPE( GravPathManager ); + ~GravPathManager(); + void Reset( void ); + void AddPath(GravPath *p); + void RemovePath(GravPath *p); + Vector CalculateGravityPull(Entity &ent, const Vector &position, qboolean *force, float *max_speed); + void DrawGravPaths( void ); + virtual void Archive( Archiver &arc ); + }; + +inline void GravPathManager::Archive + ( + Archiver &arc + ) + { + GravPath * ptr; + int i, num; + + Class::Archive( arc ); + + if ( arc.Saving() ) + { + num = pathList.NumObjects(); + } + else + { + Reset(); + } + arc.ArchiveInteger( &num ); + for ( i = 1; i <= num; i++ ) + { + if ( arc.Saving() ) + { + ptr = pathList.ObjectAt( i ); + } + else + { + ptr = new GravPath; + } + arc.ArchiveObject( ptr ); + if ( arc.Loading() ) + { + pathList.AddObject( ptr ); + } + } + } + +extern GravPathManager gravPathManager; + +#endif /* gravpath.h */ diff --git a/dlls/game/groupcoordinator.cpp b/dlls/game/groupcoordinator.cpp new file mode 100644 index 0000000..14b942a --- /dev/null +++ b/dlls/game/groupcoordinator.cpp @@ -0,0 +1,944 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/groupcoordinator.cpp $ +// $Revision:: 22 $ +// $Author:: Steven $ +// $Date:: 5/17/03 10:29p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +//------------------------------------------------------------------------------ + +#include "_pch_cpp.h" +#include "groupcoordinator.hpp" +#include "actor.h" + +//============================================================================== +// Group +//============================================================================== +CLASS_DECLARATION( Listener, Group, "group" ) +{ + { NULL, NULL } +}; + + +//-------------------------------------------------------------- +// Name: Group() +// Class: Group +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +Group::Group() +{ +} + + +//-------------------------------------------------------------- +// Name: ~Group() +// Class: Group() +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +Group::~Group() +{ + ClearList(); +} + + +//-------------------------------------------------------------- +// Name: AddMember() +// Class: Group +// +// Description: First Checks if entity to be added is already in +// the list, if it is not, then it adds it. +// After it adds it to the list, it sets that entity's +// group number +// +// Parameters: Entity *entity +// +// Returns: None +//-------------------------------------------------------------- +void Group::AddMember( Entity *entity ) +{ + // First Check if entity is in the list -- There might be + // a faster way to do this inside the Container Class + Entity *checkEntity; + for ( int i = 1 ; i <= _memberList.NumObjects() ; i++ ) + { + checkEntity = _memberList.ObjectAt( i ); + if ( !checkEntity ) + continue; + + if ( checkEntity == entity ) + return; + } + + // We didn't find a match, so we'll add it. + _memberList.AddObject( entity ); + entity->SetGroupID( _id ); +} + + +//-------------------------------------------------------------- +// Name: RemoveMember( Entity *entity ) +// Class: Group +// +// Description: Removes the entity from the list +// +// Parameters: Entity *entity +// +// Returns: None +//-------------------------------------------------------------- +void Group::RemoveMember( Entity *entity ) +{ + if ( _memberList.ObjectInList( entity ) ) + { + _memberList.RemoveObject( entity ); + } +} + + +//-------------------------------------------------------------- +// Name: ClearList() +// Class: Group +// +// Description: Clears the member list +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Group::ClearList() +{ + _memberList.FreeObjectList(); +} + + +//-------------------------------------------------------------- +// Name: IsThisTargetNameInGroup() +// Class: Group +// +// Description: Checks if an entity with the specified target name +// is in the group +// +// Parameters: const str &name +// +// Returns: true or false +//-------------------------------------------------------------- +bool Group::IsThisTargetNameInGroup( const str &name ) +{ + Entity *checkEntity; + str checkName; + + for ( int i = 1 ; i <= _memberList.NumObjects() ; i++ ) + { + checkEntity = _memberList.ObjectAt( i ); + if ( !checkEntity ) + continue; + + if ( !stricmp( name.c_str() , checkEntity->TargetName() ) ) + return true; + } + + return false; +} + + +//-------------------------------------------------------------- +// Name: IsThisEntityInGroup() +// Class: Group +// +// Description: Checks if this entity is in the group +// +// Parameters: Entity *entity +// +// Returns: true or false +//-------------------------------------------------------------- +bool Group::IsThisEntityInGroup( Entity *entity ) +{ + Entity *checkEntity; + + for ( int i = 1 ; i <= _memberList.NumObjects() ; i++ ) + { + checkEntity = _memberList.ObjectAt( i ); + if ( !checkEntity ) + continue; + + if ( checkEntity == entity ) + return true; + } + + return false; +} + +//-------------------------------------------------------------- +// Name: SendEventToGroup() +// Class: Group +// +// Description: Sends the specified event to every member of the +// group +// +// Parameters: Event *ev -- The event to send +// +// Returns: None +//-------------------------------------------------------------- +void Group::SendEventToGroup( Event *ev ) +{ + Entity *entity; + Event *copiedEvent; + + for ( int i = 1 ; i <= _memberList.NumObjects() ; i++ ) + { + entity = _memberList.ObjectAt( i ); + + if ( !entity ) + continue; + + copiedEvent = new Event( ev ); + entity->ProcessEvent( copiedEvent ); + } +} + +//-------------------------------------------------------------- +// Name: SendEventToGroup() +// Class: Group +// +// Description: Sends the specified event to every member of the +// group +// +// Parameters: Event &ev -- The event to send +// +// Returns: None +//-------------------------------------------------------------- +void Group::SendEventToGroup( Event &ev ) +{ + Entity *entity; + Event *copiedEvent; + + for ( int i = 1 ; i <= _memberList.NumObjects() ; i++ ) + { + entity = _memberList.ObjectAt( i ); + + if ( !entity ) + continue; + + copiedEvent = new Event( ev ); + entity->ProcessEvent( copiedEvent ); + } +} + +//-------------------------------------------------------------- +// Name: GroupCancelEventsOfType() +// Class: Group +// +// Description: Has each memeber of the group cancel its events +// of the specified type +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Group::GroupCancelEventsOfType( Event *ev ) +{ + Entity *entity; + + for ( int i = 1 ; i <= _memberList.NumObjects() ; i++ ) + { + entity = _memberList.ObjectAt( i ); + + if ( !entity ) + continue; + + entity->CancelEventsOfType( ev ); + } +} + +//-------------------------------------------------------------- +// Name: Archive() +// Class: Group +// +// Description: Archives the class +// +// Parameters: Archive &arc +// +// Returns: None +//---------------------------------------------------------- +void Group::Archive( Archiver &arc ) +{ + int num , i; + EntityPtr ent; + EntityPtr *entityPointer; + + Listener::Archive( arc ); + + ent = NULL; + if ( arc.Saving() ) + { + num = _memberList.NumObjects(); + arc.ArchiveInteger( &num ); + + for ( i = 1 ; i <= num ; i++ ) + { + ent = _memberList.ObjectAt( i ); + arc.ArchiveSafePointer( &ent ); + } + } + else + { + arc.ArchiveInteger( &num ); + + _memberList.ClearObjectList(); + _memberList.Resize( num ); + + for ( i = 1 ; i <= num ; i++ ) + { + _memberList.AddObject( ent ); + + entityPointer = &_memberList.ObjectAt( i ); + + arc.ArchiveSafePointer( entityPointer ); + } + } + + arc.ArchiveInteger( &_id ); + arc.ArchiveString( &_groupDeathThread ); +} + + +//-------------------------------------------------------------- +// Name: GetNextMember() +// Class: Group +// +// Description: For return the next entity in the list after +// the entity passed in +// +// Parameters: Entity *entity -- The entity to use as a base index +// +// Returns: Entity* +//-------------------------------------------------------------- +Entity* Group::GetNextMember( Entity *entity ) +{ + Entity *ent; + + if ( _memberList.NumObjects() == 0 ) + return NULL; + + if ( !entity ) + return _memberList.ObjectAt( 1 ); + + + for ( int i = 1 ; i <= _memberList.NumObjects() ; i++ ) + { + ent = _memberList.ObjectAt( i ); + + if ( i == _memberList.NumObjects() ) + return NULL; + + if ( ent == entity ) + return _memberList.ObjectAt( i + 1 ); + } + + return NULL; +} + + +//-------------------------------------------------------------- +// Name: GetPreviousMember +// Class: Group +// +// Description: Returns the entity in the list that occurs +// just previous to the entity passed in +// +// Parameters: Entity *entity +// +// Returns: Entity* +//-------------------------------------------------------------- +Entity* Group::GetPreviousMember( Entity *entity ) +{ + Entity *ent; + + if ( _memberList.NumObjects() == 0 ) + return NULL; + + if ( !entity ) + return _memberList.ObjectAt( _memberList.NumObjects() ); + + for ( int i = _memberList.NumObjects() ; i > 0 ; i-- ) + { + ent = _memberList.ObjectAt( i ); + + if ( i == 1 ) + return NULL; + + if ( ent == entity ) + return _memberList.ObjectAt( i - 1 ); + } + + return NULL; +} + +void Group::RunGroupDeathThread(Entity *entity) +{ + if ( !_groupDeathThread.length() ) + return; + + ExecuteThread(_groupDeathThread, true, entity ); + +} + +//============================================================================== +// Actor Group +//============================================================================== +CLASS_DECLARATION( Group, ActorGroup, "actorgroup" ) +{ + { NULL, NULL } +}; + +//-------------------------------------------------------------- +// Name: ActorGroup() +// Class: ActorGroup +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +ActorGroup::ActorGroup() +{ +} + +//-------------------------------------------------------------- +// Name: ~ActorGroup() +// Class: ActorGroup +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +ActorGroup::~ActorGroup() +{ +} + + +//-------------------------------------------------------------- +// Name: CountMembersWithThisName( const str &name ) +// Class: ActorGroup +// +// Description: Loops through the group list and returns a count +// of how many have a name that matches the passed +// in name +// +// Parameters: const str &name +// +// Returns: int +//-------------------------------------------------------------- +int ActorGroup::CountMembersWithThisName( const str &name ) +{ + Entity *entity = 0; + Actor *actor = 0; + int count = 0; + + entity = GetNextMember( entity ); + if ( !entity ) + return 0; + if ( !entity->isSubclassOf( Actor ) ) + return 0; + + while ( entity != NULL ) + { + // Safeguard + if ( entity->isSubclassOf( Actor ) ) + { + actor = (Actor*)entity; + if (!stricmp(name.c_str() , actor->name.c_str() ) ) + count++; + } + + entity = GetNextMember( entity ); + } + + return count; +} + + +//-------------------------------------------------------------- +// +// Name: CountMembersAttackingEnemy +// Class: ActorGroup +// +// Description: Counts the number of actors in this group attacking +// the specified enemy. If the enemy is NULL, it counts +// all actors in this group with the ACTOR_FLAG_ATTACKING_ENEMY +// set. +// +// Parameters: Entity *enemy -- Enemy to test against +// +// Returns: int +// +//-------------------------------------------------------------- +int ActorGroup::CountMembersAttackingEnemy( Entity *enemy ) +{ + Entity *entity = 0; + Actor *actor = 0; + + int count = 0; + + entity = GetNextMember( entity ); + if ( !entity ) + return 0; + if ( !entity->isSubclassOf( Actor ) ) + return 0; + + while ( entity != NULL ) + { + // Safeguard + if ( entity->isSubclassOf( Actor ) ) + { + + // This actor isn't attacking... NEXT! + actor = (Actor*)entity; + if ( !actor->GetActorFlag(ACTOR_FLAG_ATTACKING_ENEMY) ) + { + entity = GetNextMember( entity ); + continue; + } + + // If we were not supplied an enemy to check against, just + // increment the count, otherwise check to see if the enemy + // matches. + if ( !enemy ) + count++; + else if ( actor->enemyManager->GetCurrentEnemy() == enemy ) + count++; + + } + entity = GetNextMember( entity ); + } + + return count; +} + +//============================================================================== +// Group Coordinator +//============================================================================== +CLASS_DECLARATION( Listener, GroupCoordinator, "groupcoordinator" ) +{ + { NULL, NULL } +}; + +//Global Safe Pointer +GroupCoordinatorPtr groupcoordinator; + +//-------------------------------------------------------------- +// Name: GroupCoordinator() +// Class: GroupCoordinator +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GroupCoordinator::GroupCoordinator() +{ +} + + +//-------------------------------------------------------------- +// Name: ~GroupCoordinator() +// Class: GroupCoordinator +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +GroupCoordinator::~GroupCoordinator() +{ + ClearGroupList(); +} + + +//-------------------------------------------------------------- +// Name: GetGroup() +// Class: GroupCoordinator +// +// Description: Returns a group pointer to the group with the +// specified ID +// +// Parameters: int ID +// +// Returns: Group* +//-------------------------------------------------------------- +Group* GroupCoordinator::GetGroup( int ID ) +{ + Group *group; + group = NULL; + + for ( int i = 1; i <= _groupList.NumObjects() ; i++ ) + { + group = _groupList.ObjectAt( i ); + + if ( !group ) + continue; + + if ( group->GetGroupID() == ID ) + return group; + } + + return NULL; +} + +void GroupCoordinator::MemberDied( Entity *entity , int ID ) +{ + Group *group; + str threadName; + int i; + + // Now see if the requested group exists + for ( i = 1; i <= _groupList.NumObjects() ; i++ ) + { + group = _groupList.ObjectAt( i ); + + if ( !group ) + continue; + + if ( group->GetGroupID() == ID ) + { + if (group->CountMembers() == 1 ) + { + group->RunGroupDeathThread(entity); + break; + } + } + } + + RemoveEntityFromGroup( entity , ID ); + +} + +void GroupCoordinator::SetGroupDeathThread( const str &threadName , int ID ) +{ + Group *group; + int i; + + // Now see if the requested group exists + for ( i = 1; i <= _groupList.NumObjects() ; i++ ) + { + group = _groupList.ObjectAt( i ); + + if ( !group ) + continue; + + if ( group->GetGroupID() == ID ) + { + group->SetGroupDeathThread( threadName ); + return; + } + } +} + +//-------------------------------------------------------------- +// Name: AddEntityToGroup() +// Class: GroupCoordinator +// +// Description: If the group exists, it tells the the group to +// add the specified entity, if it does not exist +// a new group is created and is then told to +// add the specified entity +// +// Parameters: Entity *ent +// int ID +// +// Returns: None +//-------------------------------------------------------------- +void GroupCoordinator::AddEntityToGroup( Entity *ent , int ID ) +{ + Group *group; + int i; + + // First see if the entity is in any other group + for ( i = 1; i <= _groupList.NumObjects() ; i++ ) + { + group = _groupList.ObjectAt( i ); + + if ( !group ) + continue; + + if ( group->IsThisEntityInGroup( ent ) && group->GetGroupID() != ID ) + group->RemoveMember( ent ); + } + + // Now see if the requested group exists + for ( i = 1; i <= _groupList.NumObjects() ; i++ ) + { + group = _groupList.ObjectAt( i ); + + if ( !group ) + continue; + + if ( group->GetGroupID() == ID ) + { + group->AddMember( ent ); + return; + } + } + + // Group didn't already exist, so lets make a new one + group = new Group; + group->SetGroupID( ID ); + group->AddMember( ent ); + + _groupList.AddObject( group ); +} + +void GroupCoordinator::RemoveEntityFromGroup( Entity *ent , int ID ) +{ + Group *group; + int i; + + // Now see if the requested group exists + for ( i = 1; i <= _groupList.NumObjects() ; i++ ) + { + group = _groupList.ObjectAt( i ); + + if ( !group ) + continue; + + if ( group->GetGroupID() == ID ) + { + group->RemoveMember( ent ); + return; + } + } +} + +//-------------------------------------------------------------- +// Name: RemoveGroup() +// Class: GroupCoordinator +// +// Description: Removes a group from the group list +// +// Parameters: int ID +// +// Returns: None +//-------------------------------------------------------------- +void GroupCoordinator::RemoveGroup( int ID ) +{ + Group *group; + + + for ( int i = _groupList.NumObjects(); i > 0 ; i-- ) + { + group = _groupList.ObjectAt( i ); + + if ( !group ) + continue; + + if ( group->GetGroupID() == ID ) + { + _groupList.RemoveObjectAt( i ); + delete group; + group = NULL; + return; + } + } +} + + +//-------------------------------------------------------------- +// Name: ClearGroupList() +// Class: GroupCoordinator +// +// Description: Clears the Group List +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void GroupCoordinator::ClearGroupList() +{ + Group *group; + + for ( int i = _groupList.NumObjects(); i > 0 ; i-- ) + { + group = _groupList.ObjectAt( i ); + + if ( !group ) + continue; + + RemoveGroup( group->GetGroupID() ); + } +} + + +//-------------------------------------------------------------- +// Name: SendEventToGroup +// Class: GroupCoordinator +// +// Description: Sends the specified event to the group +// so that the group will send it to everyone. This function +// always deletes the original event when it's done with it. +// +// Parameters: Event *ev +// int ID +// +// Returns: None +//-------------------------------------------------------------- +void GroupCoordinator::SendEventToGroup( Event *ev , int ID) +{ + Group *group; + + if ( !ev ) + return; + + for ( int i = 1; i <= _groupList.NumObjects() ; i++ ) + { + group = _groupList.ObjectAt( i ); + + if ( !group ) + continue; + + if ( group->GetGroupID() == ID ) + { + group->SendEventToGroup( ev ); + break; + } + } + + // Clean up the event; + delete ev; + ev = NULL; +} + +//-------------------------------------------------------------- +// Name: SendEventToGroup +// Class: GroupCoordinator +// +// Description: Sends the specified event to the group +// so that the group will send it to everyone +// +// Parameters: Event &ev +// int ID +// +// Returns: None +//-------------------------------------------------------------- +void GroupCoordinator::SendEventToGroup( Event &ev , int ID ) +{ + Group *group; + + for ( int i = 1; i <= _groupList.NumObjects() ; i++ ) + { + group = _groupList.ObjectAt( i ); + + if ( !group ) + continue; + + if ( group->GetGroupID() == ID ) + { + group->SendEventToGroup( ev ); + return; + } + } +} + +//-------------------------------------------------------------- +// Name: GroupCancelEventsOfType() +// Class: GroupCoordinator +// +// Description: Gets the appropriate group, and has the +// group cancel events of the specified type +// +// Parameters: Event *ev +// int ID +// +// Returns: None +//-------------------------------------------------------------- +void GroupCoordinator::GroupCancelEventsOfType( Event *ev , int ID ) +{ + Group *group; + + if ( !ev ) + return; + + for ( int i = 1; i <= _groupList.NumObjects() ; i++ ) + { + group = _groupList.ObjectAt( i ); + + if ( !group ) + continue; + + if ( group->GetGroupID() == ID ) + { + group->GroupCancelEventsOfType( ev ); + break; + } + } + + delete ev; +} + +//-------------------------------------------------------------- +// Name: Archive() +// Class: GroupCoordinator +// +// Description: Archives the class +// +// Parameters: Archiver &arc +// +// Returns: None +//---------------------------------------------------------- +void GroupCoordinator::Archive( Archiver &arc ) +{ + int num , i; + Group *group; + //Group **groupPointer; + Listener::Archive( arc ); + + group = NULL; + if ( arc.Saving() ) + { + num = _groupList.NumObjects(); + arc.ArchiveInteger( &num ); + + for ( i = 1 ; i <= num ; i++ ) + { + group = _groupList.ObjectAt( i ); + arc.ArchiveObject( group ); + } + } + else + { + arc.ArchiveInteger( &num ); + + _groupList.ClearObjectList(); + _groupList.Resize( num ); + + for ( i = 1 ; i <= num ; i++ ) + { + group = new Group; + + _groupList.AddObject( group ); + + arc.ArchiveObject( group ); + } + } +} + + diff --git a/dlls/game/groupcoordinator.hpp b/dlls/game/groupcoordinator.hpp new file mode 100644 index 0000000..d873196 --- /dev/null +++ b/dlls/game/groupcoordinator.hpp @@ -0,0 +1,167 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/groupcoordinator.h $ +// $Revision:: 158 $ +// $Author:: Sketcher $ +// $Date:: 4/11/02 11:34a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Classes for groups and group coordination. +// + + +#ifndef __GROUP_COORDINATOR_HPP__ +#define __GROUP_COORDINATOR_HPP__ + +#include "entity.h" + +//------------------------- CLASS ------------------------------ +// +// Name: Group +// Base Class: None +// +// Description: Base Class for Groups +// +// Method of Use: Instantiated by the group coordinator +// to maintain group data +//-------------------------------------------------------------- +class Group : public Listener +{ + public: + CLASS_PROTOTYPE( Group ); + Group(); + ~Group(); + + virtual void AddMember ( Entity *entity ); + virtual void RemoveMember ( Entity *entity ); + virtual void SetGroupDeathThread ( const str &threadName ); + virtual void ClearList (); + virtual void RunGroupDeathThread ( Entity *entity ); + virtual int CountMembers (); + bool IsThisTargetNameInGroup ( const str &name ); + bool IsThisEntityInGroup ( Entity *entity ); + void SendEventToGroup ( Event *ev ); + void SendEventToGroup ( Event &ev ); + void GroupCancelEventsOfType ( Event *ev ); + + int GetGroupID (); + void SetGroupID ( int ID ); + Entity* GetNextMember ( Entity *entity ); + Entity* GetPreviousMember ( Entity *entity ); + + virtual void Archive ( Archiver &arc ); + + protected: + + private: + Container< EntityPtr > _memberList; + int _id; + str _groupDeathThread; +}; + +inline int Group::CountMembers() +{ + return _memberList.NumObjects(); +} + +inline int Group::GetGroupID() +{ + return _id; +} + +inline void Group::SetGroupID( int ID ) +{ + _id = ID; +} + +inline void Group::SetGroupDeathThread( const str &threadName ) +{ + _groupDeathThread = threadName; +} + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: ActorGroup +// Base Class: Group +// +// Description: Group specifically for Actors +// +// Method of Use: Instantiated by the group coordinator +// +//-------------------------------------------------------------- +class ActorGroup : public Group +{ + public: + CLASS_PROTOTYPE( ActorGroup ); + ActorGroup(); + ~ActorGroup(); + + int CountMembersWithThisName( const str &name ); + int CountMembersAttackingEnemy( Entity *enemy = 0); + + protected: + private: +}; + + + + + + + +//------------------------- CLASS ------------------------------ +// +// Name: GroupCoordinator +// Base Class: None +// +// Description: Maintains and handles groups +// +// Method of Use: Instantiated by the level. +//-------------------------------------------------------------- +class GroupCoordinator : public Listener +{ + public: + CLASS_PROTOTYPE( GroupCoordinator ); + GroupCoordinator(); + ~GroupCoordinator(); + + Group* GetGroup ( int ID ); + void AddEntityToGroup ( Entity *ent , int ID ); + void RemoveEntityFromGroup ( Entity *ent , int ID ); + int CountGroups (); + void RemoveGroup ( int ID ); + void ClearGroupList (); + void SendEventToGroup ( Event *ev , int ID ); + void SendEventToGroup ( Event &ev , int ID ); + void GroupCancelEventsOfType ( Event *ev , int ID ); + void MemberDied ( Entity *entity , int ID ); + void SetGroupDeathThread ( const str &threadName , int ID ); + virtual void Archive ( Archiver &arc ); + + protected: + + private: + Container< Group* > _groupList; +}; + +inline int GroupCoordinator::CountGroups() +{ + return _groupList.NumObjects(); +} + + +typedef SafePtr GroupCoordinatorPtr; +extern GroupCoordinatorPtr groupcoordinator; + +#endif /* __GROUP_COORDINATOR_HPP__ */ diff --git a/dlls/game/healGroupMember.cpp b/dlls/game/healGroupMember.cpp new file mode 100644 index 0000000..b01eda0 --- /dev/null +++ b/dlls/game/healGroupMember.cpp @@ -0,0 +1,732 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/healGroupMember.cpp $ +// $Revision:: 6 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Generates a triage list from the actors group, then based on priority +// of patients ( right now, solely health based -- but distance is stored +// so we can factor that in if we need to ) it will run to the patient +// and play the specified "heal" animation... After the animation it will +// send a heal event to the patient which should kick start the regen process +// +// PARAMETERS: +// _anim -- The animation to play while "healing" +// _healDistance -- How far away to get from the patient +// _maxDistance -- How far away to consider a patient +// _initialHealPercentage -- How much to heal right away +// _regenHealPercentage -- How much to heal at each interval +// _regenInterval -- How often to regen +// _maxPercentage -- How Much Total Health can be regened +// +// ANIMATIONS: +// Heal Animation : Parameter +// "run" : TIKI Requirement +// "duck" : TIKI Requirement +// +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "healGroupMember.hpp" + +extern Event EV_Sentient_HealOverTime; +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, HealGroupMember, NULL ) + { + { &EV_Behavior_Args, &HealGroupMember::SetArgs }, + { &EV_Behavior_AnimDone, &HealGroupMember::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: HealGroupMember() +// Class: HealGroupMember +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +HealGroupMember::HealGroupMember() +{ + _nextTriageUpdate = 0.0f; +} + +//-------------------------------------------------------------- +// Name: HealGroupMember() +// Class: HealGroupMember +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +HealGroupMember::~HealGroupMember() +{ + triageEntry_t* checkEntry; + + for ( int i = _triageList.NumObjects() ; i > 0 ; i-- ) + { + checkEntry = _triageList.ObjectAt( i ); + delete checkEntry; + checkEntry = NULL; + _triageList.RemoveObjectAt( i ); + } +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: HealGroupMember +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::SetArgs( Event *ev ) +{ + _anim = ev->GetString( 1 ); + _healDistance = ev->GetFloat ( 2 ); + _maxDistance = ev->GetFloat ( 3 ); + _initialHealPercentage = ev->GetFloat ( 4 ); + _regenHealPercentage = ev->GetFloat ( 5 ); + _regenInterval = ev->GetFloat ( 6 ); + _maxPercentage = ev->GetFloat ( 7 ); +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: HealGroupMember +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::AnimDone( Event *ev ) +{ +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: HealGroupMember +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::Begin( Actor &self ) +{ + init( self ); +} + + + +//-------------------------------------------------------------- +// Name: init() +// Class: HealGroupMember +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::init( Actor &self ) +{ + updateTriageList( self ); + _currentPatient = findHighestPriorityPatient( self ); + setupGotoEntity( self ); + _state = HGM_STATE_GOTO_ENTITY; + updateTriageList( self ); + +} + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: HealGroupMember +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t HealGroupMember::Evaluate( Actor &self ) +{ + Event *injuredEvent; + + if ( !_currentPatient ) + { + SetFailureReason( "No Group Member To Heal" ); + _state = HGM_STATE_FAILED; + } + + switch ( _state ) + { + case HGM_STATE_GOTO_ENTITY: + doGotoEntity( self ); + break; + + case HGM_STATE_FACE_TARGET: + doRotateToEntity( self ); + break; + + case HGM_STATE_ANIMATE: + doAnimate( self ); + break; + + case HGM_STATE_HEAL: + doHeal( self ); + break; + + case HGM_STATE_FAILED: + return BEHAVIOR_FAILED; + break; + + case HGM_STATE_SUCCESS: + if ( allPatientsTreated( self ) ) + { + injuredEvent = new Event ( EV_Sentient_GroupMemberInjured ); + injuredEvent ->AddInteger( 0 ); + groupcoordinator->SendEventToGroup( injuredEvent , self.GetGroupID() ); + return BEHAVIOR_SUCCESS; + } + else + { + init( self ); + } + break; + + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: HealGroupMember +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::End(Actor &self) +{ +} + +//-------------------------------------------------------------- +// Name: setupGotoEntity() +// Class: HealGroupMember +// +// Description: Sets up the GotoEntity Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::setupGotoEntity( Actor &self ) +{ + _gotoEntity.SetEntity ( self, _currentPatient ); + _gotoEntity.SetDistance ( _healDistance ); + _gotoEntity.SetAnim ( "run" ); + _gotoEntity.Begin ( self ); + +} + +//-------------------------------------------------------------- +// Name: doGotoEntity() +// Class: HealGroupMember +// +// Description: Evaluates GotoEntity Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::doGotoEntity( Actor &self ) +{ + BehaviorReturnCode_t result; + result = _gotoEntity.Evaluate( self ); + + // Check if we need to re-evaluate the triage list + if ( level.time > _nextTriageUpdate ) + { + EntityPtr lastPatient; + lastPatient = _currentPatient; + + updateTriageList( self ); + _currentPatient = findHighestPriorityPatient( self ); + + if ( _currentPatient != lastPatient ) + setupGotoEntity( self ); + + } + + //Check if we are close enough + if ( self.WithinDistance( _currentPatient , _healDistance ) || result == BEHAVIOR_SUCCESS ) + { + _gotoEntity.End( self ); + _state = HGM_STATE_FACE_TARGET; + setupRotateToEntity( self ); + return; + } + + //Check for any failure condition + if ( result != BEHAVIOR_EVALUATING ) + { + gotoEntityFailed( self ); + } +} + +//-------------------------------------------------------------- +// Name: gotoEntityFailed() +// Class: HealGroupMember +// +// Description: Failure Handler for GotoEntity Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::gotoEntityFailed( Actor &self ) +{ + str FailureReason; + FailureReason = "GotoEntity Component Failed: " + _gotoEntity.GetFailureReason() + "\n"; + SetFailureReason( FailureReason ); + + _gotoEntity.End( self ); + _state = HGM_STATE_FAILED; +} + +//-------------------------------------------------------------- +// Name: doHeal() +// Class: HealGroupMember +// +// Description: Creates and HealOverTime event with the paramters +// passed into the behavior and tells the "injured" +// groupMember to process it +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::doHeal( Actor &self ) +{ + Event *regenEvent; + + regenEvent = new Event ( EV_Sentient_HealOverTime ); + + if ( !regenEvent) + { + assert(regenEvent); + return; + } + + regenEvent->AddFloat( _initialHealPercentage ); + regenEvent->AddFloat( _regenHealPercentage ); + regenEvent->AddFloat( _regenInterval ); + regenEvent->AddFloat( _maxPercentage ); + + _currentPatient->ProcessEvent( regenEvent ); + treatedPatient( self, _currentPatient ); + + _state = HGM_STATE_SUCCESS; + +} + +//-------------------------------------------------------------- +// Name: setupRotateToEntity( Actor &self ) +// Class: HealGroupMember +// +// Description: Sets up the RotateToEntity Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::setupRotateToEntity( Actor &self ) +{ + _rotateToEntity.SetEntity( _currentPatient ); + _rotateToEntity.Begin( self ); +} + +//-------------------------------------------------------------- +// Name: doRotateToEntity( Actor &self ) +// Class: HealGroupMember +// +// Description: Evaluates the RotateToEntity Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::doRotateToEntity( Actor &self ) +{ + BehaviorReturnCode_t result; + result = _rotateToEntity.Evaluate( self ); + + if ( result == BEHAVIOR_SUCCESS ) + { + _rotateToEntity.End( self ); + _state = HGM_STATE_ANIMATE; + setupAnimate( self ); + return; + } + + if ( result != BEHAVIOR_EVALUATING ) + { + rotateToEntityFailed( self ); + } + +} + +//-------------------------------------------------------------- +// Name: rotateToEntityFailed( Actor &self ) +// Class: HealGroupMember +// +// Description: Failure Handler for the RotateToEntity Component +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::rotateToEntityFailed( Actor &self ) +{ + str FailureReason; + FailureReason = "RotateToEntity Component Failed: " + _rotateToEntity.GetFailureReason() + "\n"; + SetFailureReason( FailureReason ); + + _rotateToEntity.End( self ); + _state = HGM_STATE_FAILED; +} + +//-------------------------------------------------------------- +// Name: setupAnimate() +// Class: HealGroupMember +// +// Description: Sets up the Animate State +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::setupAnimate( Actor &self ) +{ +/* + PostureStates_t pState = _currentPatient->movementSubsystem->getPostureState(); + + switch ( pState ) + { + case POSTURE_TRANSITION_STAND_TO_CROUCH: + _legAnim = "duck"; + break; + + case POSTURE_TRANSITION_STAND_TO_PRONE: + _legAnim = "duck"; + break; + + case POSTURE_TRANSITION_CROUCH_TO_STAND: + _legAnim = "idle"; + break; + + case POSTURE_TRANSITION_CROUCH_TO_PRONE: + _legAnim = "duck"; + break; + + case POSTURE_TRANSITION_PRONE_TO_CROUCH: + _legAnim = "duck"; + break; + + case POSTURE_TRANSITION_PRONE_TO_STAND: + _legAnim = "idle"; + break; + + case POSTURE_STAND: + _legAnim = "idle"; + break; + + case POSTURE_CROUCH: + _legAnim = "duck"; + break; + + case POSTURE_PRONE: + _legAnim = "duck"; + break; + + default: + _legAnim = "idle"; + break; + + } + +*/ + +} + +//-------------------------------------------------------------- +// Name: doAnimate() +// Class: HealGroupMember +// +// Description: Evaluates up the Animate State +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::doAnimate( Actor &self ) +{ + self.SetAnim( _legAnim ); + self.SetAnim( _anim , NULL , torso ); + + _state = HGM_STATE_HEAL; + +} + +//-------------------------------------------------------------- +// Name: animateFailed() +// Class: HealGroupMember +// +// Description: Failure Handler the Animate State +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::animateFailed( Actor &self ) +{ + str FailureReason; + FailureReason = "Animation Not Found\n"; + SetFailureReason ( FailureReason ); + + _state = HGM_STATE_FAILED; +} + +//-------------------------------------------------------------- +// Name: treatedPatient() +// Class: HealGroupMember +// +// Description: Sets the "treated" flag of the passed in entity's +// triageEntry to true +// +// Parameters: Actor &self +// Entity *ent +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::treatedPatient( Actor &self , Entity *ent ) +{ + triageEntry_t* checkEntry; + + //Lets check if this entity is already in the triage list + for ( int i = _triageList.NumObjects() ; i > 0 ; i-- ) + { + checkEntry = _triageList.ObjectAt( i ); + if ( checkEntry->ent == ent ) + { + checkEntry->treated = true; + } + } +} + +//-------------------------------------------------------------- +// Name: findHighestPriorityPatient() +// Class: HealGroupMember +// +// Description: Finds the entity in the triage list with the +// lowest health +// +// Parameters: Actor &self +// +// Returns: Actor* +//-------------------------------------------------------------- +Actor* HealGroupMember::findHighestPriorityPatient( Actor &self ) +{ + triageEntry_t* checkEntry; + Actor *highestPriorityPatient; + + float lowestHealth; + + lowestHealth = 9999999.9f; + highestPriorityPatient = 0; + + //Lets check if this entity is already in the triage list + for ( int i = _triageList.NumObjects() ; i > 0 ; i-- ) + { + checkEntry = _triageList.ObjectAt( i ); + if ( checkEntry->health < lowestHealth && checkEntry->dist <= _maxDistance ) + { + lowestHealth = checkEntry->health; + highestPriorityPatient = (Actor*)(Entity*)checkEntry->ent; + } + } + + return highestPriorityPatient; +} + +//-------------------------------------------------------------- +// Name: allPatientsTreated() +// Class: HealGroupMember +// +// Description: Iterates through the triageList and checks if +// every entry has it's "treated" flag set to true +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +bool HealGroupMember::allPatientsTreated( Actor &self ) +{ + triageEntry_t* checkEntry; + + //Lets check if this entity is already in the triage list + for ( int i = _triageList.NumObjects() ; i > 0 ; i-- ) + { + checkEntry = _triageList.ObjectAt( i ); + if (!checkEntry->treated ) + return false; + } + + return true; +} + +//-------------------------------------------------------------- +// Name: patientTreated() +// Class: HealGroupMember +// +// Description: Checks if an individual patient has been +// treated +// +// Parameters: Actor &self +// Entity *ent +// +// Returns: True or False +//-------------------------------------------------------------- +bool HealGroupMember::patientTreated( Actor &self , Entity *ent ) +{ + triageEntry_t* checkEntry; + + //Lets check if this entity is already in the triage list + for ( int i = _triageList.NumObjects() ; i > 0 ; i-- ) + { + checkEntry = _triageList.ObjectAt( i ); + if ( checkEntry->ent == ent ) + { + return checkEntry->treated; + } + } + + return false; +} + +//-------------------------------------------------------------- +// Name: updateTriageList() +// Class: HealGroupMember +// +// Description: Grabs the actor's group from the group list, then +// iterates through it, updating the triageList as +// necessary. +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::updateTriageList( Actor &self ) +{ + Group *group; + Entity *groupEnt; + float health , maxHealth; + + groupEnt = NULL; + + group = groupcoordinator->GetGroup( self.GetGroupID() ); + + groupEnt = group->GetNextMember( groupEnt ); + + while ( groupEnt != NULL ) + { + health = groupEnt->getHealth(); + maxHealth = groupEnt->getMaxHealth(); + + // See if this group member is below 50% + if ( ( health / maxHealth ) < .5 ) + updateTriageEntry( self , groupEnt ); + + groupEnt = group->GetNextMember( groupEnt ); + } + + _nextTriageUpdate = level.time + G_Random() + 2.5f; +} + +//-------------------------------------------------------------- +// Name: updateTriageEntry() +// Class: HealGroupMember +// +// Description: Updates or Adds an indivdual triageEntry in the list +// +// Parameters: Actor &self +// Entity *ent +// +// Returns: None +//-------------------------------------------------------------- +void HealGroupMember::updateTriageEntry( Actor &self , Entity *ent) +{ + float entHealth; + float entDist; + triageEntry_t* checkEntry; + + entHealth = ent->getHealth(); + entDist = Vector::Distance( self.origin , ent->origin ); + + //Lets check if this entity is already in the triage list + for ( int i = _triageList.NumObjects() ; i > 0 ; i-- ) + { + checkEntry = _triageList.ObjectAt( i ); + if ( checkEntry->ent == ent ) + { + checkEntry->health = entHealth; + checkEntry->dist = entDist; + return; + } + } + + //Well the entity wasn't in the list, so we need to add it + checkEntry = new triageEntry_t; + checkEntry->ent = ent; + checkEntry->health = entHealth; + checkEntry->dist = entDist; + checkEntry->treated = false; + + _triageList.AddObject(checkEntry); +} diff --git a/dlls/game/healGroupMember.hpp b/dlls/game/healGroupMember.hpp new file mode 100644 index 0000000..30b66ce --- /dev/null +++ b/dlls/game/healGroupMember.hpp @@ -0,0 +1,206 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/healGroupMember.hpp $ +// $Revision:: 1 $ +// $Author:: Sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// healGroupMember Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class HealGroupMember; + +#ifndef __HEAL_GROUP_MEMBER_HPP__ +#define __HEAL_GROUP_MEMBER_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" +#include "rotateToEntity.hpp" + +//------------------------- CLASS ------------------------------ +// +// Name: HealGroupMember +// Base Class: Behavior +// +// Description: Will have the actor run to another actor in its +// group, play an animation, and send them the +// healovertime event once the actor is close enough +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class HealGroupMember : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + HGM_STATE_GOTO_ENTITY, + HGM_STATE_FACE_TARGET, + HGM_STATE_ANIMATE, + HGM_STATE_HEAL, + HGM_STATE_FAILED, + HGM_STATE_SUCCESS, + } healGroupMemberStates_t; + + typedef struct + { + EntityPtr ent; + float health; + float dist; + bool treated; + } triageEntry_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _anim; + float _healDistance; + float _maxDistance; + float _initialHealPercentage; + float _regenHealPercentage; + float _regenInterval; + float _maxPercentage; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void init ( Actor &self ); + void doHeal ( Actor &self ); + + void setupGotoEntity ( Actor &self ); + void doGotoEntity ( Actor &self ); + void gotoEntityFailed ( Actor &self ); + + void setupRotateToEntity ( Actor &self ); + void doRotateToEntity ( Actor &self ); + void rotateToEntityFailed ( Actor &self ); + + void setupAnimate ( Actor &self ); + void doAnimate ( Actor &self ); + void animateFailed ( Actor &self ); + + void updateTriageList ( Actor &self ); + void updateTriageEntry ( Actor &self , Entity *ent ); + bool allPatientsTreated ( Actor &self ); + bool patientTreated ( Actor &self , Entity *ent ); + void treatedPatient ( Actor &self , Entity *ent ); + Actor* findHighestPriorityPatient ( Actor &self ); + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( HealGroupMember ); + + HealGroupMember(); + ~HealGroupMember(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoEntity _gotoEntity; + RotateToEntity _rotateToEntity; + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + unsigned int _state; + ActorPtr _currentPatient; + str _legAnim; + float _nextTriageUpdate; + Container _triageList; + + + }; + + +inline void HealGroupMember::Archive( Archiver &arc ) +{ + int num , i; + triageEntry_t* checkEntry; + + Behavior::Archive ( arc ); + + // Archive Parameters + arc.ArchiveString ( &_anim ); + arc.ArchiveFloat ( &_healDistance ); + arc.ArchiveFloat ( &_maxDistance ); + arc.ArchiveFloat ( &_initialHealPercentage ); + arc.ArchiveFloat ( &_regenHealPercentage ); + arc.ArchiveFloat ( &_regenInterval ); + arc.ArchiveFloat ( &_maxPercentage ); + + // Archive Components + arc.ArchiveObject ( &_gotoEntity ); + arc.ArchiveObject ( &_rotateToEntity ); + + // Archive Member Vars + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveSafePointer ( &_currentPatient ); + arc.ArchiveString ( &_legAnim ); + arc.ArchiveFloat ( &_nextTriageUpdate ); + + if ( arc.Saving() ) + { + num = _triageList.NumObjects(); + arc.ArchiveInteger( &num ); + + for ( i = num ; i > 0 ; i-- ) + { + checkEntry = _triageList.ObjectAt( i ); + arc.ArchiveSafePointer( &checkEntry->ent ); + arc.ArchiveFloat( &checkEntry->health ); + arc.ArchiveFloat( &checkEntry->dist ); + arc.ArchiveBool( &checkEntry->treated ); + } + } + else + { + arc.ArchiveInteger( &num ); + _triageList.ClearObjectList(); + _triageList.Resize( num ); + + for ( i = 1 ; i<= num ; i++ ) + { + checkEntry = new triageEntry_t; + arc.ArchiveSafePointer( &checkEntry->ent); + arc.ArchiveFloat( &checkEntry->health ); + arc.ArchiveFloat( &checkEntry->dist ); + arc.ArchiveBool( &checkEntry->treated ); + } + + } +} + + + +#endif /* __HEAL_GROUP_MEMBER_HPP__ */ + diff --git a/dlls/game/health.cpp b/dlls/game/health.cpp new file mode 100644 index 0000000..923cecc --- /dev/null +++ b/dlls/game/health.cpp @@ -0,0 +1,229 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/health.cpp $ +// $Revision:: 21 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Health powerup +// + +#include "_pch_cpp.h" +#include "item.h" +#include "inventoryitem.h" +#include "sentient.h" +#include "health.h" +#include "weaputils.h" +#include "player.h" +#include "mp_manager.hpp" +#include + +CLASS_DECLARATION( InventoryItem, HealthInventoryItem, "" ) +{ + { &EV_InventoryItem_Use, &HealthInventoryItem::Use }, + { &EV_ProcessGameplayData, &HealthInventoryItem::processGameplayData }, + + { NULL, NULL } +}; + + +void HealthInventoryItem::Use( Event *ev ) +{ + Entity *other; + Sentient *sen; + //Event *event; + str sound_name; + + + other = ev->GetEntity( 1 ); + if ( !other || !other->isSubclassOf( Sentient ) ) + { + return; + } + + sen = ( Sentient * )other; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( gpm->hasObject(getArchetype()) ) + { + if ( gpm->hasFormula("HealthPotion") ) + { + GameplayFormulaData fd(this); + amount = gpm->calculate("HealthPotion", fd, sen->max_health); + } + str snd = gpm->getStringValue(getArchetype() + ".Use", "wav"); + if ( snd.length() ) + { + int channel = CHAN_BODY; + float volume = -1.0f; + float mindist = -1.0f; + if ( gpm->hasProperty(getArchetype() + ".Use","channel") ) + channel = (int)gpm->getFloatValue(getArchetype() + ".Use", "channel"); + if ( gpm->hasProperty(getArchetype() + ".Use","volume") ) + volume = (int)gpm->getFloatValue(getArchetype() + ".Use", "volume"); + if ( gpm->hasProperty(getArchetype() + ".Use","mindist") ) + mindist = (int)gpm->getFloatValue(getArchetype() + ".Use", "mindist"); + Sound(snd, channel, volume, mindist); + } + } + + sen->health += (float)amount; + + if ( sen->health > sen->max_health ) + { + sen->health = sen->max_health; + } + + // If we are on fire stop it + + sen->ProcessEvent( EV_Sentient_StopOnFire ); + + // Spawn special effect around sentient + + // Uncomment if we need it + /*sound_name = GetRandomAlias( "snd_use" ); + + if ( sound_name ) + other->Sound( sound_name.c_str(), 0 ); + + event = new Event( EV_AttachModel ); + event->AddString( "models/fx_tikifx2.tik" ); + event->AddString( "Bip01 Spine" ); + // set scale + event->AddFloat( 1.0f ); + // set targetname + event->AddString( "regen" ); + // set detach_at_death + event->AddInteger( 1 ); + // set remove_time + event->AddFloat( 2.0f ); + other->ProcessEvent( event );*/ + + PostEvent( EV_Remove, 0.0f ); +} + +CLASS_DECLARATION( Item, Health, "health_020" ) +{ + { &EV_Item_Pickup, &Health::PickupHealth }, + + { NULL, NULL } +}; + +Health::Health() +{ + if ( multiplayerManager.checkFlag( MP_FLAG_NO_HEALTH ) ) + { + PostEvent( EV_Remove, EV_REMOVE ); + return; + } + + setAmount( 20 ); + + item_name = "health"; +} + +void Health::PickupHealth( Event *ev ) +{ + Sentient *sen; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( !other || !other->isSubclassOf( Sentient ) ) + { + return; + } + + sen = ( Sentient * )other; + + if ( sen->health >= sen->max_health ) + return; + + if ( !ItemPickup( other, false ) ) + { + return; + } + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( gpm->hasObject(getArchetype()) ) + { + if ( gpm->hasFormula("HealthPotion") ) + { + GameplayFormulaData fd(this); + amount = gpm->calculate("HealthPotion", fd, sen->max_health); + } + str snd = gpm->getStringValue(getArchetype() + ".Use", "wav"); + if ( snd.length() ) + { + int channel = CHAN_BODY; + float volume = -1.0f; + float mindist = -1.0f; + if ( gpm->hasProperty(getArchetype() + ".Use","channel") ) + channel = (int)gpm->getFloatValue(getArchetype() + ".Use", "channel"); + if ( gpm->hasProperty(getArchetype() + ".Use","volume") ) + volume = (int)gpm->getFloatValue(getArchetype() + ".Use", "volume"); + if ( gpm->hasProperty(getArchetype() + ".Use","mindist") ) + mindist = (int)gpm->getFloatValue(getArchetype() + ".Use", "mindist"); + Sound(snd, channel, volume, mindist); + } + } + + sen->health += (float)amount; + + if ( sen->health > sen->max_health ) + { + sen->health = sen->max_health; + } + + // If we are on fire stop it + + sen->ProcessEvent( EV_Sentient_StopOnFire ); +} + + +//-------------------------------------------------------------- +// +// Name: processGameplayData +// Class: HealthInventoryItem +// +// Description: Called usually from the tiki file after all other +// server side events are called. +// +// Parameters: Event *ev -- not used +// +// Returns: None +// +//-------------------------------------------------------------- +void HealthInventoryItem::processGameplayData( Event *ev ) +{ + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(getArchetype()) ) + return; + + str objname = getArchetype(); + str useanim, usetype, usethread; + if ( gpm->hasProperty(objname, "useanim") ) + useanim = gpm->getStringValue(objname, "useanim"); + if ( gpm->hasProperty(objname, "usethread") ) + usethread = gpm->getStringValue(objname, "usethread"); + if ( gpm->hasProperty(objname + ".Pickup", "icon") ) + usetype = gpm->getStringValue(objname + ".Pickup", "icon"); + + // If any of these strings were set, add to our UseData object. + if ( useanim.length() || usethread.length() || usetype.length() ) + { + if ( !useData ) + useData = new UseData(); + + useData->setUseAnim(useanim); + useData->setUseThread(usethread); + useData->setUseType(usetype); + } +} diff --git a/dlls/game/health.h b/dlls/game/health.h new file mode 100644 index 0000000..72fad21 --- /dev/null +++ b/dlls/game/health.h @@ -0,0 +1,46 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/health.h $ +// $Revision:: 7 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Health powerup +// + +#ifndef __HEALTH_H__ +#define __HEALTH_H__ + +#include "g_local.h" +#include "item.h" +#include "sentient.h" +#include "item.h" + +class Health : public Item + { + public: + CLASS_PROTOTYPE( Health ); + + Health(); + virtual void PickupHealth( Event *ev ); + }; + +class HealthInventoryItem : public Item +{ + public: + CLASS_PROTOTYPE( HealthInventoryItem ); + + virtual void processGameplayData( Event *ev ); + void Use( Event *ev ); + +}; + +#endif /* health.h */ diff --git a/dlls/game/helper_node.cpp b/dlls/game/helper_node.cpp new file mode 100644 index 0000000..d1060f0 --- /dev/null +++ b/dlls/game/helper_node.cpp @@ -0,0 +1,2992 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/helper_node.cpp $ +// $Revision:: 50 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// + + +#include "_pch_cpp.h" +//#include "g_local.h" +#include "helper_node.h" +#include "actor.h" + +extern Event EV_SpawnFlags; +// +//====================================== +// Helper Node Event Declaration +//====================================== +// + +Event EV_HelperNodeController_SendEvent +( + "sendevent", + EV_DEFAULT, + "ssSSSSSSS", + "targetname eventName token1 token2 token3 token4 token5 token6 token7", + "Sends the event to the specified node" +); +Event EV_HelperNode_SetNodeAnim +( + "setnodeanim", + EV_SCRIPTONLY, + "s", + "animation", + "Sets the animation that will be played by the actor on this node" +); +Event EV_HelperNode_SetNodeEntryThread +( + "setnodeentrythread", + EV_SCRIPTONLY, + "s", + "thread", + "Sets the thread that will be called by the actor when it reaches this node" +); +Event EV_HelperNode_SetNodeExitThread +( + "setnodeexitthread", + EV_SCRIPTONLY, + "s", + "thread", + "Sets the thread that will be called by the actor when it leaves this node" +); +Event EV_HelperNode_SetAnimCount +( + "animcount", + EV_SCRIPTONLY, + "i", + "num_times", + "Sets the max number of times that an animation can be set on this node" +); +Event EV_HelperNode_SetAnimTarget +( + "animtarget", + EV_SCRIPTONLY, + "s", + "targetname", + "Allows you to specify a target name to apply the animation to" +); +Event EV_HelperNode_SetAnimActive +( + "animactive", + EV_SCRIPTONLY, + "b", + "active", + "Specifies if the animation component is active" +); +Event EV_HelperNode_SetCoverType +( + "setcovertype", + EV_SCRIPTONLY, + "s", + "cover_type", + "Specifies the covertype of the node" +); +Event EV_HelperNode_SetCoverDirection +( + "setcoverdir", + EV_SCRIPTONLY, + "s", + "cover_dir", + "Specifies the direction from the cover that the AI can attack" +); +Event EV_HelperNode_SetWait +( + "wait", + EV_SCRIPTONLY, + "f", + "time", + "Makes a patrolling AI pause at this node for the specified time" +); +Event EV_HelperNode_SetWaitRandom +( + "waitrandom", + EV_SCRIPTONLY, + "f", + "max_time", + "Makes a patrolling AI pause randomly for a min time of 1 to a max time of specified" +); +Event EV_HelperNode_WaitForAnim +( + "waitforanim", + EV_SCRIPTONLY, + "b", + "wait_for_anim", + "Makes the AI wait until the anim is finished before finishing a behavior" +); +Event EV_HelperNode_SetKillTarget +( + "attacktarget", + EV_SCRIPTONLY, + "s", + "targetname", + "Specifies a target for a sniping AI to attack" +); +Event EV_HelperNode_SetCustomType +( + "setcustomtype", + EV_SCRIPTONLY, + "s", + "type", + "Specifies a custom helper node type" +); +Event EV_HelperNode_SetMaxKills +( + "maxkills", + EV_SCRIPTONLY, + "i", + "max_kills", + "Specifies the maximum number of kills that can be done from this node" +); +Event EV_HelperNode_SetMinHealth +( + "minhealth", + EV_SCRIPTONLY, + "f", + "min_health", + "Specifies the minimum health an actor is allowed to get to before leaving this node" +); +Event EV_HelperNode_SetMinEnemyRange +( + "minenemyrange", + EV_SCRIPTONLY, + "f", + "min_range", + "Specifies the minimum range an enemy is allowed to get before actor leaves this node" +); +Event EV_HelperNode_SetActivationRange +( + "activationrange", + EV_SCRIPTONLY, + "f", + "activation_range", + "Specifies the range an actor needs to be to consider using the node" +); +Event EV_HelperNode_SetPriority +( + "priority", + EV_SCRIPTONLY, + "f", + "priority", + "Specfies the priority for this node" +); +Event EV_HelperNode_SetNodeID +( + "nodeID", + EV_SCRIPTONLY, + "i", + "node_id", + "Assigns an ID to this node" +); +Event EV_HelperNode_SetCriticalChange +( + "criticalchange", + EV_SCRIPTONLY, + NULL, + NULL, + "Alerts the node to a critical change" +); +Event EV_HelperNode_SetDescriptor +( + "descriptor", + EV_SCRIPTONLY, + "s", + "descriptorValue", + "sets an additional descriptor for a node" +); +Event EV_HelperNode_SendCommand +( + "sendnodecommand", + EV_SCRIPTONLY, + "s", + "commandToSend", + "sends an event to the user of the node" +); +Event EV_HelperNode_AddAnimation +( + "addanim", + EV_SCRIPTONLY, + "ss", + "anim waittype", + "Adds an animation to the custom anim list" +); + +//====================================== +// Helper Node Container +//====================================== + +Container HelperNodes; + +char CoverTypeStrings[ COVER_TYPE_TOTAL_NUMBER ][ 32 ] = +{ + "none", + "crate", + "wall" +}; + +char CoverDirStrings[ COVER_DIRECTION_TOTAL_NUMBER ][ 32 ] = +{ + "none", + "left", + "right", + "all" +}; + +char DescriptorTypeStrings[ DESCRIPTOR_TOTAL_NUMBER ][ 32 ] = +{ + "none", + "corridor" +}; + + + +//====================================== +// Helper Node Initialization +//====================================== +CLASS_DECLARATION( Entity, HelperNodeController, "helpernodecontroller" ) +{ + { &EV_HelperNodeController_SendEvent, &HelperNodeController::SendEventToNode }, + + { NULL, NULL } +}; + +HelperNodeController::HelperNodeController() +{ + targetname = "nodecontroller"; +} + +HelperNodeController::~HelperNodeController() +{ +} + + +void HelperNodeController::SendEventToNode( Event *ev ) +{ + str nodeTargetName; + str eventName; + Event *nodeEvent; + HelperNode* node; + int i; + + //First grab our node target and event + nodeTargetName = ev->GetString( 1 ); + eventName = ev->GetString( 2 ); + + node = NULL; + //Now lets find our target + for ( i = HelperNodes.NumObjects() ; i > 0 ; i-- ) + { + node = HelperNodes.ObjectAt( i ); + if ( node->targetname == nodeTargetName ) + break; + } + + if ( !node ) + return; + + // Create our event + nodeEvent = new Event( eventName.c_str() ); + if ( !nodeEvent ) + return; + + + //Load our event with parameters + for ( i = 3 ; i <= ev->NumArgs() ; i++ ) //start at 3, because we burnt through the first 2 already + { + nodeEvent->AddToken( ev->GetToken( i ) ); + } + + node->ProcessEvent( nodeEvent ); +} + +// +// Class Declaration and Event Registration +// +CLASS_DECLARATION( Listener, HelperNode, "info_helpernode" ) +{ + // Core Events + { &EV_SetTargetName, &HelperNode::SetTargetName }, + { &EV_SetTarget, &HelperNode::SetTarget }, + { &EV_SetAngle, &HelperNode::SetAngle }, + { &EV_SetOrigin, &HelperNode::SetOrigin }, + { &EV_SpawnFlags, &HelperNode::SetFlags }, + { &EV_HelperNode_SetNodeAnim, &HelperNode::SetAnim }, + { &EV_HelperNode_SetNodeEntryThread, &HelperNode::SetEntryThread }, + { &EV_HelperNode_SetNodeExitThread, &HelperNode::SetExitThread }, + { &EV_HelperNode_SetAnimCount, &HelperNode::SetAnimCount }, + { &EV_HelperNode_SetAnimTarget, &HelperNode::SetAnimTarget }, + { &EV_HelperNode_SetAnimActive, &HelperNode::SetAnimActive }, + { &EV_HelperNode_SetCoverType, &HelperNode::SetCoverType }, + { &EV_HelperNode_SetCoverDirection, &HelperNode::SetCoverDir }, + { &EV_HelperNode_SetWait, &HelperNode::SetWait }, + { &EV_HelperNode_SetWaitRandom, &HelperNode::SetWaitRandom }, + { &EV_HelperNode_WaitForAnim, &HelperNode::SetWaitForAnim }, + { &EV_HelperNode_SetKillTarget, &HelperNode::SetKillTarget }, + { &EV_HelperNode_SetCustomType, &HelperNode::SetCustomType }, + { &EV_HelperNode_SetMaxKills, &HelperNode::SetMaxKills }, + { &EV_HelperNode_SetMinHealth, &HelperNode::SetMinHealth }, + { &EV_HelperNode_SetMinEnemyRange, &HelperNode::SetMinEnemyRange }, + { &EV_HelperNode_SetActivationRange, &HelperNode::SetActivationRange }, + { &EV_HelperNode_SetPriority, &HelperNode::SetPriority }, + { &EV_HelperNode_SetNodeID, &HelperNode::SetID }, + { &EV_HelperNode_SetCriticalChange, &HelperNode::SetCriticalChange }, + { &EV_HelperNode_SetDescriptor, &HelperNode::SetDescriptor }, + { &EV_HelperNode_SendCommand, &HelperNode::SendCommand }, + { &EV_HelperNode_AddAnimation, &HelperNode::AddAnimation }, + { NULL, NULL } +}; + + +// +// Name: HelperNode() +// Class: HelperNode +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +// +HelperNode::HelperNode() +{ + if ( !LoadingSavegame ) + { + AddHelperNodeToList( this ); + _init(); + } +} + + +// +// Name: ~HelperNode() +// Class: HelperNode +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +HelperNode::~HelperNode() +{ + customAnimListEntry_t* checkEntry; + + for ( int i = _customAnimList.NumObjects() ; i > 0 ; i-- ) + { + checkEntry = _customAnimList.ObjectAt( i ); + delete checkEntry; + checkEntry = NULL; + _customAnimList.RemoveObjectAt( i ); + } +} + + +// +// Name: _init() +// Class: HelperNode +// +// Description: Initializes default values +// +// Parameters: None +// +// Returns: None +// +void HelperNode::_init() +{ + _entryThread = ""; + _exitThread = ""; + _anim = ""; + _animtarget = ""; + _killtarget = ""; + + // Default Value for _id means the node is open to everyone + _id = -1; + + // Default Value for _animcount means no maximum + _animcount = -1; + + // Default Value for _maxkills means no maximum + _maxkills = -1; + + + _animactive = true; + _coveractive = true; + _waitrandom = false; + _waitforanim = false; + _criticalchange = false; + + _waittime = 0.0f; + _minhealth = 0.0f; + _minenemyrange = 0.0f; + _activationRange = 0.0f; + _priority = 0.0f; + _lastUseTime = 0.0f; + + _covertype = COVER_TYPE_NONE; + _coverdir = COVER_DIRECTION_NONE; + _descriptor = DESCRIPTOR_NONE; + + _reserved = false; + _nodeflags = 0; + + _customAnimListIndex = 1; + _usingCustomAnimList = false; + _user = NULL; + +} + +//====================================== +// Core Functionality +//====================================== +void HelperNode::SetTarget( Event *ev ) +{ + str name; + name = ev->GetString( 1 ); + + SetTarget(name); +} + +void HelperNode::SetTargetName( Event *ev ) +{ + str name; + name = ev->GetString( 1 ); + + SetTargetName(name); +} + +void HelperNode::SetAngle( Event *ev ) +{ + Vector movedir; + + movedir = G_GetMovedir( ev->GetFloat( 1 ) ); + SetAngle(movedir.toAngles()); +} + +void HelperNode::SetOrigin( Event *ev ) +{ + Vector pos; + pos = ev->GetVector( 1 ); + + SetOrigin(pos); +} + +void HelperNode::SetTarget( const str &name ) +{ + target = name; +} + +void HelperNode::SetTargetName( const str &name ) +{ + targetname = name; +} + +void HelperNode::SetAngle( const Vector &ang ) +{ + angles = ang; +} + +void HelperNode::SetOrigin( const Vector &pos ) +{ + origin = pos; +} + +//====================================== +// Helper Node Event Interface +//====================================== + +// +// Name: SetThread() +// Class: HelperNode +// +// Description: Calls SetEntryThread() +// +// Parameters: Event *ev - Event containing the string +// +// Returns: None +// +void HelperNode::SetEntryThread( Event *ev ) +{ + SetEntryThread(ev->GetString( 1 ) ); +} + + +// +// Name: SetExitThread() +// Class: HelperNode +// +// Description: Calls SetExitThread() +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +void HelperNode::SetExitThread( Event *ev ) +{ + SetExitThread(ev->GetString( 1 ) ); +} + +// +// Name: SetAnim() +// Class: HelperNode +// +// Description: Calls SetAnim() +// +// Parameters: Event *ev - Event containing the string +// +// Returns: None +// +void HelperNode::SetAnim( Event *ev ) +{ + SetAnim( ev->GetString( 1 ) ); +} + + +// +// Name: SetAnimTarget() +// Class: HelperNode +// +// Description: Calls SetAnimTarget() +// +// Parameters: Event *ev - Event containing the string +// +// Returns: None +// +void HelperNode::SetAnimTarget( Event *ev ) +{ + SetAnimTarget( ev->GetString( 1 ) ); +} + + +// +// Name: SetKillTarget() +// Class: HelperNode +// +// Description: Calls SetKillTarget() +// +// Parameters: Event *ev - Event contaning the string +// +// Returns: None +// +void HelperNode::SetKillTarget( Event *ev ) +{ + SetKillTarget( ev->GetString( 1 ) ); +} + + +// +// Name: SetCustomType() +// Class: HelperNode +// +// Description: Calls SetCustomType() +// +// Parameters: Event *ev - Event contaning the string +// +// Returns: None +// +void HelperNode::SetCustomType( Event *ev ) +{ + SetCustomType( ev->GetString( 1 ) ); +} + +// +// Name: SetID() +// Class: HelperNode +// +// Description: Calls SetID() +// +// Parameters: Event *ev - Event containing the integer +// +// Returns: None +// +void HelperNode::SetID( Event *ev ) +{ + SetID(ev->GetInteger( 1 ) ); +} + + +// +// Name: SetAnimCount() +// Class: HelperNode +// +// Description: Calls SetAnimCount() +// +// Parameters: Event *ev - Event containing the integer +// +// Returns: None +// +void HelperNode::SetAnimCount( Event *ev ) +{ + SetAnimCount( ev->GetInteger( 1 ) ); +} + + +// +// Name: SetMaxKills() +// Class: HelperNode +// +// Description: Calls SetMaxKills() +// +// Parameters: Event *ev - Event containing the integer +// +// Returns: None +// +void HelperNode::SetMaxKills( Event *ev ) +{ + SetMaxKills( ev->GetInteger( 1 ) ); +} + + +// +// Name: SetWait() +// Class: HelperNode +// +// Description: Calls SetWait() +// +// Parameters: Event *ev - Event containing the float +// +// Returns: None +// +void HelperNode::SetWait( Event *ev ) +{ + SetWait( ev->GetFloat( 1 ) ); +} + + +// +// Name: SetWaitRandom() +// Class: HelperNode +// +// Description: Calls SetWaitRandom() +// +// Parameters: Event *ev - Event containing the float +// +// Returns: None +// +void HelperNode::SetWaitRandom( Event *ev ) +{ + SetWaitRandom( ev->GetFloat( 1 ) ); +} + + +// +// Name: SetWaitForAnim() +// Class: HelperNode +// +// Description: Calls SetWaitForAnim() +// +// Parameters: Event *ev +// +// Returns: None +// +void HelperNode::SetWaitForAnim( Event *ev ) +{ + SetWaitForAnim( ev->GetBoolean( 1 ) ); +} + + +// +// Name: SetCriticalChange() +// Class: HelperNode +// +// Description: Calls SetCriticalChange() +// +// Parameters: Event *ev +// +// Returns: None +// +void HelperNode::SetCriticalChange( Event *ev ) +{ + SetCriticalChange( true ); +} + + +// +// Name: SetDescriptor() +// Class: HelperNode +// +// Description: Gets the enum for the string and calls +// SetDescriptor() +// +// Parameters: Event *ev -- The event containing the string +// +// Returns: None +// +void HelperNode::SetDescriptor( Event *ev ) +{ + str descriptor = ev->GetString( 1 ); + SetDescriptor( _DescriptorForString(descriptor) ); +} + + +void HelperNode::SetPriority( Event *ev ) +{ + float priority = ev->GetFloat( 1 ); + SetPriority( priority ); +} + +// +// Name: SetMinHealth() +// Class: HelperNode +// +// Description: Calls SetMinHealth() +// +// Parameters: Event *ev - Event containing the float +// +// Returns: None +// +void HelperNode::SetMinHealth( Event *ev ) +{ + SetMinHealth( ev->GetFloat( 1 ) ); +} + + +// +// Name: SetMinEnemyRange() +// Class: HelperNode +// +// Description: Calls SetMinEnemyRange() +// +// Parameters: Event *ev - Event containing the float +// +// Returns: None +// +void HelperNode::SetMinEnemyRange( Event *ev ) +{ + SetMinEnemyRange( ev->GetFloat( 1 ) ); +} + + +void HelperNode::SetActivationRange( Event *ev ) +{ + SetActivationRange( ev->GetFloat( 1 ) ); +} + +// +// Name: SetFlags() +// Class: HelperNode +// +// Description: Calls SetFlags() +// +// Parameters: Event *ev - Event containing the float +// +// Returns: None +// +void HelperNode::SetFlags( Event *ev ) +{ + SetFlags(ev->GetInteger( 1 ) ); +} + + +// +// Name: SetAnimActive() +// Class: HelperNode +// +// Description: Calls SetAnimActive() +// +// Parameters: Event *ev - Event containing the boolean +// +// Returns: None +// +void HelperNode::SetAnimActive( Event *ev ) +{ + SetAnimActive(ev->GetBoolean( 1 ) ); +} + + +// +// Name: SetCoverActive() +// Class: HelperNode +// +// Description: Calls SetCoverActive() +// +// Parameters: Event *ev - Event containing the boolean +// +// Returns: None +// +void HelperNode::SetCoverActive( Event *ev ) +{ + SetCoverActive(ev->GetBoolean( 1 ) ); +} + +// +// Name: SetCoverType() +// Class: HelperNode +// +// Description: Calls SetCoverType() +// +// Parameters: Event *ev - Event containing the string +// +// Returns: None +// +void HelperNode::SetCoverType( Event *ev ) +{ + CoverType_t coverType; + coverType = _CoverTypeForString( ev->GetString( 1 ) ); + + SetCoverType(coverType); +} + + +// +// Name: SetCoverDir() +// Class: HelperNode +// +// Description: Calls SetCoverDir() +// +// Parameters: Event *ev - Event containing the string +// +// Returns: None +// +void HelperNode::SetCoverDir( Event *ev ) +{ + CoverDirection_t coverDir; + coverDir = _CoverDirectionForString( ev->GetString( 1 ) ); + + SetCoverDir(coverDir); +} + +//====================================== +// Helper Node Mutators +//====================================== + +// +// Name: SetEntryThread() +// Class: HelperNode +// +// Description: Sets the _entryThread variable +// +// Parameters: const str& thread - the thread to set +// +// Returns: None +// +void HelperNode::SetEntryThread( const str& thread ) +{ + _entryThread = thread; +} + + +// +// Name: SetExitThread() +// Class: HelperNode +// +// Description: Sets the _exitThread variable +// +// Parameters: const str& thread - the thread to set +// +// Returns: None +// + +void HelperNode::SetExitThread( const str& thread ) +{ + _exitThread = thread; +} + +// +// Name: SetAnim() +// Class: HelperNode +// +// Description: Sets the _anim variable +// +// Parameters: const str& anim +// +// Returns: None +// +void HelperNode::SetAnim( const str& anim ) +{ + _anim = anim; +} + + +// +// Name: SetAnimTarget() +// Class: HelperNode +// +// Description: Sets the _animtarget variable +// +// Parameters: const str& animtarget +// +// Returns: None +// +void HelperNode::SetAnimTarget( const str& animtarget ) +{ + _animtarget = animtarget; +} + + +// +// Name: SetKillTarget() +// Class: HelperNode +// +// Description: Sets the _killtarget variable +// +// Parameters: const str& killtarget +// +// Returns: None +// +void HelperNode::SetKillTarget( const str& killtarget ) +{ + _killtarget = killtarget; +} + + +// +// Name: SetCustomType() +// Class: HelperNode +// +// Description: Sets the _customType variable +// +// Parameters: const str& customtype +// +// Returns: None +// +void HelperNode::SetCustomType( const str& customtype ) +{ + _customType = customtype; +} + +// +// Name: SetID() +// Class: HelperNode +// +// Description: Sets the _id variable +// +// Parameters: int ID - The id to set +// +// Returns: None +// +void HelperNode::SetID( int ID ) +{ + _id = ID; +} + + +// +// Name: SetAnimCount() +// Class: HelperNode +// +// Description: Sets the _animcount variable +// +// Parameters: int animcount +// +// Returns: None +// +void HelperNode::SetAnimCount( int animcount ) +{ + _animcount = animcount; +} + + +// +// Name: SetMaxKills() +// Class: HelperNode +// +// Description: Sets the _maxkills variable +// +// Parameters: int maxkills +// +// Returns: None +// +void HelperNode::SetMaxKills( int maxkills ) +{ + _maxkills = maxkills; +} + +// +// Name: SetWait() +// Class: HelperNode +// +// Description: Sets the _wait variable +// +// Parameters: float wait +// +// Returns: None +// +void HelperNode::SetWait( float wait ) +{ + _waittime = wait; +} + +// +// Name: SetWaitRandom() +// Class: HelperNode +// +// Description: Sets the _wait variable, and sets _waitRandom to true; +// +// Parameters: float wait +// +// Returns: None +// +void HelperNode::SetWaitRandom( float wait ) +{ + _waittime = wait; + _waitrandom = true; +} + + +// +// Name: SetWaitForAnim() +// Class: HelperNode +// +// Description: Sets the _waitforanim variable +// +// Parameters: qboolean wait +// +// Returns: None +// +void HelperNode::SetWaitForAnim(qboolean wait) +{ + _waitforanim = wait; +} + +// +// Name: SetMinHealth() +// Class: HelperNode +// +// Description: Sets _minhealth variable +// +// Parameters: float health - The health to set +// +// Returns: None +// +void HelperNode::SetMinHealth( float health ) +{ + _minhealth = health; +} + + +// +// Name: SetMinEnemyRange() +// Class: HelperNode +// +// Description: Sets _minenemyrange variable +// +// Parameters: float range - The range to set +// +// Returns: None +// +void HelperNode::SetMinEnemyRange( float range ) +{ + _minenemyrange = range; +} + +void HelperNode::SetActivationRange( float range ) +{ + _activationRange = range; +} + +// +// Name: SetAnimActive() +// Class: HelperNode +// +// Description: Sets the _animactive variable +// +// Parameters: qboolean animactive +// +// Returns: None +// +void HelperNode::SetAnimActive( qboolean animactive ) +{ + _animactive = animactive; +} + + +// +// Name: SetCoverActive() +// Class: HelperNode +// +// Description: Sets the _coveractive variable +// +// Parameters: qboolean coveractive +// +// Returns: None +// +void HelperNode::SetCoverActive( qboolean coveractive ) +{ + _coveractive = coveractive; +} + +// +// Name: SetCoverType() +// Class: HelperNode +// +// Description: Sets the _covertype variable +// +// Parameters: CoverType_t covertype +// +// Returns: None +// +void HelperNode::SetCoverType( CoverType_t covertype ) +{ + _covertype = covertype; +} + + +// +// Name: SetCoverDir() +// Class: HelperNode +// +// Description: Sets the _coverdir variable +// +// Parameters: CoverDirection_t coverdir +// +// Returns: None +// +void HelperNode::SetCoverDir( CoverDirection_t coverdir ) +{ + _coverdir = coverdir; +} + + +// +// Name: SetCriticalChange() +// Class: HelperNode +// +// Description: Sets the _criticalchange variable +// +// Parameters: qboolean change +// +// Returns: None +// +void HelperNode::SetCriticalChange( qboolean change ) +{ + _criticalchange = change; +} + + +// +// Name: SetDescriptor() +// Class: HelperNode +// +// Description: Sets the _descriptor variable +// +// Parameters: NodeDescriptorType_t descriptor +// +// Returns: None +// +void HelperNode::SetDescriptor( NodeDescriptorType_t descriptor ) +{ + _descriptor = descriptor; +} + +void HelperNode::SetPriority( float priority ) +{ + _priority = priority; +} + +// +// Name: SetFlags() +// Class: HelperNode +// +// Description: Sets the NodeFlags +// +// Parameters: unsigned int flags - the integer mask of the flags +// +// Returns: None +// +void HelperNode::SetFlags( unsigned int flags ) +{ + _nodeflags = flags; +} + + +//====================================== +// Helper Node Accessors +//====================================== + +// +// Name: GetEntryThread() +// Class: HelperNode +// +// Description: Returns the _thread variable +// +// Parameters: None +// +// Returns: _entryThread +// +const str& HelperNode::GetEntryThread() +{ + return _entryThread; +} + + +// +// Name: GetExitThread() +// Class: HelperNode +// +// Description: Returns the _exitThread +// +// Parameters: None +// +// Returns: _exitThread +// +const str& HelperNode::GetExitThread() +{ + return _exitThread; +} + +// +// Name: GetAnim() +// Class: HelperNode +// +// Description: Returns the _anim variable +// +// Parameters: None +// +// Returns: _anim +// +const str& HelperNode::GetAnim() +{ + return _anim; +} + + +// +// Name: GetAnimTarget() +// Class: HelperNode +// +// Description: Returns _animtarget variable +// +// Parameters: None +// +// Returns: _animtarget +// +const str& HelperNode::GetAnimTarget() +{ + return _animtarget; +} + + +// +// Name: GetKillTarget() +// Class: HelperNode +// +// Description: Returns _killtarget variable +// +// Parameters: None +// +// Returns: _killtarget +// +const str& HelperNode::GetKillTarget() +{ + return _killtarget; +} + + +// +// Name: GetCustomType() +// Class: HelperNode +// +// Description: Returns _customType variable +// +// Parameters: None +// +// Returns: _customType +// +const str& HelperNode::GetCustomType() +{ + return _customType; +} + +// +// Name: GetID() +// Class: HelperNode +// +// Description: Returns _id variable +// +// Parameters: None +// +// Returns: _id +// +int HelperNode::GetID() +{ + return _id; +} + +// +// Name: GetAnimCount() +// Class: HelperNode +// +// Description: Returns the _animcount variable +// +// Parameters: None +// +// Returns: _animcount +// +int HelperNode::GetAnimCount() +{ + return _animcount; +} + +// +// Name: GetMaxKills() +// Class: HelperNode +// +// Description: Returns the _maxkills variable +// +// Parameters: None +// +// Returns: _maxkills +// +int HelperNode::GetMaxKills() +{ + return _maxkills; +} + +// +// Name: GetWaitTime() +// Class: HelperNode +// +// Description: Returns _waittime +// +// Parameters: None +// +// Returns: _waittime +// +float HelperNode::GetWaitTime() +{ + return _waittime; +} + + +// +// Name: GetMinHealth() +// Class: HelperNode +// +// Description: Returns _minhealth variable +// +// Parameters: None +// +// Returns: _minhealth +// +float HelperNode::GetMinHealth() +{ + return _minhealth; +} + + +// +// Name: GetMinEnemyRange() +// Class: HelperNode +// +// Description: Returns _minenemyrange; +// +// Parameters: None +// +// Returns: _minenemyrange +// +float HelperNode::GetMinEnemyRange() +{ + return _minenemyrange; +} + +float HelperNode::GetActivationRange() +{ + return _activationRange; +} + +// +// Name: GetCoverType() +// Class: HelperNode +// +// Description: Returns _covertype +// +// Parameters: None +// +// Returns: _covertype +// +CoverType_t HelperNode::GetCoverType() +{ + return _covertype; +} + + +// +// Name: GetCoverDirection() +// Class: HelperNode +// +// Description: Returns _coverdir +// +// Parameters: None +// +// Returns: _coverdir +// +CoverDirection_t HelperNode::GetCoverDirection() +{ + return _coverdir; +} + + +// +// Name: GetDescriptor +// Class: HelperNode +// +// Description: Returns _descriptor +// +// Parameters: None +// +// Returns: NodeDescriptorType_t +// +NodeDescriptorType_t HelperNode::GetDescriptor() +{ + return _descriptor; +} + +float HelperNode::GetPriority() +{ + return _priority; +} + +//====================================== +// Helper Node Queries +//====================================== + + +// +// Name: isOfType() +// Class: HelperNode +// +// Description: Checks if the nodeflags match the passed in mask +// +// Parameters: unsigned int mask +// +// Returns: true or false; +// +qboolean HelperNode::isOfType(unsigned int mask) +{ + return _nodeflags & ( mask ); +} + + +// +// Name: isAnimActive() +// Class: HelperNode +// +// Description: Returns _animactive +// +// Parameters: None +// +// Returns: true or false +// +qboolean HelperNode::isAnimActive() +{ + return _animactive; +} + + +// +// Name: isCoverActive() +// Class: HelperNode +// +// Description: Returns _coveractive +// +// Parameters: None +// +// Returns: true or false +// +qboolean HelperNode::isCoverActive() +{ + return _coveractive; +} + + +// +// Name: isWaitRandom() +// Class: HelperNode +// +// Description: Returns _waitrandom +// +// Parameters: None +// +// Returns: true or false +// +qboolean HelperNode::isWaitRandom() +{ + return _waitrandom; +} + + +// +// Name: isWaitForAnim() +// Class: HelperNode +// +// Description: Returns _waitforanim +// +// Parameters: None +// +// Returns: true or false +// +qboolean HelperNode::isWaitForAnim() +{ + return _waitforanim; +} + + +// +// Name: isChanged() +// Class: HelperNode +// +// Description: Returns _criticalchange +// +// Parameters: None +// +// Returns: true or false +// +qboolean HelperNode::isChanged() +{ + return _criticalchange; +} + +qboolean HelperNode::isReserved() +{ + return _reserved; +} + +//====================================== +// Helper Node Utility +//====================================== + + +// +// Name: RunEntryThread() +// Class: HelperNode +// +// Description: Runs the entry thread +// +// Parameters: None +// +// Returns: None +// +void HelperNode::RunEntryThread() +{ + if ( _entryThread.length() <= 0 ) + return; + + CThread *thread; + + thread = Director.CreateThread( _entryThread ); + + if ( thread ) + thread->DelayedStart( 0.0f ); +} + + +// +// Name: RunExitThread() +// Class: HelperNode +// +// Description: Runs the exit thread +// +// Parameters: None +// +// Returns: None +// +void HelperNode::RunExitThread() +{ + if ( _exitThread.length() <= 0 ) + return; + + CThread *thread; + + thread = Director.CreateThread( _exitThread ); + + if ( thread ) + thread->DelayedStart( 0.0f ); +} + + +//====================================== +// Helper Node Convience Functions +//====================================== + +// +// Name: _CoverTypeForString() +// Class: HelperNode +// +// Description: Returns the enumerated covertype based on the string +// +// Parameters: const str &covertype +// +// Returns: CoverType_t coverType +// +CoverType_t HelperNode::_CoverTypeForString( const str &covertype ) +{ + CoverType_t coverType; + + for ( int i = 0 ; i < COVER_TYPE_TOTAL_NUMBER ; i++ ) + { + if ( !Q_stricmp(covertype.c_str() , CoverTypeStrings[i] ) ) + { + coverType = (CoverType_t)i; + return coverType; + } + + } + + gi.WDPrintf( "Unknown CoverType - %s\n", covertype.c_str() ); + return COVER_TYPE_NONE; +} + + +// +// Name: _CoverDirectionForString() +// Class: HelperNode +// +// Description: Returns the enumerated coverdir based on the string +// +// Parameters: const str &coverdir +// +// Returns: CoverType_t coverDir +// +CoverDirection_t HelperNode::_CoverDirectionForString( const str &coverdir ) +{ + CoverDirection_t coverDir; + + for ( int i = 0 ; i < COVER_DIRECTION_TOTAL_NUMBER ; i++ ) + { + if ( !Q_stricmp(coverdir.c_str() , CoverDirStrings[i] ) ) + { + coverDir = (CoverDirection_t)i; + return coverDir; + } + + } + + gi.WDPrintf( "Unknown CoverDirection - %s\n", coverdir.c_str() ); + return COVER_DIRECTION_NONE; +} + + +// +// Name: _CoverDirectionForString() +// Class: HelperNode +// +// Description: Returns the enumerated coverdir based on the string +// +// Parameters: const str &coverdir +// +// Returns: CoverType_t coverDir +// +NodeDescriptorType_t HelperNode::_DescriptorForString( const str &descriptor ) +{ + NodeDescriptorType_t descriptorType; + + for ( int i = 0 ; i < DESCRIPTOR_TOTAL_NUMBER ; i++ ) + { + if ( !Q_stricmp(descriptor.c_str() , DescriptorTypeStrings[i] ) ) + { + descriptorType = (NodeDescriptorType_t)i; + return descriptorType; + } + + } + + gi.WDPrintf( "Unknown Descriptor - %s\n", descriptor.c_str() ); + return DESCRIPTOR_NONE; +} + +// +// Name: FindClosestHelperNode() +// Class: None +// +// Description: Return nearest valid helpernode based on the criteria passed in +// +// Parameters: Actor &self -- Actor looking for the node +// int mask -- Mask of the node +// +// Returns: HelperNode* bestNode +// +HelperNode* HelperNode::FindClosestHelperNode( Actor &self , int mask , float maxDist , float minDistanceFromPlayer, bool unreserveCurrentNode ) +{ + HelperNode *node; + HelperNode *bestNode; + float pathLen; + float bestDist; + float activationRange; + FindMovementPath find; + Path *path; + int nodeID; + + + // Set up our pathing heuristics + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + // Initialize our nodes and distances + bestNode = NULL; + bestDist = 999999999.8f; + pathLen = bestDist - 1.0f; + + //We are going to loop through all the helper nodes in the level, find ones matching + //our mask, and compare path lengths to each of them. We can't do a plain ol'line of + //sight check, because, it seems valid to me that the nearest helpernode might not be + //visible. We also can't just compare raw proximity, because we could have a node that + //is technically "closer" but behind a wall, forcing us to path a very long way to get + //to it. + + //Note this is a potientially VERY expensive function, and we need to keep an eye on it + //to make sure its not called a lot, and keep in mind that it may be a candidate for + //extensive optmization + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + nodeID = node->GetID(); + + if ( nodeID != -1 && nodeID != self.currentHelperNode.nodeID ) + continue; + + if ( node && node->isOfType(mask)) + { + activationRange = node->GetActivationRange(); + + + if ( activationRange > 0 ) + { + if ( !self.WithinDistance( node->origin, activationRange ) ) + continue; + } + else + { + if ( !self.WithinDistance( node->origin, maxDist ) ) + continue; + } + + if ( node == self.ignoreHelperNode.node ) + continue; + + if ( node->isReserved() ) + continue; + + Player* player; + player = GetPlayer(0); + if ( !player ) return false; + + if ( player->WithinDistance( node->origin , minDistanceFromPlayer ) ) + continue; + + path = find.FindPath( self.origin, node->origin ); + + if ( path ) + pathLen = path->Length(); + + delete path; + path = NULL; + + if ( pathLen < bestDist ) + { + bestDist = pathLen; + bestNode = node; + } + + } + + + } + + if ( self.currentHelperNode.node && unreserveCurrentNode ) + self.currentHelperNode.node->UnreserveNode(); + + self.currentHelperNode.node = bestNode; + self.currentHelperNode.mask = mask; + + if ( bestNode ) + bestNode->ReserveNode(); + + return bestNode; +} + + +// +// Name: FindClosestHelperNode() +// Class: None +// +// Description: Return nearest valid helpernode based on the criteria passed in +// +// Parameters: Actor &self -- Actor looking for the node +// int mask -- Mask of the node +// +// Returns: HelperNode* bestNode +// +HelperNode* HelperNode::FindClosestHelperNode( Actor &self , int mask , NodeDescriptorType_t descriptor, float maxDist) +{ + HelperNode *node; + HelperNode *bestNode; + float pathLen; + float bestDist; + float activationRange; + FindMovementPath find; + Path *path; + int nodeID; + + + // Set up our pathing heuristics + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + // Initialize our nodes and distances + bestNode = NULL; + bestDist = 999999999.8f; + pathLen = bestDist - 1.0f; + + //We are going to loop through all the helper nodes in the level, find ones matching + //our mask, and compare path lengths to each of them. We can't do a plain ol'line of + //sight check, because, it seems valid to me that the nearest helpernode might not be + //visible. We also can't just compare raw proximity, because we could have a node that + //is technically "closer" but behind a wall, forcing us to path a very long way to get + //to it. + + //Note this is a potientially VERY expensive function, and we need to keep an eye on it + //to make sure its not called a lot, and keep in mind that it may be a candidate for + //extensive optmization + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + nodeID = node->GetID(); + + if ( nodeID != -1 && nodeID != self.currentHelperNode.nodeID ) + continue; + + if ( node && node->isOfType(mask) && node->GetDescriptor() == descriptor ) + { + activationRange = node->GetActivationRange(); + + if ( node->isReserved() ) + continue; + + if ( node == self.ignoreHelperNode.node ) + continue; + + + if ( activationRange > 0 ) + { + if ( !self.WithinDistance( node->origin, activationRange ) ) + continue; + } + else + { + if ( !self.WithinDistance( node->origin, maxDist ) ) + continue; + } + + path = find.FindPath( self.origin, node->origin ); + + if ( path ) + pathLen = path->Length(); + + delete path; + path = NULL; + + if ( pathLen < bestDist ) + { + bestDist = pathLen; + bestNode = node; + } + + } + + + } + + if ( self.currentHelperNode.node ) + self.currentHelperNode.node->UnreserveNode(); + + self.currentHelperNode.node = bestNode; + self.currentHelperNode.mask = mask; + + if ( bestNode ) + bestNode->ReserveNode(); + + return bestNode; +} + +HelperNode* HelperNode::FindClosestHelperNodeAtDistanceFrom( Actor &self , Entity *ent , int mask , NodeDescriptorType_t descriptor , float maxDistFromSelf , float minDistFromEnt ) +{ + HelperNode *node; + HelperNode *bestNode; + float pathLen; + float bestDist; + float activationRange; + FindMovementPath find; + Path *path; + int nodeID; + + + // Set up our pathing heuristics + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + // Initialize our nodes and distances + bestNode = NULL; + bestDist = 999999999.8f; + pathLen = bestDist - 1.0f; + + //We are going to loop through all the helper nodes in the level, find ones matching + //our mask, and compare path lengths to each of them. We can't do a plain ol'line of + //sight check, because, it seems valid to me that the nearest helpernode might not be + //visible. We also can't just compare raw proximity, because we could have a node that + //is technically "closer" but behind a wall, forcing us to path a very long way to get + //to it. + + //Note this is a potientially VERY expensive function, and we need to keep an eye on it + //to make sure its not called a lot, and keep in mind that it may be a candidate for + //extensive optmization + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + nodeID = node->GetID(); + + if ( nodeID != -1 && nodeID != self.currentHelperNode.nodeID ) + continue; + + if ( node && node->isOfType(mask) && node->GetDescriptor() == descriptor ) + { + activationRange = node->GetActivationRange(); + + if ( node->isReserved() ) + continue; + + if ( activationRange > 0 ) + { + if ( !self.WithinDistance( node->origin, activationRange ) ) + continue; + } + else + { + if ( !self.WithinDistance( node->origin, maxDistFromSelf ) ) + continue; + } + + if ( ent->WithinDistance( node->origin , minDistFromEnt ) ) + continue; + + path = find.FindPath( self.origin, node->origin ); + + if ( path ) + pathLen = path->Length(); + + delete path; + path = NULL; + + if ( pathLen < bestDist ) + { + bestDist = pathLen; + bestNode = node; + } + } + } + + self.currentHelperNode.node = bestNode; + self.currentHelperNode.mask = mask; + return bestNode; +} + +HelperNode* HelperNode::FindHelperNodeClosestToWithoutPathing( Actor &self , Entity *ent , int mask , float maxDist ) +{ + HelperNode *node; + HelperNode *bestNode; + float pathLen; + float bestDist; + float activationRange; + int nodeID; + + + if ( !ent ) + return NULL; + + // Initialize our nodes and distances + bestNode = NULL; + bestDist = 999999999.8f; + pathLen = bestDist - 1.0f; + + //We are going to loop through all the helper nodes in the level, find ones matching + //our mask, and compare path lengths to each of them. We can't do a plain ol'line of + //sight check, because, it seems valid to me that the nearest helpernode might not be + //visible. We also can't just compare raw proximity, because we could have a node that + //is technically "closer" but behind a wall, forcing us to path a very long way to get + //to it. + + //Note this is a potientially VERY expensive function, and we need to keep an eye on it + //to make sure its not called a lot, and keep in mind that it may be a candidate for + //extensive optmization + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + nodeID = node->GetID(); + + if ( nodeID != -1 && nodeID != self.currentHelperNode.nodeID ) + continue; + + if ( node && node->isOfType(mask)) + { + activationRange = node->GetActivationRange(); + + if ( activationRange > 0 ) + { + if ( !ent->WithinDistance( node->origin, activationRange ) ) + continue; + } + else + { + if ( !ent->WithinDistance( node->origin, maxDist ) ) + continue; + } + + if ( node == self.ignoreHelperNode.node ) + continue; + + if ( node->isReserved() ) + continue; + + Vector pathToNode = ent->origin - node->origin; + pathLen = pathToNode.length(); + + if ( pathLen < bestDist ) + { + bestDist = pathLen; + bestNode = node; + } + + } + + + } + + if ( self.currentHelperNode.node ) + self.currentHelperNode.node->UnreserveNode(); + + self.currentHelperNode.node = bestNode; + self.currentHelperNode.mask = mask; + + if ( bestNode ) + bestNode->ReserveNode(); + + return bestNode; +} + + + +HelperNode* HelperNode::FindHelperNodeClosestTo( Actor &self, Entity *ent , int mask , float maxDist ) +{ + HelperNode *node; + HelperNode *bestNode; + float pathLen; + float bestDist; + float activationRange; + FindMovementPath find; + Path *path; + int nodeID; + + + if ( !ent ) + return NULL; + + // Set up our pathing heuristics + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + // Initialize our nodes and distances + bestNode = NULL; + bestDist = 999999999.8f; + pathLen = bestDist - 1.0f; + + //We are going to loop through all the helper nodes in the level, find ones matching + //our mask, and compare path lengths to each of them. We can't do a plain ol'line of + //sight check, because, it seems valid to me that the nearest helpernode might not be + //visible. We also can't just compare raw proximity, because we could have a node that + //is technically "closer" but behind a wall, forcing us to path a very long way to get + //to it. + + //Note this is a potientially VERY expensive function, and we need to keep an eye on it + //to make sure its not called a lot, and keep in mind that it may be a candidate for + //extensive optmization + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + nodeID = node->GetID(); + + if ( nodeID != -1 && nodeID != self.currentHelperNode.nodeID ) + continue; + + if ( node && node->isOfType(mask)) + { + activationRange = node->GetActivationRange(); + + if ( activationRange > 0 ) + { + if ( !ent->WithinDistance( node->origin, activationRange ) ) + continue; + } + else + { + if ( !ent->WithinDistance( node->origin, maxDist ) ) + continue; + } + + if ( node == self.ignoreHelperNode.node ) + continue; + + if ( node->isReserved() ) + continue; + + path = find.FindPath( ent->origin, node->origin ); + + if ( path ) + pathLen = path->Length(); + + delete path; + path = NULL; + + if ( pathLen < bestDist ) + { + bestDist = pathLen; + bestNode = node; + } + + } + + + } + + if ( self.currentHelperNode.node ) + self.currentHelperNode.node->UnreserveNode(); + + self.currentHelperNode.node = bestNode; + self.currentHelperNode.mask = mask; + + if ( bestNode ) + bestNode->ReserveNode(); + + return bestNode; +} + +// +// Name: FindClosestHelperNodeWithoutPathing() +// Class: None +// +// Description: Return nearest valid helpernode based on the criteria passed in +// +// Parameters: Actor &self -- Actor looking for the node +// int mask -- Mask of the node +// +// Returns: HelperNode* bestNode +// +HelperNode* HelperNode::FindClosestHelperNodeWithoutPathing( Actor &self, float maxDist) +{ + // Set up our pathing heuristics + FindMovementPath find; + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + // Initialize our nodes and distances + HelperNode *bestNode = NULL; + float bestDistance = 999999999.8f; + + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + HelperNode *node = NULL; + node = HelperNodes.ObjectAt( i ); + int nodeID = node->GetID(); + + if ( ( nodeID != -1 ) && ( nodeID != self.currentHelperNode.nodeID) ) + continue; + + float activationRange = node->GetActivationRange(); + + if ( activationRange > 0 ) + { + if ( !self.WithinDistance( node->origin, activationRange ) ) + continue; + } + else + { + if ( !self.WithinDistance( node->origin, maxDist ) ) + continue; + } + + float currentDistance = Vector::Distance( self.origin, node->origin ); + + if ( currentDistance < bestDistance ) + { + bestDistance = currentDistance; + bestNode = node; + } + } + + self.currentHelperNode.node = bestNode; + return bestNode; +} + +HelperNode* HelperNode::FindClosestHelperNodeThatCannotSeeEntity( Actor &self , int mask , unsigned int clipMask , float maxDist , float minDist , Entity *ent , float minDistFromPlayer ) +{ + HelperNode *node; + HelperNode *bestNode; + float pathLen; + float bestDist; + float activationRange; + FindMovementPath find; + Path *path; + int nodeID; + trace_t trace; + + + // Set up our pathing heuristics + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + // Initialize our nodes and distances + bestNode = NULL; + bestDist = 999999999.8f; + pathLen = bestDist - 1.0f; + + //We are going to loop through all the helper nodes in the level, find ones matching + //our mask, and compare path lengths to each of them. We can't do a plain ol'line of + //sight check, because, it seems valid to me that the nearest helpernode might not be + //visible. We also can't just compare raw proximity, because we could have a node that + //is technically "closer" but behind a wall, forcing us to path a very long way to get + //to it. + + //Note this is a potientially VERY expensive function, and we need to keep an eye on it + //to make sure its not called a lot, and keep in mind that it may be a candidate for + //extensive optmization + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + nodeID = node->GetID(); + + if ( nodeID != -1 && nodeID != self.currentHelperNode.nodeID ) + continue; + + + if ( node && node->isOfType(mask)) + { + + activationRange = node->GetActivationRange(); + + if ( activationRange > 0 ) + { + if ( !self.WithinDistance( node->origin, activationRange ) ) + continue; + } + else + { + if ( !self.WithinDistance( node->origin, maxDist ) ) + continue; + } + + if ( node->isReserved() ) + continue; + + //Check if node is within the minimum distance of the player + Player* player; + player = GetPlayer(0); + if ( !player ) return false; + + if ( player->WithinDistance( node->origin , minDistFromPlayer ) ) + continue; + + + path = find.FindPath( self.origin, node->origin ); + + if ( !path ) + continue; + + pathLen = path->Length(); + + delete path; + path = NULL; + + if ( pathLen < bestDist && !ent->WithinDistance( node->origin , minDist ) ) + { + Vector endPos; + endPos = ent->centroid; + + if ( ent->isSubclassOf(Actor) ) + { + Actor *theActor; + theActor = (Actor*)ent; + endPos = theActor->EyePosition(); + } + + trace = G_Trace( node->origin , vec_zero , vec_zero , endPos , NULL , MASK_OPAQUE, false, "FindClosestHelperNode"); + if ( trace.fraction > .9 ) + continue; + + bestDist = pathLen; + bestNode = node; + } + } + } + + + if ( bestNode && self.currentHelperNode.node ) + self.currentHelperNode.node->UnreserveNode(); + + if ( bestNode ) + { + self.currentHelperNode.node = bestNode; + self.currentHelperNode.mask = mask; + } + + if ( bestNode ) + bestNode->ReserveNode(); + + return bestNode; +} + + +HelperNode* HelperNode::FindClosestHelperNode( Actor &self , const str& customType , float maxDist) +{ + HelperNode *node; + HelperNode *bestNode; + float pathLen; + float bestDist; + FindMovementPath find; + Path *path; + int nodeID; + + + // Set up our pathing heuristics + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + // Initialize our nodes and distances + bestNode = NULL; + bestDist = 999999999.8f; + pathLen = bestDist - 1.0f; + + //We are going to loop through all the helper nodes in the level, find ones matching + //our mask, and compare path lengths to each of them. We can't do a plain ol'line of + //sight check, because, it seems valid to me that the nearest helpernode might not be + //visible. We also can't just compare raw proximity, because we could have a node that + //is technically "closer" but behind a wall, forcing us to path a very long way to get + //to it. + + //Note this is a potientially VERY expensive function, and we need to keep an eye on it + //to make sure its not called a lot, and keep in mind that it may be a candidate for + //extensive optmization + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + nodeID = node->GetID(); + + if ( nodeID != -1 && nodeID != self.currentHelperNode.nodeID ) + continue; + + if ( node == self.ignoreHelperNode.node ) + continue; + + if ( node->isReserved() ) + continue; + + if ( node && node->isOfType(NODETYPE_CUSTOM)) + { + str type; + type = node->GetCustomType(); + + if ( !Q_stricmp(type.c_str() , customType.c_str() ) ) + { + if ( !self.WithinDistance( node->origin , maxDist ) ) + continue; + + path = find.FindPath( self.origin, node->origin ); + + if ( path ) + pathLen = path->Length(); + + delete path; + path = NULL; + + if ( pathLen < bestDist ) + { + bestDist = pathLen; + bestNode = node; + } + } + + } + + + } + + + if ( self.currentHelperNode.node ) + self.currentHelperNode.node->UnreserveNode(); + + self.currentHelperNode.node = bestNode; + self.currentHelperNode.mask = NODETYPE_CUSTOM; + + if ( bestNode ) + bestNode->ReserveNode(); + + + return bestNode; +} + + + + + + +HelperNode* HelperNode::FindHighestPriorityNode( Actor& self , const str& customType ) +{ + HelperNode *node; + HelperNode *bestNode; + float priority; + float bestPriority; + int nodeID; + + + // Initialize our nodes and priorities + bestNode = NULL; + priority = 0.0f; + bestPriority = 0.0f; + + + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + nodeID = node->GetID(); + + if ( nodeID != -1 && nodeID != self.currentHelperNode.nodeID ) + continue; + + if ( node->isReserved() ) + continue; + + if ( node->isOfType(NODETYPE_CUSTOM)) + { + str type; + type = node->GetCustomType(); + + if ( !stricmp(type.c_str() , customType.c_str() ) ) + { + priority = node->GetPriority(); + if ( priority > bestPriority ) + { + bestPriority = priority; + bestNode = node; + } + } + + } + + + } + + self.currentHelperNode.node = bestNode; + self.currentHelperNode.mask = NODETYPE_CUSTOM; + return bestNode; +} + +HelperNode* HelperNode::FindHighestPriorityNode( Actor& self , const str& customType , const str &targetedTo ) +{ + HelperNode *node; + HelperNode *bestNode; + float priority; + float bestPriority; + int nodeID; + + + // Initialize our nodes and priorities + bestNode = NULL; + priority = 0.0f; + bestPriority = 999999.0f; + + + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + nodeID = node->GetID(); + + if ( nodeID != -1 && nodeID != self.currentHelperNode.nodeID ) + continue; + + if ( node->isReserved() ) + continue; + + if ( node->isOfType(NODETYPE_CUSTOM)) + { + str type; + type = node->GetCustomType(); + + if ( !stricmp(type.c_str() , customType.c_str() ) && !stricmp(node->target, targetedTo.c_str() ) ) + { + priority = node->GetPriority(); + if ( priority < bestPriority ) + { + bestPriority = priority; + bestNode = node; + } + } + + } + + + } + + self.currentHelperNode.node = bestNode; + self.currentHelperNode.mask = NODETYPE_CUSTOM; + return bestNode; +} + +// +// Name: FindClosestHelperNode() +// Class: None +// +// Description: Return nearest valid helpernode based on the criteria passed in +// +// Parameters: Actor &self -- Actor looking for the node +// int mask -- Mask of the node +// +// Returns: HelperNode* bestNode +// +HelperNode* HelperNode::FindClosestHelperNode( Actor &self , const str& targetName ) +{ + HelperNode *node; + HelperNode *bestNode; + float pathLen; + float bestDist; + FindMovementPath find; + Path *path; + + // Set up our pathing heuristics + find.heuristic.self = &self; + find.heuristic.setSize( self.size ); + find.heuristic.entnum = self.entnum; + + // Initialize our nodes and distances + bestNode = NULL; + bestDist = 999999999.8f; + pathLen = bestDist - 1.0f; + + //We are going to loop through all the helper nodes in the level, find ones matching + //our mask, and compare path lengths to each of them. We can't do a plain ol'line of + //sight check, because, it seems valid to me that the nearest helpernode might not be + //visible. We also can't just compare raw proximity, because we could have a node that + //is technically "closer" but behind a wall, forcing us to path a very long way to get + //to it. + + //Note this is a potientially VERY expensive function, and we need to keep an eye on it + //to make sure its not called a lot, and keep in mind that it may be a candidate for + //extensive optmization + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + + + + if ( node && node->targetname == targetName ) + { + path = find.FindPath( self.origin, node->origin ); + + if ( path ) + pathLen = path->Length(); + + delete path; + path = NULL; + + if ( pathLen < bestDist ) + { + bestDist = pathLen; + bestNode = node; + } + + } + + + } + + self.currentHelperNode.node = bestNode; + return bestNode; +} + +//-------------------------------------------------------------- +// Name: isHelperNodeInRange() +// Class: HelperNode +// +// Description: Checks if the helper node is within range +// +// Parameters: Actor &self, +// int mask, +// float range, +// +// Returns: true or false; +//-------------------------------------------------------------- +bool HelperNode::isHelperNodeInRange( Actor &self , int mask , float range ) +{ + HelperNode *node; + + node = FindHelperNodeClosestTo( self, &self, mask , range ); + if ( node ) + return true; + + return false; + +} + +// +// Name: GetTargetdHelperNode() +// Class: None +// +// Description: Returns the helpernode with the passed in targetName +// +// Parameters: const str &targetName -- the targetName of the node to find +// +// Returns: HelperNode* node +// +HelperNode* HelperNode::GetTargetedHelperNode( const str& targetName ) +{ + HelperNode *node; + + for ( int i = 1 ; i <= HelperNodes.NumObjects() ; i++ ) + { + node = NULL; + node = HelperNodes.ObjectAt( i ); + + if ( node && (!Q_stricmp( node->targetname , targetName.c_str() ) ) ) + { + return node; + } + } + + return NULL; +} + +// +// Name: GetHelperNodeMask() +// Class: None +// +// Description: Returns an integer bit mask based on the string passed in +// +// Parameters: const str& type -- string of the node type +// +// Returns: int mask +// +int HelperNode::GetHelperNodeMask( const str& type ) +{ + int mask; + + mask = 0; + // Create our mask + if ( !Q_stricmp( type.c_str() , "flee" ) ) + mask |= NODETYPE_FLEE; + else if ( !Q_stricmp( type.c_str() , "work" ) ) + mask |= NODETYPE_WORK; + else if ( !Q_stricmp( type.c_str() , "anim" ) ) + mask |= NODETYPE_ANIM; + else if ( !Q_stricmp( type.c_str() , "cover" ) ) + mask |= NODETYPE_COVER; + else if ( !Q_stricmp( type.c_str() , "patrol" ) ) + mask |= NODETYPE_PATROL; + else if ( !Q_stricmp( type.c_str() , "sniper" ) ) + mask |= NODETYPE_SNIPER; + else if ( !Q_stricmp( type.c_str() , "custom" ) ) + mask |= NODETYPE_CUSTOM; + else if ( !Q_stricmp( type.c_str() , "combat" ) ) + mask |= NODETYPE_COMBAT; + + return mask; +} + + +// +// Editor Setup +// + +/*****************************************************************************/ +/*QUAKED info_helpernode (1 0 1) (-16 -16 0) (16 16 16) FLEE WORK ANIM COVER PATROL SNIPER CUSTOM COMBAT + +NodeType FLEE +Purpose: Marks this spot as a safe destination for the AI when they flee + +Additional Commands for FLEE type Nodes +None at this time + + +NodeType WORK +Purpose: Marks this spot as a good place for the AI to work at. + The AI will turn to match the angles of helper node, so make sure to angle it appropriately. + The AI will also run the animation you specify with the "setnodeanim" event and execute the + thread you specify with the "setnodethread" event + +Additional Commands for WORK type Nodes +setnodeanim -- Sets the animation that the AI will run while on this node +setnodeentrythread -- Sets the thread that the AI will call when they arrive at this node +setnodeexitthread -- Sets the thread that the AI will call when they leave the node +wait -- Sets the time at this work node +waitrandom -- Sets a random waittime from 1 to the time specified +waitforanim -- Sets the actor to work until his animation is completed + + +NodeType ANIM +Purpose: Allows you to specify a "special" animation to be played at this location. For example, if + you have a "dive through window" animation that you would like played as the AI moves through + the node, you can specify it here + +Additional Commands for ANIM type Nodes +setnodeanim -- Sets the animation that the AI will run while on this node +animcount -- Number of times the anim can be played +animtarget -- TargetName for a specific entity you wish to be animated +animactive -- Sets if the animation component is active + 1 is true ( default ) + 0 is false + if animactive is 0, then actors will not change animations at the node + +NodeType COVER +Purpose: Marks this spot as a good place for the AI to use as cover. The AI will turn to match the angles + of the helper node, so make sure to angle it appropriately. The AI will also use the cover + according to the information you specify in the setcovertype and setcoverdir events. + +Additional Commands for COVER type Nodes +setcovertype -- Sets the type of cover at this spot. Valid values are ( crate , wall ) +setcoverdir -- Sets the direction that the AI can fire from ( Based Relative to the direction the node is facing ) + valid values are ( left , right, all ) +coveractive -- Sets if the cover component is active + 1 is true ( default ) + 0 is false + if coveractive is 0, then actors will not use it for cover. + + +NodeType PATROL +Purpose: Marks a position in a patrol path. To be of any value, though, patrol helper nodes must be chained together. + If you want a full circuit, then you must connect all nodes together. Any nodes CANNOT be cross linked together. + I know, I know, it sucks, but unfortunately, due to limits with targetname, it is the way it must be. + If you do not make a circuit, the AI will stand idle at the last node until it decides to do something else. + +Additional Commands for PATROL type Nodes +wait -- Makes the AI pause at this node for the specifed amount of time +waitrandom -- Makes the AI pause for a random amount of time (up to a max of the amount specified ) +waitforanim -- Makes the AI pause long enough to complete his animation + + +NodeType SNIPER +Purpose: Marks a position as a good sniping position. + +Additional Commands for SNIPER type Nodes +wait -- Specifies a maximum time the AI will sit at this Node +attacktarget -- Specifies a target to kill -- If not specified, will try and snipe any enemies in view +maxkills -- Specifies the maximum number of kills at this Node + + +NodeType CUSTOM +Purpose: Marks the node as a custom helper node. Must be used in conjunction with the customtype command + +NodeType COMBAT +Purpose: Marks the node as a general place to do combat. + + + + +NodeType + +******************************************************************************/ + + + + + + + +//============================================================================= +// HelperNodeController +//============================================================================= +void HelperNodeController::Archive( Archiver &arc ) +{ + HelperNode* checkNode; + + int num , i; + + if ( arc.Saving() ) + { + num = HelperNodes.NumObjects(); + + arc.ArchiveInteger( &num ); + + for ( i = 1 ; i <= num ; i++ ) + { + checkNode = HelperNodes.ObjectAt( i ); + arc.ArchiveObject( checkNode ); + } + } + else + { + arc.ArchiveInteger( &num ); + HelperNodes.ClearObjectList(); + HelperNodes.Resize( num ); + + for ( i = 1 ; i<= num ; i++ ) + { + checkNode = new HelperNode; + arc.ArchiveObject( checkNode ); + HelperNodes.AddObject( checkNode ); + } + + } + +} + +//-------------------------------------------------------------- +// Name: CleanupHelperNodeList() +// Class: HelperNode +// +// Description: Iterates through the helper node list and cleans +// it out +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void HelperNode::CleanupHelperNodeList() +{ + HelperNode* checkNode; + + for ( int i = HelperNodes.NumObjects() ; i > 0 ; i-- ) + { + checkNode = HelperNodes.ObjectAt( i ); + if ( checkNode ) + { + delete checkNode; + checkNode = NULL; + HelperNodes.RemoveObjectAt( i ); + } + } + +} + + +waitType_t HelperNode::GetWaitType( const str &waitType ) +{ + if ( waitType == "anim" ) + return WAITTYPE_ANIM; + else if ( waitType == "event" ) + return WAITTYPE_EVENT; + else if ( waitType == "time" ) + return WAITTYPE_TIME; + + assert( "Unknown waitType" ); + return WAITTYPE_ERROR; +} + + +void HelperNode::SendCommand( Event *ev ) +{ + Event *commandEvent; + int numArgs; + + //First make sure we have a user to send + //the command event to + if ( !_user ) + return; + + commandEvent = new Event ( EV_HelperNodeCommand ); + + numArgs = ev->NumArgs(); + for ( int i = 1 ; i <= numArgs ; i++ ) + { + commandEvent->AddToken( ev->GetToken( i ) ); + } + + _user->ProcessEvent( commandEvent ); +} + + +customAnimListEntry_t* HelperNode::GetNextAnimEntryFromList() +{ + int animCount = _customAnimList.NumObjects(); + int index = _customAnimListIndex; + + if ( index < animCount ) + index++; + + return _customAnimList.ObjectAt( index ); +} + +customAnimListEntry_t* HelperNode::GetCurrentAnimEntryFromList() +{ + int index = _customAnimListIndex; + return _customAnimList.ObjectAt( index ); +} + +void HelperNode::AddAnimation( Event *ev ) +{ + float time; + if ( ev->NumArgs() > 2 ) + time = ev->GetFloat( 3 ); + else + time = 0.0f; + + AddAnimation(ev->GetString( 1 ) , GetWaitType( ev->GetString( 2 ) ) , time ); +} + +void HelperNode::AddAnimation( const str &anim , waitType_t waitType , float time ) +{ + customAnimListEntry_t* checkEntry; + + checkEntry = new customAnimListEntry_t; + checkEntry->anim = anim; + checkEntry->waitType = waitType; + checkEntry->time = time; + _customAnimList.AddObject( checkEntry ); + + _usingCustomAnimList = true; +} + +bool HelperNode::isAnimListFinished() +{ + return _customAnimListIndex > _customAnimList.NumObjects(); +} + +void HelperNode::NextAnim() +{ + _customAnimListIndex++; +} + +// +// Global Functions +// +void AddHelperNodeToList( HelperNode* node ) +{ + if ( node ) + HelperNodes.AddUniqueObject( node ); +} + +void RemoveHelperNodeFromList( HelperNode *node ) +{ + HelperNode* checkNode; + + for ( int i = HelperNodes.NumObjects() ; i > 0 ; i-- ) + { + checkNode = HelperNodes.ObjectAt( i ); + if ( checkNode == node ) + { + delete checkNode; + checkNode = NULL; + HelperNodes.RemoveObjectAt( i ); + return; + } + } +} + diff --git a/dlls/game/helper_node.h b/dlls/game/helper_node.h new file mode 100644 index 0000000..b691003 --- /dev/null +++ b/dlls/game/helper_node.h @@ -0,0 +1,426 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/helper_node.h $ +// $Revision:: 36 $ +// $Author:: Jmartel $ +// $Date:: 2/16/03 2:00p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: + +// +// Forward Declarations +// +class HelperNode; +class HelperNodeController; + +// This is declared here, because we reference class actor, +// but we can't just #include "actor.h" because of +// cyclical reference problem revolving around the +// the typedef'd HelperNodePtr. +class Actor; + + +#ifndef __HELPER_NODE_H__ +#define __HELPER_NODE_H__ + +#include "entity.h" +typedef SafePtr HelperNodePtr; + +extern Event EV_HelperNodeCommand; + +// Spawn Flag Masks +#define NODETYPE_FLEE (1<<0) +#define NODETYPE_WORK (1<<1) +#define NODETYPE_ANIM (1<<2) +#define NODETYPE_COVER (1<<3) +#define NODETYPE_PATROL (1<<4) +#define NODETYPE_SNIPER (1<<5) +#define NODETYPE_CUSTOM (1<<6) +#define NODETYPE_COMBAT (1<<7) + +//Enums +typedef enum + { + COVER_TYPE_NONE, + COVER_TYPE_CRATE, + COVER_TYPE_WALL, + COVER_TYPE_TOTAL_NUMBER + } CoverType_t; + +typedef enum + { + COVER_DIRECTION_NONE, + COVER_DIRECTION_LEFT, + COVER_DIRECTION_RIGHT, + COVER_DIRECTION_ALL, + COVER_DIRECTION_TOTAL_NUMBER + } CoverDirection_t; + +typedef enum + { + DESCRIPTOR_NONE, + DESCRIPTOR_CORRIDOR, + DESCRIPTOR_TOTAL_NUMBER + } NodeDescriptorType_t; + +typedef enum + { + WAITTYPE_ERROR, + WAITTYPE_EVENT, + WAITTYPE_ANIM, + WAITTYPE_TIME, + WAITTYPE_TOTAL_NUMBER + } waitType_t; + +typedef struct + { + str anim; + waitType_t waitType; + float time; + } customAnimListEntry_t; + +// Created by the level to handle the archiving of HelperNodes +class HelperNodeController : public Entity + { + public: + CLASS_PROTOTYPE( HelperNodeController ); + HelperNodeController(); + ~HelperNodeController(); + + void SendEventToNode( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + + +class HelperNode : public Listener + { + public: + CLASS_PROTOTYPE( HelperNode ); + HelperNode(); + ~HelperNode(); + + // Core Functionality + void SetTarget ( Event *ev ); + void SetTargetName ( Event *ev ); + void SetAngle ( Event *ev ); + void SetOrigin ( Event *ev ); + + void SetTarget ( const str &name ); + void SetTargetName ( const str &name ); + void SetAngle ( const Vector &ang ); + void SetOrigin ( const Vector &pos ); + + + //Command + virtual void SendCommand ( Event *ev ); + + //Add Animations to list + virtual void AddAnimation ( Event *ev ); + virtual void AddAnimation ( const str &anim , waitType_t waitType , float time ); + + + //Event Interface + virtual void SetEntryThread ( Event *ev ); + virtual void SetExitThread ( Event *ev ); + virtual void SetAnim ( Event *ev ); + virtual void SetAnimTarget ( Event *ev ); + virtual void SetKillTarget ( Event *ev ); + virtual void SetCustomType ( Event *ev ); + virtual void SetID ( Event *ev ); + virtual void SetAnimCount ( Event *ev ); + virtual void SetMaxKills ( Event *ev ); + virtual void SetWait ( Event *ev ); + virtual void SetWaitRandom ( Event *ev ); + virtual void SetMinHealth ( Event *ev ); + virtual void SetMinEnemyRange ( Event *ev ); + virtual void SetActivationRange ( Event *ev ); + virtual void SetFlags ( Event *ev ); + virtual void SetAnimActive ( Event *ev ); + virtual void SetCoverActive ( Event *ev ); + virtual void SetCoverType ( Event *ev ); + virtual void SetCoverDir ( Event *ev ); + virtual void SetWaitForAnim ( Event *ev ); + virtual void SetCriticalChange ( Event *ev ); + virtual void SetDescriptor ( Event *ev ); + virtual void SetPriority ( Event *ev ); + + + //Mutators + virtual void SetEntryThread ( const str& thread ); + virtual void SetExitThread ( const str& thread ); + virtual void SetAnim ( const str& anim ); + virtual void SetAnimTarget ( const str& animtarget ); + virtual void SetKillTarget ( const str& killtarget ); + virtual void SetCustomType ( const str& customtype ); + virtual void SetID ( int ID ); + virtual void SetAnimCount ( int animcount ); + virtual void SetMaxKills ( int maxkills ); + virtual void SetWait ( float wait ); + virtual void SetWaitRandom ( float wait ); + virtual void SetMinHealth ( float health ); + virtual void SetMinEnemyRange ( float range ); + virtual void SetActivationRange ( float range ); + virtual void SetFlags ( unsigned int flags ); + virtual void SetAnimActive ( qboolean animactive ); + virtual void SetCoverActive ( qboolean coveractive ); + virtual void SetWaitForAnim ( qboolean wait ); + virtual void SetCoverType ( CoverType_t covertype ); + virtual void SetCoverDir ( CoverDirection_t coverdir ); + virtual void SetCriticalChange ( qboolean change ); + virtual void SetDescriptor ( NodeDescriptorType_t descriptor ); + virtual void SetPriority ( float priority ); + virtual void SetUser ( Listener *user ); + virtual void SetLastUseTime ( float time ) { _lastUseTime = time; } + + + + //Accessors + const str& GetEntryThread(); + const str& GetExitThread(); + const str& GetAnim(); + const str& GetAnimTarget(); + const str& GetKillTarget(); + const str& GetCustomType(); + + + int GetID(); + int GetAnimCount(); + int GetMaxKills(); + int GetAnimListIndex(); + int GetAnimListCount(); + + float GetWaitTime(); + float GetMinHealth(); + float GetMinEnemyRange(); + float GetActivationRange(); + float GetPriority(); + float GetLastUseTime() { return _lastUseTime; } + + + CoverType_t GetCoverType(); + CoverDirection_t GetCoverDirection(); + NodeDescriptorType_t GetDescriptor(); + customAnimListEntry_t* GetNextAnimEntryFromList(); + customAnimListEntry_t* GetCurrentAnimEntryFromList(); + + //Queries + qboolean isOfType( unsigned int mask ); + qboolean isAnimActive(); + qboolean isCoverActive(); + qboolean isWaitRandom(); + qboolean isWaitForAnim(); + qboolean isChanged(); + qboolean isReserved(); + bool isUsingAnimList(); + bool isAnimListFinished(); + + //Utility + void RunEntryThread(); + void RunExitThread(); + void ReserveNode(); + void UnreserveNode(); + void NextAnim(); + + //For Convience + Vector origin; + str target; + str targetname; + Vector angles; + + //Static Functions + static HelperNode* FindClosestHelperNode( Actor &self , int mask , float maxDist , float minDistanceFromPlayer, bool unreserveCurrentNode = true ); + static HelperNode* FindClosestHelperNode( Actor &self , int mask , NodeDescriptorType_t descriptor , float maxDist ); + static HelperNode* FindClosestHelperNodeAtDistanceFrom( Actor &self , Entity *ent , int mask , NodeDescriptorType_t descriptor , float maxDistFromSelf , float minDistFromEnt ); + static HelperNode* FindClosestHelperNodeWithoutPathing( Actor &self, float maxDist ); + static HelperNode* FindClosestHelperNode( Actor &self , const str& customType , float maxDist ); + static HelperNode* FindClosestHelperNode( Actor &self , const str& targetName ); + static HelperNode* FindClosestHelperNodeThatCannotSeeEntity( Actor &self , int mask , unsigned int clipMask, float maxDist , float minDist , Entity* ent , float minDistFromPlayer ); + static HelperNode* FindHighestPriorityNode( Actor& self , const str& customType ); + static HelperNode* FindHighestPriorityNode( Actor& self, const str& customType , const str& targetedTo ); + static HelperNode* GetTargetedHelperNode( const str& targetName ); + static HelperNode* FindHelperNodeClosestTo( Actor &self , Entity *ent , int mask , float maxDist ); + static HelperNode* FindHelperNodeClosestToWithoutPathing( Actor &self , Entity *ent , int mask , float maxDist ); + static waitType_t GetWaitType( const str& waitType ); + static void CleanupHelperNodeList(); + + static int GetHelperNodeMask( const str& type ); + static bool isHelperNodeInRange( Actor &self , int mask , float range ); + + + // Archiving + void Archive( Archiver &arc ); + + protected: + void _init(); + CoverType_t _CoverTypeForString( const str& covertype ); + CoverDirection_t _CoverDirectionForString( const str& coverdir ); + NodeDescriptorType_t _DescriptorForString( const str& descriptor ); + + + private: + str _entryThread; + str _exitThread; + str _anim; + str _animtarget; + str _killtarget; + str _customType; + + int _id; + int _animcount; + int _maxkills; + + qboolean _animactive; + qboolean _coveractive; + qboolean _waitrandom; + qboolean _waitforanim; + qboolean _criticalchange; + bool _reserved; + + float _waittime; + float _minhealth; + float _minenemyrange; + float _activationRange; + float _priority; + float _lastUseTime; + + CoverType_t _covertype; + CoverDirection_t _coverdir; + + unsigned int _nodeflags; + NodeDescriptorType_t _descriptor; + + + ListenerPtr _user; + int _customAnimListIndex; + bool _usingCustomAnimList; + Container _customAnimList; + + + + }; + +inline void HelperNode::SetUser( Listener *user ) +{ + _user = user; +} + +inline int HelperNode::GetAnimListIndex() +{ + return _customAnimListIndex; +} + +inline int HelperNode::GetAnimListCount() +{ + return _customAnimList.NumObjects(); +} + +inline bool HelperNode::isUsingAnimList() +{ + return _usingCustomAnimList; +} + +inline void HelperNode::ReserveNode() + { + _reserved = true; + } + +inline void HelperNode::UnreserveNode() + { + _reserved = false; + } + +inline void HelperNode::Archive( Archiver &arc ) +{ + int num , i; + customAnimListEntry_t* animEntry; + + Listener::Archive( arc ); + + arc.ArchiveVector ( &origin ); + arc.ArchiveString ( &target ); + arc.ArchiveString ( &targetname ); + arc.ArchiveVector ( &angles ); + + arc.ArchiveString ( &_entryThread ); + arc.ArchiveString ( &_exitThread ); + arc.ArchiveString ( &_anim ); + arc.ArchiveString ( &_animtarget ); + arc.ArchiveString ( &_killtarget ); + arc.ArchiveString ( &_customType ); + + arc.ArchiveInteger ( &_id ); + arc.ArchiveInteger ( &_animcount ); + arc.ArchiveInteger ( &_maxkills ); + + arc.ArchiveBoolean ( &_animactive ); + arc.ArchiveBoolean ( &_coveractive ); + arc.ArchiveBoolean ( &_waitrandom ); + arc.ArchiveBoolean ( &_waitforanim ); + arc.ArchiveBoolean ( &_criticalchange ); + arc.ArchiveBool ( &_reserved ); + + arc.ArchiveFloat ( &_waittime ); + arc.ArchiveFloat ( &_minhealth ); + arc.ArchiveFloat ( &_minenemyrange ); + arc.ArchiveFloat ( &_activationRange ); + arc.ArchiveFloat ( &_priority ); + arc.ArchiveFloat ( &_lastUseTime ); + + ArchiveEnum( _covertype, CoverType_t ); + ArchiveEnum( _coverdir, CoverDirection_t ); + + arc.ArchiveUnsigned ( &_nodeflags ); + ArchiveEnum( _descriptor, NodeDescriptorType_t ); + + arc.ArchiveSafePointer( &_user ); + arc.ArchiveInteger ( &_customAnimListIndex ); + arc.ArchiveBool ( &_usingCustomAnimList ); + + if ( arc.Saving() ) + { + num = _customAnimList.NumObjects(); + + arc.ArchiveInteger( &num ); + + for ( i = 1 ; i <= num ; i++ ) + { + animEntry = _customAnimList.ObjectAt( i ); + arc.ArchiveString ( &animEntry->anim ); + ArchiveEnum( animEntry->waitType, waitType_t ); + arc.ArchiveFloat( &animEntry->time ); + } + } + else + { + arc.ArchiveInteger( &num ); + _customAnimList.ClearObjectList(); + _customAnimList.Resize( num ); + + for ( i = 1 ; i<= num ; i++ ) + { + animEntry = new customAnimListEntry_t; + arc.ArchiveString( &animEntry->anim ); + ArchiveEnum( animEntry->waitType, waitType_t ); + arc.ArchiveFloat( &animEntry->time ); + _customAnimList.AddObject( animEntry ); + } + + } + +} + + + +// Global Utility Functions +void AddHelperNodeToList( HelperNode* node ); +void RemoveHelperNodeFromList( HelperNode* node ); + + + +#endif /* __HELPER_NODE_H__ */ diff --git a/dlls/game/holdPosition.cpp b/dlls/game/holdPosition.cpp new file mode 100644 index 0000000..a753be8 --- /dev/null +++ b/dlls/game/holdPosition.cpp @@ -0,0 +1,359 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/holdPosition.cpp $ +// $Revision:: 7 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// +// PARAMETERS: +// +// ANIMATIONS: +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "holdPosition.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, HoldPosition, NULL ) + { + { &EV_Behavior_Args, &HoldPosition::SetArgs }, + { &EV_Behavior_AnimDone, &HoldPosition::AnimDone }, + { NULL, NULL } + }; + + + +//-------------------------------------------------------------- +// Name: RotateToEntity() +// Class: RotateToEntity +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +HoldPosition::HoldPosition() +{ + _legAnim = "idle"; + _twitchAnim = "twitch"; + _weaponTwitchAnim = ""; + _torsoAnim = ""; + _holdTimeMin = 1.0f; + _holdTimeMax = 1.5f;; + _nextTwitchTime = 0.0f; + _twitchInterval = 2.5f; + _canTwitch = true; + _animDone = false; +} + + +//-------------------------------------------------------------- +// Name: ~RotateToEntity() +// Class: RotateToEntity +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +HoldPosition::~HoldPosition() +{ +} + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: RotateToEntity +// +// Description: Sets Variables based on arguments inside the event +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void HoldPosition::SetArgs( Event *ev ) +{ +} + +void HoldPosition::AnimDone( Event *ev ) +{ + _animDone = true; +} + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: RotateToEntity +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void HoldPosition::Begin( Actor &self ) +{ + init(self); +} + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: RotateToEntity +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t HoldPosition::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + switch ( _state ) + { + //--------------------------------------------------------------------- + case HOLD_POSITION_HOLD: + //--------------------------------------------------------------------- + stateResult = evaluateStateHold(); + if ( stateResult == BEHAVIOR_SUCCESS ) + { + if ( level.time >= _nextTwitchTime && _canTwitch ) + { + if ( _self->combatSubsystem->HaveWeapon() ) + { + transitionToState(HOLD_POSITION_WEAPON_TWITCH); + return BEHAVIOR_EVALUATING; + } + else + { + transitionToState(HOLD_POSITION_TWITCH); + return BEHAVIOR_EVALUATING; + } + } + + transitionToState( HOLD_POSITION_HOLD ); + } + + break; + + //--------------------------------------------------------------------- + case HOLD_POSITION_TWITCH: + //--------------------------------------------------------------------- + stateResult = evaluateStateTwitch(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( HOLD_POSITION_HOLD ); + break; + + //--------------------------------------------------------------------- + case HOLD_POSITION_WEAPON_TWITCH: + //--------------------------------------------------------------------- + stateResult = evaluateStateWeaponTwitch(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( HOLD_POSITION_HOLD ); + + break; + + //--------------------------------------------------------------------- + case HOLD_POSITION_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + break; + + //--------------------------------------------------------------------- + case HOLD_POSITION_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: RotateToEntity +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void HoldPosition::End(Actor &self) +{ + _self->SetIgnoreWatchTarget( false ); +} + +void HoldPosition::transitionToState( HoldPositionStates_t state ) +{ + switch ( state ) + { + case HOLD_POSITION_HOLD: + setupStateHold(); + setInternalState( state , "HOLD_POSITION_HOLD" ); + break; + + case HOLD_POSITION_TWITCH: + setupStateTwitch(); + setInternalState( state , "HOLD_POSITION_TWITCH" ); + break; + + case HOLD_POSITION_WEAPON_TWITCH: + setupStateWeaponTwitch(); + setInternalState( state , "HOLD_POSITION_WEAPON_TWITCH" ); + break; + + case HOLD_POSITION_SUCCESS: + setInternalState( state , "HOLD_POSITION_SUCCESS" ); + break; + + case HOLD_POSITION_FAILED: + setInternalState( state , "HOLD_POSITION_FAILED" ); + break; + } +} + +void HoldPosition::setInternalState( HoldPositionStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +void HoldPosition::init( Actor &self ) +{ + _self = &self; + + transitionToState(HOLD_POSITION_HOLD); +} + +void HoldPosition::think() +{ +} + +void HoldPosition::setupStateHold() +{ + _endHoldTime = level.time + G_Random ( _holdTimeMax + _holdTimeMin ); + if ( !_self->combatSubsystem->HaveWeapon() ) + { + _self->SetAnim( _legAnim , EV_Actor_NotifyTorsoBehavior , legs ); + return; + } + + if ( _self->enemyManager->HasEnemy() ) + _torsoAnim = _self->combatSubsystem->GetAnimForMyWeapon( "CombatGunIdle" ); + else + _torsoAnim = _self->combatSubsystem->GetAnimForMyWeapon( "IdleGunIdle" ); + + if ( _torsoAnim.length() ) + { + _self->SetAnim( _torsoAnim, NULL , torso ); + } + else + { + _self->SetAnim( _legAnim , EV_Actor_NotifyTorsoBehavior , legs ); + } + + + +} + +BehaviorReturnCode_t HoldPosition::evaluateStateHold() +{ + if ( level.time >= _endHoldTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +void HoldPosition::failureStateHold( const str& failureReason ) +{ +} + +void HoldPosition:: setupStateTwitch() +{ + _self->SetIgnoreWatchTarget( true ); + _animDone = false; + _self->SetAnim( _legAnim , EV_Actor_NotifyTorsoBehavior , legs ); +} + +BehaviorReturnCode_t HoldPosition::evaluateStateTwitch() +{ + if ( _animDone ) + { + _nextTwitchTime = level.time + _twitchInterval; + _self->SetIgnoreWatchTarget( false ); + return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_EVALUATING; +} + +void HoldPosition:: failureStateTwitch( const str& failureReason ) +{ +} + + +void HoldPosition:: setupStateWeaponTwitch() +{ + _animDone = false; + if ( _self->enemyManager->HasEnemy() ) + _torsoAnim = _self->combatSubsystem->GetAnimForMyWeapon( "CombatGunTwitch" ); + else + _torsoAnim = _self->combatSubsystem->GetAnimForMyWeapon( "IdleGunTwitch" ); + + if ( _torsoAnim.length() ) + { + _self->SetIgnoreWatchTarget( true ); + //_self->SetAnim( _torsoAnim, NULL , torso ); + _self->ClearLegAnim(); + _self->ClearTorsoAnim(); + _self->SetAnim( _torsoAnim, EV_Actor_NotifyBehavior , legs ); + } + else + _canTwitch = false; + +} + +BehaviorReturnCode_t HoldPosition::evaluateStateWeaponTwitch() +{ + if ( _animDone ) + { + _nextTwitchTime = level.time + _twitchInterval; + _self->SetIgnoreWatchTarget( false ); + return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_EVALUATING; +} + +void HoldPosition:: failureStateWeaponTwitch( const str& failureReason ) +{ +} diff --git a/dlls/game/holdPosition.hpp b/dlls/game/holdPosition.hpp new file mode 100644 index 0000000..39af402 --- /dev/null +++ b/dlls/game/holdPosition.hpp @@ -0,0 +1,166 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/holdPosition.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// holdPosition Behavior Definition +// +//-------------------------------------------------------------------------------- + + +//============================== +// Forward Declarations +//============================== +class HoldPosition; + +#ifndef __HOLD_POSITION__ +#define __HOLD_POSITION__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: HoldPosition +// Base Class: Behavior +// +// Description: Makes the actor idle in position, holding its +// weapon appropriately, and occassionally twitch +// +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class HoldPosition : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + HOLD_POSITION_HOLD, + HOLD_POSITION_TWITCH, + HOLD_POSITION_WEAPON_TWITCH, + HOLD_POSITION_SUCCESS, + HOLD_POSITION_FAILED + } HoldPositionStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( HoldPositionStates_t state ); + void setInternalState ( HoldPositionStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + + void setupStateHold (); + BehaviorReturnCode_t evaluateStateHold (); + void failureStateHold ( const str& failureReason ); + + void setupStateTwitch (); + BehaviorReturnCode_t evaluateStateTwitch (); + void failureStateTwitch ( const str& failureReason ); + + void setupStateWeaponTwitch (); + BehaviorReturnCode_t evaluateStateWeaponTwitch (); + void failureStateWeaponTwitch ( const str& failureReason ); + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( HoldPosition ); + + HoldPosition(); + ~HoldPosition(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + // Accessors + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + str _legAnim; + str _torsoAnim; + str _twitchAnim; + str _weaponTwitchAnim; + + float _holdTimeMin; + float _holdTimeMax; + float _endHoldTime; + float _nextTwitchTime; + float _twitchInterval; + bool _canTwitch; + bool _animDone; + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + HoldPositionStates_t _state; + Actor *_self; + + + }; + +inline void HoldPosition::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + + // + // Archive Components + // + + // + // Archive Member Variables + // + ArchiveEnum ( _state, HoldPositionStates_t ); + arc.ArchiveObjectPointer( ( Class ** )&_self ); + + arc.ArchiveString( &_legAnim ); + arc.ArchiveString( &_torsoAnim ); + arc.ArchiveString( &_twitchAnim ); + arc.ArchiveString( &_weaponTwitchAnim ); + arc.ArchiveFloat( &_holdTimeMin ); + arc.ArchiveFloat( &_holdTimeMax ); + arc.ArchiveFloat( &_endHoldTime ); + arc.ArchiveFloat( &_nextTwitchTime ); + arc.ArchiveFloat( &_twitchInterval ); + arc.ArchiveBool ( &_canTwitch ); + arc.ArchiveBool ( &_animDone ); +} + + +#endif /* __HOLD_POSITION__ */ + diff --git a/dlls/game/interpreter.cpp b/dlls/game/interpreter.cpp new file mode 100644 index 0000000..bd61585 --- /dev/null +++ b/dlls/game/interpreter.cpp @@ -0,0 +1,1358 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/interpreter.cpp $ +// $Revision:: 22 $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "interpreter.h" +#include "program.h" +#include "compiler.h" +#include "scriptmaster.h" +#include "scriptslave.h" +#include "camera.h" + +CLASS_DECLARATION( Listener, Interpreter, NULL ) +{ + { &EV_ScriptThread_Execute, &Interpreter::Execute }, + + { NULL, NULL } +}; + +inline void Interpreter::Archive( Archiver &arc ) +{ + int i; + + Listener::Archive( arc ); + + arc.ArchiveInteger( &pr_depth ); + + for ( i = 0; i < pr_depth; i++ ) + { + arc.ArchiveInteger( &pr_stack[i].s ); + arc.ArchiveObjectPointer( ( Class ** )&pr_stack[i].f ); + arc.ArchiveInteger( &pr_stack[i].stackbase ); + } + + arc.ArchiveRaw( localstack_type, sizeof( localstack_type[0] ) * LOCALSTACK_SIZE ); + arc.ArchiveRaw( localstack, sizeof( localstack[0] ) * LOCALSTACK_SIZE ); + arc.ArchiveInteger( &localstack_used ); + arc.ArchiveInteger( &localstack_base ); + + arc.ArchiveBool( &pr_trace ); + arc.ArchiveObjectPointer( ( Class ** )&pr_xfunction ); + arc.ArchiveInteger( &pr_xstatement ); + +// arc.ArchiveObjectPointer( ( Class ** )&program ); // shouldn't need this since only one program object + arc.ArchiveInteger( &instruction_pointer ); + arc.ArchiveBool( &doneProcessing ); + arc.ArchiveBool( &threadDying ); + + updateList.Archive( arc ); + + arc.ArchiveInteger( &threadNum ); + arc.ArchiveString( &threadName ); +} + + +/* +============ +GlobalString + +Returns a string with a description and the contents of a global, +padded to 20 field width +============ +*/ +const char *Interpreter::GlobalString( int ofs ) +{ +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + char *s; + int i; + ddef_t *def; + void *val; + static char line[ 128 ]; + + val = ( void * )&pr_globals[ ofs ]; + def = ED_GlobalAtOfs( ofs ); + if (!def) + sprintf (line,"%i(???)", ofs); + else + { + s = PR_ValueString (def->type, val); + sprintf (line,"%i(%s)%s", ofs, pr_strings + def->s_name, s); + } + + i = strlen(line); + for ( ; i<20 ; i++) + strcat (line," "); + strcat (line," "); + + return line; +#else + +****************************************************************************/ + + return ""; +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#endif + +****************************************************************************/ + +} + +const char *Interpreter::GlobalStringNoContents( int ofs ) +{ +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + int i; + ddef_t *def; + static char line[128]; + + def = ED_GlobalAtOfs(ofs); + if (!def) + sprintf (line,"%i(???)", ofs); + else + sprintf (line,"%i(%s)", ofs, pr_strings + def->s_name); + + i = strlen(line); + for ( ; i<20 ; i++) + strcat (line," "); + strcat (line," "); + + return line; +#else + +****************************************************************************/ + + return ""; +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#endif + +****************************************************************************/ + +} + +/* +================= +PrintStatement +================= +*/ +void Interpreter::PrintStatement( const dstatement_t *s ) +{ + int i; + + if ( ( unsigned )s->op < ( sizeof( pr_opcodes ) / sizeof( pr_opcodes[ 0 ] ) ) ) + { + Com_Printf( "%s ", pr_opcodes[ s->op ].name ); + + i = strlen( pr_opcodes[ s->op ].name ); + for( ; i < 10; i++ ) + { + Com_Printf( " " ); + } + } + + if ( ( s->op == OP_IF ) || ( s->op == OP_IFNOT ) ) + { + Com_Printf( "%sbranch %i", GlobalString( s->a ), s->b ); + } + else if ( s->op == OP_GOTO ) + { + Com_Printf( "branch %i", s->a ); + } + else if ( ( unsigned )( s->op - OP_STORE_F ) < 6 ) + { + Com_Printf( "%s", GlobalString( s->a ) ); + Com_Printf( "%s", GlobalStringNoContents( s->b ) ); + } + else + { + if ( s->a ) + { + Com_Printf( "%s", GlobalString( s->a ) ); + } + if ( s->b ) + { + Com_Printf( "%s", GlobalString( s->b ) ); + } + if ( s->c ) + { + Com_Printf( "%s", GlobalStringNoContents( s->c ) ); + } + } + + Com_Printf( "\n" ); +} + +/* +============ +StackTrace +============ +*/ +void Interpreter::StackTrace( void ) +{ + dfunction_t *f; + int i; + + // anything less than zero will be bad + if ( pr_depth <= 0 ) + { + Com_Printf( "\n" ); + return; + } + + pr_stack[ pr_depth ].f = pr_xfunction; + for( i = pr_depth; i >= 0; i-- ) + { + f = pr_stack[ i ].f; + + if ( !f ) + { + Com_Printf( "\n" ); + } + else + { + Com_Printf( "%12s : %s\n", f->s_file.c_str(), f->s_name.c_str() ); + } + } +} + +/* +============ +Profile + +============ +*/ +void Interpreter::Profile( void ) +{ + dfunction_t *f; + dfunction_t *best; + int max; + int num; + int i; + + num = 0; + do + { + max = 0; + best = NULL; + for( i = 0; i < program->numfunctions; i++ ) + { + f = &program->functions[ i ]; + if ( f->profile > max ) + { + max = f->profile; + best = f; + } + } + + if ( best ) + { + if ( num < 10 ) + { + Com_Printf( "%7i %s\n", best->profile, best->s_name.c_str() ); + } + + num++; + best->profile = 0; + } + } + while( best ); +} + +/* +============ +RunError + +Aborts the currently executing function +============ +*/ +void Interpreter::RunError( const char *error, ... ) +{ + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, error ); + vsprintf( string, error, argptr ); + va_end( argptr ); + + //PrintStatement( program->statements + pr_xstatement ); + StackTrace(); + Com_Printf( "%s\n", string ); + + // dump the stack so host_error can shutdown functions + pr_depth = 0; + + gi.Error( ERR_DROP, "Program error" ); +} +/* +==================== +Interpreter::EntityError +Prints an error message and stack trace when an entity error is found. +==================== +*/ +inline void Interpreter::NullEntityError( const dstatement_t* statement ) +{ + str filename( program->filenames[ statement->file - 1 ] ); + Com_Printf( "===============================================\n\n" ); + gi.WDPrintf( "Null entity referenced in %s on line %d\n", filename.c_str(), statement->linenumber ); + Com_Printf( "Stack Trace follows:\n" ); + StackTrace(); + Com_Printf( "===============================================\n\n" ); +} + +/* +==================== +ThreadCall + +Copys the args from the calling thread's stack +==================== +*/ +void Interpreter::ThreadCall( const Interpreter *source, dfunction_t *newf, int args ) +{ + int start; + int i; + + if ( scr_printfunccalls->integer ) + gi.Printf( "thread call - %s (ignore previous and next func call)\n", newf->s_name.c_str() ); + + Reset(); + start = source->localstack_used - args; + for( i = 0; i < args; i++ ) + { + localstack_type[ i ] = source->localstack_type[ start + i ]; + switch( localstack_type[ i ] ) + { + case ev_string : + localstack[ i ] = program->AllocString(); + program->strings[ localstack[ i ] ].s = program->strings[ source->localstack[ start + i ] ].s; + break; + + case ev_vector : + localstack[ i + 0 ] = source->localstack[ start + i + 0 ]; + localstack[ i + 1 ] = source->localstack[ start + i + 1 ]; + localstack[ i + 2 ] = source->localstack[ start + i + 2 ]; + + // set the type of the other two to float to be safe + localstack_type[ i + 1 ] = ev_float; + localstack_type[ i + 2 ] = ev_float; + + // only add 2 since for loop will add 1 + i += 2; + break; + + default : + localstack[ i ] = source->localstack[ start + i ]; + break; + } + } + + localstack_used = args; + localstack_base = 0; + + instruction_pointer = EnterFunction( newf ); + + CancelEventsOfType( EV_ScriptThread_Execute ); + PostEvent( EV_ScriptThread_Execute, 0.0f ); +} + +/* +==================== +EnterFunction + +Returns the new program statement counter +==================== +*/ +int Interpreter::EnterFunction( dfunction_t *f ) +{ + int i; + int c; + int off; + int stackpos; + etype_t type; + + if ( pr_depth >= MAX_STACK_DEPTH ) + { + RunError( "stack overflow" ); + } + + if ( scr_printfunccalls->integer ) + gi.Printf( "func call - %s\n", f->s_name.c_str() ); + + pr_stack[ pr_depth ].s = pr_xstatement; + pr_stack[ pr_depth ].f = pr_xfunction; + pr_stack[ pr_depth ].stackbase = localstack_base; + pr_depth++; + + // allocate space on the stack for locals + // parms are already on stack + c = f->locals - f->parm_total; + assert( c >= 0 ); + + if ( localstack_used + c > LOCALSTACK_SIZE ) + { + RunError( "EnterFuncton: locals stack overflow\n" ); + } + + off = f->parm_start + f->parm_total; + for( i = 0; i < c; i++ ) + { + stackpos = localstack_used + i; + + type = program->pr_global_defs[ off + i ]->type->type; + + localstack_type[ stackpos ] = type; + switch( localstack_type[ stackpos ] ) + { + case ev_string : + localstack[ stackpos ] = program->AllocString(); + break; + + case ev_vector : + localstack[ stackpos + 0 ] = program->getInteger( off + i + 0 ); + localstack[ stackpos + 1 ] = program->getInteger( off + i + 1 ); + localstack[ stackpos + 2 ] = program->getInteger( off + i + 2 ); + + // set the type of the other two to float to be safe + localstack_type[ stackpos + 1 ] = ev_float; + localstack_type[ stackpos + 2 ] = ev_float; + + // only add 2 since for loop will add 1 + i += 2; + break; + + default : + localstack[ stackpos ] = program->getInteger( off + i ); + break; + } + } + + localstack_used += c; + localstack_base = localstack_used - f->locals - 1; + + pr_xfunction = f; + + // offset the s++ + return f->first_statement - 1; +} + +/* +==================== +CleanupStack +==================== +*/ +void Interpreter::CleanupStack( int localstack_used, int oldstacktop ) +{ + int i; + + // delete any strings that were on the stack + for( i = localstack_used; i < oldstacktop; i++ ) + { + if ( localstack_type[ i ] == ev_string ) + program->FreeString( localstack[ i ] ); + localstack_type[ i ] = ev_void; + } +} + +/* +==================== +LeaveFunction +==================== +*/ +int Interpreter::LeaveFunction( void ) +{ + int c; + int oldstacktop; + + if ( pr_depth <= 0 ) + { + gi.Error( ERR_DROP, "prog stack underflow" ); + } + + // remove locals from the stack + c = pr_xfunction->locals; + oldstacktop = localstack_used; + localstack_used -= c; + if ( localstack_used < 0 ) + { + RunError( "LeaveFunction: locals stack underflow\n" ); + } + + assert( localstack_used == ( localstack_base + 1 ) ); + + CleanupStack( localstack_used, oldstacktop ); + + // up stack + pr_depth--; + pr_xfunction = pr_stack[ pr_depth ].f; + localstack_base = pr_stack[ pr_depth ].stackbase; + + return pr_stack[ pr_depth ].s; +} + +const char *param_types[] = {"void", "string", "float", "vector", "entity", "function" }; + +Event *Interpreter::EventForFunction( const dfunction_t *func, int args ) +{ + Event *ev; + int i; + eval_t *st; + int pos; + int start; + + ev = new Event( func->eventnum ); + ev->SetSource( EV_FROM_SCRIPT ); + ev->SetThread( ( CThread * )this ); + ev->SetLineNumber( program->statements[ instruction_pointer ].linenumber ); + + start = localstack_used - args; + for( i = 0, pos = 0; pos < args; i++ ) + { + st = ( eval_t * )&localstack[ start + pos ]; + + // remove this for shipping product. + if ( + (func->parm_type[i] != localstack_type[start + pos]) && + (func->parm_type[i] != ev_vector) && + (localstack_type[start + pos] != ev_float) + ) + { + RunError ( "===============================\n" + "Type mismatch on line %d. Func %s expected %s, got %s\n" + "===============================\n", + program->statements[ instruction_pointer ].linenumber, + func->s_name.c_str(), + param_types[func->parm_type[i]], + param_types[localstack_type[start + pos]] ); + } + + switch( func->parm_type[ i ] ) + { + case ev_string : + ev->AddString( program->strings[ st->string ].s ); + break; + + case ev_float : + ev->AddFloat( st->_float ); + break; + + case ev_vector : + ev->AddVector( Vector( st->vector ) ); + break; + + case ev_entity : + if ( st->entity > 0 ) + { + if ( st->entity >= game.maxentities ) + { + gi.WDPrintf( "Bad entity number %d from line %d in the script\n", st->entity - 1, program->statements[ instruction_pointer ].linenumber ); + } + else + { + ev->AddEntity( G_GetEntity( st->entity - 1 ) ); + } + } + else if ( st->entity < 0 ) + { + TargetList *list; + + list = world->GetTargetList( -st->entity ); + if ( list ) + { + ev->AddEntity( list->GetNextEntity( NULL ) ); + } + else + { + ev->AddEntity( NULL ); + } + } + else + { + ev->AddEntity( NULL ); + } + break; + + default: + RunError( "Bad type on builtin call" ); + } + + pos += func->parm_size[ i ]; + } + + return ev; +} + +void Interpreter::DoMove( void ) +{ +} + +/* +==================== +ExecuteProgram +==================== +*/ +void Interpreter::Execute( Event *e ) +{ + eval_t *a; + eval_t *b; + eval_t *c; + dstatement_t *st; + dfunction_t *newf; + int runaway; + int exitdepth; + Listener *obj; + Entity *ent; + Event *ev; + char text[ 128 ]; + int i; + int n; + TargetList *list; + int oldstacktop; + Interpreter *newThread; + int stridx; + + if ( threadDying ) + { + return; + } + + runaway = 100000; + + // clear the updateList so that all objects moved this frame are notified before they receive any commands + // we have to do this here as well as in DoMove, since DoMove may not be called + updateList.ClearObjectList(); + + // make a stack frame + exitdepth = 0; + + doneProcessing = false; + while( !doneProcessing && !threadDying ) + { + // next statement + instruction_pointer++; + + st = &program->statements[ instruction_pointer ]; + if ( st->a < 0 ) + { + a = ( eval_t * )&localstack[ localstack_base - st->a ]; + } + else + { + a = ( eval_t * )&program->pr_globals[ st->a ]; + } + if ( st->b < 0 ) + { + b = ( eval_t * )&localstack[ localstack_base - st->b ]; + } + else + { + b = ( eval_t * )&program->pr_globals[ st->b ]; + } + if ( st->c < 0 ) + { + c = ( eval_t * )&localstack[ localstack_base - st->c ]; + } + else + { + c = ( eval_t * )&program->pr_globals[ st->c ]; + } + + if ( !--runaway ) + { + RunError( "runaway loop error" ); + } + + pr_xfunction->profile++; + pr_xstatement = instruction_pointer; + + if ( pr_trace ) + { + PrintStatement( st ); + } + + switch( st->op ) + { + case OP_ADD_F: + c->_float = a->_float + b->_float; + break; + + case OP_ADD_V: + VectorAdd( a->vector, b->vector, c->vector ); + break; + + case OP_ADD_S: + program->strings[ c->string ].s = program->strings[ a->string ].s + program->strings[ b->string ].s; + break; + + case OP_ADD_FS: + sprintf( text, "%g", a->_float ); + program->strings[ c->string ].s = text + program->strings[ b->string ].s; + break; + + case OP_ADD_SF: + sprintf( text, "%g", b->_float ); + program->strings[ c->string ].s = program->strings[ a->string ].s + text; + break; + + case OP_ADD_VS: + sprintf( text, "(%g %g %g)", a->vector[0], a->vector[1], a->vector[2]); + program->strings[ c->string ].s = text + program->strings[ b->string ].s; + break; + + case OP_ADD_SV: + sprintf( text, "(%g %g %g)", b->vector[0], b->vector[1], b->vector[2]); + program->strings[ c->string ].s = program->strings[ a->string ].s + text ; + break ; + + case OP_SUB_F: + c->_float = a->_float - b->_float; + break; + + case OP_SUB_V: + VectorSubtract( a->vector, b->vector, c->vector ); + break; + + case OP_MUL_F: + c->_float = a->_float * b->_float; + break; + + case OP_MUL_V: + c->_float = DotProduct( a->vector, b->vector ); + break; + + case OP_MUL_FV: + VectorScale( b->vector, a->_float, c->vector ); + break; + + case OP_MUL_VF: + VectorScale( a->vector, b->_float, c->vector ); + break; + + case OP_DIV_F: + if ( b->_float != 0.0f ) + { + c->_float = a->_float / b->_float; + } + else + { + assert( 0 ); + c->_float = 0; + } + break; + + case OP_BITAND: + c->_float = ( int )a->_float & ( int )b->_float; + break; + + case OP_BITOR: + c->_float = ( int )a->_float | ( int )b->_float; + break; + + case OP_GE: + c->_float = a->_float >= b->_float; + break; + + case OP_LE: + c->_float = a->_float <= b->_float; + break; + + case OP_GT: + c->_float = a->_float > b->_float; + break; + + case OP_LT: + c->_float = a->_float < b->_float; + break; + + case OP_AND: + c->_float = a->_float && b->_float; + break; + + case OP_OR: + c->_float = a->_float || b->_float; + break; + + case OP_NOT_F: + c->_float = !a->_float; + break; + + case OP_NOT_V: + c->_float = !a->vector[ 0 ] && !a->vector[ 1 ] && !a->vector[ 2 ]; + break; + + case OP_NOT_S: + c->_float = !&program->strings[ a->string ].s || ( program->strings[ a->string ].s.length() == 0 ); + break; + + case OP_NOT_FNC: + c->_float = !a->function; + break; + + case OP_NOT_ENT: + c->_float = !a->entity; + break; + + case OP_EQ_F: + c->_float = ( a->_float == b->_float ); + break; + + case OP_EQ_V: + c->_float = ( a->vector[ 0 ] == b->vector[ 0 ] ) && + ( a->vector[ 1 ] == b->vector[ 1 ] ) && + ( a->vector[ 2 ] == b->vector[ 2 ] ); + break; + + case OP_EQ_S: + c->_float = !program->strings[ a->string ].s.cmp( program->strings[ b->string ].s ); + break; + + case OP_EQ_E: + c->_float = ( a->_int == b->_int ); + break; + + case OP_EQ_FNC: + c->_float = ( a->function == b->function ); + break; + + case OP_NE_F: + c->_float = ( a->_float != b->_float ); + break; + + case OP_NE_V: + c->_float = ( a->vector[ 0 ] != b->vector[ 0 ] ) || + ( a->vector[ 1 ] != b->vector[ 1 ] ) || + ( a->vector[ 2 ] != b->vector[ 2 ] ); + break; + + case OP_NE_S: + c->_float = program->strings[ a->string ].s.cmp( program->strings[ b->string ].s ); + break; + + case OP_NE_E: + c->_float = ( a->_int != b->_int ); + break; + + case OP_NE_FNC: + c->_float = ( a->function != b->function ); + break; + + case OP_UADD_F : + b->_float += a->_float; + break; + + case OP_USUB_F : + b->_float -= a->_float; + break; + + case OP_UMUL_F : + b->_float *= a->_float; + break; + + case OP_UDIV_F : + if ( a->_float != 0.0f ) + { + b->_float /= a->_float; + } + else + { + assert( 0 ); + b->_float = 0; + } + break; + + case OP_UOR_F : + b->_float = ( int )b->_float | ( int )a->_float; + break; + + case OP_UAND_F : + b->_float = ( int )b->_float & ( int )a->_float; + break; + + case OP_UINC_F : + a->_float++; + break; + + case OP_UDEC_F : + a->_float--; + break; + + case OP_STORE_F: + case OP_STORE_ENT: + case OP_STORE_FNC: // pointers + b->_int = a->_int; + break; + + case OP_STORE_S: + if ( st->b == OFS_RETURN ) + { + // always use a static string for return values so that we + // don't have to worry about freeing it up + b->string = 0; + } + program->strings[ b->string ] = program->strings[ a->string ]; + break; + + + case OP_STORE_V: + VectorCopy( a->vector, b->vector ); + break; + + case OP_STORE_FTOS: + if ( st->b == OFS_RETURN ) + { + // always use a static string for return values so that we + // don't have to worry about freeing it up + b->string = 0; + } + if ( a->_float == ( float )( int )a->_float ) + { + sprintf( text, "%d", ( int )a->_float ); + } + else + { + sprintf( text, "%f", a->_float ); + } + program->strings[ b->string ].s = text; + break; + + case OP_IFNOT: + if ( !a->_int ) + { + // offset the instruction_pointer++ + instruction_pointer += st->b - 1; + } + break; + + case OP_IF: + if ( a->_int ) + { + // offset the instruction_pointer++ + instruction_pointer += st->b - 1; + } + break; + + case OP_GOTO: + // offset the instruction_pointer++ + instruction_pointer += st->a - 1; + break; + + case OP_THREAD: + + if ( !a->function ) + { + RunError( "NULL function" ); + } + + newf = &program->functions[ a->function ]; + + newThread = Director.CreateThread( newf->s_name ); + newThread->ThreadCall( this, newf, st->b ); + + // return the thread number to the script + program->pr_globals[ OFS_RETURN ] = newThread->ThreadNum(); + program->pr_globals[ OFS_RETURN + 1 ] = 0; + program->pr_globals[ OFS_RETURN + 2 ] = 0; + + // pop our parms off the stack + oldstacktop = localstack_used; + localstack_used -= st->b; + if ( localstack_used < 0 ) + { + RunError( "Execute: locals stack underflow\n" ); + } + + CleanupStack( localstack_used, oldstacktop ); + break; + + case OP_CALL: + + if ( !a->function ) + { + RunError( "NULL function" ); + } + + newf = &program->functions[ a->function ]; + if ( newf->first_statement < 0 ) + { + // negative statements are events + if ( ValidEvent( newf->eventnum ) ) + { + ev = EventForFunction( newf, st->b ); + ProcessEvent( ev ); + + if ( scr_printeventcalls->integer ) + gi.Printf( "event call - %s\n", ev->getName() ); + } + + // pop our parms off the stack + oldstacktop = localstack_used; + localstack_used -= st->b; + if ( localstack_used < 0 ) + { + RunError( "Execute: locals stack underflow\n" ); + } + + CleanupStack( localstack_used, oldstacktop ); + break; + } + + instruction_pointer = EnterFunction( newf ); + break; + + case OP_OCALL: + + if ( !a->function ) + { + RunError( "NULL function" ); + } + + newf = &program->functions[ a->function ]; + assert( newf->first_statement < 0 ); + + if ( ( st->b >= RESERVED_OFS ) && ( st->b < OFS_END ) ) + { + switch( st->b ) + { + case OFS_CAM : + obj = &CameraMan; + break; + + default : + obj = NULL; // shutup compiler + RunError( "Execute: Invalid object call\n" ); + break; + } + + if ( obj->ValidEvent( newf->eventnum ) ) + { + ev = EventForFunction( newf, st->c ); + obj->ProcessEvent( ev ); + + if ( scr_printeventcalls->integer ) + gi.Printf( "event call - %s\n", ev->getName() ); + } + } + else if ( b->entity > 0 ) + { + ent = G_GetEntity( b->entity - 1 ); + if ( ent && ent->ValidEvent( newf->eventnum ) ) + { + if ( !updateList.ObjectInList( ent->entnum ) ) + { + updateList.AddObject( ent->entnum ); + + // Tell the object that we're about to send it some orders + ent->ProcessEvent( EV_ScriptSlave_NewOrders ); + } + + ev = EventForFunction( newf, st->c ); + ent->ProcessEvent( ev ); + + if ( scr_printeventcalls->integer ) + gi.Printf( "event call - %s.%s\n", ent->targetname.c_str(), ev->getName() ); + } + } + else if ( b->entity < 0 ) + { + list = world->GetTargetList( -b->entity ); + + if ( !list ) + { + NullEntityError( st ); + } + + if ( list ) + { + n = list->list.NumObjects(); + + if ( !n ) + { + NullEntityError( st ); + } + + if ( n ) + { + ev = EventForFunction( newf, st->c ); + for( i = 1; i < n; i++ ) + { + ent = list->list.ObjectAt( i ); + if ( ent->ValidEvent( newf->eventnum ) ) + { + if ( !updateList.ObjectInList( ent->entnum ) ) + { + updateList.AddObject( ent->entnum ); + + // Tell the object that we're about to send it some orders + ent->ProcessEvent( EV_ScriptSlave_NewOrders ); + } + + ent->ProcessEvent( new Event( ev ) ); + + if ( scr_printeventcalls->integer ) + gi.Printf( "event call - %s.%s\n", ent->targetname.c_str(), ev->getName() ); + } + else + { + ev->Error( "Entity '%s'(%d) cannot process event %s\n", + ent->targetname.c_str(), ent->entnum, ev->getName() ); + } + } + + ent = list->list.ObjectAt( i ); + if ( ent->ValidEvent( newf->eventnum ) ) + { + if ( !updateList.ObjectInList( ent->entnum ) ) + { + updateList.AddObject( ent->entnum ); + + // Tell the object that we're about to send it some orders + ent->ProcessEvent( EV_ScriptSlave_NewOrders ); + } + + ent->ProcessEvent( ev ); + + if ( scr_printeventcalls->integer ) + gi.Printf( "event call - %s.%s\n", ent->targetname.c_str(), ev->getName() ); + } + else + { + ev->Error( "Entity '%s'(%d) cannot process event %s\n", + ent->targetname.c_str(), ent->entnum, ev->getName() ); + delete ev; + } + } + } + } + else + { + NullEntityError( st ); + } + + // pop our parms off the stack + oldstacktop = localstack_used; + localstack_used -= st->c; + if ( localstack_used < 0 ) + { + RunError( "Execute: locals stack underflow\n" ); + } + + CleanupStack( localstack_used, oldstacktop ); + break; + + case OP_PUSH_F : + if ( localstack_used >= LOCALSTACK_SIZE ) + { + RunError( "Execute: locals stack overflow\n" ); + } + + localstack_type[ localstack_used ] = ev_float; + localstack[ localstack_used++ ] = a->_int; + break; + + case OP_PUSH_FTOS : + if ( localstack_used >= LOCALSTACK_SIZE ) + { + RunError( "Execute: locals stack overflow\n" ); + } + + if ( a->_float == ( float )( int )a->_float ) + { + sprintf( text, "%d", ( int )a->_float ); + } + else + { + sprintf( text, "%f", a->_float ); + } + localstack_type[ localstack_used ] = ev_string; + stridx = program->AllocString(); + program->strings[ stridx ].s = text; + localstack[ localstack_used++ ] = ( int )stridx; + break; + + case OP_PUSH_ENT : + if ( localstack_used >= LOCALSTACK_SIZE ) + { + RunError( "Execute: locals stack overflow\n" ); + } + + localstack_type[ localstack_used ] = ev_entity; + localstack[ localstack_used++ ] = a->_int; + break; + + case OP_PUSH_FNC : + if ( localstack_used >= LOCALSTACK_SIZE ) + { + RunError( "Execute: locals stack overflow\n" ); + } + + localstack_type[ localstack_used ] = ev_function; + localstack[ localstack_used++ ] = a->_int; + break; + + case OP_PUSH_S : + if ( localstack_used >= LOCALSTACK_SIZE ) + { + RunError( "Execute: locals stack overflow\n" ); + } + + localstack_type[ localstack_used ] = ev_string; + stridx = program->AllocString(); + program->strings[ stridx ].s = program->strings[ a->string ].s; + localstack[ localstack_used++ ] = ( int )stridx; + break; + + case OP_PUSH_V : + if ( localstack_used + 3 > LOCALSTACK_SIZE ) + { + RunError( "Execute: locals stack overflow\n" ); + } + + localstack_type[ localstack_used ] = ev_float; + localstack[ localstack_used++ ] = *( int * )&a->vector[ 0 ]; + + localstack_type[ localstack_used ] = ev_float; + localstack[ localstack_used++ ] = *( int * )&a->vector[ 1 ]; + + localstack_type[ localstack_used ] = ev_float; + localstack[ localstack_used++ ] = *( int * )&a->vector[ 2 ]; + break; + + case OP_DONE: + case OP_RETURN: + if ( st->a < 0 ) + { + if ( localstack_type[ localstack_base - st->a ] == ev_string ) + { + // always use a static string for return values so that we + // don't have to worry about freeing it up + program->pr_globals[ OFS_RETURN ] = 0; + } + else + { + // wacky casting to prevent conversion from int to float + program->pr_globals[ OFS_RETURN ] = *(float *)&localstack[ localstack_base - st->a ]; + program->pr_globals[ OFS_RETURN + 1 ] = *(float *)&localstack[ localstack_base - st->a + 1 ]; + program->pr_globals[ OFS_RETURN + 2 ] = *(float *)&localstack[ localstack_base - st->a + 2 ]; + } + } + else if ( st->a != 0 ) + { + program->pr_globals[ OFS_RETURN ] = program->pr_globals[ st->a ]; + program->pr_globals[ OFS_RETURN + 1 ] = program->pr_globals[ st->a + 1 ]; + program->pr_globals[ OFS_RETURN + 2 ] = program->pr_globals[ st->a + 2 ]; + } + + instruction_pointer = LeaveFunction(); + if ( pr_depth == exitdepth ) + { + // all done + DoMove(); + Director.NotifyOtherThreads( threadNum ); + PostEvent( EV_Remove, 0.0f ); + doneProcessing = true; + threadDying = true; + } + break; + + default: + RunError( "Bad opcode %i", st->op ); + } + } +} + +void Interpreter::Reset( void ) +{ + CleanupStack( 0, localstack_used ); + + pr_depth = 0; + localstack_used = 0; + localstack_base = 0; + pr_trace = false; + + pr_xfunction = NULL; + pr_xstatement = -1; + + threadDying = false; + doneProcessing = true; + + memset( localstack, 0, sizeof( localstack ) ); + memset( pr_stack, 0, sizeof( pr_stack ) ); +} + +Interpreter::Interpreter() +{ + localstack_used = 0; // these need to be here for cleaupStack + localstack_base = 0; + threadNum = 0; + program = &::program; + Reset(); +} + +Interpreter::~Interpreter() +{ + Director.NotifyOtherThreads( threadNum ); + Director.RemoveThread( threadNum ); + + CleanupStack( 0, localstack_used ); +} + +qboolean Interpreter::labelExists( const char *label ) +{ + func_t func; + + func = program->findFunction( label ); + if ( func < 0 || ( func >= program->numfunctions ) ) + { + return false; + } + + return true; +} + +qboolean Interpreter::Goto( const char *label ) +{ + func_t func; + + func = program->findFunction( label ); + if ( ( func < 0 ) || ( func >= program->numfunctions ) ) + { + gi.WDPrintf( "Can't find function '%s' in program\n", label ); + return false; + } + + instruction_pointer = EnterFunction( &program->functions[ func ] ); + + CancelEventsOfType( EV_ScriptThread_Execute ); + PostEvent( EV_ScriptThread_Execute, 0.0f ); + + return true; +} + +qboolean Interpreter::Setup( int num, const char *label ) +{ + Reset(); + + threadNum = num; + threadName = label; + + return Goto( label ); +} diff --git a/dlls/game/interpreter.h b/dlls/game/interpreter.h new file mode 100644 index 0000000..2337ff8 --- /dev/null +++ b/dlls/game/interpreter.h @@ -0,0 +1,132 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/interpreter.h $ +// $Revision:: 6 $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// + +#ifndef __INTERPRETER_H__ +#define __INTERPRETER_H__ + +#include "program.h" + +#define MAX_STACK_DEPTH 32 +#define LOCALSTACK_SIZE 2048 + +typedef struct + { + int s; + dfunction_t *f; + int stackbase; + } prstack_t; + +class Interpreter : public Listener + { + private: + prstack_t pr_stack[ MAX_STACK_DEPTH ]; + int pr_depth; + + byte localstack_type[ LOCALSTACK_SIZE ]; + int localstack[ LOCALSTACK_SIZE ]; + int localstack_used; + int localstack_base; + + bool pr_trace; + dfunction_t *pr_xfunction; + int pr_xstatement; + + public: + + Program *program; + int instruction_pointer; + bool doneProcessing; + bool threadDying; + + Container updateList; + + int threadNum; + str threadName; + + CLASS_PROTOTYPE( Interpreter ); + + Interpreter(); + ~Interpreter(); + + void PrintStatement( const dstatement_t *s ); + void StackTrace( void ); + void Profile( void ); + void RunError( const char *error, ... ); + inline void NullEntityError( const dstatement_t* statement ); //should be const but can't be + void ThreadCall( const Interpreter *source, dfunction_t *newf, int args ); + int EnterFunction( dfunction_t *func ); + int LeaveFunction( void ); + Event *EventForFunction( const dfunction_t *func, int args ); + void Execute( Event *ev ); + void Reset( void ); + void Wait( float time ); + + const char *GlobalString( int ofs ); + const char *GlobalStringNoContents( int ofs ); + + const char *Filename( void ); + + void SetThreadNum( int num ); + int ThreadNum( void ); + const char *ThreadName( void ); + + virtual void DoMove( void ); + + qboolean labelExists( const char *label ); + qboolean Goto( const char *label ); + qboolean Setup( int num, const char *label ); + + void Archive( Archiver &arc ); + void CleanupStack( int localstack_used, int oldstacktop ); + }; + +inline void Interpreter::SetThreadNum + ( + int num + ) + + { + threadNum = num; + } + +inline int Interpreter::ThreadNum + ( + void + ) + + { + return threadNum; + } + +inline const char *Interpreter::ThreadName + ( + void + ) + + { + return threadName.c_str(); + } + +inline const char *Interpreter::Filename( void ) +{ + if ( instruction_pointer < 0 ) + return ""; + else + return program->GetFilename( program->statements[ instruction_pointer ].file ); +} + +#endif diff --git a/dlls/game/inv.h b/dlls/game/inv.h new file mode 100644 index 0000000..aa43eb8 --- /dev/null +++ b/dlls/game/inv.h @@ -0,0 +1,152 @@ + +#define INVENTORY_NONE 0 +//armor +#define INVENTORY_ARMOR 1 +//weapons +#define INVENTORY_PHASER 4 +#define INVENTORY_SHOTGUN 5 +#define INVENTORY_MACHINEGUN 6 +#define INVENTORY_GRENADELAUNCHER 7 +#define INVENTORY_ROCKETLAUNCHER 8 +#define INVENTORY_LIGHTNING 9 +#define INVENTORY_RAILGUN 10 +#define INVENTORY_PLASMAGUN 11 +#define INVENTORY_BFG10K 13 +#define INVENTORY_GRAPPLINGHOOK 14 +#define INVENTORY_NAILGUN 15 +#define INVENTORY_PROXLAUNCHER 16 +#define INVENTORY_CHAINGUN 17 +//ammo +#define INVENTORY_PLASMA 18 +#define INVENTORY_BULLETS 19 +#define INVENTORY_FED 20 +#define INVENTORY_IDRYLL 21 + +#define INVENTORY_BURSTRIFLE 22 + +/* +#define INVENTORY_GRENADES 20 +#define INVENTORY_CELLS 21 +#define INVENTORY_LIGHTNINGAMMO 22 +#define INVENTORY_ROCKETS 23 +#define INVENTORY_SLUGS 24 +#define INVENTORY_BFGAMMO 25 +#define INVENTORY_NAILS 26 +#define INVENTORY_MINES 27 +#define INVENTORY_BELT 28 +*/ +//powerups +#define INVENTORY_HEALTH 29 +#define INVENTORY_TELEPORTER 30 +#define INVENTORY_MEDKIT 31 +#define INVENTORY_KAMIKAZE 32 +#define INVENTORY_PORTAL 33 +#define INVENTORY_INVULNERABILITY 34 +#define INVENTORY_QUAD 35 +#define INVENTORY_ENVIRONMENTSUIT 36 +#define INVENTORY_HASTE 37 +#define INVENTORY_INVISIBILITY 38 +#define INVENTORY_REGEN 39 +#define INVENTORY_FLIGHT 40 +#define INVENTORY_SCOUT 41 +#define INVENTORY_GUARD 42 +#define INVENTORY_DOUBLER 43 +#define INVENTORY_AMMOREGEN 44 + +#define INVENTORY_REDFLAG 45 +#define INVENTORY_BLUEFLAG 46 +#define INVENTORY_NEUTRALFLAG 47 +#define INVENTORY_REDCUBE 48 +#define INVENTORY_BLUECUBE 49 +//enemy stuff +#define ENEMY_HORIZONTAL_DIST 200 +#define ENEMY_HEIGHT 201 +#define NUM_VISIBLE_ENEMIES 202 +#define NUM_VISIBLE_TEAMMATES 203 + +// if running the mission pack +#ifdef MISSIONPACK + +//#error "running mission pack" + +#endif + +//item numbers (make sure they are in sync with bg_itemlist in bg_misc.c) +#define MODELINDEX_ARMORSHARD 1 +#define MODELINDEX_ARMORCOMBAT 2 +#define MODELINDEX_ARMORBODY 3 +#define MODELINDEX_HEALTHSMALL 4 +#define MODELINDEX_HEALTH 5 +#define MODELINDEX_HEALTHLARGE 6 +#define MODELINDEX_HEALTHMEGA 7 + +#define MODELINDEX_GAUNTLET 8 +#define MODELINDEX_SHOTGUN 9 +#define MODELINDEX_MACHINEGUN 10 +#define MODELINDEX_GRENADELAUNCHER 11 +#define MODELINDEX_ROCKETLAUNCHER 12 +#define MODELINDEX_LIGHTNING 13 +#define MODELINDEX_RAILGUN 14 +#define MODELINDEX_PLASMAGUN 15 +#define MODELINDEX_BFG10K 16 +#define MODELINDEX_GRAPPLINGHOOK 17 + +#define MODELINDEX_SHELLS 18 +#define MODELINDEX_BULLETS 19 +#define MODELINDEX_GRENADES 20 +#define MODELINDEX_CELLS 21 +#define MODELINDEX_LIGHTNINGAMMO 22 +#define MODELINDEX_ROCKETS 23 +#define MODELINDEX_SLUGS 24 +#define MODELINDEX_BFGAMMO 25 + +#define MODELINDEX_TELEPORTER 26 +#define MODELINDEX_MEDKIT 27 +#define MODELINDEX_QUAD 28 +#define MODELINDEX_ENVIRONMENTSUIT 29 +#define MODELINDEX_HASTE 30 +#define MODELINDEX_INVISIBILITY 31 +#define MODELINDEX_REGEN 32 +#define MODELINDEX_FLIGHT 33 + +#define MODELINDEX_REDFLAG 34 +#define MODELINDEX_BLUEFLAG 35 + +// mission pack only defines + +#define MODELINDEX_KAMIKAZE 36 +#define MODELINDEX_PORTAL 37 +#define MODELINDEX_INVULNERABILITY 38 + +#define MODELINDEX_NAILS 39 +#define MODELINDEX_MINES 40 +#define MODELINDEX_BELT 41 + +#define MODELINDEX_SCOUT 42 +#define MODELINDEX_GUARD 43 +#define MODELINDEX_DOUBLER 44 +#define MODELINDEX_AMMOREGEN 45 + +#define MODELINDEX_NEUTRALFLAG 46 +#define MODELINDEX_REDCUBE 47 +#define MODELINDEX_BLUECUBE 48 + +#define MODELINDEX_NAILGUN 49 +#define MODELINDEX_PROXLAUNCHER 50 +#define MODELINDEX_CHAINGUN 51 + + +// +#define WEAPONINDEX_PHASER 2 +#define WEAPONINDEX_MACHINEGUN 3 +#define WEAPONINDEX_SHOTGUN 4 +#define WEAPONINDEX_GRENADE_LAUNCHER 5 +#define WEAPONINDEX_ROCKET_LAUNCHER 6 +#define WEAPONINDEX_LIGHTNING 7 +#define WEAPONINDEX_RAILGUN 8 +#define WEAPONINDEX_PLASMAGUN 9 +#define WEAPONINDEX_BFG 10 +#define WEAPONINDEX_GRAPPLING_HOOK 10 +#define WEAPONINDEX_NAILGUN 11 +#define WEAPONINDEX_PROXLAUNCHER 12 +#define WEAPONINDEX_CHAINGUN 13 diff --git a/dlls/game/inventoryitem.cpp b/dlls/game/inventoryitem.cpp new file mode 100644 index 0000000..0c77e7b --- /dev/null +++ b/dlls/game/inventoryitem.cpp @@ -0,0 +1,56 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/inventoryitem.cpp $ +// $Revision:: 10 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Inventory items + +#include "_pch_cpp.h" +#include "inventoryitem.h" +#include "mp_manager.hpp" + +Event EV_InventoryItem_Use +( + "useinvitem", + EV_CODEONLY, + NULL, + NULL, + "Use this inventory item." +); + +CLASS_DECLARATION( Item, InventoryItem, NULL ) +{ + { &EV_InventoryItem_Use, &InventoryItem::UseEvent }, + + { NULL, NULL } +}; + +InventoryItem::InventoryItem() +{ + if ( LoadingSavegame ) + { + return; + } + + // All powerups are inventory items + /* if ( multiplayerManager.checkFlag( MP_FLAG_NO_POWERUPS ) ) + { + PostEvent( EV_Remove, EV_REMOVE ); + return; + } */ +} + +void InventoryItem::UseEvent( Event *ev ) +{ +} + diff --git a/dlls/game/inventoryitem.h b/dlls/game/inventoryitem.h new file mode 100644 index 0000000..553cf07 --- /dev/null +++ b/dlls/game/inventoryitem.h @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/inventoryitem.h $ +// $Revision:: 4 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Items that are visible in the player's inventory + + +#ifndef __INVITEM_H__ +#define __INVITEM_H__ + +#include "item.h" + +class InventoryItem : public Item + { + public: + CLASS_PROTOTYPE( InventoryItem ); + + InventoryItem(); + virtual void UseEvent( Event *ev ); + }; + +extern Event EV_InventoryItem_Use; + +#endif /* inventoryitem.h */ diff --git a/dlls/game/ipfilter.cpp b/dlls/game/ipfilter.cpp new file mode 100644 index 0000000..b5634ee --- /dev/null +++ b/dlls/game/ipfilter.cpp @@ -0,0 +1,326 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/ipfilter.cpp $ +// $Revision:: 5 $ +// $Date:: 12/19/02 7:06p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// PACKET FILTERING +// +// You can add or remove addresses from the filter list with: +// +// addip +// removeip +// +// The ip address is specified in dot format, and any unspecified digits will match +// any value, so you can specify an entire class C network with "addip 192.246.40". +// +// Removeip will only remove an address specified exactly the same way. You cannot +// addip a subnet, then removeip a single host. +// +// listip +// Prints the current list of filters. +// +// writeip +// Dumps "addip " commands to listip.cfg so it can be execed at a later date. +// The filter lists are not saved and restored by default, because I beleive it would +// cause too much confusion. +// +// filterban <0 or 1> +// +// If 1 (the default), then ip addresses matching the current list will be prohibited +// from entering the game. This is the default setting. +// +// If 0, then only addresses matching the list will be allowed. This lets you easily +// set up a private game, or a game that only allows players from your local network. +// + +#include "_pch_cpp.h" +#include "ipfilter.h" + +typedef struct +{ + unsigned mask; + unsigned compare; +} ipfilter_t; + +#define MAX_IPFILTERS 1024 + +ipfilter_t ipfilters[ MAX_IPFILTERS ]; +int numipfilters; + +/* +================= +StringToFilter +================= +*/ +static qboolean StringToFilter( const char *s, ipfilter_t *f ) +{ + char num[ 128 ]; + int i; + int j; + byte b[ 4 ]; + byte m[ 4 ]; + + for( i = 0; i < 4; i++ ) + { + b[ i ] = 0; + m[ i ] = 0; + } + + for( i = 0; i < 4; i++ ) + { + if ( ( *s < '0' ) || ( *s > '9' ) ) + { + gi.SendServerCommand( NULL, "print \"Bad filter address: %s\n\"", s ); + return false; + } + + j = 0; + while( ( *s >= '0' ) && ( *s <= '9' ) ) + { + num[ j++ ] = *s++; + } + + num[ j ] = 0; + b[ i ] = atoi( num ); + if ( b[ i ] != 0 ) + { + m[ i ] = 255; + } + + if ( !*s ) + { + break; + } + + s++; + } + + f->mask = *( unsigned * )m; + f->compare = *( unsigned * )b; + + return true; +} + +/* +================= +SV_FilterPacket +================= +*/ +qboolean SV_FilterPacket( const char *from ) +{ + int i; + unsigned in; + byte m[ 4 ]; + const char *p; + + if ( !from ) + return false; + + i = 0; + p = from; + while( *p && ( i < 4 ) ) + { + m[ i ] = 0; + while( ( *p >= '0' ) && ( *p <= '9' ) ) + { + m[ i ] = m[ i ] * 10 + ( *p - '0' ); + p++; + } + + if ( !*p || ( *p == ':' ) ) + { + break; + } + + i++; + p++; + } + + in = *( unsigned * )m; + for( i = 0; i < numipfilters; i++ ) + { + if ( ( in & ipfilters[ i ].mask ) == ipfilters[ i ].compare ) + { + return ( int )filterban->integer; + } + } + + return !( int )filterban->integer; +} + + +/* +================= +SV_AddIP_f +================= +*/ +void SVCmd_AddIP_f( void ) +{ + int i; + + if ( gi.argc() < 3 ) + { + gi.SendServerCommand( NULL, "print \"Usage: addip \n\"" ); + return; + } + + for( i = 0; i < numipfilters; i++ ) + { + if ( ipfilters[ i ].compare == 0xffffffff ) + { + // free spot + break; + } + } + + if ( i == numipfilters ) + { + if ( numipfilters == MAX_IPFILTERS ) + { + gi.SendServerCommand( NULL, "print \"IP filter list is full\n\"" ); + return; + } + numipfilters++; + } + + if ( !StringToFilter( gi.argv( 2 ), &ipfilters[ i ] ) ) + { + ipfilters[ i ].compare = 0xffffffff; + } +} + +/* +================= +SV_RemoveIP_f +================= +*/ +void SVCmd_RemoveIP_f( void ) +{ + ipfilter_t f; + int i; + int j; + + if ( gi.argc() < 3 ) + { + gi.SendServerCommand( NULL, "print \"Usage: sv removeip \n\"" ); + return; + } + + if ( !StringToFilter( gi.argv( 2 ), &f ) ) + { + return; + } + + for( i = 0; i < numipfilters; i++ ) + { + if ( ( ipfilters[ i ].mask == f.mask ) && ( ipfilters[ i ].compare == f.compare ) ) + { + for ( j = i + 1; j < numipfilters; j++ ) + { + ipfilters[ j - 1 ] = ipfilters[ j ]; + } + + numipfilters--; + gi.SendServerCommand( NULL, "print \"Removed.\n\"" ); + return; + } + } + + gi.SendServerCommand( NULL, "print \"Didn't find %s.\n\"", gi.argv( 2 ) ); +} + +/* +================= +SV_ListIP_f +================= +*/ +void SVCmd_ListIP_f( void ) +{ + int i; + byte b[ 4 ]; + + gi.SendServerCommand( NULL, "print \"Filter list:\n\"", gi.argv( 2 ) ); + for( i = 0; i < numipfilters; i++ ) + { + *( unsigned * )b = ipfilters[ i ].compare; + gi.SendServerCommand( NULL, "print \"%3i.%3i.%3i.%3i\n\"", b[ 0 ], b[ 1 ], b[ 2 ], b[ 3 ] ); + } +} + +/* +================= +SV_WriteIP_f +================= +*/ +void SVCmd_WriteIP_f( void ) +{ + FILE *f; + char name[ MAX_OSPATH ]; + byte b[ 4 ]; + int i; + + sprintf( name, "%s/listip.cfg", GAME_NAME ); + gi.SendServerCommand( NULL, "print \"Writing %s.\n\"", name ); + + f = fopen( name, "wb" ); + if ( !f ) + { + gi.SendServerCommand( NULL, "print \"Couldn't open %s.\n\"", name ); + return; + } + + fprintf( f, "set filterban %d\n", ( int )filterban->integer ); + + for( i = 0; i < numipfilters; i++ ) + { + *( unsigned * )b = ipfilters[ i ].compare; + fprintf( f, "sv addip %i.%i.%i.%i\n", b[ 0 ], b[ 1 ], b[ 2 ], b[ 3 ] ); + } + + fclose( f ); +} + +/* +================= +G_ServerCommand + +G_ServerCommand will be called when an "sv" command is issued. +The game can issue gi.argc() / gi.argv() commands to get the rest +of the parameters +================= +*/ +void G_ServerCommand( void ) +{ + const char *cmd; + + cmd = gi.argv(1); + if ( Q_stricmp( cmd, "addip" ) == 0 ) + { + SVCmd_AddIP_f(); + } + else if ( Q_stricmp( cmd, "removeip" ) == 0 ) + { + SVCmd_RemoveIP_f(); + } + else if ( Q_stricmp( cmd, "listip" ) == 0 ) + { + SVCmd_ListIP_f(); + } + else if ( Q_stricmp( cmd, "writeip" ) == 0 ) + { + SVCmd_WriteIP_f(); + } + else + { + gi.SendServerCommand( NULL, "print \"Unknown server command %s.\n\"", cmd ); + } +} diff --git a/dlls/game/ipfilter.h b/dlls/game/ipfilter.h new file mode 100644 index 0000000..b271cc8 --- /dev/null +++ b/dlls/game/ipfilter.h @@ -0,0 +1,24 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/ipfilter.h $ +// $Revision:: 3 $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __IPFILTER_H__ +#define __IPFILTER_H__ + +#include "g_local.h" + +qboolean SV_FilterPacket( const char *from ); + +#endif /* !__IPFILTER_H__ */ diff --git a/dlls/game/item.cpp b/dlls/game/item.cpp new file mode 100644 index 0000000..b8615c8 --- /dev/null +++ b/dlls/game/item.cpp @@ -0,0 +1,1135 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/item.cpp $ +// $Revision:: 56 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class for respawnable, carryable objects. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "trigger.h" +#include "item.h" +#include "inventoryitem.h" +#include "scriptmaster.h" +#include "health.h" +#include "mp_manager.hpp" +#include + + +// for bot code +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +#include "ai_main.h" +extern bot_state_t *botstates[MAX_CLIENTS]; + + +Event EV_Item_Pickup +( + "item_pickup", + EV_CODEONLY, + "e", + "item", + "Pickup the specified item." +); +Event EV_Item_DropToFloor +( + "item_droptofloor", + EV_CODEONLY, + NULL, + NULL, + "Drops the item to the ground." +); +Event EV_Item_Respawn +( + "respawn", + EV_CODEONLY, + NULL, + NULL, + "Respawns the item." +); +Event EV_Item_SetRespawn +( + "set_respawn", + EV_DEFAULT, + "i", + "respawn", + "Turns respawn on or off." +); +Event EV_Item_SetRespawnTime +( + "set_respawn_time", + EV_DEFAULT, + "f", + "respawn_time", + "Sets the respawn time." +); +Event EV_Item_SetAmount +( + "amount", + EV_DEFAULT, + "i", + "amount", + "Sets the amount of the item." +); +Event EV_Item_SetMaxAmount +( + "maxamount", + EV_DEFAULT, + "i", + "max_amount", + "Sets the max amount of the item." +); +Event EV_Item_SetItemName +( + "name", + EV_DEFAULT, + "s", + "item_name", + "Sets the item name." +); +Event EV_Item_RespawnSound +( + "respawnsound", + EV_DEFAULT, + NULL, + NULL, + "Turns on the respawn sound for this item." +); +Event EV_Item_DialogNeeded +( + "dialogneeded", + EV_DEFAULT, + "s", + "dialog_needed", + "Sets the dialog needed string." +); +Event EV_Item_NoRemove +( + "no_remove", + EV_DEFAULT, + NULL, + NULL, + "Makes it so the item is not removed from the world when it is picked up." +); +Event EV_Item_RespawnDone +( + "respawn_done", + EV_CODEONLY, + NULL, + NULL, + "Called when the item respawn is done." +); +Event EV_Item_PickupDone +( + "pickup_done", + EV_CODEONLY, + NULL, + NULL, + "Called when the item pickup is done." +); +Event EV_Item_SetPickupThread +( + "pickup_thread", + EV_SCRIPTONLY, + "s", + "labelName", + "A thread that is called when an item is picked up." +); +Event EV_Item_SetBotInventoryIndex +( + "bot_inventory", + EV_TIKIONLY, + "i", + "botInventoryIndex", + "sets the index used for bot inventory pickups" +); +Event EV_Item_CoolItem +( + "coolitem", + EV_DEFAULT, + "SS", + "dialog anim_to_play", + "Specify that this is a cool item when we pick it up for the first time.\n" + "If dialog is specified, than the dialog will be played during the pickup.\n" + "If anim_to_play is specified, than the specified anim will be played after\n" + "the initial cinematic." +); +Event EV_Item_ForceCoolItem +( + "forcecoolitem", + EV_DEFAULT, + "SS", + "dialog anim_to_play", + "Specify that this is a cool item when we pick it up regardless of whether or not we have it.\n" + "If dialog is specified, than the dialog will be played during the pickup.\n" + "If anim_to_play is specified, than the specified anim will be played after\n" + "the initial cinematic." +); +Event EV_Item_IconName +( + "iconName", + EV_DEFAULT, + "s", + "iconName", + "Sets the name of the icon to use for this item." +); +Event EV_Item_SetMissingSkin +( + "missingSkin", + EV_TIKIONLY, + "i", + "skinNum", + "Sets the skin number to use when its been picked up and is missing" +); +Event EV_Item_PostSpawn +( + "itemPostSpawn", + EV_CODEONLY, + NULL, + NULL, + "Tells the item that it had spawned successfully" +); + +CLASS_DECLARATION( Trigger, Item, NULL ) +{ + { &EV_Trigger_Effect, &Item::ItemTouch }, + { &EV_Item_DropToFloor, &Item::DropToFloor }, + { &EV_Item_Respawn, &Item::Respawn }, + { &EV_Item_SetAmount, &Item::SetAmountEvent }, + { &EV_Item_SetMaxAmount, &Item::SetMaxAmount }, + { &EV_Item_SetItemName, &Item::SetItemName }, + { &EV_Item_Pickup, &Item::Pickup }, + { &EV_Use, &Item::TriggerStuff }, + { &EV_Item_RespawnSound, &Item::RespawnSound }, + { &EV_Item_DialogNeeded, &Item::DialogNeeded }, + { &EV_Item_NoRemove, &Item::SetNoRemove }, + { &EV_Item_RespawnDone, &Item::RespawnDone }, + { &EV_Item_PickupDone, &Item::PickupDone }, + { &EV_Item_SetRespawn, &Item::setRespawn }, + { &EV_Item_SetRespawnTime, &Item::setRespawnTime }, + { &EV_Item_SetPickupThread, &Item::SetPickupThread }, + { &EV_Item_CoolItem, &Item::CoolItemEvent }, + { &EV_Item_ForceCoolItem, &Item::ForceCoolItemEvent }, + { &EV_Item_SetBotInventoryIndex, &Item::SetBotInventoryIndex }, + { &EV_Stop, &Item::Landed }, + { &EV_SetAngle, &Item::SetAngleEvent }, + { &EV_Item_IconName, &Item::iconNameEvent }, + { &EV_Item_SetMissingSkin, &Item::setMissingSkin }, + { &EV_Item_PostSpawn, &Item::postSpawn }, + { NULL, NULL } +}; + +//-------------------------------------------------------------- +// Name: Item() +// Class: Item +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +Item::Item() +{ + str fullname; + animate = new Animate( this ); + + if ( LoadingSavegame ) + return; + + setSolidType( SOLID_NOT ); + + // Set default respawn behavior + // Derived classes should use setRespawn + // if they want to override the default behavior + setRespawn( false ); + setRespawnTime( 20 ); + + if ( multiplayerManager.inMultiplayer() ) + { + setRespawn( true ); + edict->s.renderfx |= RF_FULLBRIGHT; + } + + // + // we want the bounds of this model auto-rotated + // + flags |= FL_ROTATEDBOUNDS; + + // + // set a minimum mins and maxs for the model + // + if ( size.length() < 10.0f ) + { + mins = Vector(-10, -10, 0); + maxs = Vector(10, 10, 20); + } + + // + // reset the mins and maxs to pickup the FL_ROTATEDBOUNDS flag + // + setSize( mins, maxs ); + + if ( !LoadingSavegame ) + { + // Items can't be immediately dropped to floor, because they might + // be on an entity that hasn't spawned yet. + PostEvent( EV_Item_DropToFloor, EV_POSTSPAWN ); + } + + respondto = TRIGGER_PLAYERS; + + // items should collide with everything that the player does + edict->clipmask = MASK_PLAYERSOLID; + + bot_inventory_index = 0; // INVENTORY_NONE + item_index = 0; + maximum_amount = 1.0f; + playrespawn = false; + + // this is an item entity + + if ( g_gametype->integer == GT_SINGLE_PLAYER ) + edict->s.eType = ET_MODELANIM; + else + edict->s.eType = ET_ITEM; + + // Set our default skill level + _skillLevel = 1.0f; + amount = 1.0f; + no_remove = false; + setName( "Unknown Item" ); + + look_at_me = true; + coolitem = false; + coolitemforced = false; + + has_been_looked_at = false; + + _nextPickupTime = 0.0f; + + _mpItemType = MP_ITEM_TYPE_NORMAL; + + _iconIndex = -1; + + _missingSkin = 0; + + if ( !LoadingSavegame ) + { + PostEvent( EV_Item_PostSpawn, EV_POSTSPAWN ); + } +} + +Item::~Item() +{ + if ( owner ) + { + owner->RemoveItem( this ); + owner = NULL; + } +} + +void Item::postSpawn( Event *ev ) +{ + cacheStrings(); +} + +void Item::SetNoRemove( Event *ev ) +{ + no_remove = true; +} + +/* +============ +PlaceItem + +Puts an item back in the world +============ +*/ +void Item::PlaceItem( void ) +{ + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( gpm->hasProperty(getArchetype(), "noautopickup") ) + { + setContents( CONTENTS_USABLE ); + setSolidType( SOLID_BBOX ); + } + else + { + setSolidType( SOLID_TRIGGER ); + } + + setMoveType( MOVETYPE_TOSS ); + showModel(); + + groundentity = NULL; +} + +/* +============ +DropToFloor + +plants the object on the floor +============ +*/ +void Item::DropToFloor( Event *ev ) +{ + str fullname; + Vector save; + + PlaceItem(); + + addOrigin( Vector(0, 0, 1) ); + + save = origin; + + if ( gravity > 0.0f ) + { + if ( !droptofloor( 8192.0f ) ) + { + gi.WDPrintf( "%s (%d) stuck in world at '%5.1f %5.1f %5.1f'\n", + getClassID(), entnum, origin.x, origin.y, origin.z ); + setOrigin( save ); + setMoveType( MOVETYPE_NONE ); + } + else + { + setMoveType( MOVETYPE_NONE ); + } + } + + // + // if the our global variable doesn't exist, lets zero it out + // + fullname = str( "playeritem_" ) + getName(); + if ( !gameVars.VariableExists( fullname.c_str() ) ) + { + gameVars.SetVariable( fullname.c_str(), 0 ); + } + + if ( !levelVars.VariableExists( fullname.c_str() ) ) + { + levelVars.SetVariable( fullname.c_str(), 0 ); + } + + if ( multiplayerManager.inMultiplayer() && gi.Anim_NumForName( edict->s.modelindex, "idle_onground" ) >= 0 ) + { + animate->RandomAnimate( "idle_onground" ); + edict->s.eType = ET_MODELANIM; + } +} + +qboolean Item::Drop( void ) +{ + if ( !owner ) + { + return false; + } + + setOrigin( owner->origin + Vector( "0 0 40" ) ); + + // drop the item + PlaceItem(); + velocity = owner->velocity * 0.5f + Vector( G_CRandom( 50.0f ), G_CRandom( 50.0f ), 100.0f ); + setAngles( owner->angles ); + avelocity = Vector( 0.0f, G_CRandom( 360.0f ), 0.0f ); + + trigger_time = level.time + 1.0f; + + if ( owner->isClient() ) + { + spawnflags |= DROPPED_PLAYER_ITEM; + } + else + { + spawnflags |= DROPPED_ITEM; + } + + // Remove this from the owner's item list + owner->RemoveItem( this ); + owner = NULL; + + return true; +} + + +void Item::ItemTouch( Event *ev ) +{ + Entity *other; + Event *e; + + if ( owner ) + { + // Don't respond to trigger events after item is picked up. + // we really don't need to see this. + //gi.DPrintf( "%s with targetname of %s was triggered unexpectedly.\n", getClassID(), TargetName() ); + return; + } + + other = ev->GetEntity( 1 ); + + e = new Event( EV_Item_Pickup ); + e->AddEntity( other ); + ProcessEvent( e ); +} + +void Item::SetOwner( Sentient *ent ) +{ + assert( ent ); + if ( !ent ) + { + // return to avoid any buggy behaviour + return; + } + + owner = ent; + edict->s.parent = ent->entnum; + setRespawn( false ); + + setSolidType( SOLID_NOT ); + hideModel(); + CancelEventsOfType( EV_Touch ); + CancelEventsOfType( EV_Item_DropToFloor ); + CancelEventsOfType( EV_Remove ); + // ItemPickup( ent ); +} + +void Item::SetBotInventoryIndex( Event *ev ) +{ + bot_inventory_index = ev->GetInteger(1); +} + +int Item::GetBotInventoryIndex( void ) +{ + return bot_inventory_index; +} + +Sentient *Item::GetOwner( void ) const +{ + return owner; +} + +Item * Item::ItemPickup( Entity *other, qboolean add_to_inventory, qboolean checkautopickup ) +{ + Sentient * sent; + Item * item = NULL; + str realname; + + // Query the gameplay manager and see if we should not auto-pickup this item + if ( checkautopickup ) + { + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( gpm->hasProperty(getArchetype(), "noautopickup") ) + return NULL; + } + + if ( !Pickupable( other ) ) + { + return NULL; + } + + sent = ( Sentient * )other; + + if ( add_to_inventory ) + { + item = sent->giveItem( model, getAmount(), true ); + + + if ( !item ) + return NULL; + } + else + { + item = this; + } + + // + // make sure to copy over the coolness factor :) + // + item->coolitem = coolitem; + item->cool_dialog = cool_dialog; + item->cool_anim = cool_anim; + item->coolitemforced = coolitemforced; + + // + // let our sent know they received it + // we put this here so we can transfer information from the original item we picked up + // + + if ( !isSubclassOf( Weapon ) || add_to_inventory ) + sent->ReceivedItem( item ); + + realname = GetRandomAlias( "snd_pickup" ); + if ( realname.length() > 1 ) + sent->Sound( realname, CHAN_ITEM ); + + if ( !Removable() ) + { + // leave the item for others to pickup + return item; + } + + look_at_me = false; + + CancelEventsOfType( EV_Item_DropToFloor ); + CancelEventsOfType( EV_Item_Respawn ); + CancelEventsOfType( EV_FadeOut ); + + setSolidType( SOLID_NOT ); + + if ( animate && animate->HasAnim( "pickup" ) ) + animate->RandomAnimate( "pickup", EV_Item_PickupDone ); + else + { + if ( !no_remove ) + { + if ( _missingSkin ) + { + ChangeSkin( _missingSkin, true ); + } + else + { + hideModel(); + } + + if ( !Respawnable() ) + PostEvent( EV_Remove, FRAMETIME ); + } + } + + if ( Respawnable() ) + PostEvent( EV_Item_Respawn, RespawnTime() ); + + // fire off any pickup_thread's + if ( pickup_thread.length() ) + { + ExecuteThread( pickup_thread ); + } + + + if ( item && multiplayerManager.checkFlag( MP_FLAG_INSTANT_ITEMS ) ) + { + Event *ev; + + ev = new Event( EV_InventoryItem_Use ); + ev->AddEntity( other ); + + item->ProcessEvent( ev ); + } + + return item; +} + +void Item::Respawn( Event *ev ) +{ + if ( _missingSkin ) + { + ChangeSkin( _missingSkin, false ); + } + else + { + showModel(); + } + + // allow it to be touched again + setSolidType( SOLID_TRIGGER ); + + // play respawn sound + if ( playrespawn ) + { + Sound( "snd_itemspawn" ); + } + + setOrigin(); + + if ( animate->HasAnim( "respawn" ) ) + animate->RandomAnimate( "respawn", EV_Item_RespawnDone ); + + look_at_me = true; + has_been_looked_at = false; +} + +void Item::setRespawn( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + setRespawn( ev->GetInteger( 1 ) ); +} + +void Item::setRespawnTime( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + setRespawnTime( ev->GetFloat( 1 ) ); +} + +void Item::RespawnDone( Event *ev ) +{ + animate->RandomAnimate( "idle" ); +} + +void Item::PickupDone( Event *ev ) +{ + if ( !no_remove ) + { + hideModel(); + + if ( !Respawnable() ) + PostEvent( EV_Remove, FRAMETIME ); + } + else + { + if ( animate->HasAnim( "pickup_idle" ) ) + animate->RandomAnimate( "pickup_idle" ); + else + animate->RandomAnimate( "pickup" ); + } +} + +void Item::setRespawn( qboolean flag ) +{ + respawnable = flag; +} + +qboolean Item::Respawnable( void ) +{ + return respawnable; +} + +void Item::setRespawnTime( float time ) +{ + respawntime = time; +} + +float Item::RespawnTime( void ) +{ + if ( multiplayerManager.inMultiplayer() ) + return respawntime * multiplayerManager.getItemRespawnMultiplayer(); + else + return respawntime; +} + +float Item::getAmount( void ) +{ + return amount; +} + +float Item::MaxAmount( void ) +{ + return maximum_amount; +} + +qboolean Item::Pickupable( Entity *other ) +{ + if ( level.time < _nextPickupTime ) + return false; + + if ( multiplayerManager.inMultiplayer() && other->isSubclassOf( Player ) ) + { + if ( !multiplayerManager.canPickup( (Player *)other, getMultiplayerItemType(), getName().c_str() ) ) + return false; + } + + if ( getSolidType() == SOLID_NOT ) + { + return NULL; + } + + if ( !other->isSubclassOf( Sentient ) ) + { + return false; + } + else + { + Sentient * sent; + Item * item; + + sent = ( Sentient * )other; + + if ( sent->deadflag || sent->health <= 0.0f ) + { + return false; + } + + item = sent->FindItem( getName() ); + + if ( item && ( item->getAmount() >= item->MaxAmount() ) ) + { + return false; + } + + // TODO : fixme + // If deathmatch and already in a powerup, don't pickup anymore when DF_INSTANT_ITEMS is on + /* if ( multiplayerManager.checkFlag( MP_FLAG_INSTANT_ITEMS ) && + this->isSubclassOf( InventoryItem ) && + sent->PowerupActive() + ) + { + return false; + } */ + } + return true; +} + +void Item::Pickup( Event * ev ) +{ + ItemPickup( ev->GetEntity( 1 ) ); +} + +void Item::setName( const char *i ) +{ + item_name = i; + item_index = gi.itemindex( i ); + strcpy( edict->entname, i ); +} + +const str Item::getName( void ) const +{ + return( item_name ); +} + +int Item::getIndex( void ) +{ + return item_index; +} + +void Item::setAmount( float startamount ) +{ + amount = startamount; + if ( amount >= MaxAmount() ) + { + SetMax( (int) amount ); + } +} + +void Item::SetMax( int maxamount ) +{ + maximum_amount = maxamount; +} + +void Item::SetAmountEvent( Event *ev ) +{ + setAmount( ev->GetInteger( 1 ) ); +} + +void Item::SetMaxAmount( Event *ev ) +{ + SetMax( ev->GetInteger( 1 ) ); +} + +void Item::SetItemName( Event *ev ) +{ + setName( ev->GetString( 1 ) ); +} + +void Item::Add( int num ) +{ + amount += num; + if ( amount >= MaxAmount() ) + amount = MaxAmount(); +} + +void Item::Remove( int num ) +{ + amount -= num; + if (amount < 0) + amount = 0; +} + + +qboolean Item::Use( int num ) +{ + if ( num > amount ) + { + return false; + } + + amount -= num; + return true; +} + +qboolean Item::Removable( void ) +{ + return true; +} + +void Item::RespawnSound( Event *ev ) +{ + playrespawn = true; +} + +void Item::DialogNeeded( Event *ev ) +{ + // + // if this item is needed for a trigger, play this dialog + // + dialog_needed = ev->GetString( 1 ); +} + +str Item::GetDialogNeeded( void ) +{ + return dialog_needed; +} + +// +// once item has landed on the floor, go to movetype none +// +void Item::Landed( Event *ev ) +{ + if ( groundentity && ( groundentity->entity != world ) ) + { + warning( "Item::Landed", "Item %d has landed on an entity that might move\n", entnum ); + } + setMoveType( MOVETYPE_NONE ); +} + +void Item::SetPickupThread( Event *ev ) +{ + pickup_thread = ev->GetString( 1 ); +} + +void Item::SetCoolItem( qboolean cool, const str &dialog, const str &anim ) +{ + coolitem = cool; + cool_dialog = dialog; + if ( cool_dialog.length() ) + { + CacheResource( cool_dialog, this ); + } + + CacheResource( "models/fx_coolitem.tik", this ); + CacheResource( "models/fx_coolitem_reverse.tik", this ); + + cool_anim = anim; +} + +void Item::CoolItemEvent( Event *ev ) +{ + qboolean cool; + str dialog, anim; + + cool = true; + if ( ev->NumArgs() > 0 ) + { + dialog = ev->GetString( 1 ); + } + if ( ev->NumArgs() > 1 ) + { + anim = ev->GetString( 2 ); + } + SetCoolItem( cool, dialog, anim ); +} + +void Item::ForceCoolItemEvent( Event *ev ) +{ + coolitemforced = true; + CoolItemEvent( ev ); +} + +qboolean Item::IsItemCool( str * dialog, str * anim, qboolean * forced ) +{ + *dialog = cool_dialog; + *anim = cool_anim; + *forced = coolitemforced; + return coolitem; +} + +MultiplayerItemType Item::getMultiplayerItemType( void ) +{ + return _mpItemType; +} + +void Item::iconNameEvent( Event *ev ) +{ + str iconName; + + iconName = ev->GetString( 1 ); + + _iconIndex = gi.imageindex( iconName.c_str() ); +} + +void Item::setMissingSkin( Event *ev ) +{ + _missingSkin = ev->GetInteger( 1 ); +} + +void Item::ChangeSkin( int skinNum, qboolean state ) +{ + str command; + + // Build the command + + if ( state ) + command = "+"; + else + command = "-"; + + command += "skin"; + + command += skinNum; + + // Change the skin + + SurfaceCommand( "all", command.c_str() ); +} + +void Item::cacheStrings( void ) +{ + G_FindConfigstringIndex( va( "$$PickedUp$$ %d $$Item-%s$$\n", (int)getAmount(), getName().c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); + G_FindConfigstringIndex( va( "$$PickedUp$$ %d $$Item-%s$$s\n", (int)getAmount(), getName().c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); + G_FindConfigstringIndex( va( "$$PickedUpThe$$ $$Item-%s$$\n", getName().c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); +} + +CLASS_DECLARATION( Item, MultiplayerItem, NULL ) +{ + { &EV_Trigger_Effect, &MultiplayerItem::notifyMultiplayerItemTriggered }, + { &EV_Killed, &MultiplayerItem::notifyMultiplayerItemDestroyed }, + { &EV_Use, &MultiplayerItem::notifyMultiplayerItemUsed }, + { &EV_Damage, &MultiplayerItem::damageEvent }, + + { NULL, NULL } +}; + +MultiplayerItem::MultiplayerItem() +{ + edict->s.eType = ET_MODELANIM; +} + +void MultiplayerItem::notifyMultiplayerItemTriggered( Event *ev ) +{ + Entity *entity; + + entity = ev->GetEntity( 1 ); + + if ( entity->isSubclassOf( Player ) ) + multiplayerManager.itemTouched( (Player *)entity, this ); +} + +void MultiplayerItem::notifyMultiplayerItemDestroyed( Event *ev ) +{ + Entity *entity; + + entity = ev->GetEntity( 1 ); + + if ( entity->isSubclassOf( Player ) ) + multiplayerManager.itemDestroyed( (Player *)entity, this ); +} + +void MultiplayerItem::notifyMultiplayerItemUsed( Event *ev ) +{ + Entity *entity; + + entity = ev->GetEntity( 1 ); + + multiplayerManager.itemUsed( entity, this ); +} + +void MultiplayerItem::damageEvent( Event *ev ) +{ + Entity *attacker; + int meansOfDeath; + float originalDamage; + float realDamage; + + // Get the parms + + originalDamage = ev->GetFloat( 1 ); + attacker = ev->GetEntity( 3 ); + meansOfDeath = ev->GetInteger( 9 ); + + // See what the real damage done is + + realDamage = originalDamage; + + if ( attacker->isSubclassOf( Player ) ) + { + realDamage = multiplayerManager.itemDamaged( this, (Player *)attacker, originalDamage, meansOfDeath ); + + // Change the event if the damage has changed + + if ( realDamage != originalDamage ) + { + ev->SetFloat( 1, realDamage ); + } + } + + if ( realDamage == 0.0f ) + return; + + // Do the real damage here + + Entity::DamageEvent( ev ); +} + +void MultiplayerItem::cacheStrings( void ) +{ +} + +CLASS_DECLARATION( Item, SecretItem, NULL ) +{ + { NULL, NULL } +}; + +SecretItem::SecretItem() +{ + if ( LoadingSavegame ) + return; + + level.total_specialItems++; + levelVars.SetVariable( "total_specialItems" , level.total_specialItems ); + + edict->s.eType = ET_ITEM; +} + +Item* SecretItem::ItemPickup( Entity *other, qboolean add_to_inventory = true, qboolean checkautopickup = true ) +{ + str pickupSoundName; + + PostEvent( EV_Remove, 0.0f ); + + level.found_specialItems++; + levelVars.SetVariable( "found_specialItems" , level.found_specialItems ); + + gi.centerprintf ( other->edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$FoundSecretItem$$" ); + + pickupSoundName = GetRandomAlias( "snd_pickup" ); + + if ( pickupSoundName.length() > 1 ) + { + other->Sound( pickupSoundName, CHAN_ITEM ); + } + + if ( other && other->isSubclassOf( Player ) ) + { + Player *player = (Player *)other; + + player->incrementSecretsFound(); + } + + return NULL; +} + +void SecretItem::cacheStrings( void ) +{ +} diff --git a/dlls/game/item.h b/dlls/game/item.h new file mode 100644 index 0000000..c947295 --- /dev/null +++ b/dlls/game/item.h @@ -0,0 +1,228 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/item.h $ +// $Revision:: 24 $ +// $Author:: Steven $ +// $Date:: 2/14/03 5:37p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class for respawnable, carryable objects. +// + +#ifndef __ITEM_H__ +#define __ITEM_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "sentient.h" + +extern Event EV_Item_Pickup; +extern Event EV_Item_DropToFloor; +extern Event EV_Item_Respawn; +extern Event EV_Item_SetAmount; +extern Event EV_Item_SetMaxAmount; +extern Event EV_Item_RespawnSound; +extern Event EV_Item_DialogNeeded; +extern Event EV_Item_PickupDone; + +#define DROPPED_ITEM 0x00008000 +#define DROPPED_PLAYER_ITEM 0x00010000 + +class Item : public Trigger + { + private: + float _skillLevel; + int _iconIndex; + + protected: + SentientPtr owner; + qboolean respawnable; + qboolean playrespawn; + qboolean coolitem; + qboolean coolitemforced; + str cool_dialog; + str cool_anim; + float respawntime; + str dialog_needed; + int item_index; + str item_name; + float maximum_amount; + float amount; + str pickup_thread; + qboolean no_remove; + int bot_inventory_index; + MultiplayerItemType _mpItemType; + int _missingSkin; + + void ItemTouch( Event *ev ); + + public: + qboolean has_been_looked_at; + float _nextPickupTime; + + CLASS_PROTOTYPE( Item ); + + Item(); + ~Item(); + virtual void PlaceItem( void ); + virtual void SetOwner( Sentient *ent ); + virtual Sentient* GetOwner( void ) const; + void SetNoRemove( Event *ev ); + virtual void DropToFloor( Event *ev ); + virtual Item* ItemPickup( Entity *other, qboolean add_to_inventory = true, qboolean checkautopickup = true ); + virtual void Respawn( Event *ev ); + virtual void setRespawn( qboolean flag ); + void setRespawn( Event *ev ); + virtual qboolean Respawnable( void ); + virtual void setRespawnTime( float time ); + void setRespawnTime( Event *ev ); + virtual float RespawnTime( void ); + void RespawnDone( Event *ev ); + void PickupDone( Event *ev ); + virtual int GetItemIndex( void ) { return item_index; }; + virtual float getAmount( void ); + virtual void setAmount( float startamount ); + + virtual float MaxAmount( void ); + virtual qboolean Pickupable( Entity *other ); + + virtual void setName( const char *i ); + virtual const str getName( void ) const ; + virtual int getIndex( void ); + virtual void SetAmountEvent( Event *ev ); + virtual void SetMaxAmount( Event *ev ); + virtual void SetItemName( Event *ev ); + virtual void SetPickupThread( Event *ev ); + + virtual void SetMax( int maxamount ); + virtual void Add( int num ); + virtual void Remove( int num ); + virtual qboolean Use( int amount ); + virtual qboolean Removable( void ); + virtual void Pickup( Event *ev ); + virtual qboolean Drop( void ); + virtual void RespawnSound( Event *ev ); + virtual void DialogNeeded( Event *ev ); + virtual str GetDialogNeeded( void ); + void Landed( Event *ev ); + void CoolItemEvent( Event *ev ); + void ForceCoolItemEvent( Event *ev ); + int GetBotInventoryIndex( void ); + void SetBotInventoryIndex( Event *ev ); + qboolean IsItemCool( str * dialog, str * anim, qboolean *force ); + void SetCoolItem( qboolean cool, const str &dialog, const str &anim ); + + void SetSkillLevel( float skillLevel ); + float GetSkillLevel(); + + MultiplayerItemType getMultiplayerItemType( void ); + + void iconNameEvent( Event *ev ); + int getIcon( void ) { return _iconIndex; } + + void setMissingSkin( Event *ev ); + + void ChangeSkin( int skinNum, qboolean state ); + + virtual void cacheStrings(); + void postSpawn( Event *ev ); + + virtual void Archive( Archiver &arc ); + virtual void ArchivePersistantData( Archiver &arc ) {}; + }; + +inline void Item::SetSkillLevel( float skillLevel ) +{ + _skillLevel = skillLevel; +} + +inline float Item::GetSkillLevel() +{ + return _skillLevel; +} + +inline void Item::Archive( Archiver &arc ) +{ + str iconName; + + + Trigger::Archive( arc ); + + arc.ArchiveFloat( &_skillLevel ); + + if ( arc.Loading() ) + { + arc.ArchiveString( &iconName ); + + _iconIndex = gi.imageindex( iconName.c_str() ); + } + else + { + if ( _iconIndex >= 0 ) + iconName = gi.getConfigstring( CS_IMAGES + _iconIndex ); + else + iconName = ""; + + arc.ArchiveString( &iconName ); + } + + arc.ArchiveSafePointer( &owner ); + arc.ArchiveBoolean( &respawnable ); + arc.ArchiveBoolean( &playrespawn ); + arc.ArchiveBoolean( &coolitem ); + arc.ArchiveBoolean( &coolitemforced ); + arc.ArchiveString( &cool_dialog ); + arc.ArchiveString( &cool_anim ); + arc.ArchiveFloat( &respawntime ); + arc.ArchiveString( &dialog_needed ); + arc.ArchiveString( &item_name ); + if ( arc.Loading() ) + { + setName( item_name.c_str() ); + } + arc.ArchiveFloat( &maximum_amount ); + arc.ArchiveFloat( &amount ); + arc.ArchiveString( &pickup_thread ); + arc.ArchiveBoolean( &no_remove ); + + arc.ArchiveInteger( &bot_inventory_index ); + ArchiveEnum( _mpItemType, MultiplayerItemType ); + arc.ArchiveInteger( &_missingSkin ); + + arc.ArchiveBoolean( &has_been_looked_at ); + + arc.ArchiveFloat( &_nextPickupTime ); +} + +class MultiplayerItem : public Item +{ + public: + CLASS_PROTOTYPE( MultiplayerItem ); + MultiplayerItem(); + + void notifyMultiplayerItemTriggered( Event *ev ); + void notifyMultiplayerItemDestroyed( Event *ev ); + void notifyMultiplayerItemUsed( Event *ev ); + void damageEvent( Event *ev ); + /* virtual */ void cacheStrings( void ); +}; + +class SecretItem : public Item +{ + public: + CLASS_PROTOTYPE( SecretItem ); + SecretItem(); + + /* virtual */ Item* ItemPickup( Entity *other, qboolean add_to_inventory, qboolean checkautopickup ); + /* virtual */ void cacheStrings( void ); +}; + +#endif /* item.h */ diff --git a/dlls/game/level.cpp b/dlls/game/level.cpp new file mode 100644 index 0000000..caff5b9 --- /dev/null +++ b/dlls/game/level.cpp @@ -0,0 +1,1362 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/level.cpp $ +// $Revision:: 90 $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "level.h" +#include "scriptmaster.h" +#include "navigate.h" +#include "helper_node.h" +#include "gravpath.h" +#include "g_spawn.h" +#include "player.h" +#include "characterstate.h" +#include "mp_manager.hpp" +#include "armor.h" +#include "CinematicArmature.h" +#include "earthquake.h" +#include "teammateroster.hpp" + +Level level; + +extern Container SpecialPathNodes; + +CLASS_DECLARATION( Class, Level, NULL ) +{ + { NULL, NULL } +}; + +Level::Level() +{ + Init(); +} + +Level::~Level() +{ + _earthquakes.FreeObjectList(); +} + +void Level::Init( void ) +{ + spawn_entnum = -1; + + restart = false;; + + framenum = 0; + time = 0; + frametime = 0; + + level_name = ""; + mapname = ""; + spawnpoint = ""; + nextmap = ""; + + playerfrozen = false; + intermissiontime = 0; + exitintermission = 0; + + next_edict = NULL; + + total_secrets = 0; + found_secrets = 0; + total_specialItems = 0; + found_specialItems = 0; + + _totalEnemiesSpawned = 0; + + memset( &impact_trace, 0, sizeof( impact_trace ) ); + + cinematic = false; + ai_on = true; + + mission_failed = false; + died_already = false; + near_exit = false; + started = false; + + water_color = vec_zero; + water_alpha = 0; + + slime_color = vec_zero; + slime_alpha = 0; + + lava_color = vec_zero; + lava_alpha = 0; + + saved_soundtrack = ""; + current_soundtrack = ""; + + consoleThread = NULL; + + + // clear out automatic cameras + automatic_cameras.ClearObjectList(); + + // init level script variables + levelVars.ClearList(); + + m_fade_time_start = 0; + m_fade_time = -1; + m_fade_color = vec_zero; + m_fade_alpha = 0; + m_fade_style = additive; + m_fade_type = fadein; + m_letterbox_fraction = 0; + m_letterbox_time = -1; + m_letterbox_time_start = 0; + m_letterbox_dir = letterbox_out; + + hNodeController = NULL; + + _cleanup = false; + + _showIntermission = true; + + _saveOrientation = true; + + currentInstanceNumber = 0; +} + +//----------------------------------------------------- +// +// Name: update +// Class: Level +// +// Description: Updates the level variables. This is called on the frame update. +// +// Parameters: levelTime - the game clock time. +// frameTime - the time the frame occured. +// +// Returns: None +//----------------------------------------------------- +void Level::update( int levelTime, int frameTime ) +{ + setTime(levelTime, frameTime); + + if ( level.intermissiontime && dedicated->integer ) + { + if ( g_endintermission->integer > 0 ) + { + g_endintermission->integer = 0; + level.exitintermission = true; + } + + // can exit intermission after 10 seconds (default) + + if ( ( ( level.time - level.intermissiontime ) > level.intermission_advancetime ) && + ( level.intermission_advancetime != 0 ) ) + { + if ( multiplayerManager.inMultiplayer() ) + { + level.exitintermission = true; + } + } + } +} + + +void Level::SetIntermissionAdvanceTime(float time) +{ + intermission_advancetime = time; +} + +void Level::EndIntermission() +{ + exitintermission = true; +} + +void Level::CleanUp( qboolean restart ) +{ + _cleanup = true; + + if ( multiplayerManager.inMultiplayer() ) + { + multiplayerManager.cleanup( restart ); + } + else + { + Player* p = GetPlayer(0); + if( p ) p->LevelCleanup(); + } + + ClearCachedStatemaps(); + ClearCachedFuzzyEngines(); + _playerDeathThread = ""; +#ifndef DEDICATED + gi.MObjective_ClearObjectiveList(); +#endif + + AdaptiveArmor::ClearAdaptionList(); + theCinematicArmature.clearCinematicsList(); + + HelperNode::CleanupHelperNodeList(); + + assert( active_edicts.next ); + assert( active_edicts.next->prev == &active_edicts ); + assert( active_edicts.prev ); + assert( active_edicts.prev->next == &active_edicts ); + assert( free_edicts.next ); + assert( free_edicts.next->prev == &free_edicts ); + assert( free_edicts.prev ); + assert( free_edicts.prev->next == &free_edicts ); + + while( active_edicts.next != &active_edicts ) + { + assert( active_edicts.next != &free_edicts ); + assert( active_edicts.prev != &free_edicts ); + + assert( active_edicts.next ); + assert( active_edicts.next->prev == &active_edicts ); + assert( active_edicts.prev ); + assert( active_edicts.prev->next == &active_edicts ); + assert( free_edicts.next ); + assert( free_edicts.next->prev == &free_edicts ); + assert( free_edicts.prev ); + assert( free_edicts.prev->next == &free_edicts ); + + if ( active_edicts.next->entity ) + { + delete active_edicts.next->entity; + } + else + { + FreeEdict( active_edicts.next ); + } + } + + cinematic = false; + ai_on = true; + + mission_failed = false; + died_already = false; + near_exit = false; + started = false; + + globals.num_entities = game.maxclients + 1; + + // clear up all AI node information + thePathManager.ResetNodes(); + + // Reset the gravity paths + gravPathManager.Reset(); + + if ( consoleThread ) + { + Director.KillThread( consoleThread->ThreadNum() ); + consoleThread = NULL; + } + + // close all the scripts + Director.CloseScript(); + + // invalidate player readiness + Director.PlayerNotReady(); + + // clear out automatic cameras + automatic_cameras.ClearObjectList(); + + // clear out level script variables + levelVars.ClearList(); + + // initialize the game variables + // these get restored by the persistant data, so we can safely clear them here + gameVars.ClearList(); + + // clearout any waiting events + L_ClearEventList(); + + ResetEdicts(); + + // Reset the boss health cvar + gi.cvar_set( "bosshealth", "0" ); + + _earthquakes.ClearObjectList(); + + _cleanup = false; +} + +/* +============== +ResetEdicts +============== +*/ +void Level::ResetEdicts( void ) +{ + int i; + + memset( g_entities, 0, game.maxentities * sizeof( g_entities[ 0 ] ) ); + + // Add all the edicts to the free list + LL_Reset( &free_edicts, next, prev ); + LL_Reset( &active_edicts, next, prev ); + for( i = 0; i < game.maxentities; i++ ) + { + LL_Add( &free_edicts, &g_entities[ i ], next, prev ); + } + + for( i = 0; i < game.maxclients; i++ ) + { + //char savedTeamName[ 16 ]; + + // set client fields on player ents + g_entities[ i ].client = game.clients + i; + + //strcpy( savedTeamName, game.clients[i].pers.lastTeam ); + + G_InitClientPersistant (&game.clients[i]); + + //strcpy( game.clients[i].pers.lastTeam, savedTeamName ); + } + + globals.num_entities = game.maxclients; +} + +/* +============== +Start + +Does all post-spawning setup. This is NOT called for savegames. +============== +*/ +void Level::Start( void ) +{ + CThread *gamescript; + + // initialize secrets + + levelVars.SetVariable( "total_secrets", total_secrets ); + levelVars.SetVariable( "found_secrets", found_secrets ); + levelVars.SetVariable( "total_specialItems" , total_specialItems ); + levelVars.SetVariable( "found_specialItems" , found_specialItems ); + levelVars.SetVariable( "total_enemies_spawned", _totalEnemiesSpawned ); + + + FindTeams(); + + // call the precache scripts + + Precache(); + + // start executing the game script + + if ( game_script.length() ) + { + gi.ProcessLoadingScreen( "$$LoadingScript$$" ); + + program.Load( game_script ); + + gi.ProcessLoadingScreen( "$$DoneLoadingScript$$" ); + + // Create the main thread + + gamescript = Director.CreateThread( "main" ); + + if ( gamescript ) + { + // Run the precache thread if it exists + + if ( gamescript->labelExists( "precache" ) ) + { + CThread *precache_script; + precache_script = Director.CreateThread( "precache" ); + + if ( precache_script ) + precache_script->DelayedStart( 0.0f ); + } + + // Run the main thread + + gamescript->DelayedStart( 0.0f ); + } + } + + loadLevelStrings(); + started = true; +} + +//---------------------------------------------------------------- +// Name: postLoad +// Class: +// +// Description: Does everything necessary to the level after a load has happened +// +// Parameters: None +// +// Returns: none +//---------------------------------------------------------------- + +void Level::postLoad( void ) +{ + thePathManager.FindAllTargets(); + TeammateRoster::getInstance()->clearTeammates(); + //thePathManager.InsertNodesIntoGrid(); + //thePathManager.ConnectPathNodes(); + //thePathManager.OptimizeNodes( NULL ); + + thePathManager.SavePaths(); +} + +//---------------------------------------------------------------- +// Name: postSublevelLoad +// Class: Level +// +// Description: Does everything necessary to the level after a sublevel has been loaded +// +// Parameters: const char *mapName - map name string (also possibly contains spawn position) +// +// Returns: none +//---------------------------------------------------------------- + +void Level::postSublevelLoad( const char *spawnPosName ) +{ + int i; + gentity_t *ent; + Player *player; + + + // Save off the spawn position + + spawnpoint = spawnPosName; + + // Make sure the player starts in the correct place + + for( i = 0; i < game.maxclients; i++ ) + { + ent = &g_entities[ i ]; + + if ( !ent->inuse || !ent->client || !ent->entity ) + continue; + + if ( ent->entity->isSubclassOf( Player ) ) + { + player = (Player *)ent->entity; + + player->ChooseSpawnPoint(); + } + } + + + // Get rid of any fading + + G_ClearFade(); +} + +qboolean Level::inhibitEntity( int spawnflags ) +{ + if ( !developer->integer && ( spawnflags & SPAWNFLAG_DEVELOPMENT ) ) + { + return true; + } + + if ( !detail->integer && ( spawnflags & SPAWNFLAG_DETAIL ) ) + { + return true; + } + + if ( multiplayerManager.inMultiplayer() ) + { + if ( spawnflags & SPAWNFLAG_NOT_DEATHMATCH ) + { + return true; + } + + return false; + } + + switch( skill->integer ) + { + case 0 : + return ( spawnflags & SPAWNFLAG_NOT_EASY ) != 0; + break; + + case 1 : + return ( spawnflags & SPAWNFLAG_NOT_MEDIUM ) != 0; + break; + + case 2 : + case 3 : + return ( spawnflags & SPAWNFLAG_NOT_HARD ); + break; + } + + return false; +} + +void Level::setSkill( int value ) +{ + int skill_level; + + skill_level = (int) floor( value ); + skill_level = bound( skill_level, 0, 3 ); + + gi.cvar_set( "skill", va( "%d", skill_level ) ); + + gameVars.SetVariable( "skill", skill_level ); +} + +int Level::getSkill( void ) +{ + ScriptVariable* skill_var = gameVars.GetVariable("skill"); + return skill_var->intValue(); +} + +void Level::setTime( int levelTime, int frameTime ) +{ + inttime = levelTime; + fixedframetime = 1.0f / sv_fps->value; + frametime = ( ( float )frameTime / 1000.0f ); + time = ( ( float )levelTime / 1000.0f ); + + if(intermissiontime == 0.0f && mission_failed == false) + timeInLevel = time; +} + +/* +============== +SpawnEntities + +Creates a server's entity / program execution context by +parsing textual entity definitions out of an ent file. +============== +*/ +void Level::SpawnEntities( const char *themapname, const char *entities, int levelTime ) +{ + int inhibit,count=0; + const char *value; + SpawnArgs args; + char *spawnpos; + + // Init the level variables + Init(); + + spawnpos = strchr( themapname, '$' ); + if ( spawnpos ) + { + mapname = str( themapname, 0, spawnpos - themapname ); + spawnpoint = spawnpos + 1; + } + else + { + mapname = themapname; + spawnpoint = ""; + } + + // set up time so functions still have valid times + setTime( levelTime, 1000 / 20 ); + + if ( !LoadingServer ) + { + // Get rid of anything left over from the last level + //CleanUp( false ); + + // Set up for a new map + thePathManager.Init( mapname ); + + } + + + setSkill( skill->integer ); + + // reset out count of the number of game traces + sv_numtraces = 0; + + // parse world + entities = args.Parse( entities ); + spawn_entnum = ENTITYNUM_WORLD; + args.Spawn(); + + if ( !world ) + Com_Error( ERR_FATAL, "No world\n" ); + + if ( g_gametype->integer == GT_MULTIPLAYER || g_gametype->integer == GT_BOT_SINGLE_PLAYER ) + { + multiplayerManager.initMultiplayerGame(); + } + + // parse ents + inhibit = 0; + for( entities = args.Parse( entities ); entities != NULL; entities = args.Parse( entities ) ) + { + // remove things (except the world) from different skill levels or deathmatch + spawnflags = 0; + value = args.getArg( "spawnflags" ); + if ( value ) + { + spawnflags = atoi( value ); + if ( inhibitEntity( spawnflags ) ) + { + inhibit++; + continue; + } + } + + args.Spawn(); + count++; + + gi.ProcessLoadingScreen( "$$SpawningEntities$$" ); + } + + gi.DPrintf( "%i entities spawned\n", count ); + gi.DPrintf( "%i entities inhibited\n", inhibit ); + + // Process the spawn events + L_ProcessPendingEvents(); + + if ( multiplayerManager.inMultiplayer() ) + { + multiplayerManager.initItems(); + } + + // Setup bots + + if ( gi.Cvar_VariableIntegerValue( "bot_enable" ) && multiplayerManager.inMultiplayer() ) + { + BotAIShutdown( 0 ); + + BotAISetup( 0 ); + BotAILoadMap( 0 ); + G_InitBots( 0 ); + } + + if ( !LoadingServer || game.autosaved ) + { + Start(); + } + + postLoad(); + + + //------------------------------------------------------------------------------- + // + // Deletion Note: + // Since hNodeController is an Entity, it is deleted + // when all the other entities are deleted in the clean up function + // specifically the line + // + // + // if ( active_edicts.next->entity ) + // { + // delete active_edicts.next->entity; + // } + // + // + // Since it is already being deleted like this + // We do not need to explcitily delete the controller... In fact + // you will error out if you try to. + //-------------------------------------------------------------------------------- + hNodeController = new HelperNodeController; + if ( hNodeController ) + hNodeController->SetTargetName( "HelperNodeController" ); + + + + // + // if this is a single player game, spawn the single player in now + // this allows us to read persistant data into the player before the client + // is completely ready + // + if ( game.maxclients == 1 ) + { + spawn_entnum = 0; + new Player; + } +} + +void Level::NewMap( const char *mapname, const char *entities, int levelTime ) +{ + theCinematicArmature.deleteAllCinematics(); + current_map = mapname; + current_entities = entities; + + SpawnEntities( current_map, current_entities, levelTime ); +} + +void Level::Restart( void ) +{ + theCinematicArmature.deleteAllCinematics(); + + SpawnEntities( current_map, current_entities, inttime ); + + L_ProcessPendingEvents(); + + G_ClientConnect( 0, true, false, true ); + G_ClientBegin( &g_entities[ 0 ], NULL ); +} + +void Level::PlayerRestart( void ) +{ + // we need to restart through the server code + gi.SendConsoleCommand( "restart\n" ); + //restart = true; + level.mission_failed = false; +} + +void Level::Archive( Archiver &arc ) +{ + int num; + int i; + + Class::Archive( arc ); + + if ( arc.Saving() ) + { + SafePtr ent; + + num = _earthquakes.NumObjects(); + arc.ArchiveInteger( &num ); + + for ( i = 1 ; i <= num ; i++ ) + { + ent = _earthquakes.ObjectAt( i ); + arc.ArchiveSafePointer( &ent ); + } + } + else + { + SafePtr ent; + SafePtr *entityPointer; + + arc.ArchiveInteger( &num ); + + _earthquakes.ClearObjectList(); + _earthquakes.Resize( num ); + + for ( i = 1 ; i <= num ; i++ ) + { + _earthquakes.AddObject( ent ); + + entityPointer = &_earthquakes.ObjectAt( i ); + + arc.ArchiveSafePointer( entityPointer ); + } + } + + arc.ArchiveInteger( &_totalEnemiesSpawned ); + + // Don't archive these + + //const char *current_map; + //const char *current_entities; + + //int spawn_entnum; + arc.ArchiveInteger( ¤tInstanceNumber ); + //int spawnflags; + + arc.ArchiveInteger( &framenum ); + arc.ArchiveInteger( &inttime ); + arc.ArchiveFloat( &time ); + arc.ArchiveFloat( &timeInLevel ); + arc.ArchiveFloat( &frametime ); + arc.ArchiveFloat( &fixedframetime ); + arc.ArchiveInteger( &startTime ); + + arc.ArchiveString( &level_name ); + arc.ArchiveString( &mapname ); + arc.ArchiveString( &spawnpoint ); + arc.ArchiveString( &nextmap ); + + arc.ArchiveBoolean( &restart ); + arc.ArchiveBoolean( &started ); + + arc.ArchiveBoolean( &playerfrozen ); + + arc.ArchiveFloat( &intermissiontime ); + arc.ArchiveInteger( &exitintermission ); + arc.ArchiveFloat( &intermission_advancetime ); + arc.ArchiveBool( &_showIntermission ); + arc.ArchiveBool( &_saveOrientation ); + + // Don't archive + //gentity_s *next_edict; + + arc.ArchiveInteger( &total_secrets ); + arc.ArchiveInteger( &found_secrets ); + arc.ArchiveInteger( &total_specialItems ); + arc.ArchiveInteger( &found_specialItems ); + + arc.ArchiveString( &game_script ); + + // Don't archive + //trace_t impact_trace; + + arc.ArchiveBoolean( &cinematic ); + arc.ArchiveBoolean( &ai_on ); + + arc.ArchiveBoolean( &mission_failed ); + arc.ArchiveBoolean( &died_already ); + + arc.ArchiveBoolean( &near_exit ); + + arc.ArchiveVector( &water_color ); + arc.ArchiveFloat( &water_alpha ); + + arc.ArchiveVector( &slime_color ); + arc.ArchiveFloat( &slime_alpha ); + + arc.ArchiveVector( &lava_color ); + arc.ArchiveFloat( &lava_alpha ); + + arc.ArchiveString( ¤t_soundtrack ); + arc.ArchiveString( &saved_soundtrack ); + + arc.ArchiveObjectPointer( ( Class ** )&consoleThread ); + + arc.ArchiveVector( &m_fade_color ); + arc.ArchiveFloat( &m_fade_alpha ); + arc.ArchiveFloat( &m_fade_time ); + arc.ArchiveFloat( & m_fade_time_start ); + ArchiveEnum( m_fade_type, fadetype_t ); + ArchiveEnum( m_fade_style, fadestyle_t ); + + arc.ArchiveFloat( &m_letterbox_fraction ); + arc.ArchiveFloat( &m_letterbox_time ); + arc.ArchiveFloat( &m_letterbox_time_start ); + ArchiveEnum( m_letterbox_dir, letterboxdir_t ); + + arc.ArchiveBool( &_cleanup ); + + arc.ArchiveString( &_playerDeathThread ); + + arc.ArchiveObjectPointer( ( Class ** )&hNodeController ); + + // Don't archive, will already be setup from camera code + // Container automatic_cameras; + + arc.ArchiveVector( & m_intermission_origin ); + arc.ArchiveVector( & m_intermission_angle ); + + if ( arc.Loading() ) + { + str temp_soundtrack; + + // Change the sound track to the one just loaded + + temp_soundtrack = saved_soundtrack; + ChangeSoundtrack( current_soundtrack.c_str() ); + saved_soundtrack = temp_soundtrack; + + // not archived since we can't save mid-frame + next_edict = NULL; + // not archived since we can't save mid-frame + memset( &impact_trace, 0, sizeof( impact_trace ) ); + + loadLevelStrings(); + } + +} + +//----------------------------------------------------- +// +// Name: loadLevelStrings +// Class: Level +// +// Description: Loads the string resource file for the level. +// +// Parameters: None +// +// Returns: +//----------------------------------------------------- +void Level::loadLevelStrings( void ) +{ + const char* sublevelName; + const char* levelName; + const char* environmentName; + + gi.GetLevelDefs(mapname, &environmentName, &levelName, &sublevelName); + gi.SR_LoadLevelStrings(environmentName); +} + +/* +============== +Precache + +Calls precache scripts +============== +*/ +void Level::Precache( void ) +{ + str filename; + const char *environmentName; + const char *levelName; + const char *sublevelName; + int i; + str mapName; + const char *spawnPoint; + + // + // load in global0-9.scr + // + for( i = 0; i < 100; i++ ) + { + filename = va( "global/global%i.scr", i ); + + if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 ) + level.consoleThread->Parse( filename.c_str() ); + else + break; + } + + // Get the level defs + if( !current_map ) + return; + + mapName = current_map; + spawnPoint = strstr( mapName.c_str(), "$" ); + + if ( spawnPoint ) + { + mapName.CapLength( spawnPoint - mapName.c_str() ); + } + + gi.GetLevelDefs( mapName, &environmentName, &levelName, &sublevelName ); + + // Precache global stuff + + for( i = 0 ; i < 10 ; i++ ) + { + if ( i == 0 ) + filename = va( "precache/server/global.txt" ); + else + filename = va( "precache/server/global%d.txt", i ); + + if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 ) + level.consoleThread->Parse( filename.c_str() ); + else + break; + } + + // Precache environment stuff + + if ( strlen( environmentName ) ) + { + for( i = 0 ; i < 10 ; i++ ) + { + if ( i == 0 ) + filename = va( "precache/server/%s.txt", environmentName ); + else + filename = va( "precache/server/%s%d.txt", environmentName, i ); + + if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 ) + level.consoleThread->Parse( filename.c_str() ); + else + break; + } + } + + // Precache level stuff + + if ( strlen( levelName ) ) + { + for( i = 0 ; i < 10 ; i++ ) + { + if ( i == 0 ) + filename = va( "precache/server/%s.txt", levelName ); + else + filename = va( "precache/server/%s%d.txt", levelName, i ); + + if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 ) + level.consoleThread->Parse( filename.c_str() ); + else + break; + } + } + + // Precache sublevel stuff + + if ( strlen( sublevelName ) && ( stricmp( levelName, sublevelName ) != 0 ) ) + { + for( i = 0 ; i < 10 ; i++ ) + { + if ( i == 0 ) + filename = va( "precache/server/%s.txt", sublevelName ); + else + filename = va( "precache/server/%s%d.txt", sublevelName, i ); + + if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 ) + level.consoleThread->Parse( filename.c_str() ); + else + break; + } + } + + // load in universal_script.scr + + //G_LoadAndExecScript( "global/universal_script.scr", "precache:", true ); +} + +/* +================ +FindTeams + +Chain together all entities with a matching team field. + +All but the first will have the FL_TEAMSLAVE flag set. +All but the last will have the teamchain field set to the next one +================ +*/ +void Level::FindTeams( void ) +{ + gentity_t *e; + gentity_t *e2; + gentity_t *next; + gentity_t *next2; + Entity *chain; + Entity *ent; + Entity *ent2; + int c; + int c2; + + c = 0; + c2 = 0; + + for( e = active_edicts.next; e != &active_edicts; e = next ) + { + assert( e ); + assert( e->inuse ); + assert( e->entity ); + + next = e->next; + + ent = e->entity; + if ( !ent->bind_info || ( ent->bind_info->moveteam.length() == 0 ) ) + { + continue; + } + + if ( ent->flags & FL_TEAMSLAVE ) + { + continue; + } + + chain = ent; + ent->bind_info->teammaster = ent; + c++; + c2++; + for( e2 = next; e2 != &active_edicts; e2 = next2 ) + { + assert( e2 ); + assert( e2->inuse ); + assert( e2->entity ); + + next2 = e2->next; + + ent2 = e2->entity; + if ( !ent2->bind_info || ( ent2->bind_info->moveteam.length() == 0 ) ) + { + continue; + } + + if ( ent2->flags & FL_TEAMSLAVE ) + { + continue; + } + + if ( ent->bind_info->moveteam == ent2->bind_info->moveteam ) + { + c2++; + chain->bind_info->teamchain = ent2; + ent2->bind_info->teammaster = ent; + chain = ent2; + ent2->flags |= FL_TEAMSLAVE; + } + } + } + + gi.DPrintf( "%i teams with %i entities\n", c, c2 ); +} + +/* +================= +AllocEdict + +Either finds a free edict, or allocates a new one. +Try to avoid reusing an entity that was recently freed, because it +can cause the client to think the entity morphed into something else +instead of being removed and recreated, which can cause interpolated +angles and bad trails. +================= +*/ +gentity_t *Level::AllocEdict( Entity *ent ) +{ + int i; + gentity_t *edict; + + if ( spawn_entnum >= 0 ) + { + edict = &g_entities[ spawn_entnum ]; + spawn_entnum = -1; + + assert( !edict->inuse && !edict->entity ); + + // free up the entity pointer in case we took one that still exists + if ( edict->inuse && edict->entity ) + { + delete edict->entity; + } + } + else + { + edict = &g_entities[ maxclients->integer ]; + for ( i = maxclients->integer; i < globals.num_entities; i++, edict++ ) + { + // the first couple seconds of server time can involve a lot of + // freeing and allocating, so relax the replacement policy + if ( + !edict->inuse && + ( + ( edict->freetime < ( 2.0f + startTime ) ) || + ( time - edict->freetime > 0.5f ) + ) + ) + { + break; + } + } + + // allow two spots for none and world + if ( i == game.maxentities - 2.0f ) + { + // Try one more time before failing, relax timing completely + + edict = &g_entities[ maxclients->integer ]; + + for ( i = maxclients->integer; i < globals.num_entities; i++, edict++ ) + { + if ( !edict->inuse ) + { + break; + } + } + + if ( i == game.maxentities - 2.0f ) + { + gi.Error( ERR_DROP, "Level::AllocEdict: no free edicts" ); + } + } + } + + assert( edict->next ); + assert( edict->prev ); + LL_Remove( edict, next, prev ); + InitEdict( edict ); + assert( active_edicts.next ); + assert( active_edicts.prev ); + LL_Add( &active_edicts, edict, next, prev ); + assert( edict->next ); + assert( edict->prev ); + + assert( edict->next != &free_edicts ); + assert( edict->prev != &free_edicts ); + + // Tell the server about our data since we just spawned something + if ( ( edict->s.number < ENTITYNUM_WORLD ) && ( globals.num_entities <= edict->s.number ) ) + { + globals.num_entities = edict->s.number + 1; + gi.LocateGameData( g_entities, globals.num_entities, sizeof( gentity_t ), &game.clients[ 0 ].ps, sizeof( game.clients[ 0 ] ) ); + } + + edict->entity = ent; + edict->s.instanceNumber = currentInstanceNumber; + currentInstanceNumber++; + + if ( currentInstanceNumber < 0 ) + currentInstanceNumber = 0; + + return edict; +} + +/* +================= +FreeEdict + +Marks the edict as free +================= +*/ +void Level::FreeEdict( gentity_t *ed ) +{ + gclient_t *client; + + assert( ed != &free_edicts ); + + // unlink from world + gi.unlinkentity ( ed ); + + assert( ed->next ); + assert( ed->prev ); + + if ( next_edict == ed ) + { + next_edict = ed->next; + } + + LL_Remove( ed, next, prev ); + + assert( ed->next == ed ); + assert( ed->prev == ed ); + assert( free_edicts.next ); + assert( free_edicts.prev ); + + client = ed->client; + memset( ed, 0, sizeof( *ed ) ); + ed->client = client; + ed->freetime = time; + ed->inuse = false; + ed->s.number = ed - g_entities; + + assert( free_edicts.next ); + assert( free_edicts.prev ); + + LL_Add( &free_edicts, ed, next, prev ); + + assert( ed->next ); + assert( ed->prev ); +} + +void Level::InitEdict( gentity_t *e ) +{ + int i; + + e->inuse = true; + e->s.number = e - g_entities; + + // make sure a default scale gets set + e->s.scale = 1.0f; + // make sure the default constantlight gets set, initalize to r 1.0, g 1.0, b 1.0, r 0 + e->s.constantLight = 0xffffff; + e->s.renderfx |= RF_FRAMELERP; + e->spawntime = time; + e->s.frame = 0; + + e->svflags = 0; + + for( i = 0; i < NUM_BONE_CONTROLLERS; i++ ) + { + e->s.bone_tag[ i ] = -1; + VectorClear( e->s.bone_angles[ i ] ); + EulerToQuat( e->s.bone_angles[ i ], e->s.bone_quat[ i ] ); + } + + for( i = 0; i < NUM_MORPH_CONTROLLERS; i++ ) + { + e->s.morph_controllers[ i ].index = -1; + e->s.morph_controllers[ i ].percent = 0.0; + } + + e->s.animationRate = 1.0f; +} + +void Level::AddAutomaticCamera( Camera *cam ) +{ + automatic_cameras.AddUniqueObject( cam ); +} + +void Level::SetGameScript( const char *scriptname ) +{ + game_script = scriptname; +} + +//---------------------------------------------------------------- +// Name: addEarthQuake +// Class: Level +// +// Description: Adds an earthquake to the list +// +// Parameters: Entity *earthquake - earthquake to add to the list +// +// Returns: none +//---------------------------------------------------------------- + +void Level::addEarthquake( Earthquake *earthquake ) +{ + if ( !_earthquakes.ObjectInList( earthquake ) ) + { + _earthquakes.AddObject( earthquake ); + } +} + +//---------------------------------------------------------------- +// Name: removeEarthQuake +// Class: Level +// +// Description: Removes an earthquake from the list +// +// Parameters: Entity *earthquake - earthquake to remove from the list +// +// Returns: none +//---------------------------------------------------------------- + +void Level::removeEarthquake( Earthquake *earthquake ) +{ + if ( _earthquakes.ObjectInList( earthquake ) ) + { + _earthquakes.RemoveObject( earthquake ); + } +} + +//---------------------------------------------------------------- +// Name: getEarthquakeMagnitudeAtPosition +// Class: Level +// +// Description: Gets the total magnitude of all earthquakes from a particular position +// +// Parameters: const Vector &origin - position to test against +// +// Returns: float - total magnitude of all the earthquakes at the given position +//---------------------------------------------------------------- + +float Level::getEarthquakeMagnitudeAtPosition( const Vector &origin ) +{ + int i; + float totalMagnitude; + Earthquake *earthquake; + + totalMagnitude = 0.0f; + + // Add up all of the earthquakes magnitudes to get the total + + for( i = 1 ; i <= _earthquakes.NumObjects() ; i++ ) + { + // This should be a safe cast since only Earthquakes are allowed to be added to this list + + earthquake = _earthquakes.ObjectAt( i ); + + if ( earthquake ) + { + // Add this earthquake to the total + + totalMagnitude += earthquake->getMagnitudeAtPosition( origin ); + } + } + + // Return the total magnitude of all the earthquakes + + return totalMagnitude; +} + +void Level::enemySpawned( Entity *enemy ) +{ + _totalEnemiesSpawned++; + levelVars.SetVariable( "total_enemies_spawned", _totalEnemiesSpawned ); +} + +void Level::setPlayerDeathThread( const str &threadName ) +{ + _playerDeathThread = threadName; +} + +str Level::getPlayerDeathThread( void ) +{ + return _playerDeathThread; +} diff --git a/dlls/game/level.h b/dlls/game/level.h new file mode 100644 index 0000000..aeafb2e --- /dev/null +++ b/dlls/game/level.h @@ -0,0 +1,189 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/level.h $ +// $Revision:: 31 $ +// $Date:: 3/02/03 8:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __LEVEL_H__ +#define __LEVEL_H__ + +#include "g_local.h" +#include "container.h" + +class CThread; +class Camera; +class HelperNodeController; +class Earthquake; + +// +// this structure is cleared as each map is entered +// it is read/written to the level.sav file for savegames +// + +enum fadetype_t { fadein, fadeout }; +enum fadestyle_t { alphablend, additive }; +enum letterboxdir_t { letterbox_in, letterbox_out }; + +class Level : public Class +{ + private: + Container< SafePtr > _earthquakes; + + int _totalEnemiesSpawned; + + public: + const char *current_map; + const char *current_entities; + + int spawn_entnum; + int currentInstanceNumber; + int spawnflags; + + int framenum; + int inttime; // level time in millisecond integer form + float time; + float timeInLevel; + float frametime; + float fixedframetime; // preset frame time based on sv_fps + int startTime; // level.time the map was started + + str level_name; // the descriptive name (Outer Base, etc) + str mapname; // the server name (base1, etc) + str spawnpoint; // targetname of spawnpoint + str nextmap; // go here when fraglimit is hit + + qboolean restart; // set true when game loop should restart + qboolean started; // set when the level is started. + + // used for cinematics + qboolean playerfrozen; + + // intermiss ion state + float intermissiontime; // time the intermission was started + int exitintermission; + float intermission_advancetime; + bool _showIntermission; + bool _saveOrientation; + + gentity_s *next_edict; // Used to keep track of the next edict to process in G_RunFrame + + int total_secrets; + int found_secrets; + int total_specialItems; + int found_specialItems; + + str game_script; + + // FIXME - r emove this later when it is passed in the event. + trace_t impact_trace; + + qboolean cinematic; + + qboolean ai_on; + + qboolean mission_failed; + qboolean died_already; + + qboolean near_exit; + + // Blending color for water, light volumes,lava + Vector water_color; + float water_alpha; + + Vector slime_color; + float slime_alpha; + + Vector lava_color; + float lava_alpha; + + str current_soundtrack; + str saved_soundtrack; + + CThread *consoleThread; + + Vector m_fade_color; + float m_fade_alpha; + float m_fade_time; + float m_fade_time_start; + fadetype_t m_fade_type; + fadestyle_t m_fade_style; + + float m_letterbox_fraction; + float m_letterbox_time; + float m_letterbox_time_start; + letterboxdir_t m_letterbox_dir; + + bool _cleanup; + + str _playerDeathThread; + + HelperNodeController* hNodeController; + // + // list of automatic_cameras on the level + // + Container automatic_cameras; + + Vector m_intermission_origin; + Vector m_intermission_angle; + + CLASS_PROTOTYPE( Level ); + + Level(); + ~Level(); + + void Init( void ); + void CleanUp( qboolean restart ); + + void ResetEdicts( void ); + gentity_t *AllocEdict( Entity *ent ); + void FreeEdict( gentity_t *ed ); + void InitEdict( gentity_t *e ); + + void Start( void ); + void Restart( void ); + void PlayerRestart( void ); + void update(int levelTime, int frameTime); + void Precache( void ); + void FindTeams( void ); + + void SpawnEntities( const char *mapname, const char *entities, int levelTime ); + void NewMap( const char *mapname, const char *entities, int levelTime ); + void postLoad( void ); + void postSublevelLoad( const char *spawnPosName ); + + qboolean inhibitEntity( int spawnflags ); + void setSkill( int value ); + int getSkill( void ); + void setTime( int levelTime, int frameTime ); + void SetGameScript( const char *scriptname ); + void AddAutomaticCamera( Camera * cam ); + virtual void Archive( Archiver &arc ); + + void loadLevelStrings( void ); + + void addEarthquake( Earthquake *earthquake ); + void removeEarthquake( Earthquake *earthquake ); + float getEarthquakeMagnitudeAtPosition( const Vector &origin ); + + void enemySpawned( Entity *enemy ); + //1st Playable hack + void SetIntermissionAdvanceTime(float); + void EndIntermission(); + + void setPlayerDeathThread( const str &threadName ); + str getPlayerDeathThread( void ); + }; + +extern Level level; + +#endif /* !__LEVEL_H__ */ diff --git a/dlls/game/lexer.cpp b/dlls/game/lexer.cpp new file mode 100644 index 0000000..6aa1ac2 --- /dev/null +++ b/dlls/game/lexer.cpp @@ -0,0 +1,694 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/lexer.cpp $ +// $Revision:: 7 $ +// $Date:: 10/08/02 7:35a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "lexer.h" + +// longer symbols must be before a shorter partial match +static const char *pr_punctuation[] = +{ + "+=", "-=", "*=", "/=", "&=", "|=", "++", "--", + "&&", "||", "<=", ">=", "==", "!=", ";", ",", "!", + "*", "/", "(", ")", "-", "+", "=", "[", "]", + "{", "}", "...", ".", "<", ">" , "#" , "&", "|", + NULL +}; + +// simple types. function types are dynamically allocated +type_t type_void( ev_void, &def_void ); +type_t type_string( ev_string, &def_string ); +type_t type_float( ev_float, &def_float ); +type_t type_vector( ev_vector, &def_vector ); +type_t type_entity( ev_entity, &def_entity ); +type_t type_function( ev_function, &def_function, NULL, &type_void ); + +// type_function is a void() function used for state defs + +int type_size[6] = { 1, 1, 1, 3, 1, 1 }; + +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + +def_t def_void = { &type_void, "temp" }; +def_t def_string = { &type_string, "temp" }; +def_t def_float = { &type_float, "temp" }; +def_t def_vector = { &type_vector, "temp" }; +def_t def_entity = { &type_entity, "temp" }; +def_t def_function = { &type_function, "temp" }; + +#else + +****************************************************************************/ + + +def_t def_void; +def_t def_string; +def_t def_float; +def_t def_vector; +def_t def_entity; +def_t def_function; + +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#endif + +****************************************************************************/ + + +Lexer::Lexer( Program &prg ) + : program( prg ) +{ + source_line = 0; + pr_file_p = NULL; + pr_line_start = NULL; + pr_bracelevel = 0; + pr_immediate_type = NULL; +} + +/* +============== +NewLine + +Call at start of file and when *pr_file_p == '\n' +============== +*/ +void Lexer::NewLine( void ) +{ + bool m; + + if ( *pr_file_p == '\n' ) + { + pr_file_p++; + m = true; + } + else + { + m = false; + } + + source_line++; + pr_line_start = pr_file_p; + + if ( m ) + { + pr_file_p--; + } +} + +/* +============== +LexString + +Parses a quoted string +============== +*/ +void Lexer::LexString( void ) +{ + int c; + int len; + + len = 0; + pr_file_p++; + do + { + c = *pr_file_p++; + if ( !c ) + { + ParseError( "EOF inside quote" ); + } + + if ( c == '\n' ) + { + ParseError( "newline inside quote" ); + } + + if ( c=='\\' ) + { + // escape char + c = *pr_file_p++; + if ( !c ) + { + ParseError( "EOF inside quote" ); + } + + if ( c == 'n' ) + { + c = '\n'; + } + else if ( c == '"' ) + { + c = '"'; + } + else + { + ParseError( "Unknown escape char" ); + } + } + else if ( c=='\"' ) + { + pr_token[ len ] = 0; + pr_token_type = tt_immediate; + pr_immediate_type = &type_string; + strcpy( pr_immediate_string, pr_token ); + + return; + } + + pr_token[ len ] = c; + len++; + } + while( 1 ); +} + +/* +============== +LexNumber +============== +*/ +float Lexer::LexNumber( void ) +{ + int c; + int len; + + len = 0; + c = *pr_file_p; + do + { + pr_token[ len ] = c; + len++; + pr_file_p++; + c = *pr_file_p; + } + while( ( ( c >= '0' ) && ( c <= '9' ) ) || ( c == '.' ) ); + + pr_token[ len ] = 0; + + return ( float )atof( pr_token ); +} + +/* +============== +LexVector + +Parses a single quoted vector +============== +*/ +void Lexer::LexVector( void ) +{ + int i; + + pr_file_p++; + pr_token_type = tt_immediate; + pr_immediate_type = &type_vector; + for( i = 0; i < 3; i++ ) + { + pr_immediate.vector[ i ] = LexNumber(); + LexWhitespace(); + } + + if ( *pr_file_p != '\'' ) + { + ParseError( "Bad vector" ); + } + + pr_file_p++; +} + +/* +============== +LexEntity + +Parses an identifier +============== +*/ +void Lexer::LexEntity( void ) +{ + int c; + int len; + TargetList *list; + + pr_file_p++; + len = 0; + c = *pr_file_p; + do + { + pr_token[ len ] = c; + len++; + pr_file_p++; + c = *pr_file_p; + } + while( ( ( c >= 'a' ) && ( c <= 'z' ) ) || ( ( c >= 'A' ) && ( c <= 'Z' ) ) || ( c == '_' ) || ( ( c >= '0' ) && ( c <= '9' ) ) ); + + pr_token[ len ] = 0; + pr_token_type = tt_immediate; + pr_immediate_type = &type_entity; + + list= world->GetTargetList( str( pr_token ), true ); + if ( list ) + { + pr_immediate.entity = -list->index; + } + else + { + pr_immediate.entity = 0; + } +} + +/* +============== +LexName + +Parses an identifier +============== +*/ +void Lexer::LexName( void ) +{ + int c; + int len; + + len = 0; + c = *pr_file_p; + do + { + pr_token[ len ] = c; + len++; + pr_file_p++; + c = *pr_file_p; + } + while( ( ( c >= 'a' ) && ( c <= 'z' ) ) || ( ( c >= 'A' ) && ( c <= 'Z' ) ) || ( c == '_' ) || ( ( c >= '0' ) && ( c <= '9' ) ) ); + + pr_token[ len ] = 0; + pr_token_type = tt_name; +} + +/* +============== +LexPunctuation +============== +*/ +void Lexer::LexPunctuation( void ) +{ + int len; + const char **ptr; + const char *p; + + pr_token_type = tt_punct; + + for( ptr = pr_punctuation; *ptr != NULL; ptr++ ) + { + p = *ptr; + len = strlen( p ); + if ( !strncmp( p, pr_file_p, len ) ) + { + strcpy( pr_token, p ); + if ( p[ 0 ] == '{' ) + { + pr_bracelevel++; + } + else if ( p[ 0 ] == '}' ) + { + pr_bracelevel--; + } + + pr_file_p += len; + return; + } + } + + ParseError( "Unknown punctuation" ); +} + +/* +============== +LexWhitespace +============== +*/ +void Lexer::LexWhitespace( void ) +{ + int c; + + while( 1 ) + { + // skip whitespace + while( ( c = *pr_file_p ) <= ' ' ) + { + if ( c=='\n' ) + { + NewLine(); + } + + if ( c == 0 ) + { + // end of file + return; + } + + pr_file_p++; + } + + // skip // comments + if ( ( c == '/' ) && ( pr_file_p[ 1 ] == '/' ) ) + { + while( *pr_file_p && ( *pr_file_p != '\n' ) ) + { + pr_file_p++; + } + + NewLine(); + pr_file_p++; + + continue; + } + + // skip /* */ comments + if ( ( c == '/' ) && ( pr_file_p[ 1 ] == '*' ) ) + { + do + { + pr_file_p++; + if ( pr_file_p[ 0 ]=='\n' ) + { + NewLine(); + } + + if ( pr_file_p[ 1 ] == 0 ) + { + return; + } + } + while( ( pr_file_p[ -1 ] != '*' ) || ( pr_file_p[ 0 ] != '/' ) ); + + pr_file_p++; + continue; + } + + // a real character has been found + break; + } +} + +void Lexer::LexPreprocessor( void ) +{ + int c; + int len; + + len = 0; + c = *pr_file_p; + do + { + pr_token[ len ] = c; + len++; + pr_file_p++; + c = *pr_file_p; + } + while( ( ( c >= 'a' ) && ( c <= 'z' ) ) || ( ( c >= 'A' ) && ( c <= 'Z' ) ) || ( c == '_' ) || ( ( c >= '0' ) && ( c <= '9' ) ) ); + + pr_token[ len ] = 0; + pr_token_type = tt_preprocessor; +} + +//============================================================================ + +/* +============== +Lex + +Sets pr_token, pr_token_type, and possibly pr_immediate and pr_immediate_type +============== +*/ +void Lexer::Lex( void ) +{ + int c; + + pr_token[ 0 ] = 0; + + if ( !pr_file_p ) + { + pr_token_type = tt_eof; + return; + } + + LexWhitespace(); + + c = *pr_file_p; + if ( !c ) + { + pr_token_type = tt_eof; + return; + } + + // handle quoted strings as a unit + if ( c == '\"' ) + { + LexString (); + return; + } + + // handle quoted vectors as a unit + if ( c == '\'' ) + { + LexVector (); + return; + } + + // if the first character is a valid identifier, parse until a non-id + // character is reached + if ( ( ( c >= '0' ) && ( c <= '9' ) ) || ( ( c == '-' ) && ( pr_file_p[ 1 ] >= '0' ) && ( pr_file_p[ 1 ] <= '9' ) ) || + ( ( c == '.' ) && ( pr_file_p[ 1 ] >= '0' ) && ( pr_file_p[ 1 ] <= '9' ) ) ) + { + pr_token_type = tt_immediate; + pr_immediate_type = &type_float; + pr_immediate._float = LexNumber(); + return; + } + + // entity names + if ( c == '$' ) + { + LexEntity(); + return; + } + + if ( c == '#' ) + { + LexPreprocessor(); + return; + } + + if ( ( ( c >= 'a' ) && ( c <= 'z' ) ) || ( ( c >= 'A' ) && ( c <= 'Z' ) ) || ( c == '_' ) ) + { + LexName(); + return; + } + + // parse symbol strings until a non-symbol is found + LexPunctuation(); +} + +//============================================================================= + +/* +============ +ParseError + +Aborts the current file load +============ +*/ +void Lexer::ParseError( const char *error, ... ) +{ + va_list argptr; + char string[ 1024 ]; + + va_start( argptr, error ); + vsprintf( string, error, argptr ); + va_end( argptr ); + + gi.Printf( "%s(%i) : %s\n", program.s_file.c_str(), source_line, string ); + + throw "Error"; +} + +/* +============= +Expect + +Issues an error if the current token isn't equal to string +Gets the next token +============= +*/ +void Lexer::Expect( char *string ) +{ + if ( strcmp( string, pr_token ) ) + { + ParseError( "expected %s, found %s",string, pr_token ); + } + + Lex(); +} + +/* +============= +Check + +Returns true and gets the next token if the current token equals string +Returns false and does nothing otherwise +============= +*/ +bool Lexer::Check( const char *string ) +{ + if ( strcmp( string, pr_token ) ) + { + return false; + } + + Lex(); + + return true; +} + +/* +============ +ParseName + +Checks to see if the current token is a valid name +============ +*/ +char *Lexer::ParseName( void ) +{ + static char ident[ MAX_NAME ]; + + if ( pr_token_type != tt_name ) + { + ParseError( "not a name" ); + } + + if ( strlen( pr_token ) >= ( MAX_NAME - 1 ) ) + { + ParseError( "name too long" ); + } + + strcpy( ident, pr_token ); + Lex(); + + return ident; +} + +/* +============ +SkipOutOfFunction + +For error recovery, pops out of nested braces +============ +*/ +void Lexer::SkipOutOfFunction( void ) +{ + do + { + if ( !pr_bracelevel ) + { + return; + } + + Lex(); + } + while( pr_token[ 0 ] ); // eof will return a null token +} + +/* +============ +SkipToSemicolon + +For error recovery +============ +*/ +void Lexer::SkipToSemicolon( void ) +{ + do + { + if ( Check( ";" ) ) + { + return; + } + + Lex(); + } + while( pr_token[ 0 ] ); // eof will return a null token +} + +/* +============ +CheckType + +Parses a variable type, including functions types +============ +*/ +type_t *Lexer::CheckType( void ) +{ + type_t *type; + + if ( !strcmp( pr_token, "float" ) ) + { + type = &type_float; + } + else if ( !strcmp( pr_token, "vector" ) ) + { + type = &type_vector; + } + else if ( !strcmp( pr_token, "entity" ) ) + { + type = &type_entity; + } + else if ( !strcmp( pr_token, "string" ) ) + { + type = &type_string; + } + else if ( !strcmp( pr_token, "void" ) ) + { + type = &type_void; + } + else + { + type = NULL; + } + + return type; +} + +/* +============ +ParseType + +Parses a variable type, including functions types +============ +*/ +type_t *Lexer::ParseType( void ) +{ + type_t *type; + + type = CheckType(); + if ( !type ) + { + ParseError( "\"%s\" is not a type", pr_token ); + } + + Lex(); + + return type; +} diff --git a/dlls/game/lexer.h b/dlls/game/lexer.h new file mode 100644 index 0000000..cc78569 --- /dev/null +++ b/dlls/game/lexer.h @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/lexer.h $ +// $Revision:: 4 $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// + +#ifndef __LEXER_H__ +#define __LEXER_H__ + +#include "program.h" + +#define MAX_FRAMES 256 + +extern int type_size[ 6 ]; + +extern type_t type_void; +extern type_t type_string; +extern type_t type_float; +extern type_t type_vector; +extern type_t type_entity; +extern type_t type_function; + +extern def_t def_void; +extern def_t def_string; +extern def_t def_float; +extern def_t def_vector; +extern def_t def_entity; +extern def_t def_function; + +extern def_t def_ret; +extern def_t junkdef; + +typedef enum + { + tt_eof, // end of file reached + tt_name, // an alphanumeric name token + tt_punct, // code punctuation + tt_immediate, // string, float, vector + tt_preprocessor // #-prefixed command + } token_type_t; + +class Lexer + { + private: + int source_line; + + const char *pr_file_p; + const char *pr_line_start; // start of current source line + + int pr_bracelevel; + + char pr_token[ 2048 ]; + + public: + + token_type_t pr_token_type; + + type_t *pr_immediate_type; + eval_t pr_immediate; + char pr_immediate_string[ 2048 ]; + + Program &program; + + Lexer( Program &prg ); + + void NewLine( void ); + void LexString( void ); + float LexNumber( void ); + void LexVector( void ); + void LexEntity( void ); + void LexName( void ); + void LexPunctuation( void ); + void LexWhitespace( void ); + void LexPreprocessor( void ); + void Lex( void ); + void ParseError( const char *error, ... ); + void Expect( char *string ); + bool Check( const char *string ); + char *ParseName( void ); + void SkipOutOfFunction( void ); + void SkipToSemicolon( void ); + type_t *CheckType( void ); + type_t *ParseType( void ); + + int SourceLine( void ); + void SetSource( const char *text ); + }; + +inline int Lexer::SourceLine + ( + void + ) + + { + return source_line; + } + +inline void Lexer::SetSource + ( + const char *text + ) + + { + pr_file_p = text; + source_line = 0; + NewLine(); + } + +#endif diff --git a/dlls/game/light.cpp b/dlls/game/light.cpp new file mode 100644 index 0000000..c99a54e --- /dev/null +++ b/dlls/game/light.cpp @@ -0,0 +1,138 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/light.cpp $ +// $Revision:: 9 $ +// $Author:: Steven $ +// $Date:: 10/08/02 7:35a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Classes for creating and controlling lights. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "trigger.h" +#include "light.h" +#include "scriptmaster.h" + +/*****************************************************************************/ +/*QUAKED light (0.75 0.5 0) (-8 -8 -8) (8 8 8) LINEAR NO_ENTITIES ENTITY_TRACE SUN DYNAMIC LENSFLARE NO_WORLD + +Non-displayed light. If it targets another entity it will become a spot light +if "LINEAR" is set, it will be a linear light +if "NO_ENTITIES" is set, this light will only affect the world, not entities +if "ENTITY_TRACE" is set, a trace is done betwee the light and the entity. +if "SUN" is set, the light basically acts like a sun (infinite distance away, no falloff, etc.) +if "DYNAMIC" is set, the light can be dynamicly changed on & off +if "LENSFLARE" is set, the light will always have a lensflare attached to it +if "NO_WORLD" is set, the light will not affect the world + +The light is only added if the trace is clear + +"no_entity_light" - this light will not effect entities, just the world +"light" - the intensity of the light, default 300 +"color" - the color of the light +"falloff" - if linear, specify the linear falloff (defaults to 1) +"radius" - make this a spot light of the given radius +"angles" - make this a spot light centered on angles +"spot_angle" - if this is a spot light, what angle to use (default 45) +"entity_trace" - trace between the entity and the light + +Dynamic light stuff + +"minlight" - the intensity of the light, default 0 +"group_name" - specifies the dynamic light group name for this light + +******************************************************************************/ + +Event EV_Light_SetLight +( + "light", + EV_DEFAULT, + NULL, + NULL, + "Set the intensity of the light" +); +Event EV_Light_SetColor +( + "color", + EV_DEFAULT, + NULL, + NULL, + "" +); +Event EV_Light_SetFalloff +( + "falloff", + EV_CODEONLY, + NULL, + NULL, + "" +); +Event EV_Light_SetRadius +( + "falloff", + EV_CODEONLY, + NULL, + NULL, + "" +); +Event EV_Light_SpotDir +( + "spot_dir", + EV_CODEONLY, + NULL, + NULL, + "" +); +Event EV_Light_SpotRadiusByDistance +( + "spot_radiusbydistance", + EV_CODEONLY, + NULL, + NULL, + "" +); +Event EV_Light_NoEntityLight +( + "no_entity_light", + EV_CODEONLY, + NULL, + NULL, + "" +); +Event EV_Light_EntityTrace +( + "entity_trace", + EV_CODEONLY, + NULL, + NULL, + "" +); +Event EV_Light_SpotAngle +( + "spot_angle", + EV_CODEONLY, + NULL, + NULL, + "" +); + +CLASS_DECLARATION( Entity, Light, "light" ) +{ + { &EV_Light_SetLight, NULL }, + + { NULL, NULL } +}; + +Light::Light() +{ + PostEvent( EV_Remove, 0.0f ); +} diff --git a/dlls/game/light.h b/dlls/game/light.h new file mode 100644 index 0000000..a45ea57 --- /dev/null +++ b/dlls/game/light.h @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/light.h $ +// $Revision:: 3 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Classes for creating and controlling lights. +// + +#ifndef __LIGHT_H__ +#define __LIGHT_H__ + +#include "g_local.h" +#include "entity.h" + +class Light : public Entity + { + public: + CLASS_PROTOTYPE( Light ); + + Light(); + }; + +#endif /* light.h */ diff --git a/dlls/game/listener.cpp b/dlls/game/listener.cpp new file mode 100644 index 0000000..51ddb35 --- /dev/null +++ b/dlls/game/listener.cpp @@ -0,0 +1,3067 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/listener.cpp $ +// $Revision:: 29 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 4:00p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// +// WARNING: This file is shared between game, cgame and possibly the user interface. +// It is instanced in each one of these directories because of the way that SourceSafe works. +// + +#include "listener.h" +#include + +#if defined( GAME_DLL ) + +#include "worldspawn.h" +#include "scriptmaster.h" +#include "mp_manager.hpp" + +#elif defined( CGAME_DLL ) + +#elif defined( UI_LIB ) + +#else + +//#include "client.h" + +#endif + + +Event EV_Remove +( + "immediateremove", + EV_DEFAULT, + NULL, + NULL, + "Removes this listener immediately." +); +Event EV_ScriptRemove +( + "remove", + EV_DEFAULT, + NULL, + NULL, + "Removes this listener the next time events are processed." +); + +extern "C" +{ + int numEvents = 0; + int numFreeEvents = 0; +} + +cvar_t *g_showevents; +cvar_t *g_eventlimit; +cvar_t *g_timeevents; +cvar_t *g_watch; +cvar_t *g_eventstats; + +Event FreeEventHead; +Event *FreeEvents = &FreeEventHead; +Event EventQueueHead; +Event *EventQueue = &EventQueueHead; +Event ActiveEventHead; +Event *ActiveEvents = &ActiveEventHead; + +Container *Event::commandList = NULL; +Container *Event::flagList = NULL; +Container *Event::sortedList = NULL; +Container *Event::eventDefList = NULL; +qboolean Event::dirtylist = false; + +int Event::numfinds; +int Event::numfirstsearch; +int Event::numcompares; +int Event::lastevent; +bool Event::EventSystemStarted; + +int Event_totalmemallocated; + +Event NullEvent; + +void EV_Print( FILE *event_file, const char *fmt, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + if ( event_file ) + fprintf( event_file, text ); + else + EVENT_Printf( text ); +} + +EventVar::EventVar( const char *text ) +{ + assert( text ); + if ( !text ) + { + text = ""; + } + + dirtyFlags = DIRTY_ALL & ~DIRTY_STRING; + stringValue = text; + type = IS_STRING; + intValue = 0; + floatValue = 0; +} + +EventVar::EventVar( const str &text ) +{ + dirtyFlags = DIRTY_ALL & ~DIRTY_STRING; + stringValue = text; + type = IS_STRING; + intValue = 0; + floatValue = 0; +} + +#ifdef GAME_DLL + +EventVar::EventVar( const Entity *ent ) +{ + type = IS_ENTITY; + dirtyFlags = DIRTY_ALL & ~DIRTY_INTEGER; + if ( ent ) + { + intValue = ent->entnum; + } + else + { + intValue = ENTITYNUM_NONE; + } + floatValue = 0; +} + +void EventVar::Archive( Archiver &arc ) +{ + arc.ArchiveShort( &type ); + arc.ArchiveShort( &dirtyFlags ); + arc.ArchiveString( &stringValue ); + arc.ArchiveVector( &vectorValue ); + arc.ArchiveInteger( &intValue ); + arc.ArchiveFloat( &floatValue ); +} + +#endif + +const char *EventVar::GetToken( Event &ev ) +{ + if ( dirtyFlags & DIRTY_STRING ) + { + switch( type ) + { + case IS_VECTOR : + stringValue = va( "(%f %f %f)", vectorValue.x, vectorValue.y, vectorValue.z ); + break; + + case IS_INTEGER : + stringValue = va( "%d", intValue ); + break; + + case IS_FLOAT : + stringValue = va( "%f", floatValue ); + break; + + case IS_ENTITY : + stringValue = va( "*%d", intValue ); + break; + } + + dirtyFlags &= ~DIRTY_STRING; + } + + return stringValue.c_str(); +} + +const char *EventVar::GetString( Event &ev ) +{ + if ( dirtyFlags & DIRTY_STRING ) + { + switch( type ) + { + case IS_VECTOR : + stringValue = va( "(%f %f %f)", vectorValue.x, vectorValue.y, vectorValue.z ); + break; + + case IS_INTEGER : + stringValue = va( "%d", intValue ); + break; + + case IS_FLOAT : + stringValue = va( "%f", floatValue ); + break; + + case IS_ENTITY : + stringValue = va( "*%d", intValue ); + break; + } + + dirtyFlags &= ~DIRTY_STRING; + } + + return stringValue.c_str(); +} + +int EventVar::GetInteger( Event &ev ) +{ + if ( dirtyFlags & DIRTY_INTEGER ) + { + switch( type ) + { + case IS_STRING : + intValue = atoi( stringValue.c_str() ); + break; + + case IS_VECTOR : + intValue = 0; + break; + + case IS_FLOAT : + intValue = int( floatValue ); + break; + } + + dirtyFlags &= ~DIRTY_INTEGER; + } + + return intValue; +} + +float EventVar::GetFloat( Event &ev ) +{ + if ( dirtyFlags & DIRTY_FLOAT ) + { + switch( type ) + { + case IS_STRING : + floatValue = (float)atof( stringValue.c_str() ); + break; + + case IS_VECTOR : + floatValue = 0; + break; + + case IS_ENTITY : + case IS_INTEGER : + floatValue = float( intValue ); + break; + } + + dirtyFlags &= ~DIRTY_FLOAT; + } + + return floatValue; +} + +Vector EventVar::GetVector( Event &ev ) +{ + if ( dirtyFlags & DIRTY_VECTOR ) + { + switch( type ) + { + case IS_STRING : + const char *text; + + text = stringValue.c_str(); + if ( text[ 0 ] == '(' ) + { + vectorValue = Vector(&text[ 1 ]); + } +#ifdef GAME_DLL + // is it an entity + else if ( text[ 0 ] == '$' ) + { + Entity * ent; + + // allow console users to not use '$' + ent = G_FindTarget( NULL, &text[ 1 ] ); + + if ( ent ) + { + vectorValue = ent->origin; + } + else + { + vectorValue = vec_zero; + } + } +#endif + else + { + vectorValue = Vector(text); + } + break; + + case IS_FLOAT : + case IS_INTEGER : + vectorValue = vec_zero; + break; + case IS_ENTITY : +#ifdef GAME_DLL + { + Entity * ent; + + ent = G_GetEntity( intValue ); + if ( ent ) + { + vectorValue = ent->origin; + } + else + { + vectorValue = vec_zero; + } + } +#else + vectorValue = vec_zero; +#endif + break; + } + + dirtyFlags &= ~DIRTY_VECTOR; + } + + return vectorValue; +} + +#ifdef GAME_DLL +Entity *EventVar::GetEntity( Event &ev ) +{ + if ( dirtyFlags & DIRTY_INTEGER ) + { + switch( type ) + { + case IS_VECTOR : + intValue = 0; + break; + + case IS_FLOAT : + intValue = int( floatValue ); + break; + + case IS_STRING : + const char *name; + int t; + + t = 0; + + name = stringValue.c_str(); + if ( ev.GetSource() == EV_FROM_CONSOLE ) + { + Entity * ent; + + // allow console users to not use '$' + ent = G_FindTarget( NULL, name ); + intValue = ent->entnum; + break; + } + + if ( name[ 0 ] == '$' ) + { + Entity * ent; + + ent = G_FindTarget( NULL, &name[ 1 ] ); + if ( !ent ) + { + ev.Error( "Entity with targetname of '%s' not found", &name[ 1 ] ); + + return NULL; + } + else + { + t = ent->entnum; + } + } + else + { + if ( name[ 0 ] != '*' ) + { + if ( ev.GetSource() == EV_FROM_CONSOLE ) + { + ev.Error( "Entity with targetname of '%s' not found", name ); + } + else + { + ev.Error( "Expecting a '*'-prefixed entity number but found '%s'.", name ); + } + + return NULL; + } + + if ( !::IsNumeric( &name[ 1 ] ) ) + { + ev.Error( "Expecting a numeric value but found '%s'.", &name[ 1 ] ); + + return NULL; + } + else + { + t = atoi( &name[ 1 ] ); + } + } + + intValue = t; + + break; + } + + dirtyFlags &= ~DIRTY_INTEGER; + } + + if ( ( intValue < 0 ) || ( intValue > game.maxentities ) ) + { + ev.Error( "%d out of valid range for entity.", intValue ); + return NULL; + } + + return G_GetEntity( intValue ); +} + +//=============================================================== +// Name: IsEntity -- Game DLL only +// Class: EventVar +// +// Description: Determines if this event parameter describes an +// entity. +// +// Parameters: Event& -- the event +// +// Returns: qboolean -- qtrue if it is an entity. +// +//=============================================================== +qboolean EventVar::IsEntity( Event &ev ) +{ + Entity *ent = 0 ; + const char *name; + + if ( dirtyFlags & DIRTY_INTEGER ) + { + switch( type ) + { + case IS_VECTOR: + break ; + + case IS_FLOAT: + break ; + + case IS_INTEGER: + if ( ( intValue < 0 ) || ( intValue > game.maxentities ) ) + { + break ; + } + ent = G_GetEntity( intValue ); + break ; + + case IS_STRING : + name = stringValue.c_str(); + if ( ev.GetSource() == EV_FROM_CONSOLE ) + { + ent = G_FindTarget( NULL, name ); + } + else if ( name[ 0 ] == '$' ) + { + ent = G_FindTarget( NULL, &name[ 1 ] ); + } + else if ( (name[0]=='*') && ::IsNumeric(&name[1]) ) + { + ent = G_GetEntity( atoi(&name[1]) ); + } + break; + case IS_ENTITY : + if ( ( intValue < 0 ) || ( intValue > game.maxentities ) ) + { + break ; + } + ent = G_GetEntity( intValue ); + break ; + } + } + else + { + if ( ( intValue >= 0 ) && ( intValue < game.maxentities ) ) + { + ent = G_GetEntity( intValue ); + } + } + + if ( ent ) + { + intValue = ent->entnum; + dirtyFlags &= ~DIRTY_INTEGER; + return true; + } + else + { + return false; + } +} + +#else + +qboolean IsNumeric( const char *str ) +{ + int len; + int i; + qboolean dot; + + if ( *str == '-' ) + { + str++; + } + + dot = false; + len = strlen( str ); + for( i = 0; i < len; i++ ) + { + if ( !isdigit( str[ i ] ) ) + { + if ( ( str[ i ] == '.' ) && !dot ) + { + dot = true; + continue; + } + return false; + } + } + + return true; +} + +#endif + +qboolean EventVar::IsVector( Event &ev ) +{ + switch( type ) + { + case IS_VECTOR : + return true; + break; + + case IS_STRING : + if ( stringValue.c_str()[ 0 ] == '(' ) + { + return true; + } + break; + } + + return false; +} + +qboolean EventVar::IsNumeric( Event &ev ) +{ + switch( type ) + { + case IS_STRING : + return ::IsNumeric( stringValue.c_str() ); + break; + case IS_FLOAT : + case IS_INTEGER : + return true; + break; + } + + return false; +} + +//=========================================================================== +// EventArgDef +//=========================================================================== + +void EventArgDef::Setup( const char *eventName, const char *argName, const char *argType, const char *argRange ) +{ + char scratch[ 256 ]; + const char *ptr; + char *tokptr; + const char *endptr; + int index; + + // set name + name = argName; + + // set optionality + if ( isupper( argType[ 0 ] ) ) + { + optional = true; + } + else + { + optional = false; + } + + // grab the ranges + index = 0; + memset( minRangeDefault, true, sizeof( minRangeDefault ) ); + memset( minRange, 0, sizeof( minRange ) ); + memset( maxRangeDefault, true, sizeof( maxRangeDefault ) ); + memset( maxRange, 0, sizeof( maxRange ) ); + + if ( argRange && argRange[ 0 ] ) + { + ptr = argRange; + while( 1 ) + { + // find opening '[' + tokptr = strchr( ptr, '[' ); + if ( !tokptr ) + { + break; + } + // find closing ']' + endptr = strchr( tokptr, ']' ); + if ( !endptr ) + { + assert( 0 ); + EVENT_WDPrintf( "Argument defintion %s, no matching ']' found for range spec in event %s.\n", name.c_str(), eventName ); + break; + } + // point to the next range + ptr = endptr; + // skip the '[' + tokptr++; + // copy off the range spec + // skip the ']' + strncpy( scratch, tokptr, endptr - tokptr ); + // terminate the range + scratch[ endptr - tokptr ] = 0; + // see if there is one or two parameters here + tokptr = strchr( scratch, ',' ); + if ( !tokptr ) + { + // just one parameter + minRange[ index >> 1 ] = (float)atof( scratch ); + minRangeDefault[ index >> 1 ] = false; + index++; + // skip the second parameter + index++; + } + else if ( tokptr == scratch ) + { + // just second parameter + // skip the first paremeter + index++; + tokptr++; + maxRange[ index >> 1 ] = (float)atof( scratch ); + maxRangeDefault[ index >> 1 ] = false; + index++; + } + else + { + qboolean second; + // one or two parameters + // see if there is anything behind the ',' + if ( strlen( scratch ) > ( tokptr - scratch + 1) ) + second = true; + else + second = false; + // zero out the ',' + *tokptr = 0; + minRange[ index >> 1 ] = (float)atof( scratch ); + minRangeDefault[ index >> 1 ] = false; + index++; + // skip over the nul character + tokptr++; + if ( second ) + { + maxRange[ index >> 1 ] = (float)atof( tokptr ); + maxRangeDefault[ index >> 1 ] = false; + } + index++; + } + } + } + + // figure out the type of variable it is + switch( tolower( argType[ 0 ] ) ) + { + case 'e': + type = IS_ENTITY; + break; + case 'v': + type = IS_VECTOR; + break; + case 'i': + type = IS_INTEGER; + break; + case 'f': + type = IS_FLOAT; + break; + case 's': + type = IS_STRING; + break; + case 'b': + type = IS_BOOLEAN; + break; + } +} + +void EventArgDef::PrintRange( FILE *event_file ) +{ + qboolean integer; + qboolean single; + int numRanges; + int i; + + single = false; + integer = true; + numRanges = 1; + switch( type ) + { + case IS_VECTOR: + integer = false; + numRanges = 3; + break; + case IS_FLOAT: + integer = false; + break; + case IS_STRING: + single = true; + break; + } + for( i = 0; i < numRanges; i++ ) + { + if ( single ) + { + if ( !minRangeDefault[ i ] ) + { + if ( integer ) + { + EV_Print( event_file, "<%d>", ( int )minRange[ i ] ); + } + else + { + EV_Print( event_file, "<%.2f>", minRange[ i ] ); + } + } + } + else + { + // both non-default + if ( !minRangeDefault[ i ] && !maxRangeDefault[ i ] ) + { + if ( integer ) + { + EV_Print( event_file, "<%d...%d>", ( int )minRange[ i ], ( int )maxRange[ i ] ); + } + else + { + EV_Print( event_file, "<%.2f...%.2f>", minRange[ i ], maxRange[ i ] ); + } + } + // max default + else if ( !minRangeDefault[ i ] && maxRangeDefault[ i ] ) + { + if ( integer ) + { + EV_Print( event_file, "<%d...max_integer>", ( int )minRange[ i ] ); + } + else + { + EV_Print( event_file, "<%.2f...max_float>", minRange[ i ] ); + } + } + // min default + else if ( minRangeDefault[ i ] && !maxRangeDefault[ i ] ) + { + if ( integer ) + { + EV_Print( event_file, "", ( int )maxRange[ i ] ); + } + else + { + EV_Print( event_file, "", maxRange[ i ] ); + } + } + } + } +} + +void EventArgDef::PrintArgument( FILE *event_file ) +{ + if ( optional ) + { + EV_Print( event_file, "[ " ); + } + switch( type ) + { + case IS_ENTITY: + EV_Print( event_file, "Entity " ); + break; + case IS_VECTOR: + EV_Print( event_file, "Vector " ); + break; + case IS_INTEGER: + EV_Print( event_file, "Integer " ); + break; + case IS_FLOAT: + EV_Print( event_file, "Float " ); + break; + case IS_STRING: + EV_Print( event_file, "String " ); + break; + case IS_BOOLEAN: + EV_Print( event_file, "Boolean " ); + break; + } + EV_Print( event_file, "%s", name.c_str() ); + + // print the range of the argument + PrintRange( event_file ); + + if ( optional ) + { + EV_Print( event_file, " ]" ); + } +} + +#ifdef GAME_DLL +void EventArgDef::Archive( Archiver &arc ) +{ + int i; + + arc.ArchiveInteger( &type ); + arc.ArchiveString( &name ); + + for ( i = 0; i < 3; i++ ) + arc.ArchiveFloat( &minRange[i] ); + + for ( i = 0; i < 3; i++ ) + arc.ArchiveBoolean( &minRangeDefault[i] ); + + for ( i = 0; i < 3; i++ ) + arc.ArchiveFloat( &maxRange[i] ); + + for ( i = 0; i < 3; i++ ) + arc.ArchiveBoolean( &maxRangeDefault[i] ); + + arc.ArchiveBoolean( &optional ); +} +#endif + +//=========================================================================== +// EventCode +//=========================================================================== + +CLASS_DECLARATION( Class, Event, NULL ) +{ + { NULL, NULL } +}; + +// Free Event Management routines +static Event *RealAllocateEvent( void ) +{ + Event *event; + + event = ( Event * )::new char[ sizeof( Event ) ]; + Event_totalmemallocated += sizeof( Event ); + + return event; +} + +static void RealDeallocateEvent( Event * event ) +{ + ::delete[]( ( void * )event ); + Event_totalmemallocated -= sizeof( Event ); +} + +void * Event::operator new( size_t s ) +{ + Event * newevent; + + assert( sizeof( Event ) == s ); + + // if the list is empty create a new one + if ( LL_Empty( FreeEvents, next, prev ) ) + { + // this is here to let us know that it is happening + assert( 0 ); + newevent = RealAllocateEvent(); + } + else + { + newevent = FreeEvents->next; + LL_Remove( newevent, next, prev ); + numFreeEvents--; + } + + // add it to the active list of events + LL_Add( ActiveEvents, newevent, next, prev ); + + newevent->time = EVENT_time; + newevent->flags = 0; + + return newevent; +} + +void Event::operator delete( void *ptr ) +{ + Event * event; + + event = ( Event * )ptr; + + // clear out any safe pointers we have setup + event->ClearSafePointers(); + + LL_Remove( event, next, prev ); + LL_Add( FreeEvents, event, next, prev ); + numFreeEvents++; +} + +#if defined( GAME_DLL ) +#define INITIALLY_ALLOCATED_EVENTS 6000 +#elif defined ( CGAME_DLL ) +#define INITIALLY_ALLOCATED_EVENTS 512 +#elif defined ( UI_LIB ) +#define INITIALLY_ALLOCATED_EVENTS 192 +#else +#define INITIALLY_ALLOCATED_EVENTS 192 +#endif + +void Event::InitializeEventLists( void ) +{ + Event * newevent; + int i; + + numEvents = 0; + numFreeEvents = 0; + // + // initialize lists + // + LL_Reset( FreeEvents, next, prev ); + LL_Reset( EventQueue, next, prev ); + LL_Reset( ActiveEvents, next, prev ); + + // + // allocate the initial allottment of events + // + for( i = 0; i < INITIALLY_ALLOCATED_EVENTS; i++ ) + { + newevent = RealAllocateEvent(); + LL_Add( FreeEvents, newevent, next, prev ); + numFreeEvents++; + } +} + +void Event::ShutdownEventLists( void ) +{ + Event *event, *next; + + // free queued events + for( event = EventQueue->next; event != EventQueue; event = next ) + { + next = event->next; + delete event; + } + // free active events + for( event = ActiveEvents->next; event != ActiveEvents; event = next ) + { + next = event->next; + delete event; + } + // free free events + for( event = FreeEvents->next; event != FreeEvents; event = next ) + { + next = event->next; + RealDeallocateEvent( event ); + } + + assert( Event_totalmemallocated == 0 ); + + numEvents = 0; + numFreeEvents = 0; + + // + // initialize lists + // + LL_Reset( FreeEvents, next, prev ); + LL_Reset( EventQueue, next, prev ); + LL_Reset( ActiveEvents, next, prev ); +} + +int Event::NumEventCommands( void ) +{ + if ( commandList ) + { + // Add 1 since container gives the inclusive number of objects + return commandList->NumObjects() + 1; + } + + return 0; +} + +int Event::compareEvents( const void *arg1, const void *arg2 ) +{ + int ev1; + int ev2; + + ev1 = *( int * )arg1; + ev2 = *( int * )arg2; + + return stricmp( commandList->ObjectAt( ev1 )->c_str(), commandList->ObjectAt( ev2 )->c_str() ); +} + +void Event::SortEventList( void ) +{ + dirtylist = false; + + if ( sortedList && commandList ) + { + qsort( ( void * )sortedList->AddressOfObjectAt( 1 ), + ( size_t )sortedList->NumObjects(), + sizeof( int ), compareEvents ); + } +} + +int Event::FindEvent( const char *name ) +{ + int eventnum; + int index; + int l; + int r; + int diff; + + assert( name ); + if ( !name ) + { + return 0; + } + + if ( !commandList ) + { + return 0; + } + + if ( dirtylist ) + { + SortEventList(); + } + + numfinds++; + numcompares++; + if ( lastevent && !stricmp( name, commandList->ObjectAt( lastevent )->c_str() ) ) + { + numfirstsearch++; + return lastevent; + } + + l = 1; + r = sortedList->NumObjects(); + while( r >= l ) + { + index = ( l + r ) >> 1; + eventnum = sortedList->ObjectAt( index ); + diff = stricmp( name, commandList->ObjectAt( eventnum )->c_str() ); + numcompares++; + if ( diff < 0 ) + { + r = index - 1; + } + else if ( diff > 0 ) + { + l = index + 1; + } + else + { + lastevent = eventnum; + return eventnum; + } + } + + return 0; +} + +int Event::MapSortedEventIndex( int index ) +{ + return sortedList->ObjectAt( index ); +} + +int Event::FindEvent( const str &name ) +{ + return FindEvent( name.c_str() ); +} + +void Event::ListCommands( const char *mask ) +{ + str name; + int flags; + int eventnum; + int num; + int i; + int n; + int l; + int p; + int hidden; + str text; + + if ( !commandList ) + { + EVENT_WDPrintf( "No events.\n" ); + return; + } + + if ( dirtylist ) + { + SortEventList(); + } + + l = 0; + if ( mask ) + { + l = strlen( mask ); + } + + hidden = 0; + num = 0; + n = sortedList->NumObjects(); + for( i = 1; i <= n; i++ ) + { + eventnum = sortedList->ObjectAt( i ); + name = commandList->ObjectAt( eventnum )->c_str(); + flags = flagList->ObjectAt( eventnum ); + + if ( flags & EV_CODEONLY ) + { + hidden++; + continue; + } + + if ( mask && Q_stricmpn( name.c_str(), mask, l ) ) + { + continue; + } + + num++; + + text = " "; + p = 0; + if ( flags & EV_CONSOLE ) + { + text[ p++ ] = '*'; + } + if ( flags & EV_CHEAT ) + { + text[ p++ ] = 'C'; + } + if ( flags & EV_CACHE ) + { + text[ p++ ] = '%'; + } + + EVENT_Printf( "%4d : %s%s\n", eventnum, text.c_str(), name.c_str() ); + } + + EVENT_Printf( "\n* = console command.\nC = cheat command.\n% = cache command.\n\n" + "Printed %d of %d total commands.\n", num, n - hidden ); + + if ( developer->integer && hidden ) + { + EVENT_Printf( "Suppressed %d commands.\n", hidden ); + } +} + +#ifdef GAME_DLL + +void Event::PrintEvent( Event * event ) +{ + int i; + int n; + Listener * obj; + str text; + + obj = event->GetSourceObject(); + + text = va( "%6.1f:%1d: %s", event->time, event->flags, obj->getClassname() ); + if ( obj->isSubclassOf( Entity ) ) + { + text += va( " (*%d) ", ( ( Entity * )obj )->entnum ); + if ( ( ( Entity * )obj )->Targeted() ) + { + text += va( "'%s'", ( ( Entity * )obj )->TargetName() ); + } + } + else if ( obj->isSubclassOf( CThread ) ) + { + text += va( " #%d:'%s'", ( ( CThread * )obj )->ThreadNum(), ( ( CThread * )obj )->ThreadName() ); + } + + switch( event->GetSource() ) + { + default : + case EV_FROM_CODE : + text += " : Code :"; + break; + + case EV_FROM_SCRIPT : + assert( event->GetThread() ); + if ( event->GetThread() ) + { + text += va( " : %s(%d) :", event->GetThread()->Filename(), event->info.linenumber ); + } + else + { + text += " : NULL :"; + } + break; + + case EV_FROM_CONSOLE : + text += " : Console :"; + break; + } + + text += event->getName(); + n = event->NumArgs(); + for( i = 1; i <= n; i++ ) + { + text += va( " %s", event->GetToken( i ) ); + } + + text += "\n"; + + if ( !g_watch->integer || ( obj->isSubclassOf( Entity ) && ( g_watch->integer == ( ( Entity * )obj )->entnum ) ) ) + { + if ( g_showevents->integer == 2 ) + { + EVENT_DebugPrintf( text.c_str() ); + } + else + { + EVENT_DPrintf( "%s", text.c_str() ); + } + } +} + +#else + +void Event::PrintEvent( Event * event ) +{ + int i; + int n; + str text; + + + text = va( "%6.1f:%1d ", event->time, event->flags ); + + switch( event->GetSource() ) + { + default : + case EV_FROM_CODE : + text += " : Code :"; + break; + + case EV_FROM_CONSOLE : + text += " : Console :"; + break; + } + + text += event->getName(); + n = event->NumArgs(); + for( i = 1; i <= n; i++ ) + { + text += va( " %s", event->GetToken( i ) ); + } + + text += "\n"; + + if ( g_showevents->integer == 2 ) + { + EVENT_DebugPrintf( text.c_str() ); + } + else + { + EVENT_DPrintf( text.c_str() ); + } +} + +#endif + +void Event::PendingEvents( const char *mask ) +{ + Event *event; + int l, num; + + l = 0; + if ( mask ) + { + l = strlen( mask ); + } + + assert( EventQueue ); + assert( EventQueue->next ); + assert( EventQueue->prev ); + + num = 0; + event = EventQueue->next; + while( event != EventQueue ) + { + assert( event ); + assert( event->m_sourceobject ); + + if ( !mask || !Q_stricmpn( event->getName(), mask, l ) ) + { + num++; + Event::PrintEvent( event ); + } + + event = event->next; + } + EVENT_Printf( "%d pending events as of %.2f\n", num, EVENT_time ); +} + +Event *Event::GetEventDef( int num ) +{ + if ( !eventDefList ) + { + return NULL; + } + + assert( ( num > 0 ) && ( num <= eventDefList->NumObjects() ) ); + + if ( ( num <= 0 ) || ( num > eventDefList->NumObjects() ) ) + { + return NULL; + } + + return eventDefList->ObjectAt( num ); +} + +int Event::NumEventDefs( void ) +{ + if ( !eventDefList ) + { + return 0; + } + + return eventDefList->NumObjects(); +} + +void Event::PrintDocumentation( FILE *event_file, qboolean html ) +{ + int i; + int p; + str text; + + if ( !html ) + { + text = " "; + p = 0; + + if ( flags & EV_CONSOLE ) + { + text[ p++ ] = '*'; + } + if ( flags & EV_CHEAT ) + { + text[ p++ ] = 'C'; + } + if ( flags & EV_CACHE ) + { + text[ p++ ] = '%'; + } + } + + if ( html ) + { + EV_Print( event_file, "\n

%s", name ); + } + else + { + if ( text[ 0 ] != ' ' ) + { + EV_Print( event_file, "\n%s:%s", text.c_str(), name ); + } + else + { + EV_Print( event_file, "\n%s %s", text.c_str(), name ); + } + } + + if ( definition ) + { + if ( html ) + { + EV_Print( event_file, "( " ); + } + else + { + EV_Print( event_file, "( " ); + } + for( i = 1; i <= definition->NumObjects(); i++ ) + { + definition->ObjectAt( i ).PrintArgument( event_file ); + if ( i < definition->NumObjects() ) + { + EV_Print( event_file, ", " ); + } + } + if ( html ) + { + EV_Print( event_file, " )
\n" ); + } + else + { + EV_Print( event_file, " )\n" ); + } + } + else + { + if ( html ) + { + EV_Print( event_file, "
\n" ); + } + else + { + EV_Print( event_file, "\n" ); + } + } + if ( documentation ) + { + char new_doc[1024]; + int old_index; + int new_index = 0; + + for ( old_index = 0 ; old_index < strlen ( documentation ) ; old_index++ ) + { + if ( documentation[old_index] == '\n' ) + { + if ( html ) + { + new_doc[new_index ] = '<'; + new_doc[new_index + 1] = 'B'; + new_doc[new_index + 2] = 'R'; + new_doc[new_index + 3] = '>'; + new_doc[new_index + 4] = '\n'; + new_index += 5; + } + else + { + new_doc[new_index ] = '\n'; + new_doc[new_index + 1] = '\t'; + new_doc[new_index + 2] = '\t'; + new_index += 3; + } + } + else + { + new_doc[new_index] = documentation[old_index]; + new_index++; + } + + } + + new_doc[new_index] = 0; + + if ( html ) + { + // EV_Print( event_file, "

%s
\n", new_doc ); + EV_Print( event_file, "
    %s
\n", new_doc ); + } + else + { + EV_Print( event_file, "\t\t- %s\n", new_doc ); + } + } +} + +void Event::PrintEventDocumentation( Event * ePtr, FILE *event_file, qboolean html, int typeFlag ) +{ + int flags; + + flags = ePtr->GetFlags(); + + if ( flags & EV_CODEONLY && ( typeFlag == EVENT_SCRIPT_ONLY || typeFlag == EVENT_TIKI_ONLY ) ) + { + return; + } + + if ( (flags & EV_TIKIONLY) && typeFlag == EVENT_SCRIPT_ONLY ) + return; + + if ( (flags & EV_SCRIPTONLY) && typeFlag == EVENT_TIKI_ONLY ) + return; + + if ( (flags & EV_TIKIONLY) && typeFlag == EVENT_SCRIPT_ONLY ) + return; + + if ( (flags & EV_SCRIPTONLY) && typeFlag == EVENT_TIKI_ONLY ) + return; + + // purposely suppressed + if ( ePtr->name[ 0 ] == '_' ) + { + return; + } + + ePtr->PrintDocumentation( event_file, html ); +} + +void Event::ListDocumentation( const char *mask, qboolean print_to_disk ) +{ + Event * ePtr; + int num; + int i; + int n; + int l; + int flags; + int hidden; + str name; + str text; + FILE *event_file = NULL; + str event_filename; + + if ( !eventDefList ) + { + EVENT_DPrintf( "No events.\n" ); + return; + } + + if ( print_to_disk ) + { + if ( !mask || !mask[0] ) + event_filename = EVENT_FILENAME; + else + event_filename = str ( mask ) + ".txt"; + + event_file = fopen( event_filename.c_str(), "w" ); + + if ( event_file == NULL ) + return; + } + + l = 0; + if ( mask ) + { + l = strlen( mask ); + } + + + EV_Print( event_file, "\nCommand Documentation\n" ); + EV_Print( event_file, "=====================\n" ); + + hidden = 0; + num = 0; + n = eventDefList->NumObjects(); + for( i = 1; i <= n; i++ ) + { + ePtr = eventDefList->ObjectAt( i ); + flags = ePtr->GetFlags(); + name = ePtr->getName(); + + if ( flags & EV_CODEONLY ) + { + hidden++; + continue; + } + + if ( mask && Q_stricmpn( name, mask, l ) ) + { + continue; + } + + num++; + + ePtr->PrintDocumentation( event_file ); + } + + + EV_Print( event_file, "\n* = console command.\nC = cheat command.\n% = cache command.\n\n" + "Printed %d of %d total commands.\n", num, n - hidden ); + + if ( developer->integer && hidden ) + { + EV_Print( event_file, "Suppressed %d commands.\n", hidden ); + } + + if ( event_file != NULL ) + { + EVENT_Printf( "Printed event info to file %s\n", event_filename.c_str() ); + fclose( event_file ); + } +} + +void Event::initCommandList( void ) +{ + int flags; + str *n; + + flags = 0; + commandList = new Container; + + n = new str( "NULL" ); + NullEvent.eventnum = commandList->AddObject( n ); + + flagList = new Container; + flagList->AddObject( flags ); + + sortedList = new Container; + sortedList->AddObject( NullEvent.eventnum ); + + eventDefList = new Container; + + dirtylist = false; + + NullEvent.data = NULL; + NullEvent.info.inuse = 0; + NullEvent.info.source = EV_FROM_CODE; + NullEvent.info.flags = 0; + NullEvent.info.linenumber = 0; +} + +Event::Event() +{ + info.inuse = 0; + info.source = EV_FROM_CODE; + info.flags = 0; + info.linenumber = 0; + threadnum = -1; + eventnum = 0; + data = NULL; + definition = NULL; + name = NULL; + returntype = NULL; + documentation = NULL; +} + +Event::Event( int num ) +{ + if ( !commandList ) + { + initCommandList(); + } + + assert( ( num > 0 ) && ( num <= commandList->NumObjects() ) ); + + if ( ( num <= 0 ) || ( num > commandList->NumObjects() ) ) + { + EVENT_WDPrintf( "Event %d out of range.\n", num ); + num = 0; + name = NULL; + info.flags = 0; + } + else + { + name = commandList->ObjectAt( num )->c_str(); + info.flags = flagList->ObjectAt( num ); + } + + eventnum = num; + data = NULL; + definition = NULL; + returntype = NULL; + documentation = NULL; + info.inuse = 0; + info.source = EV_FROM_CODE; + info.linenumber = 0; + threadnum = -1; +} + +Event::Event( const Event &ev ) +{ + int num; + int i; + + //eventnum = ( int )ev; + eventnum = ev.eventnum; + assert( ( eventnum > 0 ) && ( eventnum <= commandList->NumObjects() ) ); + data = NULL; + definition = NULL; + returntype = NULL; + documentation = NULL; + + name = commandList->ObjectAt( eventnum )->c_str(); + info.inuse = 0; + info.source = ev.info.source; + info.flags = ev.info.flags; + info.linenumber = ev.info.linenumber; + threadnum = ev.threadnum; + + if ( ev.data ) + { + num = ev.data->NumObjects(); + + data = new Container; + data->Resize( num ); + + for( i = 1; i <= num; i++ ) + { + data->AddObject( ev.data->ObjectAt( i ) ); + } + } +} + +Event::Event( Event *ev ) +{ + int num; + int i; + + assert( ev ); + if ( !ev ) + { + EVENT_Error( ERR_DROP, "NULL Event\n" ); + } + + //eventnum = ( int )*ev; + eventnum = ev->eventnum; + assert( ( eventnum > 0 ) && ( eventnum <= commandList->NumObjects() ) ); + data = NULL; + definition = NULL; + returntype = NULL; + documentation = NULL; + name = commandList->ObjectAt( eventnum )->c_str(); + info.inuse = 0; + info.source = ev->info.source; + info.flags = ev->info.flags; + info.linenumber = ev->info.linenumber; + threadnum = ev->threadnum; + if ( ev->data ) + { + num = ev->data->NumObjects(); + + data = new Container; + data->Resize( num ); + + for( i = 1; i <= num; i++ ) + { + data->AddObject( ev->data->ObjectAt( i ) ); + } + } +} + +Event::Event( const char *command, int flags, const char *theFormatspec, const char *theArgument_names, + const char *theDocumentation ) +{ + str *t; + + if ( !commandList ) + { + initCommandList(); + } + + eventnum = FindEvent( command ); + if ( !eventnum ) + { + t = new str( command ); + eventnum = commandList->AddObject( t ); + // check for default flags + if ( flags == EV_DEFAULT ) + { + flags = 0; + } + flagList->AddObject( ( int )flags ); + sortedList->AddObject( eventnum ); + dirtylist = true; + } + + // Use the name stored in the command list in case the string passed in + // is not in static memory. + name = commandList->ObjectAt( eventnum )->c_str(); + + data = NULL; + info.inuse = 0; + info.source = EV_FROM_CODE; + info.linenumber = 0; + threadnum = -1; + formatspec = theFormatspec; + argument_names = theArgument_names; + returntype = NULL; + documentation = theDocumentation; + definition = NULL; + + // If flags have changed, let the user know. It's probably a development bug. + int &flagobj = flagList->ObjectAt( eventnum ); + + // check for default flags + if ( flags == EV_DEFAULT ) + { + flags = flagobj; + } + + //assert( flags == flagobj ); + if ( flags != flagobj ) + { + // Flags not equal. Use combined value. + flagobj |= flags; + } + + info.flags = flagobj; + + // add it to the list for Documentation + // suppress it if it starts with '_' + if ( documentation && ( command[ 0 ] != '_' ) ) + { + eventDefList->AddObject( ( Event * )this ); + } + else + { + // purposely suppressed + if ( command[ 0 ] != '_' ) + { + // empty documentation! + assert( 0 ); + } + } +} + +Event::Event( const char *command ) +{ + if ( !commandList ) + { + initCommandList(); + } + + eventnum = FindEvent( command ); + if ( !eventnum ) + { + EVENT_WDPrintf( "Event '%s' does not exist.\n", command ); + name = NULL; + info.flags = 0; + } + else + { + name = commandList->ObjectAt( eventnum )->c_str(); + info.flags = flagList->ObjectAt( eventnum ); + } + + data = NULL; + definition = NULL; + returntype = NULL; + documentation = NULL; + info.inuse = 0; + info.source = EV_FROM_CODE; + info.linenumber = 0; + threadnum = -1; +} + +Event::Event( const str &command ) +{ + if ( !commandList ) + { + initCommandList(); + } + + eventnum = FindEvent( command ); + if ( !eventnum ) + { + EVENT_WDPrintf( "Event '%s' does not exist.\n", command.c_str() ); + name = NULL; + info.flags = 0; + } + else + { + name = commandList->ObjectAt( eventnum )->c_str(); + info.flags = flagList->ObjectAt( eventnum ); + } + + data = NULL; + definition = NULL; + returntype = NULL; + documentation = NULL; + info.inuse = 0; + info.source = EV_FROM_CODE; + info.linenumber = 0; + threadnum = -1; +} + +Event::~Event() +{ + if ( data ) + { + delete data; + data = NULL; + } + if ( definition ) + { + delete definition; + definition = NULL; + } + + if ( returntype ) + { + delete returntype; + returntype = NULL; + } +} + +void Event::SetupDocumentation( void ) +{ + // setup documentation + if ( formatspec ) + { + if ( argument_names ) + { + char argumentNames[ 256 ]; + str argSpec; + str rangeSpec; + str argName; + EventArgDef argDef; + const char *namePtr; + const char *specPtr; + int specLength, index; + Container argNames; + + specLength = strlen( formatspec ); + specPtr = formatspec; + // + // store off all the names + // + strcpy( argumentNames, argument_names ); + namePtr = strtok( argumentNames, " " ); + while ( namePtr != NULL ) + { + argNames.AddObject( str( namePtr ) ); + namePtr = strtok( NULL, " " ); + } + + index = 0; + + // + // Create the return type, if any + // + if ( specLength && ( *specPtr == '@' ) ) + { + if ( specLength < 2 ) + { + assert( 0 ); + Error( "Return type indicated, but no return type specified in event %s\n", name ); + } + + // clear the rangeSpec + rangeSpec = ""; + + // get the argSpec + argSpec = ""; + argSpec += specPtr[ 1 ]; + specPtr += 2; + specLength -= 2; + + if ( index < argNames.NumObjects() ) + { + argName = argNames.ObjectAt( index + 1 ); + + returntype = new EventArgDef; + returntype->Setup( name, argName, argSpec, rangeSpec ); + } + else + { + assert( 0 ); + Error( "More format specifiers than argument names for event %s\n", name ); + } + + index++; + } + + // + // create the definition container + // + definition = new Container; + definition->Resize( argNames.NumObjects() ); + + // go throught he formatspec + while( specLength ) + { + // clear the rangeSpec + rangeSpec = ""; + // get the argSpec + argSpec = ""; + argSpec += *specPtr; + specPtr++; + specLength--; + // see if there is a range specified + while ( *specPtr == '[' ) + { + // add in all the characters until NULL or ']' + while( specLength && ( *specPtr != ']' ) ) + { + rangeSpec += *specPtr; + specPtr++; + specLength--; + } + if ( specLength && ( *specPtr == ']' ) ) + { + rangeSpec += *specPtr; + specPtr++; + specLength--; + } + } + if ( index < argNames.NumObjects() ) + { + argName = argNames.ObjectAt( index + 1 ); + argDef.Setup( name, argName, argSpec, rangeSpec ); + definition->AddObject( argDef ); + } + else + { + assert( 0 ); + Error( "More format specifiers than argument names for event %s\n", name ); + } + index++; + } + if ( index < argNames.NumObjects() ) + { + assert( 0 ); + Error( "More argument names than format specifiers for event %s\n", name ); + } + } + } +} + +void Event::DeleteDocumentation( void ) +{ + // setup documentation + if ( formatspec ) + { + if ( argument_names ) + { + definition->FreeObjectList(); + delete definition; + definition = NULL; + } + } + + if ( returntype ) + { + delete returntype; + returntype = NULL; + } +} + +#ifdef GAME_DLL + +void Event::SetThread( CThread *thread ) +{ + if ( thread ) + { + threadnum = thread->ThreadNum(); + } + else + { + threadnum = -1; + } +} + +CThread *Event::GetThread( void ) +{ + if ( threadnum != -1 ) + { + return Director.GetThread( threadnum ); + } + + return NULL; +} + +void Event::ReturnString( const char *text ) +{ + CThread *thread; + + thread = GetThread(); + if ( thread ) + { + thread->program->setString( OFS_RETURN, text ); + } +} + +void Event::ReturnFloat( float value ) +{ + CThread *thread; + + thread = GetThread(); + if ( thread ) + { + thread->program->setFloat( OFS_RETURN, value ); + } +} + +void Event::ReturnInteger( int value ) +{ + CThread *thread; + + thread = GetThread(); + if ( thread ) + { + thread->program->setInteger( OFS_RETURN, value ); + } +} + +void Event::ReturnVector( const Vector &vec ) +{ + CThread *thread; + + thread = GetThread(); + if ( thread ) + { + thread->program->setVector( OFS_RETURN, vec ); + } +} + +void Event::ReturnEntity( Entity *ent ) +{ + CThread *thread; + + thread = GetThread(); + if ( thread ) + { + thread->program->setEntity( OFS_RETURN, ent ); + } +} + +#endif + +void Event::Error( const char *fmt, ... ) +{ + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + switch( GetSource() ) + { + default : + case EV_FROM_CODE : +#if defined( GAME_DLL ) + EVENT_WDPrintf( "Game: '%s' : %s\n", getName(), text ); +#elif defined( CGAME_DLL ) + EVENT_WDPrintf( "CGame: '%s' : %s\n", getName(), text ); +#elif defined( UI_LIB ) + EVENT_WDPrintf( "UI_Lib: '%s' : %s\n", getName(), text ); +#else + EVENT_WDPrintf( "Client: '%s' : %s\n", getName(), text ); +#endif + break; +#ifdef GAME_DLL + case EV_FROM_SCRIPT : + { + CThread *thread = GetThread(); + const char *filename = "Dead script"; + if ( thread ) + { + filename = thread->Filename(); + } + EVENT_DPrintf( "%s(%d): '%s' :\n%s\n", filename, info.linenumber, getName(), text ); + } + break; + + case EV_FROM_CONSOLE : + gi.SendServerCommand( GetConsoleEdict()-g_entities, "print \"Console: '%s' : %s\n\"", getName(), text ); + break; +#endif + } +} + +qboolean Event::IsVectorAt( int pos ) +{ + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return false; + } + + return data->ObjectAt( pos ).IsVector( *this ); +} + +#ifdef GAME_DLL +qboolean Event::IsEntityAt( int pos ) +{ + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return false; + } + + return data->ObjectAt( pos ).IsEntity( *this ); +} +#endif + +qboolean Event::IsNumericAt( int pos ) +{ + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return false; + } + + return data->ObjectAt( pos ).IsNumeric( *this ); +} + +#ifdef GAME_DLL +void Event::Archive( Archiver &arc ) +{ + str name; + int num; + int i; + EventVar var; + EventArgDef def; + + if ( arc.Saving() ) + { + name = getName(); + } + arc.ArchiveString( &name ); + if ( arc.Loading() ) + { + if ( data ) + { + delete data; + data = NULL; + } + *this = Event( name ); + } + + arc.ArchiveRaw( &info, sizeof( info ) ); + arc.ArchiveInteger( &threadnum ); + arc.ArchiveShort( &anim_number ); + arc.ArchiveShort( &anim_frame ); + arc.ArchiveSafePointer( &m_sourceobject ); + arc.ArchiveFloat( &time ); + arc.ArchiveInteger( &flags ); + + // save data + + if ( arc.Saving() ) + { + if ( !data ) + { + num = 0; + } + else + { + num = data->NumObjects(); + } + } + arc.ArchiveInteger( &num ); + if ( arc.Loading() && num ) + { + data = new Container; + data->Resize( num ); + } + + for( i = 1; i <= num; i++ ) + { + if ( arc.Saving() ) + { + var = data->ObjectAt( i ); + } + var.Archive( arc ); + if ( arc.Loading() ) + { + data->AddObject( var ); + } + } + + // save definition + if ( arc.Saving() ) + { + if ( !definition ) + { + num = 0; + } + else + { + num = definition->NumObjects(); + } + } + arc.ArchiveInteger( &num ); + if ( arc.Loading() && num ) + { + definition = new Container; + definition->Resize( num ); + } + + for( i = 1; i <= num; i++ ) + { + if ( arc.Saving() ) + { + def = definition->ObjectAt( i ); + } + def.Archive( arc ); + if ( arc.Loading() ) + { + definition->AddObject( def ); + } + } + + // save return type + if ( arc.Saving() ) + { + if ( returntype ) + num = 1; + else + num = 0; + } + + arc.ArchiveInteger( &num ); + + if ( num == 1 ) + { + if ( arc.Loading() ) + returntype = new EventArgDef; + + arc.ArchiveObject( ( Class * )&returntype ); + } +} + +#endif + +CLASS_DECLARATION( Class, Listener, NULL ) +{ + { &EV_Remove, &Listener::Remove }, + { &EV_ScriptRemove, &Listener::ScriptRemove }, + + { NULL, NULL } +}; + +void Listener::Remove( Event *e ) +{ + delete this; +} + +void Listener::ScriptRemove( Event *e ) +{ + // Forces the remove to be done at a safe time + PostEvent( EV_Remove, 0.0f ); +} + +qboolean Listener::EventPending( Event &ev ) +{ + Event *event; + int eventnum; + + assert( EventQueue ); + assert( EventQueue->next ); + + event = EventQueue->next; + + eventnum = ( int )ev; + while( event != EventQueue ) + { + if ( ( event->m_sourceobject.Pointer() == this ) && ( (int)*event == eventnum ) ) + { + return true; + } + event = event->next; + } + + return false; +} + +inline qboolean Listener::CheckEventFlags( Event *event ) +{ +#ifdef GAME_DLL + // Special handling of console events + if ( event->GetSource() == EV_FROM_CONSOLE ) + { + if ( !( event->info.flags & (EV_CONSOLE|EV_CHEAT) ) ) + { + if ( isSubclassOf( Entity ) ) + { + Entity *ent; + + ent = ( Entity * )this; + gi.SendServerCommand( ent->edict-g_entities, "print \"Command not available from console.\n\"" ); + } + + // don't process + return false; + } + + // don't allow console cheats unless the server says it's ok. + + if ( + ( event->info.flags & EV_CHEAT ) && + //( multiplayerManager.inMultiplayer() ) && + ( !sv_cheats->integer ) + ) + { + if ( isSubclassOf( Entity ) ) + { + Entity *ent; + + ent = ( Entity * )this; + gi.SendServerCommand( ent->edict-g_entities, "print \"You must run the server with '+set cheats 1' to enable this command.\n\"" ); + } + + // don't process + return false; + } + } +#endif + + // ok to process + return true; +} + +qboolean Listener::ProcessEvent( Event *event ) +{ + ClassDef *c; + int ev; + + if ( !Event::EventSystemStarted ) + { + assert( 0 ); + return false; + } + + if(event == NULL) + { + assert ( 0 ); + return false; + } + + // make sure the event has only been used once + if ( event->info.inuse ) + { + EVENT_Error( ERR_DROP, "ProcessEvent : Event '%s' has already been posted.\n", event->getName() ); + } + + if ( g_showevents->integer ) + { + Listener *obj; + + obj = event->GetSourceObject(); + if ( !obj ) + { + event->SetSourceObject( this ); + } + Event::PrintEvent( event ); + } + + ev = ( int )*event; + c = this->classinfo(); + if ( ev >= c->numEvents ) + { + event->Error( "Event out of response range for class '%s'. Event probably invalid or allocated late.\n", getClassname() ); + return false; + } + + if ( c->responseLookup[ ev ] ) + { + int start; + int end; + + event->info.inuse++; + + if ( !g_timeevents->integer ) + { + // only process the event if we allow it + if ( CheckEventFlags( event ) ) + { + ( this->**c->responseLookup[ ev ] )( event ); + // Very dirty hack to dump event names to a text file + // as they are posted + //FILE *edata = fopen("edataclient.txt", "a"); + //fprintf(edata,"%s\n",event->getName()); + //fclose(edata); + } + } + else + { + start = EVENT_realtime; + + // only process the event if we allow it + if ( CheckEventFlags( event ) ) + { + ( this->**c->responseLookup[ ev ] )( event ); + } + + end = EVENT_realtime; + + if ( end - start >= g_timeevents->integer ) + { +#ifdef GAME_DLL + if ( event != EV_Remove && this->isSubclassOf( Entity ) ) + { + Entity *ent = (Entity *)this; + + EVENT_DPrintf( "(%d) ", ent->entnum ); + } +#endif + + EVENT_DebugPrintf( "'%s' : %d\n", event->getName(), end - start ); + } + } + + // Prevent an event from being freed twice, in case it's been re-used + event->info.inuse--; + if ( !event->info.inuse ) + { + delete event; + } + else + { + EVENT_Error( ERR_DROP, "ProcessEvent : Event '%s' is still in use after being processed.\n", event->getName() ); + } + + return true; + } + + if ( !event->info.inuse ) + { + delete event; + } + else + { + EVENT_Error( ERR_DROP, "ProcessEvent : Event '%s' is still in use after being processed.\n", event->getName() ); + } + + return false; +} + +void Listener::PostEvent( Event *ev, float time, int flags ) +{ + int evnum; + ClassDef *c; + Event *event; + +#ifdef GAME_DLL + if ( LoadingSavegame ) + { + // while technically not bad, if we got here we have an event that is being posted while loading which is a bad thing + assert( 0 ); + if ( !ev->info.inuse ) + { + delete ev; + } + else + { + EVENT_Error( ERR_DROP, "PostEvent : Event '%s' is still in use during loading.\n", ev->getName() ); + } + + return; + } +#endif + + if ( !Event::EventSystemStarted ) + { + assert( 0 ); + return; + } + + evnum = ( int )*ev; + c = this->classinfo(); + if ( evnum >= c->numEvents ) + { + ev->Error( "Event out of response range for class '%s'. Event probably invalid or allocated late.\n", getClassname() ); + return; + } + + if ( !c->responseLookup[ evnum ] ) + { + // we don't need it so lets delete it + delete ev; + return; + } + + LL_Remove( ev, next, prev ); + + ev->SetSourceObject( this ); + ev->time = EVENT_time + time; + ev->flags = flags; + + event = EventQueue->next; + while( ( event != EventQueue ) && ( ev->time >= event->time ) ) + { + event = event->next; + } + + LL_Add( event, ev, next, prev ); + numEvents++; +} + +qboolean Listener::PostponeEvent( Event &ev, float time ) +{ + Event *event; + Event *node; + int eventnum; + + assert( EventQueue ); + assert( EventQueue->next ); + + eventnum = ( int )ev; + + event = EventQueue->next; + while( event != EventQueue ) + { + if ( ( event->GetSourceObject() == this ) && ( ( int )*event == eventnum ) ) + { + event->time += time; + + node = event->next; + while( ( node != EventQueue ) && ( event->time >= node->time ) ) + { + node = node->next; + } + + LL_Remove( event, next, prev ); + LL_Add( node, event, next, prev ); + + return true; + } + event = event->next; + } + + return false; +} + +bool Listener::CancelEventsOfType( Event *ev ) +{ + Event *event; + Event *next; + int eventnum; + bool found = false; + + assert( EventQueue ); + assert( EventQueue->next ); + + event = EventQueue->next; + + eventnum = (int)*ev; + while( event != EventQueue ) + { + next = event->next; + if ( ( event->GetSourceObject() == this ) && ( (int)*event == eventnum ) ) + { + LL_Remove( event, next, prev ); + numEvents--; + delete event; + found = true; + } + event = next; + } + + return found; +} + +void Listener::CancelFlaggedEvents( int flags ) +{ + Event *event; + Event *next; + + assert( EventQueue ); + assert( EventQueue->next ); + + event = EventQueue->next; + + while( event != EventQueue ) + { + next = event->next; + if ( ( event->GetSourceObject() == this ) && ( event->flags & flags ) ) + { + LL_Remove( event, next, prev ); + numEvents--; + delete event; + } + event = next; + } +} + +void Listener::CancelPendingEvents( void ) +{ + Event *event; + Event *next; + + assert( EventQueue ); + assert( EventQueue->next ); + + event = EventQueue->next; + + while( event != EventQueue ) + { + next = event->next; + if ( event->GetSourceObject() == this ) + { + LL_Remove( event, next, prev ); + numEvents--; + delete event; + } + event = next; + } +} + +qboolean Listener::ProcessPendingEvents( void ) +{ + Event *event; + qboolean processedEvents; + float t; + + assert( EventQueue ); + assert( EventQueue->next ); + assert( EventQueue->prev ); + + processedEvents = false; + + t = EVENT_time + 0.001f; + + event = EventQueue->next; + while( event != EventQueue ) + { + Listener * obj; + + assert( event ); + + obj = event->GetSourceObject(); + + if ( event->time > t ) + { + break; + } + + if ( obj != this ) + { + // traverse normally + event = event->next; + } + else + { + // the event is removed from its list and temporarily added to the active list + LL_Remove( event, next, prev ); + numEvents--; + LL_Add( ActiveEvents, event, next, prev ); + + + // ProcessEvent will dispose of this event when it is done + obj->ProcessEvent( event ); + + // start over, since can't guarantee that we didn't process any previous or following events + event = EventQueue->next; + + processedEvents = true; + } + } + + return processedEvents; +} + +Listener::~Listener() +{ + if ( Event::EventSystemStarted ) + CancelPendingEvents(); +} + +void L_ClearEventList( void ) +{ + Event *event; + + // go through active and event queue lists + while( !LL_Empty( EventQueue, next, prev ) ) + { + event = EventQueue->next; + numEvents--; + LL_Remove( event, next, prev ); + delete event; + } + + while( !LL_Empty( ActiveEvents, next, prev ) ) + { + event = ActiveEvents->next; + LL_Remove( event, next, prev ); + delete event; + } + + numEvents = 0; +} + +void L_ProcessPendingEvents( void ) +{ + Event *event; + float t; + int num; + int maxevents; + + assert( EventQueue ); + assert( EventQueue->next ); + assert( EventQueue->prev ); + + maxevents = ( int )g_eventlimit->integer; + + num = 0; + t = EVENT_time + 0.001f; + while( !LL_Empty( EventQueue, next, prev ) ) + { + Listener * obj; + + event = EventQueue->next; + + assert( event ); + + obj = event->GetSourceObject(); + + assert( obj ); + + if ( event->time > t ) + { + break; + } + + // the event is removed from its list and temporarily added to the active list + LL_Remove( event, next, prev ); + numEvents--; + LL_Add( ActiveEvents, event, next, prev ); + + + // ProcessEvent will dispose of this event when it is done + obj->ProcessEvent( event ); + + // Don't allow ourselves to stay in here too long. An abnormally high number + // of events being processed is evidence of an infinite loop of events. + num++; + if ( num > maxevents ) + { + EVENT_WPrintf( "Event overflow. Possible infinite loop in script. " + "Enable g_showevents to trace. Aborting frame.\n" ); + break; + } + } + + if ( g_eventstats->integer ) + { + if ( g_eventstats->integer == 1 ) + { + EVENT_Printf( "finds %d, searches %d, num first search %d, avg %f\n", + Event::numfinds, Event::numcompares, Event::numfirstsearch, + ( float )Event::numcompares / ( float )Event::numfinds ); + } + else if ( g_eventstats->integer == 2 ) + { + EVENT_Printf( "pending %5d free %5d active+pending %4d\n", + numEvents, numFreeEvents, numFreeEvents + numEvents ); + } + } +} + +#ifdef GAME_DLL + +void Event::SetConsoleEdict( const gentity_t *consoleedict ) +{ + // linenumber does double duty in the case of the console commands + if ( consoleedict ) + { + info.linenumber = consoleedict->s.number; + } + else + { + // default to player 1 + info.linenumber = 0; + } +} + +gentity_t *Event::GetConsoleEdict( void ) +{ + // linenumber does double duty in the case of the console commands + if ( ( info.source != EV_FROM_CONSOLE ) || ( info.linenumber >= game.maxclients ) ) + { + // default to player 1 for release + return &g_entities[ 0 ]; + } + + return &g_entities[ info.linenumber ]; +} + +void L_ArchiveEvents( Archiver &arc ) +{ + Event *event; + int num; + + assert( EventQueue ); + assert( EventQueue->next ); + assert( EventQueue->prev ); + + num = 0; + for( event = EventQueue->next; event != EventQueue; event = event->next ) + { + Listener * obj; + + assert( event ); + + obj = event->GetSourceObject(); + + assert( obj ); + + if ( obj->isSubclassOf( Entity ) && + ( ( ( Entity * )obj )->flags & FL_DONTSAVE ) ) + { + continue; + } + + num++; + } + + arc.ArchiveInteger( &num ); + for( event = EventQueue->next; event != EventQueue; event = event->next ) + { + Listener * obj; + + assert( event ); + + obj = event->GetSourceObject(); + + assert( obj ); + + if ( obj->isSubclassOf( Entity ) && + ( ( ( Entity * )obj )->flags & FL_DONTSAVE ) ) + { + continue; + } + + arc.ArchiveEvent( event ); + arc.ArchiveFloat( &event->time ); + arc.ArchiveInteger( &event->flags ); + } +} + +void L_UnarchiveEvents( Archiver &arc ) +{ + Event *e; + int i; + + // the FreeEvents list would already be allocated at this point + // clear out any events that may exist + L_ClearEventList(); + + arc.ArchiveInteger( &numEvents ); + for( i = 0; i < numEvents; i++ ) + { + e = new Event(); + LL_Remove( e, next, prev ); + arc.ArchiveEvent( e ); + arc.ArchiveFloat( &e->time ); + arc.ArchiveInteger( &e->flags ); + + LL_Add( EventQueue, e, next, prev ); + } +} + +#endif + +void Event::InitializeDocumentation( void ) +{ + Event * ePtr; + int i; + + for( i = 1; i <= eventDefList->NumObjects(); i++ ) + { + ePtr = eventDefList->ObjectAt( i ); + ePtr->SetupDocumentation(); + } +} + +void Event::ShutdownDocumentation( void ) +{ + Event * ePtr; + int i; + + for( i = 1; i <= eventDefList->NumObjects(); i++ ) + { + ePtr = eventDefList->ObjectAt( i ); + ePtr->DeleteDocumentation(); + } +} + +void L_InitEvents( void ) +{ + if ( Event::EventSystemStarted ) + { + assert( 0 ); + L_ShutdownEvents(); + } + + Event::lastevent = 0; + Event::numfinds = 0; + Event::numcompares = 0; + Event::numfirstsearch = 0; + Event_totalmemallocated = 0; + +#if defined( GAME_DLL ) + g_showevents = gi.cvar ( "g_showevents", "0", 0 ); + g_eventlimit = gi.cvar ( "g_eventlimit", "5000", 0 ); + g_timeevents = gi.cvar ( "g_timeevents", "0", 0 ); + g_watch = gi.cvar ( "g_watch", "0", 0 ); + g_eventstats = gi.cvar ( "g_eventstats", "0", 0 ); +#elif defined( CGAME_DLL ) + g_showevents = cgi.Cvar_Get ( "cg_showevents", "0", 0 ); + g_eventlimit = cgi.Cvar_Get ( "cg_eventlimit", "500", 0 ); + g_timeevents = cgi.Cvar_Get ( "cg_timeevents", "0", 0 ); + g_eventstats = cgi.Cvar_Get ( "cg_eventstats", "0", 0 ); +#else + g_showevents = Cvar_Get ( "cl_showevents", "0", 0 ); + g_eventlimit = Cvar_Get ( "cl_eventlimit", "500", 0 ); + g_timeevents = Cvar_Get ( "cl_timeevents", "0", 0 ); + g_eventstats = Cvar_Get ( "cl_eventstats", "0", 0 ); +#endif + + BuildEventResponses(); + + Event::InitializeEventLists(); + + L_ClearEventList(); + + // setup the documentation on all the events + Event::InitializeDocumentation(); + + // Sort the list before we go on since we won't be adding any more events + Event::SortEventList(); + + // the event system has started + Event::EventSystemStarted = true; +} + +void L_ShutdownEvents( void ) +{ + int i; + + if ( !Event::EventSystemStarted ) + return; + + Event::ShutdownEventLists(); + + // deletes all the documentation + Event::ShutdownDocumentation(); + + if ( Event::commandList ) + { + for ( i=Event::commandList->NumObjects(); i>0; i-- ) + { + str *s; + + s = Event::commandList->ObjectAt( i ); + assert( s ); + if ( s ) + delete s; + } + delete Event::commandList; + Event::commandList = NULL; + } + + if ( Event::eventDefList ) + { + delete Event::eventDefList; + Event::eventDefList = NULL; + } + + if ( Event::flagList ) + { + delete Event::flagList; + Event::flagList = NULL; + } + + if ( Event::sortedList ) + { + delete Event::sortedList; + Event::sortedList = NULL; + } + + // say it is now shutdown + Event::EventSystemStarted = false; +} diff --git a/dlls/game/listener.h b/dlls/game/listener.h new file mode 100644 index 0000000..778e423 --- /dev/null +++ b/dlls/game/listener.h @@ -0,0 +1,1411 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/listener.h $ +// $Revision:: 26 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// +// WARNING: This file is shared between game, cgame and possibly the user interface. +// It is instanced in each one of these directories because of the way that SourceSafe works. +// + +// +// this has to be placed in front of the __LISTENER_H__ +// if it is not, listener.cpp will not compile +// +#if defined( GAME_DLL ) +// +// game dll specific defines +// +#include "g_local.h" + +#endif + +#ifndef __LISTENER_H__ +#define __LISTENER_H__ + +#if defined( GAME_DLL ) +// +// game dll specific defines +// +#define EVENT_DebugPrintf gi.DebugPrintf +#define EVENT_DPrintf gi.DPrintf +#define EVENT_Printf gi.Printf +#define EVENT_WDPrintf gi.WDPrintf +#define EVENT_WPrintf gi.WPrintf +#define EVENT_time level.time +#define EVENT_realtime gi.Milliseconds() +#define EVENT_Error gi.Error + +#define EVENT_FILENAME "events.txt" + +class Entity; +class CThread; +class Archiver; + +#elif defined ( CGAME_DLL ) +// +// cgame dll specific defines +// +#include +#include "vector.h" +#include "str.h" +#include + +#define EVENT_DebugPrintf cgi.DebugPrintf +#define EVENT_DPrintf cgi.DPrintf +#define EVENT_Printf cgi.Printf +#define EVENT_WDPrintf cgi.WDPrintf +#define EVENT_WPrintf cgi.WPrintf +#define EVENT_time ( ( ( float )cg.time / 1000.0f ) ) +#define EVENT_realtime cgi.Milliseconds() +#define EVENT_Error cgi.Error + +#define EVENT_FILENAME "cg_events.txt" + +#elif defined ( UI_LIB ) + +#include +#include "vector.h" +#include "str.h" +#include +#include "ui_local.h" + +#define EVENT_DebugPrintf Com_DebugPrintf +#define EVENT_DPrintf Com_DPrintf +#define EVENT_Printf Com_Printf +#define EVENT_WDPrintf Com_WDPrintf +#define EVENT_WPrintf Com_WPrintf +//#define EVENT_time ( ( ( float )cls.realtime / 1000.0f ) ) +//#define EVENT_realtime Sys_Milliseconds() +#define EVENT_time ( ( ( float )cls.unscaledTime / 1000.0f ) ) +#define EVENT_realtime ( cls.unscaledTime ) +#define EVENT_Error Com_Error + +#define EVENT_FILENAME "ui_events.txt" + +#else + +// +// client specific defines +// +#include +#include "vector.h" +#include "str.h" +#include + +#define EVENT_DebugPrintf Com_DebugPrintf +#define EVENT_DPrintf Com_DPrintf +#define EVENT_Printf Com_Printf +#define EVENT_WDPrintf Com_WDPrintf +#define EVENT_WPrintf Com_WPrintf + +/** + * EVENT_time looks like it should be an obsolute time. Possibly the time + * from when the game started or when the level started. + * EVENT_time used to be set to cls.unscaledTime. The problem is that on a dedicated linux + * server, cls is not defined nor is it needed. So Sys_Milliseconds replaces this, but I am not + * sure if this is the correct time to use since Sys_Milliseconds returns the real time, not the + * absolute time. + */ +#define EVENT_time ( ( ( float )Sys_Milliseconds() / 1000.0f ) ) +#define EVENT_realtime Sys_Milliseconds() +#define EVENT_Error Com_Error + +#define EVENT_FILENAME "cl_events.txt" +#endif + +#include "class.h" +#include "container.h" + +typedef enum + { + EV_FROM_CODE, + EV_FROM_CONSOLE, + EV_FROM_SCRIPT, + EV_FROM_ANIMATION + } eventsource_t; + +// Posted Event Flags +#define EVENT_LEGS_ANIM (1<<0) // this event is associated with an animation for the legs +#define EVENT_TORSO_ANIM (1<<1) // this event is associated with an animation for the torso +#define EVENT_DIALOG_ANIM (1<<2) // this event is associated with an animation for dialog lip syncing + + +// Event flags +#define EV_CONSOLE (1<<0) // Allow entry from console +#define EV_CHEAT (1<<1) // Only allow entry from console if cheats are enabled +#define EV_CODEONLY (1<<2) // Hide from eventlist +#define EV_CACHE (1<<3) // This event is used to cache data in +#define EV_TIKIONLY (1<<4) // This command only applies to TIKI files +#define EV_SCRIPTONLY (1<<5) // This command only applies to SCRIPT files + +#define EV_DEFAULT -1 // default flags + +#define INUSE_BITS 2 +#define MAX_EVENT_USE ( ( 1 << INUSE_BITS ) - 1 ) + +typedef enum + { + IS_STRING, + IS_VECTOR, + IS_BOOLEAN, + IS_INTEGER, + IS_FLOAT, + IS_ENTITY + } vartype; + +#define DIRTY_STRING ( 1 << 0 ) +#define DIRTY_VECTOR ( 1 << 1 ) +#define DIRTY_INTEGER ( 1 << 2 ) +#define DIRTY_FLOAT ( 1 << 3 ) + +#define DIRTY_ALL ( 0x7fff ) + +class EventVar : public Class + { + private: + short type; + short dirtyFlags; + str stringValue; + Vector vectorValue; + int intValue; + float floatValue; + + public: + + EventVar() + { + type = IS_INTEGER; + dirtyFlags = DIRTY_ALL; + intValue = 0; + floatValue = 0; + }; + + EventVar( const EventVar &ev ) + { + type = ev.type; + dirtyFlags = ev.dirtyFlags; + intValue = ev.intValue; + floatValue = ev.floatValue; + }; + + EventVar( const char *text ); + EventVar( const str &text ); + + EventVar( int val ) + { + type = IS_INTEGER; + dirtyFlags = DIRTY_ALL & ~DIRTY_INTEGER; + intValue = val; + floatValue = 0; + }; + + EventVar( float val ) + { + type = IS_FLOAT; + dirtyFlags = DIRTY_ALL & ~DIRTY_FLOAT; + intValue = 0; + floatValue = val; + }; + + EventVar( const Vector &vec ) + { + type = IS_VECTOR; + dirtyFlags = DIRTY_ALL & ~DIRTY_VECTOR; + vectorValue = vec; + intValue = 0; + floatValue = 0; + }; + +#ifdef GAME_DLL + EventVar( const Entity *ent ); +#endif + + const char *GetToken( Event &ev ); + const char *GetString( Event &ev ); + qboolean GetBoolean( Event &ev ); + int GetInteger( Event &ev ); + float GetFloat( Event &ev ); + Vector GetVector( Event &ev ); +#ifdef GAME_DLL + Entity *GetEntity( Event &ev ); + qboolean IsEntity( Event &ev ); +#endif + + void SetString( const char *text ); + + qboolean IsVector( Event &ev ); + qboolean IsNumeric( Event &ev ); + +#ifdef GAME_DLL + void Archive( Archiver &arc ); +#endif + }; + + +class EventArgDef : public Class + { + private: + int type; + str name; + float minRange[ 3 ]; + qboolean minRangeDefault[ 3 ]; + float maxRange[ 3 ]; + qboolean maxRangeDefault[ 3 ]; + qboolean optional; + public: + + EventArgDef() + { + type = IS_INTEGER; + //name = "undefined"; + optional = false; + }; + void Setup( const char * eventName, const char *argName, const char *argType, const char *argRange ); + void PrintArgument( FILE *event_file = NULL ); + void PrintRange( FILE *event_file = NULL ); + int getType( void ); + const char *getName( void ); + qboolean isOptional( void ); + + float GetMinRange(int index) + { + if ( index < 3 ) + return minRange[index]; + return 0.0; + } + + qboolean GetMinRangeDefault(int index) + { + if ( index < 3 ) + return minRangeDefault[index]; + return false; + } + + float GetMaxRange(int index) + { + if ( index < 3 ) + return maxRange[index]; + return 0.0; + } + + qboolean GetMaxRangeDefault(int index) + { + if ( index < 3 ) + return maxRangeDefault[index]; + return false; + } + +#ifdef GAME_DLL + void Archive( Archiver &arc ); +#endif + }; + +inline int EventArgDef::getType + ( + void + ) + + { + return type; + } + +inline const char *EventArgDef::getName + ( + void + ) + + { + return name.c_str(); + } + +inline qboolean EventArgDef::isOptional + ( + void + ) + + { + return optional; + } + +#ifndef GAME_DLL +extern "C" + { + // interface functions + void L_ProcessPendingEvents( void ); + void L_ClearEventList( void ); + void L_InitEvents( void ); + void L_ShutdownEvents( void ); + } +#endif + + +class Listener; +typedef SafePtr ListenerPtr; + + +class Event : public Class + { + private: + friend class Listener; + + typedef struct EventInfo + { + unsigned inuse : INUSE_BITS; // must change MAX_EVENT_USE to reflect maximum number stored here + unsigned source : 2; + unsigned flags : 9; + unsigned linenumber : 19; // linenumber does double duty in the case of the console commands + } EventInfo_t; + + int eventnum; + EventInfo info; + const char *name; + const char *formatspec; + const char *argument_names; + const char *documentation; + Container *data; + Container *definition; + EventArgDef *returntype; + int threadnum; + short anim_number; + short anim_frame; + SafePtr m_sourceobject; + + //Listener *obj; + float time; + int flags; + + friend class Level; + + static void initCommandList( void ); + static void InitializeEventLists( void ); + static void ShutdownEventLists( void ); + static void InitializeDocumentation( void ); + static void ShutdownDocumentation( void ); + + static int numfinds; + static int numfirstsearch; + static int numcompares; + static int lastevent; + static bool EventSystemStarted; + + friend void L_ProcessPendingEvents( void ); + friend void L_ClearEventList( void ); + friend void L_InitEvents( void ); + friend void L_ShutdownEvents( void ); + +#ifdef GAME_DLL + friend void L_ArchiveEvents( Archiver &arc ); + friend void L_UnarchiveEvents( Archiver &arc ); + +#endif + + static Container *commandList; + static Container *flagList; + static Container *sortedList; + static Container *eventDefList; + static qboolean dirtylist; + + static int compareEvents( const void *arg1, const void *arg2 ); + static void SortEventList( void ); + static void PrintEvent( Event *ev ); + + public: + CLASS_PROTOTYPE( Event ); + + Event *next; // next event in the list, used for event recycling + Event *prev; // previous event int the list, used for event recycling + + void * operator new( size_t ); + void operator delete( void * ); + + static int FindEvent( const char *name ); + static int FindEvent( const str &name ); + + static int NumEventCommands( void ); + static void ListCommands( const char *mask = NULL ); + static void ListDocumentation( const char *mask, qboolean print_to_file = false ); + static void PendingEvents( const char *mask = NULL ); + static Event *GetEventDef( int i ); + static int NumEventDefs( void ); + static void PrintEventDocumentation( Event * event, FILE *event_file = NULL, qboolean html = false, int typeFlag = 0 ); + static int MapSortedEventIndex( int index ); + + Event(); + Event( const Event &ev ); + Event( Event *ev ); + Event( int num ); + Event + ( + const char *command, + int flags, + const char *formatspec, + const char *argument_names, + const char *documentation + ); + Event( const char *command ); + Event( const str &command ); + ~Event(); + + void SetupDocumentation( void ); + void DeleteDocumentation( void ); + void PrintDocumentation( FILE *event_file = NULL, qboolean html = false ); + + int getNumArgDefs( void ); + EventArgDef *getArgDef( int num ); + EventArgDef *getReturnType( void ); + + const char *getName( void ); + + void SetSource( eventsource_t source ); + void SetLineNumber( unsigned int linenumber ); + void SetSourceObject( Listener *source ); + Listener *GetSourceObject( void ); + SafePtr *GetSourceObjectPointer( void ); + + eventsource_t GetSource( void ); + int GetLineNumber( void ); + int GetAnimationNumber( void ); + int GetAnimationFrame( void ); + void SetAnimationNumber( int anim ); + void SetAnimationFrame( int frame ); + + int GetFlags( void ); + + void Error( const char *fmt, ... ); + + static Event Find( const char *command ); + static qboolean Exists( const char *command ); + static Event Find( const str &command ); + + Event& printInfo(void); + + friend bool operator==( const Event &a, const Event &b ); + friend bool operator!=( const Event &a, const Event &b ); + void operator=( const Event &ev ); + + operator int(); + operator const char *(); + + int NumArgs( void ); + + qboolean IsVectorAt( int pos ); + qboolean IsEntityAt( int pos ); + qboolean IsNumericAt( int pos ); + + const char *GetToken( int pos ); + const char *GetString( int pos ); + int GetInteger( int pos ); + float GetFloat( int pos ); + Vector GetVector( int pos ); + bool GetBoolean( int pos ); + + void SetToken( int pos, const char *text ); + void SetString( int pos, const str &text ); + void SetInteger( int pos, int value ); + void SetFloat( int pos, float value ); + void SetVector( int pos, const Vector &vector ); + void SetBoolean( int pos, bool value ); + + void AddToken( const char *text ); + void AddTokens( int argc, const char **argv ); + void AddString( const char *text ); + void AddString( const str &text ); + void AddInteger( int val ); + void AddFloat( float val ); + void AddVector( const Vector &vec ); + + const char *GetDocumentation() { return documentation; } + +#ifdef GAME_DLL + void ReturnString( const char *text ); + void ReturnFloat( float value ); + void ReturnInteger( int value ); + void ReturnVector( const Vector &vec ); + void ReturnEntity( Entity *ent ); + + void AddEntity( Entity *ent ); + Entity *GetEntity( int pos ); + + void SetThread( CThread *thread ); + CThread *GetThread( void ); + void SetConsoleEdict( const gentity_t *consoleedict ); + gentity_t *GetConsoleEdict( void ); + + virtual void Archive( Archiver &arc ); +#endif + }; + +extern Event NullEvent; +extern Event EV_Remove; + +class Listener; + +class Listener : public Class + { + private: + void ScriptRemove( Event *e ); + + protected: + qboolean CheckEventFlags( Event *event ); + + public: + CLASS_PROTOTYPE( Listener ); + + ~Listener(); + void Remove( Event *e ); + qboolean ValidEvent( int ev ); + qboolean ValidEvent( Event &e ); + qboolean ValidEvent( const char *name ); + qboolean EventPending( Event &ev ); + qboolean ProcessEvent( Event *event ); + qboolean ProcessEvent( const Event &event ); + void PostEvent( Event *event, float time, int flags = 0 ); + void PostEvent( const Event &event, float time, int flags = 0 ); + qboolean PostponeEvent( Event &event, float time ); + qboolean PostponeEvent( Event *event, float time ); + bool CancelEventsOfType( Event *event ); + bool CancelEventsOfType( Event &event ); + void CancelFlaggedEvents( int flags ); + void CancelPendingEvents( void ); + qboolean ProcessPendingEvents( void ); + }; + +inline void Event::SetSourceObject + ( + Listener *source + ) + + { + m_sourceobject = source; + } + +inline Listener *Event::GetSourceObject + ( + void + ) + + { + return m_sourceobject; + } + +inline SafePtr *Event::GetSourceObjectPointer + ( + void + ) + + { + return &m_sourceobject; + } + +inline qboolean Event::Exists + ( + const char *command + ) + + { + int num; + str c; + + if ( !commandList ) + { + initCommandList(); + } + + c = command; + num = FindEvent( c ); + if ( num ) + { + return true; + } + + return false; + } + + +inline Event Event::Find + ( + const char *command + ) + + { + int num; + str c; + + if ( !commandList ) + { + initCommandList(); + } + + c = command; + num = FindEvent( c ); + if ( num ) + { + Event ev( num ); + return ev; + } + + return NullEvent; + } + +inline Event Event::Find + ( + const str &command + ) + + { + int num; + + if ( !commandList ) + { + initCommandList(); + } + + num = FindEvent( command ); + if ( num ) + { + Event ev( num ); + return ev; + } + + return NullEvent; + } + +inline int Event::getNumArgDefs + ( + void + ) + + { + if ( definition ) + { + return definition->NumObjects(); + } + + return 0; + } + +inline EventArgDef *Event::getArgDef + ( + int num + ) + + { + if ( !definition ) + { + return NULL; + } + + return &definition->ObjectAt( num ); + } + +inline EventArgDef *Event::getReturnType + ( + void + ) + + { + return returntype; + } + +inline void Event::SetSource + ( + eventsource_t source + ) + + { + info.source = ( unsigned )source; + } + +inline void Event::SetLineNumber + ( + unsigned int linenumber + ) + + { + info.linenumber = linenumber; + } + +inline eventsource_t Event::GetSource + ( + void + ) + + { + return ( eventsource_t )info.source; + } + +inline int Event::GetAnimationNumber + ( + void + ) + + { + return anim_number; + } + +inline int Event::GetAnimationFrame + ( + void + ) + + { + return anim_frame; + } + +inline void Event::SetAnimationNumber + ( + int anim + ) + + { + anim_number = anim; + } + +inline void Event::SetAnimationFrame + ( + int frame + ) + + { + anim_frame = frame; + } + +inline int Event::GetLineNumber + ( + void + ) + + { + // linenumber does double duty in the case of the console commands + if ( info.source == EV_FROM_SCRIPT ) + { + return info.linenumber; + } + + return 0; + } + +inline int Event::GetFlags + ( + void + ) + + { + return info.flags; + } + +inline const char *Event::getName + ( + void + ) + + { + assert( name || !eventnum ); + + if ( !name ) + { + return "NULL"; + } + + return name; + } + +inline Event& Event::printInfo + ( + void + ) + + { + EVENT_DPrintf( "event '%s' is number %d\n", getName(), eventnum ); + + return *this; + } + +inline bool operator== + ( + const Event &a, + const Event &b + ) + + { + return a.eventnum == b.eventnum; + } + +inline bool operator!= + ( + const Event &a, + const Event &b + ) + + { + return a.eventnum != b.eventnum; + } + +inline void Event::operator= + ( + const Event &ev + ) + { + eventnum = ev.eventnum; + info = ev.info; + if ( ev.data ) + { + int i; + + data = new Container; + data->Resize( ev.data->NumObjects() ); + for( i = 1; i < ev.data->NumObjects(); i++ ) + { + data->AddObject( ev.data->ObjectAt( i ) ); + } + } + name = ev.name; + definition = NULL; + threadnum = ev.threadnum; + anim_number = ev.anim_number; + anim_frame = ev.anim_frame; + m_sourceobject = ev.m_sourceobject; + time = ev.time; + flags = ev.flags; + } + + +inline Event::operator int() + { + return eventnum; + } + +inline Event::operator const char *() + { + return getName(); + } + +inline int Event::NumArgs + ( + void + ) + + { + if ( !data ) + { + return 0; + } + + return ( data->NumObjects() ); + } + +#ifdef GAME_DLL +inline void Event::AddEntity + ( + Entity *ent + ) + + { + if ( !data ) + { + data = new Container; + data->Resize( 1 ); + } + + + EventVar var( ent ); + data->AddObject( var ); + } +#endif + +inline void Event::AddToken + ( + const char *text + ) + + { + if ( !data ) + { + data = new Container; + data->Resize( 1 ); + } + + EventVar var( text ); + data->AddObject( var ); + } + +inline void Event::AddTokens + ( + int argc, + const char **argv + ) + + { + int i; + + if ( !data ) + { + data = new Container; + data->Resize( argc ); + } + + for( i = 0; i < argc; i++ ) + { + assert( argv[ i ] ); + EventVar var( argv[ i ] ); + data->AddObject( var ); + } + } + +inline void Event::AddString + ( + const char *text + ) + + { + if ( !data ) + { + data = new Container; + data->Resize( 1 ); + } + + EventVar var( text ); + data->AddObject( var ); + } + +inline void Event::AddString + ( + const str &text + ) + + { + if ( !data ) + { + data = new Container; + data->Resize( 1 ); + } + + EventVar var( text ); + data->AddObject( var ); + } + +inline void Event::AddInteger + ( + int val + ) + + { + if ( !data ) + { + data = new Container; + data->Resize( 1 ); + } + + EventVar var( val ); + data->AddObject( var ); + } + +inline void Event::AddFloat + ( + float val + ) + + { + if ( !data ) + { + data = new Container; + data->Resize( 1 ); + } + + EventVar var( val ); + data->AddObject( var ); + } + +inline void Event::AddVector + ( + const Vector &vec + ) + + { + if ( !data ) + { + data = new Container; + data->Resize( 1 ); + } + + EventVar var( vec ); + data->AddObject( var ); + } + +inline const char *Event::GetToken + ( + int pos + ) + + { + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return ""; + } + + return data->ObjectAt( pos ).GetToken( *this ); + } + +inline const char *Event::GetString + ( + int pos + ) + + { + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return ""; + } + + return data->ObjectAt( pos ).GetString( *this ); + } + +inline int Event::GetInteger + ( + int pos + ) + + { + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return 0; + } + + return data->ObjectAt( pos ).GetInteger( *this ); + } + +inline float Event::GetFloat + ( + int pos + ) + + { + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return 0; + } + + return data->ObjectAt( pos ).GetFloat( *this ); + } + +inline Vector Event::GetVector + ( + int pos + ) + + { + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return vec_zero; + } + + return data->ObjectAt( pos ).GetVector( *this ); + } + +inline bool Event::GetBoolean + ( + int pos + ) + + { + int val; + + val = this->GetInteger( pos ); + + return ( val != 0 ) ? true : false; + } + +#ifdef GAME_DLL + +inline Entity *Event::GetEntity + ( + int pos + ) + + { + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return NULL; + } + + return data->ObjectAt( pos ).GetEntity( *this ); + } + +#endif + + +//=============================================================== +// Name: SetToken +// Class: Event +// +// Description: Replaces the argument at the specified position +// with a new string argument. +// +// Parameters: int -- the position to replace ( 1-based ) +// const str& -- the new string argument +// +// Returns: None +// +//=============================================================== +inline void Event::SetToken +( + int pos, + const char *token +) +{ + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return ; + } + + EventVar var( token ); + data->SetObjectAt( pos, var ); +} + + +//=============================================================== +// Name: SetString +// Class: Event +// +// Description: Replaces the argument at the specified position +// with a new string argument. +// +// Parameters: int -- the position to replace ( 1-based ) +// const str& -- the new string argument +// +// Returns: None +// +//=============================================================== +inline void Event::SetString +( + int pos, + const str &text +) +{ + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return ; + } + + EventVar var( text ); + data->SetObjectAt( pos, var ); +} + +//=============================================================== +// Name: SetInteger +// Class: Event +// +// Description: Replaces the argument at the specified position +// with a new integer argument. +// +// Parameters: int pos -- the position to replace ( 1-based ) +// int value -- the new integer argument +// +// Returns: None +// +//=============================================================== +inline void Event::SetInteger +( + int pos, + int value +) +{ + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return ; + } + + EventVar var( value ); + data->SetObjectAt( pos, var ); +} + + +//=============================================================== +// Name: SetFloat +// Class: Event +// +// Description: Replaces the argument at the specified position +// with a new string argument. +// +// Parameters: int -- the position to replace ( 1-based ) +// float -- the new float argument +// +// Returns: None +// +//=============================================================== +inline void Event::SetFloat +( + int pos, + float value +) +{ + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return ; + } + + EventVar var( value ); + data->SetObjectAt( pos, var ); +} + +//=============================================================== +// Name: SetVector +// Class: Event +// +// Description: Replaces the argument at the specified position +// with a new vector argument. +// +// Parameters: int -- the position to replace ( 1-based ) +// const Vector& -- the new vector argument +// +// Returns: None +// +//=============================================================== +inline void Event::SetVector +( + int pos, + const Vector &vector +) +{ + if ( !data || ( pos < 1 ) || ( data->NumObjects() < pos ) ) + { + Error( "Index %d out of range.", pos ); + return ; + } + + EventVar var( vector ); + data->SetObjectAt( pos, var ); +} + + +inline qboolean Listener::ProcessEvent + ( + const Event &event + ) + + { + Event *ev; + + ev = new Event( event ); + return ProcessEvent( ev ); + } + +inline void Listener::PostEvent + ( + const Event &event, + float time, + int flags + ) + + { + Event *ev; + + ev = new Event( event ); + PostEvent( ev, time, flags ); + } + +inline qboolean Listener::PostponeEvent + ( + Event *event, + float time + ) + + { + return PostponeEvent( *event, time ); + } + +inline bool Listener::CancelEventsOfType + ( + Event &event + ) + + { + return CancelEventsOfType( &event ); + } + +inline qboolean Listener::ValidEvent + ( + int ev + ) + + { + ClassDef *c; + + c = this->classinfo(); + assert( ( ev >= 0 ) && ( ev < c->numEvents ) ); + if ( ( ev < 0 ) || ( ev >= c->numEvents ) ) + { + return false; + } + + return ( c->responseLookup[ ev ] != NULL ); + } + +inline qboolean Listener::ValidEvent + ( + Event &e + ) + + { + ClassDef *c; + int ev; + + ev = ( int )e; + + c = this->classinfo(); + assert( ( ev >= 0 ) && ( ev < c->numEvents ) ); + if ( ( ev < 0 ) || ( ev >= c->numEvents ) ) + { + warning( "ValidEvent", "Event '%s' out of response range for class '%s'. " + "Event probably invalid or allocated late.\n", e.getName(), getClassname() ); + return false; + } + + return ( c->responseLookup[ ev ] != NULL ); + } + +inline qboolean Listener::ValidEvent + ( + const char *name + ) + + { + ClassDef *c; + int ev; + + ev = Event::FindEvent( name ); + + c = this->classinfo(); + assert( ( ev >= 0 ) && ( ev < c->numEvents ) ); + if ( ( ev < 0 ) || ( ev >= c->numEvents ) ) + { + warning( "ValidEvent", "Event '%s' out of response range for class '%s'. " + "Event probably invalid or allocated late.\n", name, getClassname() ); + return false; + } + + return ( c->responseLookup[ ev ] != NULL ); + } + +#endif diff --git a/dlls/game/match.h b/dlls/game/match.h new file mode 100644 index 0000000..ea14f3d --- /dev/null +++ b/dlls/game/match.h @@ -0,0 +1,122 @@ +//=========================================================================== +// +// Name: match.h +// Function: match template defines +// Programmer: Mr Elusive +// Last update: +// Tab Size: 4 (real tabs) +// +//=========================================================================== + +// make sure this is the same character as we use in chats in g_cmd.c +#define EC "\x19" + +//match template contexts +#define MTCONTEXT_MISC 2 +#define MTCONTEXT_INITIALTEAMCHAT 4 +#define MTCONTEXT_TIME 8 +#define MTCONTEXT_TEAMMATE 16 +#define MTCONTEXT_ADDRESSEE 32 +#define MTCONTEXT_PATROLKEYAREA 64 +#define MTCONTEXT_REPLYCHAT 128 +#define MTCONTEXT_CTF 256 + +//message types +#define MSG_NEWLEADER 1 //new leader +#define MSG_ENTERGAME 2 //enter game message +#define MSG_HELP 3 //help someone +#define MSG_ACCOMPANY 4 //accompany someone +#define MSG_DEFENDKEYAREA 5 //defend a key area +#define MSG_RUSHBASE 6 //everyone rush to base +#define MSG_GETFLAG 7 //get the enemy flag +#define MSG_STARTTEAMLEADERSHIP 8 //someone wants to become the team leader +#define MSG_STOPTEAMLEADERSHIP 9 //someone wants to stop being the team leader +#define MSG_WHOISTEAMLAEDER 10 //who is the team leader +#define MSG_WAIT 11 //wait for someone +#define MSG_WHATAREYOUDOING 12 //what are you doing? +#define MSG_JOINSUBTEAM 13 //join a sub-team +#define MSG_LEAVESUBTEAM 14 //leave a sub-team +#define MSG_CREATENEWFORMATION 15 //create a new formation +#define MSG_FORMATIONPOSITION 16 //tell someone his/her position in a formation +#define MSG_FORMATIONSPACE 17 //set the formation intervening space +#define MSG_DOFORMATION 18 //form a known formation +#define MSG_DISMISS 19 //dismiss commanded team mates +#define MSG_CAMP 20 //camp somewhere +#define MSG_CHECKPOINT 21 //remember a check point +#define MSG_PATROL 22 //patrol between certain keypoints +#define MSG_LEADTHEWAY 23 //lead the way +#define MSG_GETITEM 24 //get an item +#define MSG_KILL 25 //kill someone +#define MSG_WHEREAREYOU 26 //where is someone +#define MSG_RETURNFLAG 27 //return the flag +#define MSG_WHATISMYCOMMAND 28 //ask the team leader what to do +#define MSG_WHICHTEAM 29 //ask which team a bot is in +#define MSG_TASKPREFERENCE 30 //tell your teamplay task preference +#define MSG_ATTACKENEMYBASE 31 //attack the enemy base +#define MSG_HARVEST 32 //go harvest +#define MSG_SUICIDE 33 //order to suicide +// +#define MSG_ME 100 +#define MSG_EVERYONE 101 +#define MSG_MULTIPLENAMES 102 +#define MSG_NAME 103 +#define MSG_PATROLKEYAREA 104 +#define MSG_MINUTES 105 +#define MSG_SECONDS 106 +#define MSG_FOREVER 107 +#define MSG_FORALONGTIME 108 +#define MSG_FORAWHILE 109 +// +#define MSG_CHATALL 200 +#define MSG_CHATTEAM 201 +#define MSG_CHATTELL 202 +// +#define MSG_CTF 300 //ctf message + +//command sub types +#define ST_SOMEWHERE 0 +#define ST_NEARITEM 1 +#define ST_ADDRESSED 2 +#define ST_METER 4 +#define ST_FEET 8 +#define ST_TIME 16 +#define ST_HERE 32 +#define ST_THERE 64 +#define ST_I 128 +#define ST_MORE 256 +#define ST_BACK 512 +#define ST_REVERSE 1024 +#define ST_SOMEONE 2048 +#define ST_GOTFLAG 4096 +#define ST_CAPTUREDFLAG 8192 +#define ST_RETURNEDFLAG 16384 +#define ST_TEAM 32768 +#define ST_1FCTFGOTFLAG 65535 +//ctf task preferences +#define ST_DEFENDER 1 +#define ST_ATTACKER 2 +#define ST_ROAMER 4 + + +//word replacement variables +#define THE_ENEMY 7 +#define THE_TEAM 7 +//team message variables +#define NETNAME 0 +#define PLACE 1 +#define FLAG 1 +#define MESSAGE 2 +#define ADDRESSEE 2 +#define ITEM 3 +#define TEAMMATE 4 +#define TEAMNAME 4 +#define ENEMY 4 +#define KEYAREA 5 +#define FORMATION 5 +#define POSITION 5 +#define NUMBER 5 +#define TIME 6 +#define NAME 6 +#define MORE 6 + + diff --git a/dlls/game/misc.cpp b/dlls/game/misc.cpp new file mode 100644 index 0000000..a9666bb --- /dev/null +++ b/dlls/game/misc.cpp @@ -0,0 +1,3075 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/misc.cpp $ +// $Revision:: 38 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Basically the big stew pot of the DLLs, or maybe a garbage bin, whichever +// metaphore you prefer. This really should be cleaned up. Anyway, this +// should contain utility functions that could be used by any entity. +// Right now it contains everything from entities that could be in their +// own file to my mother's pot roast recipes. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "trigger.h" +#include "explosion.h" +#include "misc.h" +#include "navigate.h" +#include "specialfx.h" +#include "player.h" +#include "g_utils.h" +#include "weaputils.h" +#include +#include "mp_manager.hpp" + +/*****************************************************************************/ +/*QUAKED detail (0.5 0 1.0) ? + +Used to fake detail brushes, convenient for grouping + +******************************************************************************/ + +/*****************************************************************************/ +/*QUAKED func_group (0.5 0.5 0.5) ? + +Used to group brushes together just for editor convenience. + +******************************************************************************/ + +/*****************************************************************************/ +/*QUAKED func_remove (0.75 0.75 0.75) ? + +Used for lighting and such + +******************************************************************************/ + +CLASS_DECLARATION( Entity, FuncRemove, "func_remove" ) +{ + { NULL, NULL } +}; + +FuncRemove::FuncRemove() +{ + PostEvent( EV_Remove, EV_REMOVE ); +} + +/*****************************************************************************/ +/*QUAKED misc_model (1 0.5 1) (0 0 0) (0 0 0) +"model" arbitrary .tik file to display +******************************************************************************/ + +CLASS_DECLARATION( Entity, MiscModel, "misc_model" ) +{ + { NULL, NULL } +}; + +MiscModel::MiscModel() +{ + PostEvent( EV_Remove, EV_REMOVE ); +} + + +/*****************************************************************************/ +/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) + +Used as a positional target for spotlights, etc. + +******************************************************************************/ + +CLASS_DECLARATION( Listener, InfoNull, "info_null" ) +{ + { NULL, NULL } +}; + +InfoNull::InfoNull() +{ + PostEvent( EV_Remove, EV_REMOVE ); +} + +/*****************************************************************************/ +/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) + +Used as a positional target for lightning. + +******************************************************************************/ + +CLASS_DECLARATION( Entity, InfoNotNull, "info_notnull" ) +{ + { NULL, NULL } +}; + + + +/*****************************************************************************/ +/*QUAKED func_explodingwall (0 0.25 0.5) ? RANDOMANGLES LANDSHATTER NOT_PLAYERS MONSTERS PROJECTILES INVISIBLE ACCUMALATIVE TWOSTAGE + +Blows up on activation or when attacked + +"explosions" number of explosions to spawn ( default 1 ) +"land_angles" The angles you want this piece to\ + orient to when it lands on the ground +"land_radius" The distance of the ground the piece\ + should be when on the ground ( default 16 ) +"anglespeed" Speed at which pieces rotate ( default 100 ) \ + if RANDOMANGLES ( default is 600 ) +"key" The item needed to activate this. (default nothing) +"base_velocity" The speed that the debris will have when triggered. (default 0 0 280) +"random_velocity" The variation of the velocity. x & y will be from -n < X,Y < n and z is 0 <= Z < n. (default 140 140 140) + + +IF RANDOMANGLES is set, object randomly spins while in the air. +IF LANDSHATTER is set, object shatters when it hits the ground. +IF TWOSTAGE is set, object can be shattered once it lands on the ground. +IF ACCUMALATIVE is set, damage is accumlative not threshold +IF INVISIBLE is set, these are invisible and not solid until triggered +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +******************************************************************************/ +#define RANDOMANGLES ( 1 << 0 ) +#define LANDSHATTER ( 1 << 1 ) +#define INVISIBLE ( 1 << 5 ) +#define ACCUMULATIVE ( 1 << 6 ) +#define TWOSTAGE ( 1 << 7 ) + +Event EV_ExplodingWall_StopRotating +( + "stoprotating", + EV_SCRIPTONLY, + NULL, + NULL, + "Stop rotating the wall." +); +Event EV_ExplodingWall_OnGround +( + "checkonground", + EV_CODEONLY, + NULL, + NULL, + "Check if exploding wall is on ground." +); +Event EV_ExplodingWall_AngleSpeed +( + "anglespeed", + EV_SCRIPTONLY, + "f", + "speed", + "Set the angle speed." +); +Event EV_ExplodingWall_LandRadius +( + "land_radius", + EV_SCRIPTONLY, + "f", + "radius", + "Set the land radius." +); +Event EV_ExplodingWall_LandAngles +( + "land_angles", + EV_SCRIPTONLY, + "v", + "angles", + "Set the land angles." +); +Event EV_ExplodingWall_BaseVelocity +( + "base_velocity", + EV_SCRIPTONLY, + "v", + "velocity", + "Set the base velocity." +); +Event EV_ExplodingWall_RandomVelocity +( + "random_velocity", + EV_SCRIPTONLY, + "v", + "velocity", + "Set the amount of random variation of the base velocity." +); +Event EV_ExplodingWall_SetDmg +( + "dmg", + EV_SCRIPTONLY, + "i", + "dmg", + "Set the damage from the exploding wall." +); +Event EV_ExplodingWall_SetExplosions +( + "explosions", + EV_SCRIPTONLY, + "i", + "explosions", + "Set the number of explosions." +); +Event EV_ExplodingWall_Setup +( + "setup", + EV_CODEONLY, + NULL, + NULL, + "Initializes the exploding wall." +); + +CLASS_DECLARATION( Trigger, ExplodingWall, "func_explodingwall" ) +{ + { &EV_ExplodingWall_Setup, &ExplodingWall::Setup }, + { &EV_Trigger_Effect, &ExplodingWall::Explode }, + { &EV_Damage, &ExplodingWall::DamageEvent }, + { &EV_Touch, &ExplodingWall::TouchFunc }, + { &EV_ExplodingWall_StopRotating, &ExplodingWall::StopRotating }, + { &EV_ExplodingWall_OnGround, &ExplodingWall::CheckOnGround }, + { &EV_ExplodingWall_AngleSpeed, &ExplodingWall::AngleSpeed }, + { &EV_ExplodingWall_LandRadius, &ExplodingWall::LandRadius }, + { &EV_ExplodingWall_LandAngles, &ExplodingWall::LandAngles }, + { &EV_ExplodingWall_BaseVelocity, &ExplodingWall::BaseVelocity }, + { &EV_ExplodingWall_RandomVelocity, &ExplodingWall::RandomVelocity }, + { &EV_ExplodingWall_SetDmg, &ExplodingWall::SetDmg }, + { &EV_ExplodingWall_SetExplosions, &ExplodingWall::SetExplosions }, + { &EV_SetGameplayDamage, &ExplodingWall::setDamage }, + + { NULL, NULL } +}; + +//-------------------------------------------------------------- +// +// Name: setDamage +// Class: ExplodingWall +// +// Description: This function acts as a filter to the real function. +// It gets data from the database, and then passes it +// along to the original event. This is here as an attempt +// to sway people into using the database standard instead of +// hardcoded numbers. +// +// Parameters: Event *ev +// str -- The value keyword from the database (low, medium, high, etc). +// +// Returns: None +// +//-------------------------------------------------------------- +void ExplodingWall::setDamage( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasFormula("OffensiveDamage") ) + return; + + str damagestr = ev->GetString( 1 ); + float damagemod = 1.0f; + if ( gpm->getDefine(damagestr) != "" ) + damagemod = (float)atof(gpm->getDefine(damagestr)); + GameplayFormulaData fd(this, 0, 0, ""); + float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod); + Event *newev = new Event(EV_ExplodingWall_SetDmg); + newev->AddInteger((int)finaldamage); + ProcessEvent(newev); +} + +void ExplodingWall::AngleSpeed( Event *ev ) +{ + angle_speed = ev->GetFloat( 1 ); +} + +void ExplodingWall::LandRadius( Event *ev ) +{ + land_radius = ev->GetFloat( 1 ); +} + +void ExplodingWall::LandAngles( Event *ev ) +{ + land_angles = ev->GetVector( 1 ); +} + +void ExplodingWall::BaseVelocity( Event *ev ) +{ + base_velocity = ev->GetVector( 1 ); +} + +void ExplodingWall::RandomVelocity( Event *ev ) +{ + random_velocity = ev->GetVector( 1 ); +} + +void ExplodingWall::SetDmg( Event *ev ) +{ + dmg = ev->GetInteger( 1 ); +} + +void ExplodingWall::SetExplosions( Event *ev ) +{ + explosions = ev->GetInteger( 1 ); +} + +void ExplodingWall::Explode( Event *ev ) +{ + Entity *other; + Vector pos; + Vector mins, maxs; + int i; + + if ( spawnflags & INVISIBLE ) + { + showModel(); + setSolidType( SOLID_BSP ); + takedamage = DAMAGE_YES; + } + + if ( takedamage == DAMAGE_NO ) + { + return; + } + + other = ev->GetEntity( 1 ); + + health = 0; + takedamage = DAMAGE_NO; + + // Create explosions + for( i = 0; i < explosions; i++ ) + { + pos[ 0 ] = absmin[ 0 ] + G_Random( size[ 0 ] ); + pos[ 1 ] = absmin[ 1 ] + G_Random( size[ 1 ] ); + pos[ 2 ] = absmin[ 2 ] + G_Random( size[ 2 ] ); + + CreateExplosion( pos, dmg, this, other, this ); + } + + // throw itself + state = 1; + on_ground = false; + PostEvent( EV_ExplodingWall_OnGround, FRAMETIME ); + + velocity.x = base_velocity.x + G_CRandom( random_velocity.x ); + velocity.y = base_velocity.y + G_CRandom( random_velocity.y ); + velocity.z = base_velocity.z + G_Random( random_velocity.z ); + + setMoveType( MOVETYPE_BOUNCE ); + // setSolidType(SOLID_NOT); + + if ( spawnflags & RANDOMANGLES ) + { + avelocity[ 0 ] = G_Random( angle_speed ); + avelocity[ 1 ] = G_Random( angle_speed ); + avelocity[ 2 ] = G_Random( angle_speed ); + } + else + { + Vector delta; + float most; + float time; + int t; + + delta = land_angles - angles; + if ( delta[ 0 ] > 180.0f ) + delta[ 0 ] -= 360.0f; + if ( delta[ 0 ] < -180.0f ) + delta[ 0 ] += 360.0f; + if ( delta[ 1 ] > 180.0f ) + delta[ 1 ] -= 360.0f; + if ( delta[ 1 ] < -180.0f ) + delta[ 1 ] += 360.0f; + if ( delta[ 2 ] > 180.0f ) + delta[ 2 ] -= 360.0f; + if ( delta[ 2 ] < -180.0f ) + delta[ 2 ] += 360.0f; + most = MaxValue( delta ); + if ( !angle_speed ) + angle_speed = 1; + t = (int)(10.0f * most / angle_speed); + time = (float)t / 10.0f; + delta = delta * (1.0f / time); + avelocity = delta; + PostEvent( EV_ExplodingWall_StopRotating, time ); + state = 2; + } + + ActivateTargets( ev ); + + if ( land_radius > 0.0f ) + { + mins[0] = mins[1] = mins[2] = -land_radius; + maxs[0] = maxs[1] = maxs[2] = land_radius; + setSize( mins, maxs ); + } + + attack_finished = 0; +} + +void ExplodingWall::DamageEvent( Event *ev ) +{ + Event *event; + //Entity *inflictor; + Entity *attacker; + float damage; + + if ( takedamage == DAMAGE_NO ) + { + return; + } + + if ( on_ground ) + { + GroundDamage( ev ); + return; + } + + damage = ev->GetFloat( 1 ); + //inflictor = ev->GetEntity( 2 ); + attacker = ev->GetEntity( 3 ); + + if ( spawnflags & ACCUMULATIVE ) + { + health -= damage; + if ( health > 0.0f ) + return; + } + else + { + if ( damage < health ) + { + return; + } + } + + event = new Event( EV_Activate ); + event->AddEntity( attacker ); + ProcessEvent( event ); +} + +void ExplodingWall::GroundDamage( Event *ev ) +{ + //Entity *inflictor; + Entity *attacker; + Vector pos; + int damage; + + if ( takedamage == DAMAGE_NO ) + { + return; + } + + damage = ev->GetInteger( 1 ); + //inflictor = ev->GetEntity( 2 ); + attacker = ev->GetEntity( 3 ); + + if ( spawnflags & ACCUMULATIVE ) + { + health -= damage; + if ( health > 0.0f ) + return; + } + else + { + if ( damage < health ) + { + return; + } + } + + if ( explosions ) + { + pos[ 0 ] = absmin[ 0 ] + G_Random( size[ 0 ] ); + pos[ 1 ] = absmin[ 1 ] + G_Random( size[ 1 ] ); + pos[ 2 ] = absmin[ 2 ] + G_Random( size[ 2 ] ); + + CreateExplosion( pos, damage, this, attacker, this ); + } + takedamage = DAMAGE_NO; + hideModel(); + BroadcastSound(); + PostEvent( EV_Remove, 0.0f ); +} + +void ExplodingWall::SetupSecondStage( void ) +{ + health = max_health; + takedamage = DAMAGE_YES; +} + +void ExplodingWall::StopRotating( Event *ev ) +{ + avelocity = vec_zero; + setAngles( land_angles ); + if ( spawnflags & TWOSTAGE ) + SetupSecondStage(); +} + +void ExplodingWall::CheckOnGround( Event *ev ) +{ + if ( ( velocity == vec_zero ) && groundentity ) + { + Vector delta; + float most; + float time; + float t; + + delta = land_angles - angles; + if ( delta.length() > 1.0f ) + { + if ( delta[ 0 ] > 180.0f ) + delta[ 0 ] -= 360.0f; + if ( delta[ 0 ] < -180.0f ) + delta[ 0 ] += 360.0f; + if ( delta[ 1 ] > 180.0f ) + delta[ 1 ] -= 360.0f; + if ( delta[ 1 ] < -180.0f ) + delta[ 1 ] += 360.0f; + if ( delta[ 2 ] > 180.0f ) + delta[ 2 ] -= 360.0f; + if ( delta[ 2 ] < -180.0f ) + delta[ 2 ] += 360.0f; + most = MaxValue( delta ); + if ( angle_speed > 3.0f ) + t = 10.0f * most / ( angle_speed / 3.0f ); + else + t = 10.0f * most; + time = t / 10.0f; + delta = delta * ( 1.0f / time ); + avelocity = delta; + PostEvent( EV_ExplodingWall_StopRotating, time ); + } + state = 2; + setSize( orig_mins, orig_maxs ); + on_ground = true; + } + else + PostEvent( ev, FRAMETIME ); +} + +void ExplodingWall::TouchFunc( Event *ev ) +{ + Entity *other; + + if ( ( velocity == vec_zero ) || ( level.time < attack_finished ) ) + { + return; + } + + other = ev->GetEntity( 1 ); + + if ( ( spawnflags & LANDSHATTER ) && ( other == world ) ) + { + Vector pos; + + takedamage = DAMAGE_NO; + + if ( explosions ) + { + pos[ 0 ] = absmin[ 0 ] + G_Random( size[ 0 ] ); + pos[ 1 ] = absmin[ 1 ] + G_Random( size[ 1 ] ); + pos[ 2 ] = absmin[ 2 ] + G_Random( size[ 2 ] ); + + CreateExplosion( pos, dmg, this, other, this ); + } + hideModel(); + BroadcastSound(); + PostEvent( EV_Remove, 0.0f ); + return; + } + + if ( other->takedamage ) + { + other->Damage( this, activator, dmg, origin, vec_zero, vec_zero, 20, 0, MOD_EXPLODEWALL ); + Sound( "debris_generic", CHAN_WEAPON ); + attack_finished = level.time + FRAMETIME; + } +} + +void ExplodingWall::Setup( Event *ev ) +{ + if ( spawnflags & INVISIBLE ) + { + if ( Targeted() ) + takedamage = DAMAGE_YES; + else + takedamage = DAMAGE_NO; + hideModel(); + setSolidType( SOLID_NOT ); + } + else + { + showModel(); + setSolidType( SOLID_BSP ); + takedamage = DAMAGE_YES; + } + + setMoveType( MOVETYPE_PUSH ); + setOrigin(); +} + +ExplodingWall::ExplodingWall() +{ + if ( LoadingSavegame ) + { + return; + } + + health = 60; + max_health = health; + on_ground = false; + + state = 0; + angle_speed = ( spawnflags & RANDOMANGLES ) ? 600 : 100; + land_radius = 16; + dmg = 10; + explosions = 1; + + base_velocity = Vector( 0.0f, 0.0f, 280.0f ); + random_velocity = Vector( 140.0f, 140.0f, 140.0f ); + + orig_mins = mins; + orig_maxs = maxs; + + respondto = spawnflags ^ TRIGGER_PLAYERS; + + attack_finished = false; + + PostEvent( EV_ExplodingWall_Setup, EV_POSTSPAWN ); +} + +/*****************************************************************************/ +/*QUAKED trigger_teleport (0.5 0.5 0.5) ? VISIBLE x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES NO_EFFECTS FAST_EFFECTS + +Touching this entity will teleport players to the targeted object. + +"key" The item needed to activate this. (default nothing) + +"teleportthread" The thread that is run when the player is teleported + +If NOT_PLAYERS is set, the teleporter does not teleport players +If NOT_MONSTERS is set, the teleporter does not teleport monsters +If NOT_PROJECTILES is set, the teleporter does not teleport projectiles (rockets, grenades, etc.) +If NO_EFFECTS is set, the special effect will not happen and the teleport will be instant +If FAST_EFFECTS is set, the teleport will be instant, and the effects will appear +******************************************************************************/ + +#define NO_EFFECTS ( 1 << 5 ) +#define FAST_EFFECTS ( 1 << 6 ) + +Event EV_Teleporter_Teleport +( + "teleport", + EV_CODEONLY, + "e", + "entity", + "Teleports the entity to destination." +); +Event EV_Teleporter_StopTeleport +( + "stopteleport", + EV_CODEONLY, + "e", + "entity", + "Releases the entity at the end of the teleport." +); +Event EV_Teleporter_SetThread +( + "teleportthread", + EV_SCRIPTONLY, + "s", + "thread_name", + "Sets the thread to run when the player is teleported." +); + +CLASS_DECLARATION( Trigger, Teleporter, "trigger_teleport" ) +{ + { &EV_Trigger_Effect, &Teleporter::StartTeleport }, + { &EV_Teleporter_Teleport, &Teleporter::Teleport }, + { &EV_Teleporter_StopTeleport, &Teleporter::StopTeleport }, + { &EV_Teleporter_SetThread, &Teleporter::SetThread }, + + { NULL, NULL } +}; + +void Teleporter::SetThread( Event *ev ) +{ + teleport_thread = ev->GetString( 1 ); +} + +void Teleporter::StartTeleport( Event *ev ) +{ + //Entity *fx; + Entity *other; + Event *event; + qboolean is_sentient; + //Vector new_position; + + + if ( in_use ) + return; + + other = ev->GetEntity( 1 ); + + if ( !other || other->isSubclassOf( Projectile ) ) + return; + + in_use = true; + + if ( doFullTeleport( other ) ) + { + Sound( "snd_teleport", CHAN_AUTO, 1, 400 ); + } + + if ( spawnflags & NO_EFFECTS ) + { + event = new Event( EV_Teleporter_Teleport ); + event->AddEntity( other ); + ProcessEvent( event ); + return; + } + + if ( other->isSubclassOf( Sentient ) ) + is_sentient = true; + else + is_sentient = false; + + /*if ( is_sentient ) + { + new_position = origin; + new_position.z += mins.z; + other->setOrigin( new_position ); + } */ + + if ( doFullTeleport( other ) ) + { + Event *newEvent = new Event( EV_DisplayEffect ); + newEvent->AddString( "TransportOut" ); + newEvent->AddString( "Multiplayer" ); + other->PostEvent( newEvent, 0.0f ); + } + + // Create the teleport special effect + + /* fx = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + fx->setOrigin( other->origin ); + + if ( is_sentient ) + { + fx->setModel( "fx_bigteleport.tik" ); + //fx->Sound( "sound/environment/electric/singles/dimming.wav" ); + } + else + { + fx->setModel( "fx_teleport2.tik" ); + } + + fx->animate->RandomAnimate( "idle", EV_Remove ); + + if ( is_sentient && !( spawnflags & FAST_EFFECTS ) ) + { + // Freeze the entity that went into the teleporter + other->flags |= FL_IMMOBILE; + other->takedamage = DAMAGE_NO; + } */ + + // Make the entity teleport + + event = new Event( EV_Teleporter_Teleport ); + event->AddEntity( other ); + + if ( is_sentient && !( spawnflags & FAST_EFFECTS ) ) + { + PostEvent( event, 4.0f ); + other->PostEvent( EV_Hide, 2.0f ); + } + else + PostEvent( event, 0.0f ); +} + +void Teleporter::Teleport( Event *ev ) +{ + Entity *dest; + int i; + Entity *other; + Vector mid; + //Entity *fx; + //Event *event; + Event *newEvent; + + other = ev->GetEntity( 1 ); + + if ( !other || ( other == world ) ) + { + in_use = false; + return; + } + + dest = G_FindTarget( NULL, Target() ); + if ( !dest ) + { + warning( "Teleport", "Couldn't find destination\n" ); + return; + } + + assert( dest ); + + // unlink to make sure it can't possibly interfere with KillBox + other->unlink(); + + if ( other->isSubclassOf( Sentient ) ) + { + thePathManager.Teleport( other, other->origin, dest->origin ); + other->origin = dest->origin + Vector( 0.0f, 0.0f, 1.0f ); + other->velocity = vec_zero; + other->origin.copyTo( other->edict->s.origin2 ); + other->NoLerpThisFrame(); + } + else + { + mid = ( absmax - absmin ) * 0.5f; + other->origin = dest->origin + Vector( 0.0f, 0.0f, 1.0f ); + other->origin += mid; + } + + // set angles + other->setAngles( dest->angles ); + + if ( other->client ) + { + client = other->client; + + // clear the velocity and hold them in place briefly + client->ps.pm_time = 100; + client->ps.pm_flags |= PMF_TIME_TELEPORT; + + // cut the camera on the client + ( ( Player * )other )->CameraCut(); + + for( i = 0; i < 3; i++ ) + { + client->ps.delta_angles[ i ] = ANGLE2SHORT( dest->angles[ i ] - client->cmd_angles[ i ] ); + } + + VectorCopy( angles, client->ps.viewangles ); + } + + if ( dest->isSubclassOf( TeleporterDestination ) ) + { + float len; + + len = other->velocity.length(); + // + // give them a bit of a push + // + if ( len < 400.0f ) + len = 400.0f; + other->velocity = ( ( TeleporterDestination * )dest )->movedir * len; + } + + // kill anything at the destination + //don't do this if spectator + + if ( doFullTeleport( other ) ) + { + KillBox( other ); + + dest->Sound( "snd_teleport", CHAN_AUTO, 1, 400 ); + } + + other->setOrigin( other->origin ); + other->origin.copyTo( other->edict->s.origin2 ); + + // Run the teleport thread if set + + if ( teleport_thread.length() && other->isSubclassOf( Player ) ) + { + if ( !ExecuteThread( teleport_thread.c_str() ) ) + warning( "RunThread", "could not process thread" ); + } + + // Skip effects if no_effects set + + if ( spawnflags & NO_EFFECTS ) + { + Event *event = new Event( EV_Teleporter_StopTeleport ); + event->AddEntity( other ); + ProcessEvent( event ); + return; + } + + // Add teleport effect + + if ( doFullTeleport( other ) ) + { + newEvent = new Event( EV_DisplayEffect ); + newEvent->AddString( "TransportIn" ); + newEvent->AddString( "Multiplayer" ); + other->PostEvent( newEvent, 0.0f ); + } + + // Always turn off teleport instantly + + newEvent = new Event( EV_Teleporter_StopTeleport ); + newEvent->AddEntity( other ); + ProcessEvent( newEvent ); + + /* // Spawn in effect + + fx = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + fx->setOrigin( other->origin ); + + if ( other->isSubclassOf( Sentient ) ) + { + fx->setModel( "fx_bigteleport.tik" ); + //fx->Sound( "sound/environment/electric/singles/dimming.wav" ); + } + else + { + fx->setModel( "fx_teleport2.tik" ); + } + + fx->animate->RandomAnimate( "idle", EV_Remove ); + + event = new Event( EV_Teleporter_StopTeleport ); + event->AddEntity( other ); + + if ( other->isSubclassOf( Sentient ) ) + PostEvent( event, 1.75f ); + else + PostEvent( event, FRAMETIME ); */ +} + +void Teleporter::StopTeleport( Event *ev ) +{ + Entity *other; + + other = ev->GetEntity( 1 ); + + if ( other && other->isSubclassOf( Sentient ) ) + { + if ( doFullTeleport( other ) ) + { + other->flags &= ~FL_IMMOBILE; + other->takedamage = DAMAGE_AIM; + other->showModel(); + } + } + + //if ( !( spawnflags & NO_EFFECTS ) ) + // other->Sound( "snd_teleport" ); + + in_use = false; +} + +bool Teleporter::doFullTeleport( Entity *entity ) +{ + if ( entity && entity->isSubclassOf( Player ) ) + { + Player *player = (Player *)entity; + + if ( multiplayerManager.inMultiplayer() && multiplayerManager.isPlayerSpectator( player ) ) + return false; + } + + return true; +} + +Teleporter::Teleporter() +{ + if ( LoadingSavegame ) + { + return; + } + + if ( spawnflags & 1 ) + { + PostEvent( EV_Show, EV_POSTSPAWN ); + } + + //respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES ); + respondto = spawnflags ^ ( TRIGGER_PLAYERS ); + + in_use = false; + + // Cache all needed stuff + + if ( !( spawnflags & NO_EFFECTS ) ) + { + //CacheResource( "models/fx_bigteleport.tik", this ); + //CacheResource( "sound/environment/electric/singles/dimming.wav", this ); + //CacheResource( "fx_teleport2.tik", this ); + //CacheResource( "snd_teleport", this ); + } +} + +/*****************************************************************************/ +/*QUAKED func_teleportdest (0 0.25 0.5) (-32 -32 0) (32 32 8) + +Point trigger_teleport at these. + +******************************************************************************/ + +CLASS_DECLARATION( Entity, TeleporterDestination, "func_teleportdest" ) +{ + { &EV_SetAngle, &TeleporterDestination::SetMoveDir }, + + { NULL, NULL } +}; + +TeleporterDestination::TeleporterDestination() +{ + movedir = G_GetMovedir( 0.0f ); +} + +void TeleporterDestination::SetMoveDir( Event *ev ) +{ + float angle; + + angle = ev->GetFloat( 1 ); + movedir = G_GetMovedir( angle ); + setAngles( movedir.toAngles() ); +} + +/*****************************************************************************/ +/*QUAKED func_useanim (0 0.5 0) ? VISIBLE TOUCHABLE CONTINUOUS + +This object allows you to place the player into a specific animation for the +purposes of using an object within the world. + +This object should point at a func_useanimdest which contains specific +information about how the player is supposed to be posed. + +"count" - how many times this should trigger (default -1, infinite) +"thread" - thread to fire when used +"triggertarget" - what to trigger when used. +"delay" - how long it takes to be re-triggered ( default 3 seconds ) +"key" - item needed to activate this + +VISIBLE - if this is checked the trigger itself will be visible +TOUCHABLE - if this is set we can activate the trigger by standing in it. +CONTINUOUS - if this is checked the thing will re-trigger continously, otherwise +it waits until the player has left the trigger field. + +******************************************************************************/ + +Event EV_UseAnim_Reset +( + "_reset", + EV_CODEONLY, + NULL, + NULL, + "Reset's the Use Anim after it has no longer been touched." +); +Event EV_UseAnim_Thread +( + "thread", + EV_SCRIPTONLY, + "s", + "label", + "Sets which thread to use when this UseAnim is triggered." +); +Event EV_UseAnim_Count +( + "count", + EV_DEFAULT, + "i", + "newCount", + "Sets how many times the UseAnim can be triggered." +); +Event EV_UseAnim_TriggerTarget +( + "triggertarget", + EV_SCRIPTONLY, + "s", + "targetname", + "Sets what should be triggered, when this UseAnim is triggered." +); +Event EV_UseAnim_SetAnim +( + "anim", + EV_DEFAULT, + "s", + "animName", + "set the animation to use for player." +); +Event EV_UseAnim_SetKey +( + "key", + EV_SCRIPTONLY, + "s", + "keyName", + "set the key needed to make this UseAnim function." +); +Event EV_UseAnim_SetState +( + "state", + EV_CHEAT, + "s", + "stateName", + "set the state to use for the player." +); +Event EV_UseAnim_SetCamera +( + "camera", + EV_SCRIPTONLY, + "s", + "cameraPosition", + "set the camera to use when in this animation.\n" + "topdown, behind, front, side, behind_fixed, side_left, side_right" +); +Event EV_UseAnim_SetNumLoops +( + "num_loops", + EV_DEFAULT, + "i", + "loopCount", + "set the number of times to loop an animation per use." +); +Event EV_UseAnim_SetDelay +( + "delay", + EV_DEFAULT, + "f", + "delayTime", + "how long it takes for the UseAnim to be retriggered once you leave it." +); +Event EV_UseAnim_SetActionType +( + "action_type", + EV_DEFAULT, + "S", + "actionType", + "Action type (kick, ignite, etc)" +); + +CLASS_DECLARATION( Entity, UseAnim, "func_useanim" ) +{ + { &EV_Use, NULL }, + { &EV_Touch, &UseAnim::Touched }, + { &EV_UseAnim_Reset, &UseAnim::Reset }, + { &EV_UseAnim_Thread, &UseAnim::SetThread }, + { &EV_UseAnim_TriggerTarget, &UseAnim::SetTriggerTarget }, + { &EV_UseAnim_Count, &UseAnim::SetCount }, + { &EV_UseAnim_SetAnim, &UseAnim::SetAnim }, + { &EV_UseAnim_SetState, &UseAnim::SetState }, + { &EV_UseAnim_SetKey, &UseAnim::SetKey }, + { &EV_UseAnim_SetNumLoops, &UseAnim::SetNumLoops }, + { &EV_UseAnim_SetCamera, &UseAnim::SetCamera }, + { &EV_UseAnim_SetActionType, &UseAnim::SetActionType }, + + { NULL, NULL } +}; + +UseAnim::UseAnim() +{ + if ( LoadingSavegame ) + { + return; + } + + setMoveType( MOVETYPE_NONE ); + + anim = "stand_use"; + num_loops = 1; + hideModel(); + + // + // make it not solid unless we want it touchable + // + if ( !( spawnflags & 2 ) ) + { + setSolidType( SOLID_BBOX ); + setContents( CONTENTS_BODY ); + } + else + { + setSolidType( SOLID_TRIGGER ); + edict->svflags |= SVF_NOCLIENT; + } + + // by default this can activated infinitely + count = -1; + // clear out the triggertarget + triggertarget = ""; + // clear out the thread + thread = ""; + // set the default delay + delay = 3; + // initialize the last time the door was triggered + last_active_time = -delay; + // initially its not active + active = 0; + // set the default camera to be side view + camera = "behind"; + // + // only make it visible if so desired + // + if ( spawnflags & 1 ) + { + PostEvent( EV_Show, EV_POSTSPAWN ); + } +} + +void UseAnim::Touched( Event *ev ) +{ + Entity *other; + + if ( active && ( !( spawnflags & 4 ) ) ) + { + CancelEventsOfType( EV_UseAnim_Reset ); + PostEvent( EV_UseAnim_Reset, 0.25f ); + return; + } + + // don't retrigger to soon + if ( level.time < last_active_time ) + return; + + other = ev->GetEntity( 1 ); + if ( other->isSubclassOf( Player ) ) + { + ( ( Player * ) other )->TouchedUseAnim( this ); + } +} + +bool UseAnim::canBeUsed( Entity * activator ) +{ + Entity *dest; + + // if this is no longer usable, return false + if ( !count ) + { + return false; + } + + // don't retrigger to soon + if ( level.time < last_active_time ) + { + return false; + } + + if ( key.length() ) + { + if ( !activator->isSubclassOf( Sentient ) ) + { + return false; + } + if ( !( ( (Sentient *)activator )->HasItem( key.c_str() ) ) ) + { + qboolean setModel; + Item *item; + ClassDef *cls; + str dialog; + + cls = FindClass( key.c_str(), &setModel ); + if ( !cls || !checkInheritance( "Item", cls->classname ) ) + { + gi.WDPrintf( "No item named '%s'\n", key.c_str() ); + return true; + } + item = ( Item * )cls->newInstance(); + if ( setModel ) + { + item->setModel( key.c_str() ); + } + item->CancelEventsOfType( EV_Item_DropToFloor ); + item->CancelEventsOfType( EV_Remove ); + item->ProcessPendingEvents(); + dialog = item->GetDialogNeeded(); + if ( dialog.length() > 0 ) + { + activator->Sound( dialog ); + } + else + { + gi.centerprintf ( activator->edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$ItemNeeded$$%s", item->getName().c_str() ); + } + delete item; + + // don't retrigger for 5 seconds + last_active_time = level.time + 5.0f; + return false; + } + else + { + return true; + } + } + + if ( isSubclassOf( TouchAnim ) ) + { + return true; + } + + dest = G_FindTarget( NULL, Target() ); + if ( !dest || !dest->isSubclassOf( UseAnimDestination ) ) + { + warning( "UseAnim", "Couldn't find destination\n" ); + return false; + } + + return true; +} + +bool UseAnim::GetInformation( const Entity *activator, Vector *org, Vector *angles, str *animation, int *loopcount, + str *state, str *camera ) +{ + Entity *dest; + UseAnimDestination *uadest; + + // if this is no longer usable, return false + if ( !count ) + { + return false; + } + + dest = G_FindTarget( NULL, Target() ); + + if ( !dest || !dest->isSubclassOf( UseAnimDestination ) ) + { + // grab the information from this entity instead + // set the destination origin + *org = origin; + // set the destination angles + *angles = this->angles; + // set the desination animation + *animation = anim; + // set the number of loops + *loopcount = num_loops; + // get the state if necessary + *state = this->state; + // set the camera + *camera = this->camera; + } + else + { + uadest = ( UseAnimDestination * )dest; + + // set the destination origin + *org = uadest->origin; + // set the destination angles + *angles = uadest->angles; + // set the desination animation + *animation = uadest->GetAnim(); + // set the number of loops + *loopcount = uadest->GetNumLoops(); + // get the state if necessary + *state = uadest->GetState(); + // set the camera + *camera = this->camera; + } + + + // make this guy active + active = true; + + // if this is a TouchAnim see if it is linked to another TouchAnim + if ( isSubclassOf( TouchAnim ) ) + { + dest = NULL; + do { + dest = G_FindTarget( dest, Target() ); + if ( dest ) + { + if ( dest->isSubclassOf( UseAnim ) ) + { + // make our linked UseAnim's active as well + ( ( UseAnim * )dest )->active = true; + } + } + else + { + break; + } + } + while( 1 ); + } + // + // decrement the use + // + if ( count > 0 ) + { + count--; + } + + return true; +} + +void UseAnim::TriggerTargets( Entity *activator ) +{ + // + // fire off our trigger target if appropriate + // + if ( triggertarget.length() ) + { + Event *event; + Entity *ent; + + ent = NULL; + do + { + ent = G_FindTarget( ent, triggertarget.c_str() ); + if ( !ent ) + { + break; + } + event = new Event( EV_Activate ); + event->AddEntity( activator ); + ent->PostEvent( event, 0.0f ); + } + while ( 1 ); + } + + // + // fire off a thread if necessary + // + if ( thread.length() ) + { + if ( !ExecuteThread( thread ) ) + { + warning( "TriggerTargets", "Null game script" ); + } + } +} + +void UseAnim::Reset( Event *ev ) +{ + // + // find out if our triggertarget is of type door and only reset if the door is closed + // + if ( triggertarget.length() ) + { + Entity *ent; + + ent = NULL; + do + { + ent = G_FindTarget( ent, triggertarget.c_str() ); + if ( !ent ) + { + break; + } + if ( ent->isSubclassOf( Door ) ) + { + if ( !( ( Door * )ent )->isCompletelyClosed() ) + { + CancelEventsOfType( EV_UseAnim_Reset ); + PostEvent( EV_UseAnim_Reset, 0.25f ); + // + // wait for a little bit + // + return; + } + } + } + while ( 1 ); + } + + active = false; + last_active_time = level.time + delay; + + // if this is a TouchAnim see if it is linked to another TouchAnim + if ( isSubclassOf( TouchAnim ) ) + { + Entity *dest; + + dest = NULL; + do { + dest = G_FindTarget( dest, Target() ); + if ( dest ) + { + if ( dest->isSubclassOf( UseAnim ) ) + { + // make our linked UseAnim's reset as well + ( ( UseAnim * )dest )->active = false; + ( ( UseAnim * )dest )->last_active_time = level.time + delay; + } + } + else + { + break; + } + } + while( 1 ); + } +} + +void UseAnim::SetThread( Event *ev ) +{ + thread = ev->GetString( 1 ); +} + +void UseAnim::SetActionType( Event *ev ) +{ + action_type = ev->GetString( 1 ); +} + +void UseAnim::SetDelay( Event *ev ) +{ + delay = ev->GetFloat( 1 ); +} + +void UseAnim::SetTriggerTarget( Event *ev ) +{ + triggertarget = ev->GetString( 1 ); +} + +void UseAnim::SetCount( Event *ev ) +{ + count = ev->GetInteger( 1 ); +} + +void UseAnim::SetAnim( Event *ev ) +{ + anim = ev->GetString( 1 ); +} + +void UseAnim::SetState( Event *ev ) +{ + state = ev->GetString( 1 ); +} + +void UseAnim::SetKey( Event *ev ) +{ + key = ev->GetString( 1 ); +} + +void UseAnim::SetCamera( Event *ev ) +{ + camera = ev->GetString( 1 ); +} + +void UseAnim::SetNumLoops( Event *ev ) +{ + num_loops = ev->GetInteger( 1 ); +} + + +CLASS_DECLARATION( UseAnim, TouchAnim, "func_touchanim" ) +{ + { NULL, NULL } +}; + +TouchAnim::TouchAnim() +{ + if ( LoadingSavegame ) + { + return; + } + + spawnflags |= 2; + + if ( spawnflags & 8 ) + { + setSize( Vector(-32, -32, 0), Vector(32, 32, 96) ); + } + else + { + setSize( Vector(-16, -16, 0), Vector(16, 16, 96) ); + } + + setSolidType( SOLID_TRIGGER ); + edict->svflags |= SVF_NOCLIENT; +} + +/*****************************************************************************/ +/* func_useanimdest (0 0.25 0.5) (0 0 0) (0 0 0) + +Point func_useanim's at these. + +The player will be lerped to this position and this orientation +and placed into the specified animation + +"anim" specifies the animation that the player should be in. +"state" instead of an animation, sets a state the player should go into +"camera" camera position to use when player is in animation +"num_loops" number of animation loops to play. + + +******************************************************************************/ + +CLASS_DECLARATION( Entity, UseAnimDestination, "func_useanimdest" ) +{ + { &EV_UseAnim_SetAnim, &UseAnimDestination::SetAnim }, + { &EV_UseAnim_SetState, &UseAnimDestination::SetState }, + { &EV_UseAnim_SetNumLoops, &UseAnimDestination::SetNumLoops }, + + { NULL, NULL } +}; + +UseAnimDestination::UseAnimDestination() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + // + // default animation to use + // + anim = "stand_use"; + num_loops = 1; + setSolidType( SOLID_NOT ); + hideModel(); +} + +void UseAnimDestination::SetAnim( Event *ev ) +{ + anim = ev->GetString( 1 ); +} + +void UseAnimDestination::SetState( Event *ev ) +{ + state = ev->GetString( 1 ); +} + +str UseAnimDestination::GetAnim( void ) +{ + return anim; +} + +str UseAnimDestination::GetState( void ) +{ + return state; +} + +void UseAnimDestination::SetNumLoops( Event *ev ) +{ + num_loops = ev->GetInteger( 1 ); +} + +int UseAnimDestination::GetNumLoops( void ) +{ + return num_loops; +} + +Event EV_UseObject_MoveThread +( + "move_thread", + EV_SCRIPTONLY, + "s", + "label", + "Sets which move thread to use when this UseObject has finshed looping." +); +Event EV_UseObject_StopThread +( + "stop_thread", + EV_SCRIPTONLY, + "s", + "label", + "Sets which stop thread to use when this UseObject is finished." +); +Event EV_UseObject_ResetThread +( + "reset_thread", + EV_SCRIPTONLY, + "s", + "label", + "Sets which thread to call when resetting." +); +Event EV_UseObject_Count +( + "count", + EV_DEFAULT, + "i", + "newCount", + "Sets how many times the UseObject can be triggered." +); +Event EV_UseObject_Cone +( + "cone", + EV_SCRIPTONLY, + "f", + "newCone", + "Sets the cone in angles of where the Useobject can be used." +); +Event EV_UseObject_Offset +( + "offset", + EV_DEFAULT, + "v", + "newOffset", + "Sets the offset to use for this UseObject." +); +Event EV_UseObject_YawOffset +( + "yaw_offset", + EV_DEFAULT, + "f", + "newYawOffset", + "Sets the yaw offset to use for this UseObject." +); +Event EV_UseObject_State +( + "state", + EV_DEFAULT, + "s", + "newState", + "Sets the state to use for this UseObject." +); +Event EV_UseObject_StateBackwards +( + "state_backwards", + EV_DEFAULT, + "s", + "newState", + "Sets the backward state to use for this UseObject." +); +Event EV_UseObject_TriggerTarget +( + "triggertarget", + EV_SCRIPTONLY, + "s", + "targetname", + "Sets what should be triggered, when this UseObject is triggered." +); +Event EV_UseObject_ResetTime +( + "reset_time", + EV_DEFAULT, + "f", + "newResetTime", + "Sets the time it takes for the UseObject to reset itself." +); +Event EV_UseObject_DamageType +( + "damage_type", + EV_DEFAULT, + "s", + "newDamageType", + "Sets what kind of damage is needed to activate the trigger." +); +Event EV_UseObject_Reset +( + "_useobject_reset", + EV_CODEONLY, + NULL, + NULL, + "Resets the useobject to the start state after a certain amount of time." +); +Event EV_UseObject_Resetting +( + "_useobject_resetting", + EV_CODEONLY, + NULL, + NULL, + "Intermediate function for useobject reset." +); +Event EV_UseObject_DamageTriggered +( + "_useobject_damagetriggered", + EV_CODEONLY, + "e", + "activator", + "Intermediate function for when the useobject was triggered by damage." +); +Event EV_UseObject_Activate +( + "activate", + EV_SCRIPTONLY, + NULL, + NULL, + "Allow the useobject to be used." +); +Event EV_UseObject_Deactivate +( + "deactivate", + EV_SCRIPTONLY, + NULL, + NULL, + "Do not allow the useobject to be used." +); +Event EV_UseObject_UseMaterial +( + "usematerial", + EV_DEFAULT, + "s", + "nameOfUseMaterial", + "the name of the material that glows when active." +); +Event EV_UseObject_SetActiveState +( + "_setactivestate", + EV_CODEONLY, + NULL, + NULL, + "event that sets up the proper skin for the useobject." +); +Event EV_UseObject_SetActionType +( + "action_type", + EV_DEFAULT, + "S", + "actionType", + "The type of action (kick, ignite, etc)" +); +Event EV_UseObject_MoveThePlayer +( + "movetheplayer", + EV_DEFAULT, + NULL, + NULL, + "Attempt to move the player to the UseObject location." +); + +#define MULTI_STATE ( 1 << 0 ) + +/*****************************************************************************/ +/*QUAKED func_useobject (0 0.5 0) ? MULTI-STATE + +Allows you to setup a special object that places the player into a specific state +sequence. Primarily used for levers and cranks. + +Object starts out in the "start" animation, when used the following occurs: + +It is determined whether or not the player is in the right position to activate +the object, if it is, the player is moved to the exact offset and angle specified +by "offset" and "yaw_offset". The right position is determined by a dot product +with "offset" and "yaw_offset". The "cone" parameter controls the cone in which the +object can be triggered. Once the player is in the right position, the player is placed +into "state" and the "move" animation is played. Once the player animation ends, the +"move_thread" will be called. If the use button is continued to be held down and count +is not finite, the animation will be continued to be played until the use key is held +down. Once the use key is let go, the "stop" animation will be played on the lever and +the "stop_thread" will be called. + +"activate" - turns the useobject on +"deactivate" - turns the useobject off +"offset" - vector offset of where the player should stand +"state" - state to go into when used +"state_backwards" - what state to use when reversing the object +"yaw_offset" - what direction the player should be facing when using the object +"cone" - the cone in which the object can be used +"count" - how many times this should trigger (default -1, infinite) +"move_thread" - thread that is fired when the object has cycled one animation +"stop_thread" - thread that is fired when the object has finished animating +"reset_thread" - thread that is fired when the object is resetting itself +"reset_time" - the time it takes for the object to reset, (default 0, it doesn't) +"triggertarget" - target to trigger when finished animating, if reset_time is set, target +will be fired again when resetting +"damage_type" - if set, can be triggered by using a weapon to activate it. If set to "all", +any damage will activate it. +"action_type" - sets the type of action to perform (for icon display). + +MULTI-STATE - the object has two different states and must be used each time to set the state +when multi state is active, the reset_thread is called instead of stop_thread. All UseObjects +have two states on and off. When reset_time is set, the object will automatically return to the +off state after a preset amount of time. When multi-state is set this must be done manually. + +******************************************************************************/ + +CLASS_DECLARATION( Entity, UseObject, "func_useobject" ) +{ + { &EV_Use, NULL }, + { &EV_UseObject_MoveThread, &UseObject::SetMoveThread }, + { &EV_UseObject_StopThread, &UseObject::SetStopThread }, + { &EV_UseObject_ResetThread, &UseObject::SetResetThread }, + { &EV_UseObject_TriggerTarget, &UseObject::SetTriggerTarget }, + { &EV_UseObject_Offset, &UseObject::SetOffset }, + { &EV_UseObject_YawOffset, &UseObject::SetYawOffset }, + { &EV_UseObject_Count, &UseObject::SetCount }, + { &EV_UseObject_Cone, &UseObject::SetCone }, + { &EV_UseObject_State, &UseObject::SetState }, + { &EV_UseObject_StateBackwards, &UseObject::SetBackwardsState }, + { &EV_UseObject_ResetTime, &UseObject::SetResetTime }, + { &EV_UseObject_Reset, &UseObject::Reset }, + { &EV_UseObject_DamageType, &UseObject::DamageType }, + { &EV_UseObject_Resetting, &UseObject::Resetting }, + { &EV_UseObject_DamageTriggered, &UseObject::DamageTriggered }, + { &EV_Damage, &UseObject::DamageFunc }, + { &EV_UseObject_Activate, &UseObject::ActivateEvent }, + { &EV_UseObject_Deactivate, &UseObject::DeactivateEvent }, + { &EV_UseObject_UseMaterial, &UseObject::UseMaterialEvent }, + { &EV_UseObject_SetActiveState, &UseObject::SetActiveState }, + { &EV_UseObject_SetActionType, &UseObject::SetActionType }, + { &EV_UseObject_MoveThePlayer, &UseObject::MoveThePlayer }, + + { NULL, NULL } +}; + +UseObject::UseObject() +{ + Event * e; + + animate = new Animate( this ); + + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_BBOX ); + setContents( CONTENTS_BODY ); + + // make sure the bounds get rotated with the object + flags |= FL_ROTATEDBOUNDS; + // by default this can activated infinitely + count = -1; + // clear out the triggertarget + triggertarget = ""; + // clear out the move thread + move_thread = ""; + // clear out the stop thread + stop_thread = ""; + // clear out the reset thread + reset_thread = ""; + // clear out the damage type, by default useobjects do not respond to damage + damage_type = MOD_NONE; + // turn on damage taking ability + takedamage = DAMAGE_YES; + // give it some health + health = 100.0f; + // set the default yaw offset + yaw_offset = 0.0f; + // set the cone + cone = (float)cos( DEG2RAD( 90.0f ) ); + // set the state + state = ""; + // set the backwards state + state_backwards = ""; + // clear out the reset_time + reset_time = 0; + // clear out the object state + objectState = 0; + // the useobject is active by default + active = true; + movetheplayer = false; + + // start off in the start animation + e = new Event( EV_Anim ); + e->AddString( "start" ); + PostEvent( e, 0.0f ); + PostEvent( EV_Show, 0.0f ); + // setup our skins once we are spawned + PostEvent( EV_UseObject_SetActiveState, 0.0f ); + + look_at_me = true; +} + +void UseObject::SetActiveState( Event *ev ) +{ + if ( !useMaterial.length() ) + { + return; + } + + if ( active && count ) + { + SurfaceCommand( useMaterial.c_str(), "+skin1" ); + } + else + { + SurfaceCommand( useMaterial.c_str(), "-skin1" ); + } + if ( objectState ) + { + SurfaceCommand( useMaterial.c_str(), "+skin2" ); + } + else + { + SurfaceCommand( useMaterial.c_str(), "-skin2" ); + } +} + +void UseObject::SetMoveThread( Event *ev ) +{ + move_thread = ev->GetString( 1 ); +} + +void UseObject::MoveThePlayer( Event *ev) +{ + movetheplayer = true; +} + +void UseObject::SetStopThread( Event *ev ) +{ + stop_thread = ev->GetString( 1 ); +} + +void UseObject::SetResetThread( Event *ev ) +{ + reset_thread = ev->GetString( 1 ); +} + +void UseObject::ActivateEvent( Event *ev ) +{ + active = true; + PostEvent( EV_UseObject_SetActiveState, 0.0f ); +} + +void UseObject::DeactivateEvent( Event *ev ) +{ + active = false; + PostEvent( EV_UseObject_SetActiveState, 0.0f ); +} + +void UseObject::SetTriggerTarget( Event *ev ) +{ + triggertarget = ev->GetString( 1 ); +} + +void UseObject::SetOffset( Event *ev ) +{ + offset = ev->GetVector( 1 ); +} + +void UseObject::SetYawOffset( Event *ev ) +{ + yaw_offset = ev->GetFloat( 1 ); +} + +void UseObject::SetCount( Event *ev ) +{ + count = ev->GetInteger( 1 ); +} + +void UseObject::SetCone( Event *ev ) +{ + cone = (float)cos( DEG2RAD( ev->GetFloat( 1 ) ) ); +} + +void UseObject::SetState( Event *ev ) +{ + state = ev->GetString( 1 ); +} + +void UseObject::SetBackwardsState( Event *ev ) +{ + state_backwards = ev->GetString( 1 ); +} + +void UseObject::UseMaterialEvent( Event *ev ) +{ + useMaterial = ev->GetString( 1 ); +} + +void UseObject::SetResetTime( Event *ev ) +{ + reset_time = ev->GetFloat( 1 ); +} + +void UseObject::Reset( Event *ev ) +{ + animate->RandomAnimate( "move_backward", EV_UseObject_Resetting ); +} + +void UseObject::Resetting( Event *ev ) +{ + SetActiveState( NULL ); + animate->RandomAnimate( "start" ); + + setSolidType( SOLID_BBOX ); + setContents( CONTENTS_BODY ); + + // reset the count + count = 1; + + // + // fire off our trigger target if appropriate + // + if ( triggertarget.length() ) + { + Event *event; + Entity *ent; + + ent = NULL; + do + { + ent = G_FindTarget( ent, triggertarget.c_str() ); + if ( !ent ) + { + break; + } + event = new Event( EV_Activate ); + event->AddEntity( this ); + ent->PostEvent( event, 0.0f ); + } + while ( 1 ); + } + + // + // fire off a thread if necessary + // + if ( reset_thread.length() ) + { + if ( !ExecuteThread( reset_thread ) ) + { + warning( "Resetting", "Null game script" ); + } + } +} + +bool UseObject::canBeUsed( const Vector &org, const Vector &dir ) +{ + float dot; + Vector forward; + Vector diff; + Vector ang; + + // see if it is active + if ( !active ) + { + return false; + } + + // if this is no longer usable, return false + if ( !count ) + { + return false; + } + + // convert our yawoffset to a vector + ang = vec_zero; + ang[ YAW ] = angles[ YAW ] + yaw_offset; + ang.AngleVectors( &forward ); + dot = forward * dir; + if ( dot < cone ) + { + return false; + } + + /* + // convert our offset to a vector in worldspace + forward = getLocalVector( offset ); + forward.normalize(); + diff = org - origin; + // diff = origin - org; + diff.normalize(); + dot = forward * dir; + if ( dot < cone ) + { + return false; + } + */ + + return true; +} + +void UseObject::DamageFunc( Event *ev ) +{ + Event *e; + Entity *attacker; + int mod; + + // if this is no longer usable, return false + if ( !count ) + { + return; + } + + // what kind of damage hit us + mod = ev->GetInteger( 9 ); + + // if we don't respond to any kind of damage, and our damage types do not match, return + if ( !MOD_matches( mod, damage_type ) ) + { + return; + } + + // get the attacker + attacker = ev->GetEntity( 3 ); + + // + // decrement the use + // + if ( count > 0 ) + { + count--; + if ( !count ) + { + setSolidType( SOLID_NOT ); + setContents( 0 ); + } + } + // setup our damage triggered event + e = new Event( EV_UseObject_DamageTriggered ); + // add our attacker + e->AddEntity( attacker ); + // start up the object with our special event + Start( e ); +} + +void UseObject::DamageTriggered( Event * ev ) +{ + // grab the attacker from our event + Stop( ev->GetEntity( 1 ) ); +} + +void UseObject::Setup( const Entity *activator, Vector *org, Vector *ang, str *newstate ) +{ + if ( ( spawnflags & MULTI_STATE ) && objectState ) + { + *newstate = state_backwards; + } + else + { + *newstate = state; + } + + // convert our offset to a vector in worldspace + MatrixTransformVector( offset, orientation, *org ); + *org += origin; + + *ang = angles; + ang->y += yaw_offset; + + // + // decrement the use + // + if ( count > 0 ) + { + count--; + if ( !count ) + { + setSolidType( SOLID_NOT ); + setContents( 0 ); + } + } +} + +void UseObject::Start( Event * ev ) +{ + // + // fire off the move_thread + // + if ( move_thread.length() ) + { + if ( !ExecuteThread( move_thread ) ) + { + warning( "Start", "Null game script" ); + } + } + + if ( ( spawnflags & MULTI_STATE ) && objectState ) + { + animate->RandomAnimate( "move_backward", ev ); + } + else + { + animate->RandomAnimate( "move", ev ); + } + SetActiveState( NULL ); +} + +bool UseObject::Loop( void ) +{ + if ( !count ) + return false; + + return true; +} + +void UseObject::Stop( Entity *activator ) +{ + if ( ( spawnflags & MULTI_STATE ) && objectState ) + { + animate->RandomAnimate( "start" ); + } + else + { + animate->RandomAnimate( "stop" ); + } + + // + // fire off our trigger target if appropriate + // + if ( triggertarget.length() ) + { + Event *event; + Entity *ent; + + ent = NULL; + do + { + ent = G_FindTarget( ent, triggertarget.c_str() ); + if ( !ent ) + { + break; + } + event = new Event( EV_Activate ); + event->AddEntity( activator ); + ent->PostEvent( event, 0.0f ); + } + while ( 1 ); + } + + // + // fire off a thread if necessary + // + if ( ( spawnflags & MULTI_STATE ) && objectState ) + { + if ( reset_thread.length() ) + { + if ( !ExecuteThread( reset_thread ) ) + { + warning( "Stop", "Null game script" ); + } + } + } + else + { + if ( stop_thread.length() ) + { + if ( !ExecuteThread( stop_thread ) ) + { + warning( "Stop", "Null game script" ); + } + } + } + + // toggle the state + objectState ^= 1; + + if ( reset_time ) + { + count = 0; + PostEvent( EV_UseObject_Reset, reset_time ); + } + + SetActiveState( NULL ); +} + +void UseObject::SetActionType( Event *ev ) +{ + action_type = ev->GetString( 1 ); +} + +/*****************************************************************************/ +/*QUAKED info_waypoint (0 0.5 0) (-8 -8 -8) (8 8 8) + +Used as a positioning device for objects + +******************************************************************************/ + +CLASS_DECLARATION( Entity, Waypoint, "info_waypoint" ) +{ + { NULL, NULL } +}; + + +/*****************************************************************************/ +// TossObject +/*****************************************************************************/ + +Event EV_TossObject_SetBounceSound +( + "bouncesound", + EV_DEFAULT, + "s", + "sound", + "When bouncing, what sound to play on impact" +); +Event EV_TossObject_SetBounceSoundChance +( + "bouncesoundchance", + EV_DEFAULT, + "f[0,1]", + "chance", + "When bouncing, the chance that the bounce sound will be played" +); + +CLASS_DECLARATION( Entity, TossObject, "TossObject" ) +{ + { &EV_Touch, &TossObject::Touch }, + { &EV_Stop, &TossObject::Stop }, + { &EV_TossObject_SetBounceSound, &TossObject::SetBounceSound }, + { &EV_TossObject_SetBounceSoundChance, &TossObject::SetBounceSoundChance }, + + { NULL, NULL } +}; + +TossObject::TossObject() +{ + animate = new Animate( this ); + + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + setMoveType( MOVETYPE_GIB ); + setSolidType( SOLID_NOT ); + bouncesound = ""; + bouncesoundchance = 1.0f; +} + +TossObject::TossObject( const str &model ) +{ + animate = new Animate( this ); + + setMoveType( MOVETYPE_GIB ); + setSolidType( SOLID_NOT ); + bouncesound = ""; + bouncesoundchance = 1.0f; + setModel( model ); +} + +void TossObject::SetBounceSound( const str &bounce ) +{ + bouncesound = bounce; +} + +void TossObject::SetBounceSound( Event *ev ) +{ + bouncesound = ev->GetString( 1 ); +} + +void TossObject::SetBounceSoundChance( float chance ) +{ + bouncesoundchance = chance; +} + +void TossObject::SetBounceSoundChance( Event *ev ) +{ + bouncesoundchance = ev->GetFloat( 1 ); +} + +void TossObject::Stop( Event *ev ) +{ + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_NOT ); + // cancel the previous fade out command + CancelEventsOfType( EV_FadeOut ); + PostEvent( EV_FadeOut, 7.0f + G_Random( 5.0f ) ); + setAngles( Vector(0, 0, 0) ); + animate->RandomAnimate( "landed" ); +} + +void TossObject::Touch( Event *ev ) +{ + Entity * ent; + + ent = ev->GetEntity( 1 ); + + // only touch the world + if ( !ent || ( ent != world ) ) + { + return; + } + // + // every time we bounce try to go back to our nominal angles + // + setAngles( angles * 0.5f ); + + if ( bouncesound.length() ) + { + if ( G_Random( 1.0f ) < bouncesoundchance ) + { + Sound( bouncesound ); + } + } +} + +void TossObject::SetVelocity( float severity ) +{ + setSolidType( SOLID_BBOX ); + velocity[0] = 100.0f * crandom(); + velocity[1] = 100.0f * crandom(); + velocity[2] = 200.0f + 100.0f * random(); + + avelocity = Vector( G_Random( 600.0f ), G_Random( 600.0f ), G_Random( 600.0f ) ); + + velocity *= severity; + + if (velocity[0] < -400.0f) + velocity[0] = -400.0f; + else if (velocity[0] > 400.0f) + velocity[0] = 400.0f; + if (velocity[1] < -400.0f) + velocity[1] = -400.0f; + else if (velocity[1] > 400.0f) + velocity[1] = 400.0f; + if (velocity[2] < 200.0f) + velocity[2] = 200.0f; // always some upwards + else if (velocity[2] > 600.0f) + velocity[2] = 600.0f; + animate->RandomAnimate( "idle" ); + // we give it 8 seconds to fall, if not it will get faded out + PostEvent( EV_FadeOut, 8.0f ); +} + +/*****************************************************************************/ +/*QUAKED func_pushobject (0.75 0.75 0.75) ? + +Pushable object + +"dmg" how much damage to cause when blocked. (default 2) +"pushsound" Sound to play when object is pushed (default is none) + +******************************************************************************/ + +Event EV_PushObject_Start +( + "start", + EV_DEFAULT, + NULL, + NULL, + "Sets up the pushobject." +); +Event EV_PushObject_SetDamage +( + "dmg", + EV_DEFAULT, + "i", + "damage", + "Set the damage." +); +Event EV_PushObject_SetPushSound +( + "pushsound", + EV_DEFAULT, + "s", + "sound", + "Set the pushing sound" +); + +CLASS_DECLARATION( Entity, PushObject, "func_pushobject" ) +{ + { &EV_PushObject_Start, &PushObject::Start }, + { &EV_Blocked, &PushObject::BlockFunc }, + { &EV_PushObject_SetDamage, &PushObject::SetDamage }, + { &EV_PushObject_SetPushSound, &PushObject::SetPushSound }, + { &EV_SetGameplayDamage, &PushObject::setDamage }, + + { NULL, NULL } +}; + +PushObject::PushObject() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + dmg = 2; + attack_finished = 0; + + pushsound = "object_slide"; + + PostEvent( EV_PushObject_Start, EV_POSTSPAWN ); +} + +//-------------------------------------------------------------- +// +// Name: setDamage +// Class: PushObject +// +// Description: This function acts as a filter to the real function. +// It gets data from the database, and then passes it +// along to the original event. This is here as an attempt +// to sway people into using the database standard instead of +// hardcoded numbers. +// +// Parameters: Event *ev +// str -- The value keyword from the database (low, medium, high, etc). +// +// Returns: None +// +//-------------------------------------------------------------- +void PushObject::setDamage( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasFormula("OffensiveDamage") ) + return; + + str damagestr = ev->GetString( 1 ); + float damagemod = 1.0f; + if ( gpm->getDefine(damagestr) != "" ) + damagemod = (float)atof(gpm->getDefine(damagestr)); + GameplayFormulaData fd(this, 0, 0, ""); + float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod); + Event *newev = new Event(EV_PushObject_SetDamage); + newev->AddInteger((int)finaldamage); + ProcessEvent(newev); +} + +void PushObject::SetPushSound( Event *ev ) +{ + pushsound = ev->GetString( 1 ); +} + +void PushObject::Start( Event *ev ) +{ + // make sure that this touches triggers + flags |= FL_TOUCH_TRIGGERS; + edict->clipmask = MASK_SOLID; + setSolidType( SOLID_BSP ); + setMoveType( MOVETYPE_PUSH ); + + // fix the bounding box so that the object isn't stuck in the ground + setSize( mins + Vector( 1.0f, 1.0f, 2.0f ), maxs - Vector( 1.0f, 1.0f, 1.0f ) ); +} + +qboolean PushObject::canPush( const Vector &dir ) +{ + trace_t trace; + + Vector end( origin.x + dir.x, origin.y + dir.y, origin.z ); + + trace = G_Trace( origin, mins, maxs, end, this, MASK_DEADSOLID, false, "PushObject::Push" ); + return ( !trace.startsolid && ( trace.fraction == 1.0f ) ); +} + +qboolean PushObject::Push( Entity *pusher, const Vector &move ) +{ + trace_t trace; + + if ( pushsound.length() ) + { + if ( !edict->s.loopSound ) + { + LoopSound( pushsound ); + PostEvent( EV_StopLoopSound, level.frametime * 5.0f ); + } + } + + Vector end( origin.x + move.x, origin.y + move.y, origin.z ); + + trace = G_Trace( origin, mins, maxs, end, this, MASK_DEADSOLID, false, "PushObject::Push" ); + if ( !trace.startsolid && ( trace.fraction > 0.0f ) ) + { + owner = pusher; + + G_PushMove( this, trace.endpos - origin, vec_zero ); + + if ( edict->s.loopSound ) + PostponeEvent( EV_StopLoopSound, level.frametime ); + + return true; + } + + return false; +} + +Entity *PushObject::getOwner( void ) +{ + return ( Entity * )owner; +} + +void PushObject::BlockFunc( Event *ev ) +{ + Entity *other; + + if ( ( dmg != 0 ) && ( level.time >= attack_finished ) ) + { + attack_finished = level.time + 0.5f; + other = ev->GetEntity( 1 ); + if ( other != owner ) + { + other->Damage( this, this, dmg, origin, vec_zero, vec_zero, 0, 0, MOD_CRUSH ); + } + } +} + +void PushObject::SetDamage( Event *ev ) +{ + dmg = ev->GetInteger( 1 ); +} + +#define SPAWN_AUTO_RESET ( 1 << 0 ) +#define NO_RANDOMNESS ( 1 << 1 ) +#define REMOVE_ON_GROUND ( 1 << 2 ) +/*****************************************************************************/ +/*QUAKED func_fallingrock (0.75 0.75 0.75) ? AUTO_RESET NO_RANDOMNESS REMOVE_ON_GROUND + +Creates a rock that, when triggered, begins falling and bounces along a path +specified by targetname. Use info_waypoint for the path. + +"targetname" the path to follow. +"dmg" how much damage to cause creatures it hits (default 20). +"speed" how fast to move (default 200). +"wait" how long to wait before falling when triggered (default 0). +"noise" sound to play when rock touches the world + +AUTO_RESET - when done falling, automatically return to the start +NO_RANDOMNESS - don't use any randomness when making the rocks fall +REMOVE_ON_GROUND - remove the rocks when done + +******************************************************************************/ + +Event EV_FallingRock_Bounce +( + "bounce", + EV_CODEONLY, + NULL, + NULL, + "sent to entity when touched." +); +Event EV_FallingRock_Rotate +( + "rotate", + EV_SCRIPTONLY, + NULL, + NULL, + "rotates the falling rock." +); +Event EV_FallingRock_SetWait +( + "wait", + EV_SCRIPTONLY, + "f", + "wait", + "How long to wait before rock starts falling." +); +Event EV_FallingRock_Start +( + "start", + EV_DEFAULT, + NULL, + NULL, + "Starts rock falling." +); +Event EV_FallingRock_SetDmg +( + "dmg", + EV_SCRIPTONLY, + "i", + "dmg", + "Set the damage from the rock." +); +Event EV_FallingRock_SetSpeed +( + "speed", + EV_DEFAULT, + "f", + "speed", + "Set the speed that the rock moves at." +); +Event EV_FallingRock_SetBounceSound +( + "noise", + EV_SCRIPTONLY, + "s", + "sound", + "Set the sound to play when the rock bounces" +); + +CLASS_DECLARATION( Entity, FallingRock, "func_fallingrock" ) +{ + { &EV_Activate, &FallingRock::Activate }, + { &EV_Touch, &FallingRock::Touch }, + { &EV_FallingRock_Bounce, &FallingRock::Bounce }, + { &EV_FallingRock_Rotate, &FallingRock::Rotate }, + { &EV_FallingRock_Start, &FallingRock::StartFalling }, + { &EV_FallingRock_SetWait, &FallingRock::SetWait }, + { &EV_FallingRock_SetSpeed, &FallingRock::SetSpeed }, + { &EV_FallingRock_SetDmg, &FallingRock::SetDmg }, + { &EV_FallingRock_SetBounceSound, &FallingRock::SetBounceSound }, + { &EV_SetGameplayDamage, &FallingRock::setDamage }, + + { NULL, NULL } +}; + +FallingRock::FallingRock() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + active = 0; + current = NULL; + setMoveType( MOVETYPE_NONE ); + wait = 0; + dmg = 20; + speed = 200; + activator = NULL; + attack_finished = 0; + + SetBounceSound( "impact_rock" ); +} + +//-------------------------------------------------------------- +// +// Name: setDamage +// Class: FallingRock +// +// Description: This function acts as a filter to the real function. +// It gets data from the database, and then passes it +// along to the original event. This is here as an attempt +// to sway people into using the database standard instead of +// hardcoded numbers. +// +// Parameters: Event *ev +// str -- The value keyword from the database (low, medium, high, etc). +// +// Returns: None +// +//-------------------------------------------------------------- +void FallingRock::setDamage( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasFormula("OffensiveDamage") ) + return; + + str damagestr = ev->GetString( 1 ); + float damagemod = 1.0f; + if ( gpm->getDefine(damagestr) != "" ) + damagemod = (float)atof(gpm->getDefine(damagestr)); + GameplayFormulaData fd(this, 0, 0, ""); + float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod); + Event *newev = new Event(EV_FallingRock_SetDmg); + newev->AddInteger((int)finaldamage); + ProcessEvent(newev); +} + + +Entity *FallingRock::SetNextBounceDir( void ) +{ + Entity *ent; + + if ( !current->target.length() ) + { + return NULL; + } + + ent = G_FindTarget( NULL, current->target.c_str() ); + if ( !ent ) + { + gi.Error( ERR_DROP, "FallingRock :: Entity with targetname of '%s' not found", current->target.c_str() ); + } + + bounce_dir = ent->origin - current->origin; + bounce_dir.normalize(); + + return ent; +} + +void FallingRock::NextBounce( void ) +{ + float time; + float distance; + Vector delta, xydelta; + float xy_speed; + float vertical_speed; + + delta = current->origin - origin; + xydelta = delta; + xydelta.z = 0; + xy_speed = speed; + + distance = xydelta.normalize(); + + time = distance / xy_speed; + + if ( !( spawnflags & NO_RANDOMNESS ) ) + { + if ( time > 1.0f ) + { + time = 0.75f + G_Random( 1.0f ); + } + + if ( time < 0.4f ) + { + time = 0.4f; + } + } + + vertical_speed = ( delta.z / time ) + ( 0.5f * gravity * sv_currentGravity->value * time ); + if ( vertical_speed < 0.0f ) + { + vertical_speed = 0.0f; + } + + velocity = xydelta * xy_speed; + velocity.z = vertical_speed; + + Vector ang( 0.0f, vectoyaw( delta ), 0.0f ); + ang.AngleVectors( NULL, &rotateaxis ); + + // make sure it leaves the ground + groundentity = NULL; +} + +void FallingRock::Rotate( Event *ev ) +{ + float mat[ 3 ][ 3 ]; + float ang; + + ang = 360.0f * FRAMETIME; + RotatePointAroundVector( mat[ 0 ], rotateaxis, orientation[ 0 ], ang ); + RotatePointAroundVector( mat[ 1 ], rotateaxis, orientation[ 1 ], ang ); + RotatePointAroundVector( mat[ 2 ], rotateaxis, orientation[ 2 ], ang ); + MatrixToEulerAngles( mat, angles ); + setAngles( angles ); + + if ( velocity != vec_zero ) + { + PostEvent( EV_FallingRock_Rotate, FRAMETIME ); + } +} + +void FallingRock::SetWait( Event *ev ) +{ + wait = ev->GetFloat( 1 ); +} + +void FallingRock::SetSpeed( Event *ev ) +{ + speed = ev->GetFloat( 1 ); +} + +void FallingRock::SetDmg( Event *ev ) +{ + dmg = ev->GetInteger( 1 ); +} + +void FallingRock::SetBounceSound( const str &sound ) +{ + bouncesound = sound; + // cache the sound in + CacheResource( bouncesound.c_str(), this ); +} + +void FallingRock::SetBounceSound( Event *ev ) +{ + SetBounceSound( ev->GetString( 1 ) ); +} + +void FallingRock::Activate( Event *ev ) +{ + if ( active == 1 ) + return; + + if ( ( active == 2 ) && ( spawnflags & SPAWN_AUTO_RESET ) ) + { + current = NULL; + activator = NULL; + setMoveType( MOVETYPE_NONE ); + NoLerpThisFrame(); + setOrigin( start_origin ); + } + + activator = ev->GetEntity( 1 ); + + if ( wait ) + { + PostEvent( EV_FallingRock_Start, wait ); + } + else + { + ProcessEvent( EV_FallingRock_Start ); + } +} + +void FallingRock::StartFalling( Event *ev ) +{ + if ( current ) + { + return; + } + + if ( !active ) + { + start_origin = origin; + } + + active = 1; + setMoveType( MOVETYPE_BOUNCE ); + setSolidType( SOLID_BBOX ); + PostEvent( EV_FallingRock_Rotate, FRAMETIME ); + edict->clipmask = MASK_SOLID|CONTENTS_BODY; + + last_bounce_origin = origin; + current = this; + current = SetNextBounceDir(); + if ( current ) + { + NextBounce(); + } +} + +void FallingRock::Touch( Event *ev ) +{ + Entity *other; + + other = ev->GetEntity( 1 ); + + if ( other != world ) + { + if ( ( velocity != vec_zero ) && ( other->takedamage ) && ( level.time >= attack_finished ) ) + { + other->Damage( this, activator, dmg, origin, vec_zero, vec_zero, 20, 0, MOD_THROWNOBJECT ); + attack_finished = level.time + FRAMETIME; + } + } + + if ( !current || ( other != world ) ) + { + return; + } + if ( bouncesound.length() ) + { + Vector delta( origin - last_bounce_origin ); + + if ( delta.length() > 8.0f ) + { + last_bounce_origin = origin; + Sound( bouncesound.c_str(), CHAN_VOICE ); + } + } + + // we have to wait to set the velocity since the physics code + // will modify it when we return. + PostEvent( EV_FallingRock_Bounce, 0.0f ); +} + +void FallingRock::Bounce( Event *ev ) +{ + Vector delta; + + if ( !current ) + { + return; + } + + do + { + // check if we've passed the waypoint + delta = origin - current->origin; + if ( ( delta * bounce_dir ) >= ( -2.0f * edict->radius ) ) + { + // call any threads on the current waypoint + if ( current->isSubclassOf( Trigger ) ) + { + current->ProcessEvent( EV_Trigger_StartThread ); + } + current = SetNextBounceDir(); + if ( !current ) + { + velocity = vec_zero; + if ( spawnflags & SPAWN_AUTO_RESET ) + { + active = 2; + } + else if ( spawnflags & REMOVE_ON_GROUND ) + { + PostEvent( EV_Remove, 0.0f ); + } + break; + } + } + else + { + NextBounce(); + break; + } + } + while( 1 ); +} diff --git a/dlls/game/misc.h b/dlls/game/misc.h new file mode 100644 index 0000000..333099d --- /dev/null +++ b/dlls/game/misc.h @@ -0,0 +1,489 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/misc.h $ +// $Revision:: 18 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Basically the big stew pot of the DLLs, or maybe a garbage bin, whichever +// metaphore you prefer. This really should be cleaned up. Anyway, this +// should contain utility functions that could be used by any entity. +// Right now it contains everything from entities that could be in their +// own file to my mother pot roast recipes. +// + +#ifndef __MISC_H__ +#define __MISC_H__ + +#include "g_local.h" +#include "entity.h" +#include "mover.h" +#include "animate.h" + +class InfoNull : public Listener + { + public: + CLASS_PROTOTYPE( InfoNull ); + + InfoNull(); + }; + +class FuncRemove : public Entity + { + public: + CLASS_PROTOTYPE( FuncRemove ); + + FuncRemove(); + }; + +class MiscModel : public Entity + { + public: + CLASS_PROTOTYPE( MiscModel ); + + MiscModel(); + }; + +class InfoNotNull : public Entity + { + public: + CLASS_PROTOTYPE( InfoNotNull ); + }; + +class ExplodingWall : public Trigger + { + protected: + int dmg; + int explosions; + float attack_finished; + Vector land_angles; + float land_radius; + float angle_speed; + int state; + Vector base_velocity; + Vector random_velocity; + Vector orig_mins, orig_maxs; + qboolean on_ground; + + public: + CLASS_PROTOTYPE( ExplodingWall ); + + ExplodingWall(); + void Setup( Event *ev ); + void AngleSpeed( Event *ev ); + void LandRadius( Event *ev ); + void LandAngles( Event *ev ); + void BaseVelocity( Event *ev ); + void RandomVelocity( Event *ev ); + void SetDmg( Event *ev ); + void SetExplosions( Event *ev ); + void SetupSecondStage( void ); + void Explode( Event *ev ); + void DamageEvent( Event *ev ); + void GroundDamage( Event *ev ); + void TouchFunc( Event *ev ); + void StopRotating( Event *ev ); + void CheckOnGround( Event *ev ); + void setDamage( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void ExplodingWall::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.ArchiveInteger( &dmg ); + arc.ArchiveInteger( &explosions ); + arc.ArchiveFloat( &attack_finished ); + arc.ArchiveVector( &land_angles ); + arc.ArchiveFloat( &land_radius ); + arc.ArchiveFloat( &angle_speed ); + arc.ArchiveInteger( &state ); + arc.ArchiveVector( &base_velocity ); + arc.ArchiveVector( &random_velocity ); + arc.ArchiveVector( &orig_mins ); + arc.ArchiveVector( &orig_maxs ); + arc.ArchiveBoolean( &on_ground ); + } + +class Teleporter : public Trigger + { + public: + str teleport_thread; + qboolean in_use; + + CLASS_PROTOTYPE( Teleporter ); + + Teleporter(); + virtual void StartTeleport( Event *ev ); + virtual void Teleport( Event *ev ); + virtual void StopTeleport( Event *ev ); + void SetThread( Event *ev ); + bool doFullTeleport( Entity *entity ); + + virtual void Archive( Archiver &arc ); + }; + +inline void Teleporter::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.ArchiveString( &teleport_thread ); + arc.ArchiveBoolean( &in_use ); + } + +class TeleporterDestination : public Entity + { + public: + Vector movedir; + + CLASS_PROTOTYPE( TeleporterDestination ); + + TeleporterDestination(); + void SetMoveDir( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void TeleporterDestination::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveVector( &movedir ); + } + +class UseAnim : public Entity + { + public: + int count; + qboolean active; + str thread; + str triggertarget; + int num_loops; + str state; + str camera; + str anim; + str key; + float delay; + float last_active_time; + str action_type; + + CLASS_PROTOTYPE( UseAnim ); + + UseAnim(); + virtual void Touched( Event *ev ); + void Reset( Event *ev ); + void SetThread( Event * ev ); + void SetTriggerTarget( Event * ev ); + void SetCount( Event * ev ); + void SetAnim( Event *ev ); + void SetState( Event *ev ); + void SetKey( Event *ev ); + void SetCamera( Event *ev ); + void SetNumLoops( Event *ev ); + void SetDelay( Event *ev ); + bool canBeUsed( Entity *activator ); + bool GetInformation( const Entity *activator, Vector * org, Vector * angles, str * animatoin, int * loopcount, str * state, str * camera ); + void TriggerTargets( Entity *activator ); + void SetActionType( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void UseAnim::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveInteger( &count ); + arc.ArchiveBoolean( &active ); + arc.ArchiveString( &thread ); + arc.ArchiveString( &triggertarget ); + arc.ArchiveInteger( &num_loops ); + arc.ArchiveString( &state ); + arc.ArchiveString( &camera ); + arc.ArchiveString( &anim ); + arc.ArchiveString( &key ); + arc.ArchiveFloat( &delay ); + arc.ArchiveFloat( &last_active_time ); + arc.ArchiveString( &action_type ); + } + +class TouchAnim : public UseAnim + { + public: + + CLASS_PROTOTYPE( TouchAnim ); + + TouchAnim(); + }; + + +class UseAnimDestination : public Entity + { + public: + int num_loops; + str state; + str anim; + + CLASS_PROTOTYPE( UseAnimDestination ); + + UseAnimDestination(); + void SetAnim( Event *ev ); + void SetState( Event *ev ); + void SetNumLoops( Event *ev ); + int GetNumLoops( void ); + str GetAnim( void ); + str GetState( void ); + virtual void Archive( Archiver &arc ); + }; + +inline void UseAnimDestination::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveInteger( &num_loops ); + arc.ArchiveString( &state ); + arc.ArchiveString( &anim ); + } + +class UseObject : public Entity + { + public: + str move_thread; + str stop_thread; + str reset_thread; + str triggertarget; + Vector offset; + float yaw_offset; + int count; + float cone; + str state; + str state_backwards; + str useMaterial; + int objectState; + float reset_time; + str action_type; + qboolean active; + bool movetheplayer; + + CLASS_PROTOTYPE( UseObject ); + + UseObject(); + void SetMoveThread( Event * ev ); + void SetStopThread( Event * ev ); + void SetResetThread( Event * ev ); + void SetTriggerTarget( Event * ev ); + void SetOffset( Event * ev ); + void SetYawOffset( Event * ev ); + void SetCount( Event * ev ); + void SetCone( Event * ev ); + void SetState( Event * ev ); + void SetBackwardsState( Event * ev ); + void SetResetTime( Event * ev ); + void Reset( Event * ev ); + void Resetting( Event * ev ); + void DamageTriggered( Event * ev ); + void DamageFunc( Event * ev ); + bool canBeUsed( const Vector &org, const Vector &dir ); + void Setup( const Entity *activator, Vector *org, Vector *ang, str *newstate ); + void Start( Event * ev = NULL ); + bool Loop( void ); + void SetActiveState( Event *ev ); + void Stop( Entity *activator ); + void ActivateEvent( Event *ev ); + void DeactivateEvent( Event *ev ); + void UseMaterialEvent( Event *ev ); + void SetActionType( Event *ev ); + void MoveThePlayer( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void UseObject::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveString( &move_thread ); + arc.ArchiveString( &stop_thread ); + arc.ArchiveString( &reset_thread ); + arc.ArchiveString( &triggertarget ); + arc.ArchiveVector( &offset ); + arc.ArchiveFloat( &yaw_offset ); + arc.ArchiveInteger( &count ); + arc.ArchiveFloat( &cone ); + arc.ArchiveString( &state ); + arc.ArchiveString( &state_backwards ); + arc.ArchiveString( &useMaterial ); + arc.ArchiveInteger( &objectState ); + arc.ArchiveFloat( &reset_time ); + arc.ArchiveString( &action_type ); + arc.ArchiveBoolean( &active ); + arc.ArchiveBool( &movetheplayer ); + + } + + +class Waypoint : public Entity + { + public: + CLASS_PROTOTYPE( Waypoint ); + }; + +class TossObject : public Entity + { + private: + str bouncesound; + float bouncesoundchance; + void Stop( Event *ev ); + void Touch( Event *ev ); + void SetBounceSound( Event *ev ); + void SetBounceSoundChance( Event *ev ); + public: + CLASS_PROTOTYPE( TossObject ); + + TossObject(); + TossObject( const str &modelname ); + void SetBounceSound( const str &bounce ); + void SetBounceSoundChance( float chance ); + void SetVelocity( float severity ); + virtual void Archive( Archiver &arc ); + }; + +inline void TossObject::Archive + ( + Archiver &arc + ) + + { + Entity::Archive( arc ); + + arc.ArchiveString( &bouncesound ); + arc.ArchiveFloat( &bouncesoundchance ); + } + + +class PushObject : public Entity + { + private: + EntityPtr owner; + float attack_finished; + int dmg; + str pushsound; + + public: + CLASS_PROTOTYPE( PushObject ); + + PushObject(); + void Start( Event *ev ); + void SetDamage( Event *ev ); + void BlockFunc( Event *ev ); + void SetPushSound( Event *ev ); + qboolean Push( Entity *pusher, const Vector &move ); + qboolean canPush( const Vector &dir ); + void setDamage( Event *ev ); + + Entity *getOwner( void ); + + virtual void Archive( Archiver &arc ); + }; + +inline void PushObject::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveSafePointer( &owner ); + arc.ArchiveFloat( &attack_finished ); + arc.ArchiveInteger( &dmg ); + arc.ArchiveString( &pushsound ); + } + +class FallingRock : public Entity + { + private: + int active; + Vector start_origin; + Vector last_bounce_origin; + Entity *current; + Entity *activator; + Vector bounce_dir; + Vector rotateaxis; + float attack_finished; + float wait; + float speed; + int dmg; + str bouncesound; + + void Touch( Event *ev ); + void Bounce( Event *ev ); + void Rotate( Event *ev ); + void Activate( Event *ev ); + void NextBounce( void ); + void StartFalling( Event *ev ); + void SetWait( Event *ev ); + void SetSpeed( Event *ev ); + void SetDmg( Event *ev ); + Entity *SetNextBounceDir( void ); + void SetBounceSound( const str &sound ); + void SetBounceSound( Event *ev ); + void setDamage( Event *ev ); + virtual void Archive( Archiver &arc ); + + public: + CLASS_PROTOTYPE( FallingRock ); + + FallingRock(); + }; + +inline void FallingRock::Archive + ( + Archiver &arc + ) + + { + Entity::Archive( arc ); + + arc.ArchiveInteger( &active ); + arc.ArchiveVector( &start_origin ); + arc.ArchiveVector( &last_bounce_origin ); + arc.ArchiveObjectPointer( ( Class ** )¤t ); + arc.ArchiveObjectPointer( ( Class ** )&activator ); + arc.ArchiveVector( &bounce_dir ); + arc.ArchiveVector( &rotateaxis ); + arc.ArchiveFloat( &attack_finished ); + arc.ArchiveFloat( &wait ); + arc.ArchiveFloat( &speed ); + arc.ArchiveInteger( &dmg ); + arc.ArchiveString( &bouncesound ); + if ( arc.Loading() ) + { + SetBounceSound( bouncesound ); + } + } + +#endif /* misc.h */ diff --git a/dlls/game/mover.cpp b/dlls/game/mover.cpp new file mode 100644 index 0000000..6bc2fd2 --- /dev/null +++ b/dlls/game/mover.cpp @@ -0,0 +1,281 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/mover.cpp $ +// $Revision:: 11 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class for any object that needs to move to specific locations over a +// period of time. This class is kept separate from most entities to keep +// class size down for objects that don't need such behavior. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "trigger.h" +#include "mover.h" + +CLASS_DECLARATION( Listener, Mover, "mover" ) +{ + { &EV_MoveDone, &Mover::MoveDone }, + + { NULL, NULL } +}; + + +Mover::Mover() +{ + // Should always use other constructor + + assert( 0 ); +} + +Mover::Mover( Entity *ent ) +{ + self = ent; + + self->edict->s.pos.trType = TR_LERP; + endevent = NULL; + finaldest = ent->origin; +} + +Mover::~Mover() +{ +} + +void Mover::MoveDone( Event *ev ) +{ + Event * event; + Vector move; + Vector amove; + + // zero out the movement + if ( moveflags & MOVE_ANGLES ) + { + self->avelocity = vec_zero; + amove = angledest - self->localangles; + } + else + { + amove = vec_zero; + } + + if ( moveflags & MOVE_ORIGIN ) + { + self->velocity = vec_zero; + move = finaldest - self->GetLocalOrigin(); + } + else + { + move = vec_zero; + } + + if ( !G_PushMove( self, move, amove ) ) + { + // Delay finish till we can move into the final position + PostEvent( EV_MoveDone, FRAMETIME ); + return; + } + + // + // After moving, set origin to exact final destination + // + if ( moveflags & MOVE_ORIGIN ) + { + self->setOrigin( finaldest ); + } + + if ( moveflags & MOVE_ANGLES ) + { + self->localangles = angledest; + + if ( ( self->localangles.x >= 360.0f ) || ( self->localangles.x < 0.0f ) ) + { + self->localangles.x -= (float)( ( (int)self->localangles.x / 360 ) * 360 ); + } + if ( ( self->localangles.y >= 360.0f ) || ( self->localangles.y < 0.0f ) ) + { + self->localangles.y -= (float)( ( (int)self->localangles.y / 360 ) * 360 ); + } + if ( ( self->localangles.z >= 360.0f ) || ( self->localangles.z < 0.0f ) ) + { + self->localangles.z -= (float)( ( (int)self->localangles.z / 360 ) * 360 ); + } + } + + event = endevent; + endevent = NULL; + self->ProcessEvent( event ); +} + +/* +============= +MoveTo + +calculate self.velocity and self.nextthink to reach dest from +self.origin traveling at speed +=============== +*/ +void Mover::MoveTo( const Vector &tdest, const Vector &angdest, float tspeed, Event &event ) +{ + Vector vdestdelta; + Vector angdestdelta; + float len; + float traveltime; + + assert( tspeed >= 0.0f ); + + if ( !tspeed ) + { + error( "MoveTo", "No speed is defined!" ); + } + + if ( tspeed < 0.0f ) + { + error( "MoveTo", "Speed is negative!" ); + } + + // Cancel previous moves + CancelEventsOfType( EV_MoveDone ); + + moveflags = 0; + + if ( endevent ) + { + delete endevent; + } + endevent = new Event( event ); + + finaldest = tdest; + angledest = angdest; + + if ( finaldest != self->GetLocalOrigin() ) + { + moveflags |= MOVE_ORIGIN; + } + if ( angledest != self->localangles ) + { + moveflags |= MOVE_ANGLES; + } + + if ( !moveflags ) + { + // stop the object from moving + self->velocity = vec_zero; + self->avelocity = vec_zero; + + // post the event so we don't wait forever + PostEvent( EV_MoveDone, FRAMETIME ); + return; + } + + // set destdelta to the vector needed to move + vdestdelta = tdest - self->GetLocalOrigin(); + angdestdelta = angdest - self->localangles; + + if ( tdest == self->GetLocalOrigin() ) + { + // calculate length of vector based on angles + len = angdestdelta.length(); + } + else + { + // calculate length of vector based on distance + len = vdestdelta.length(); + } + + // divide by speed to get time to reach dest + traveltime = len / tspeed; + + // Quantize to FRAMETIME + // E3 HACK + // traveltime *= ( 1 / FRAMETIME ); + // traveltime = ( float )( (int)traveltime ) * FRAMETIME; + if ( traveltime < FRAMETIME ) + { + traveltime = FRAMETIME; + vdestdelta = vec_zero; + angdestdelta = vec_zero; + } + + // scale the destdelta vector by the time spent traveling to get velocity + if ( moveflags & MOVE_ORIGIN ) + { + self->velocity = vdestdelta * ( 1.0f / traveltime ); + } + + if ( moveflags & MOVE_ANGLES ) + { + self->avelocity = angdestdelta * ( 1.0f / traveltime ); + } + + PostEvent( EV_MoveDone, traveltime ); +} + +/* +============= +LinearInterpolate +=============== +*/ +void Mover::LinearInterpolate( const Vector &tdest, const Vector &angdest, float time, Event &event ) +{ + Vector vdestdelta; + Vector angdestdelta; + float t; + + if ( endevent ) + { + delete endevent; + } + endevent = new Event( event ); + finaldest = tdest; + angledest = angdest; + + // Cancel previous moves + CancelEventsOfType( EV_MoveDone ); + + // Quantize to FRAMETIME + //E3 HACK + // time *= ( 1 / FRAMETIME ); + // time = ( float )( (int)time ) * FRAMETIME; + if ( time < FRAMETIME ) + { + time = FRAMETIME; + } + + moveflags = 0; + t = 1.0f / time; + // scale the destdelta vector by the time spent traveling to get velocity + if ( finaldest != self->GetLocalOrigin() ) + { + vdestdelta = tdest - self->GetLocalOrigin(); + self->velocity = vdestdelta * t; + moveflags |= MOVE_ORIGIN; + } + + if ( angledest != self->localangles ) + { + angdestdelta = angdest - self->localangles; + self->avelocity = angdestdelta * t; + moveflags |= MOVE_ANGLES; + } + + PostEvent( EV_MoveDone, time ); +} + +void Mover::SetEndEvent( const int endEvent ) +{ + if ( endevent ) + { + delete endevent; + } + endevent = new Event( endEvent ); +} diff --git a/dlls/game/mover.h b/dlls/game/mover.h new file mode 100644 index 0000000..70ab8c1 --- /dev/null +++ b/dlls/game/mover.h @@ -0,0 +1,70 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/mover.h $ +// $Revision:: 7 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class for any object that needs to move to specific locations over a +// period of time. This class is kept separate from most entities to keep +// class size down for objects that don't need such behavior. +// + +#ifndef __MOVER_H__ +#define __MOVER_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" + +#define MOVE_ANGLES 1 +#define MOVE_ORIGIN 2 + +class Mover : public Listener + { + private: + Vector finaldest; + Vector angledest; + Event *endevent; + int moveflags; + + Entity *self; + + public: + CLASS_PROTOTYPE( Mover ); + + Mover(); + Mover( Entity *ent ); + virtual ~Mover(); + void MoveDone( Event *ev ); + void MoveTo( const Vector &tdest, const Vector &angdest, float tspeed, Event &event ); + void LinearInterpolate( const Vector &tdest, const Vector &angdest, float time, Event &event ); + const int GetMoveFlags( void ) const { return moveflags; } + void SetMoveFlags( const int moveFlags ) { moveflags = moveFlags; } + void SetEndEvent( const int endEvent ); + void SetFinalDestination( const Vector &finalDestination) { finaldest = finalDestination; } + virtual void Archive( Archiver &arc ); + }; + +inline void Mover::Archive + ( + Archiver &arc + ) + { + Listener::Archive( arc ); + + arc.ArchiveVector( &finaldest ); + arc.ArchiveVector( &angledest ); + arc.ArchiveEventPointer( &endevent ); + arc.ArchiveInteger( &moveflags ); + } + +#endif diff --git a/dlls/game/mp_awardsystem.cpp b/dlls/game/mp_awardsystem.cpp new file mode 100644 index 0000000..9effbc1 --- /dev/null +++ b/dlls/game/mp_awardsystem.cpp @@ -0,0 +1,768 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/mp_awardsystem.cpp $ +// $Revision:: 22 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// Description: +// + +#include "_pch_cpp.h" + +#include "mp_manager.hpp" +#include "mp_awardsystem.hpp" +#include "powerups.h" + +// Setup constants + +const float AwardSystem::_minEfficiencyForEfficiencyAward = 0.5f; +const int AwardSystem::_minImpressivesForSharpshooter = 5; +const float AwardSystem::_minPercentForDemolitionist = 0.75f; +const float AwardSystem::_minPercentForVaporizer = 0.75f; + +const int AwardSystem::_minKillsForChampion = 20; +const int AwardSystem::_minKillsForMaster = 15; +const int AwardSystem::_minKillsForExpert = 10; +const int AwardSystem::_minKillsForAce = 5; + +const float AwardSystem::_maxExcellentTime = 2.0f; +const float AwardSystem::_deniedDistance = 160.0f; + +MultiplayerPlayerAwardData::MultiplayerPlayerAwardData() +{ + init(); +} + +void MultiplayerPlayerAwardData::init( void ) +{ + _entnum = 0; + _playing = false; + + reset(); + + multiplayerManager.cacheMultiplayerFiles( "mp_awardsystem" ); +} + +void MultiplayerPlayerAwardData::reset( void ) +{ + int i; + + _lastKillTime = 0.0f; + _killStreak = 0; + _highestKillStreak = 0; + _shotsFired = 0; + _shotsHit = 0; + _lastHitTime = 0.0f; + + _weaponsKilledWith = 0; + _powerupsUsed = 0; + + _numKills = 0; + _numKillsWithExplosives = 0; + + _numFlagCaptures = 0; + _numFlagReturns = 0; + _numFlagGuardingKills = 0; + + _numImpressives = 0; + + _numDeaths = 0; + + _numFirstStrikeAwards = 0; + _numImpressiveAwards = 0; + _numExcellentAwards = 0; + _numAceAwards = 0; + _numExpertAwards = 0; + _numMasterAwards = 0; + _numChampionAwards = 0; + _numDeniedAwards = 0; + + _lastAwardIconIndex = 0; + _lastAwardTime = 0.0f; + + for ( i = 0 ; i < MAX_SCORE_ICONS ; i++ ) + { + _afterMatchAwardIndexes[ i ] = 0; + } +} + +AwardSystem::AwardSystem() +{ + _playerAwardData = NULL; + + // Save off icon indexes + + _efficiencyIconIndex = gi.imageindex( "sysimg/icons/mp/award_efficiency" ); + _sharpshooterIconIndex = gi.imageindex( "sysimg/icons/mp/award_sharpshooter" ); + _untouchableIconIndex = gi.imageindex( "sysimg/icons/mp/award_untouchable" ); + + _logisticsIconIndex = gi.imageindex( "sysimg/icons/mp/award_logistics" ); + _tacticianIconIndex = gi.imageindex( "sysimg/icons/mp/award_tactician" ); + _demolitionistIconIndex = gi.imageindex( "sysimg/icons/mp/award_demolitionist" ); + + _aceIconIndex = gi.imageindex( "sysimg/icons/mp/award_ace" ); + _expertIconIndex = gi.imageindex( "sysimg/icons/mp/award_expert" ); + _masterIconIndex = gi.imageindex( "sysimg/icons/mp/award_master" ); + _championIconIndex = gi.imageindex( "sysimg/icons/mp/award_champion" ); + + _mvpIconIndex = gi.imageindex( "sysimg/icons/mp/award_mvp" ); + _defenderIconIndex = gi.imageindex( "sysimg/icons/mp/award_defender" ); + _warriorIconIndex = gi.imageindex( "sysimg/icons/mp/award_warrior" ); + _carrierIconIndex = gi.imageindex( "sysimg/icons/mp/award_carrier" ); + _interceptorIconIndex = gi.imageindex( "sysimg/icons/mp/award_interceptor" ); + _braveryIconIndex = gi.imageindex( "sysimg/icons/mp/award_bravery" ); + + _firstStrikeIconIndex = gi.imageindex( "sysimg/icons/mp/award_firstStrike" ); + _impressiveIconIndex = gi.imageindex( "sysimg/icons/mp/award_impressive" ); + _excellentIconIndex = gi.imageindex( "sysimg/icons/mp/award_excellent" ); + _deniedIconIndex = gi.imageindex( "sysimg/icons/mp/award_denied" ); + + _powerups.FreeObjectList(); + _weapons.FreeObjectList(); +} + +void AwardSystem::init( int maxPlayers ) +{ + _maxPlayers = maxPlayers; + _playerAwardData = new MultiplayerPlayerAwardData[ _maxPlayers ]; + + _hadFirstStrike = false; +} + +AwardSystem::~AwardSystem() +{ + delete [] _playerAwardData; + _playerAwardData = NULL; + + _powerups.FreeObjectList(); + _weapons.FreeObjectList(); +} + +void AwardSystem::initItems( void ) +{ + Entity *entity; + gentity_t *edict; + int i; + + // Build the list of powerups and weapons + + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) + { + edict = &g_entities[ i ]; + + if ( !edict->inuse || !edict->entity ) + continue; + + entity = edict->entity; + + if ( entity->isSubclassOf( Powerup ) ) + { + Powerup *powerup; + + // Add powerup name to the list if it's not already in there + + powerup = (Powerup *)entity; + + if ( !getItemIndex( _powerups, powerup->getName() ) ) + { + _powerups.AddObject( powerup->getName() ); + } + } + else if ( entity->isSubclassOf( Weapon ) ) + { + Weapon *weapon; + + // Add weapon name to the list if it's not already in there + + weapon = (Weapon *)entity; + + if ( !getItemIndex( _weapons, weapon->getName() ) ) + { + _weapons.AddObject( weapon->getName() ); + } + } + } +} + +int AwardSystem::getItemIndex( Container &itemContainer, const str &itemName ) +{ + int i; + int numItems; + str name; + + numItems = itemContainer.NumObjects(); + + // Find the index of this item + + for ( i = 1 ; i <= numItems ; i++ ) + { + name = itemContainer.ObjectAt( i ); + + if ( name == itemName ) + { + return i; + } + } + + return 0; +} + +int AwardSystem::getNumItems( Container &itemContainer ) +{ + return itemContainer.NumObjects(); +} + +int AwardSystem::getNumItemBits( unsigned int bits ) +{ + int i; + int numBits; + + numBits = 0; + + for ( i = 0 ; i < 32 ; i++ ) + { + if ( bits & ( 1 << i ) ) + { + numBits++; + } + } + + return numBits; +} + +int AwardSystem::getAfterMatchAward( Player *player, int index ) +{ + if ( !player || ( index < 0 ) || ( index >= MAX_SCORE_ICONS ) ) + return 0; + + return _playerAwardData[ player->entnum ]._afterMatchAwardIndexes[ index ]; +} + +void AwardSystem::matchOver( void ) +{ + int i; + + // Calculate all after match awards + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + if ( _playerAwardData[ i ]._playing ) + { + // Efficiency award + + if ( ( _playerAwardData[ i ]._shotsFired > 0 ) ) + { + if ( (float)_playerAwardData[ i ]._shotsHit / (float)_playerAwardData[ i ]._shotsFired > _minEfficiencyForEfficiencyAward ) + { + _playerAwardData[ i ]._afterMatchAwardIndexes[ SCOREICON1 ] = _efficiencyIconIndex; + } + } + + // Sharpshooter award + + if ( _playerAwardData[ i ]._numImpressives >= _minImpressivesForSharpshooter ) + { + _playerAwardData[ i ]._afterMatchAwardIndexes[ SCOREICON2 ] = _sharpshooterIconIndex; + } + + // Untouchable award + + if ( _playerAwardData[ i ]._numDeaths == 0 ) + { + _playerAwardData[ i ]._afterMatchAwardIndexes[ SCOREICON3 ] = _untouchableIconIndex; + } + + // Player type award + + if ( getNumItems( _weapons ) && ( getNumItemBits( _playerAwardData[ i ]._weaponsKilledWith ) == getNumItems( _weapons ) ) ) + { + _playerAwardData[ i ]._afterMatchAwardIndexes[ SCOREICON4 ] = _tacticianIconIndex; + } + else if ( getNumItems( _powerups ) && ( getNumItemBits( _playerAwardData[ i ]._powerupsUsed ) == getNumItems( _powerups ) ) ) + { + _playerAwardData[ i ]._afterMatchAwardIndexes[ SCOREICON4 ] = _logisticsIconIndex; + } + else if ( _playerAwardData->_numKills && ( _playerAwardData->_numKillsWithExplosives / _playerAwardData->_numKills > _minPercentForDemolitionist ) ) + { + _playerAwardData[ i ]._afterMatchAwardIndexes[ SCOREICON4 ] = _demolitionistIconIndex; + } + + // Streak award + + if ( _playerAwardData[ i ]._highestKillStreak >= _minKillsForChampion ) + _playerAwardData[ i ]._afterMatchAwardIndexes[ SCOREICON5 ] = _championIconIndex; + else if ( _playerAwardData[ i ]._highestKillStreak >= _minKillsForMaster ) + _playerAwardData[ i ]._afterMatchAwardIndexes[ SCOREICON5 ] = _masterIconIndex; + else if ( _playerAwardData[ i ]._highestKillStreak >= _minKillsForExpert ) + _playerAwardData[ i ]._afterMatchAwardIndexes[ SCOREICON5 ] = _expertIconIndex; + else if ( _playerAwardData[ i ]._highestKillStreak >= _minKillsForAce ) + _playerAwardData[ i ]._afterMatchAwardIndexes[ SCOREICON5 ] = _aceIconIndex; + } + } + + // Team player awards + + awardTeamAward( AFTERMATCH_TEAM_AWARD_MVP, "Red" ); + awardTeamAward( AFTERMATCH_TEAM_AWARD_DEFENDER, "Red" ); + awardTeamAward( AFTERMATCH_TEAM_AWARD_WARRIOR, "Red" ); + awardTeamAward( AFTERMATCH_TEAM_AWARD_CARRIER, "Red" ); + awardTeamAward( AFTERMATCH_TEAM_AWARD_INTERCEPTOR, "Red" ); + awardTeamAward( AFTERMATCH_TEAM_AWARD_BRAVERY, "Red" ); + + awardTeamAward( AFTERMATCH_TEAM_AWARD_MVP, "Blue" ); + awardTeamAward( AFTERMATCH_TEAM_AWARD_DEFENDER, "Blue" ); + awardTeamAward( AFTERMATCH_TEAM_AWARD_WARRIOR, "Blue" ); + awardTeamAward( AFTERMATCH_TEAM_AWARD_CARRIER, "Blue" ); + awardTeamAward( AFTERMATCH_TEAM_AWARD_INTERCEPTOR, "Blue" ); + awardTeamAward( AFTERMATCH_TEAM_AWARD_BRAVERY, "Blue" ); +} + +void AwardSystem::awardTeamAward( AfterMatchTeamAwardType teamAward, const char *teamName ) +{ + int i; + Player *player; + Player *bestPlayer = NULL; + Team *team; + int bestNumber = 0; + int numToUse; + int iconIndex; + + // Go through all of the player and calculate this award for this team + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + if ( !_playerAwardData[ i ]._playing ) + continue; + + // Make sure this player hasn't already gotten a team award + + if ( _playerAwardData[ i ]._afterMatchAwardIndexes[ SCOREICON6 ] >= 0 ) + continue; + + player = (Player *)g_entities[ _playerAwardData[ i ]._entnum ].entity; + + team = multiplayerManager.getPlayersTeam( player ); + + if ( !team ) + continue; + + // Make sure this player is on the correct team + + if ( stricmp( team->getName().c_str(), teamName ) != 0 ) + continue; + + // Get the number to use for comparision purposes + + numToUse = 0; + + switch( teamAward ) + { + case AFTERMATCH_TEAM_AWARD_MVP : + numToUse = multiplayerManager.getPoints( player ); + break; + case AFTERMATCH_TEAM_AWARD_DEFENDER : + numToUse = _playerAwardData[ i ]._numFlagGuardingKills; + break; + case AFTERMATCH_TEAM_AWARD_WARRIOR : + numToUse = _playerAwardData[ i ]._numKills; + break; + case AFTERMATCH_TEAM_AWARD_CARRIER : + numToUse = _playerAwardData[ i ]._numFlagCaptures; + break; + case AFTERMATCH_TEAM_AWARD_INTERCEPTOR : + numToUse = _playerAwardData[ i ]._numFlagReturns; + break; + case AFTERMATCH_TEAM_AWARD_BRAVERY : + numToUse = _playerAwardData[ i ]._numDeaths; + break; + } + + // See if this player has the highest score for this type + + if ( numToUse > bestNumber ) + { + bestPlayer = player; + bestNumber = numToUse; + } + } + + if ( bestPlayer ) + { + // Get the icon index to use for this award + + iconIndex = -1; + + switch( teamAward ) + { + case AFTERMATCH_TEAM_AWARD_MVP : + iconIndex = _mvpIconIndex; + break; + case AFTERMATCH_TEAM_AWARD_DEFENDER : + iconIndex = _defenderIconIndex; + break; + case AFTERMATCH_TEAM_AWARD_WARRIOR : + iconIndex = _warriorIconIndex; + break; + case AFTERMATCH_TEAM_AWARD_CARRIER : + iconIndex = _carrierIconIndex; + break; + case AFTERMATCH_TEAM_AWARD_INTERCEPTOR : + iconIndex = _interceptorIconIndex; + break; + case AFTERMATCH_TEAM_AWARD_BRAVERY : + iconIndex = _braveryIconIndex; + break; + } + + // Give the player the award + + _playerAwardData[ bestPlayer->entnum ]._afterMatchAwardIndexes[ SCOREICON6 ] = iconIndex; + } +} + +void AwardSystem::playerEventNotification( const char *eventName, const char *eventItemName, Player *eventPlayer ) +{ + // See if we care about this player notification + + if ( stricmp( eventName, "flag-captured" ) == 0 ) + { + _playerAwardData[ eventPlayer->entnum ]._numFlagCaptures++; + } + else if ( stricmp( eventName, "flag-returned" ) == 0 ) + { + _playerAwardData[ eventPlayer->entnum ]._numFlagReturns++; + } + else if ( stricmp( eventName, "flag-guarded" ) == 0 ) + { + _playerAwardData[ eventPlayer->entnum ]._numFlagGuardingKills++; + } +} + +void AwardSystem::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + str weaponName; + int itemIndex; + + MultiplayerPlayerAwardData *attackerAwardData; + + // Modify the killed player's stats + + _playerAwardData[ killedPlayer->entnum ]._killStreak = 0; + _playerAwardData[ killedPlayer->entnum ]._numDeaths++; + + // Make sure this isn't a suicide + + if ( killedPlayer == attackingPlayer ) + return; + + if ( !attackingPlayer ) + return; + + // Make sure this isn't one teammate killing another + + if ( multiplayerManager.getPlayersTeam( attackingPlayer ) && multiplayerManager.getPlayersTeam( killedPlayer ) && + ( multiplayerManager.getPlayersTeam( attackingPlayer ) == multiplayerManager.getPlayersTeam( killedPlayer ) ) ) + return; + + attackerAwardData = &_playerAwardData[ attackingPlayer->entnum ]; + + // Test for humiliation + + if ( meansOfDeath == MOD_SWORD ) + { + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_humil.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + multiplayerManager.playerSound( killedPlayer->entnum, "localization/sound/dialog/dm/comp_humil.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + } + + // Test for first strike award (first kill of the match) + + if ( !_hadFirstStrike ) + { + multiplayerManager.centerPrint( attackingPlayer->entnum, "$$FirstStrike$$\n", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_firstst.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + + attackerAwardData->_numFirstStrikeAwards++; + + attackerAwardData->_lastAwardIconIndex = _firstStrikeIconIndex; + attackerAwardData->_lastAwardTime = multiplayerManager.getTime(); + + _hadFirstStrike = true; + } + + // Test for impressive award (multiple kills at same time) + // and test for excellent award (multiple kills within 2 seconds) + + if ( attackerAwardData->_lastKillTime == level.time ) + { + multiplayerManager.centerPrint( attackingPlayer->entnum, "$$Impressive$$\n", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_impress.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + + attackerAwardData->_numImpressives++; + + attackerAwardData->_numImpressiveAwards++; + + attackerAwardData->_lastAwardIconIndex = _impressiveIconIndex; + attackerAwardData->_lastAwardTime = multiplayerManager.getTime(); + } + else if ( ( attackerAwardData->_lastKillTime > 0.0f ) && ( attackerAwardData->_lastKillTime + _maxExcellentTime > level.time ) ) + { + multiplayerManager.centerPrint( attackingPlayer->entnum, "$$Excellent$$\n", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_excell.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + + attackerAwardData->_numExcellentAwards++; + + attackerAwardData->_lastAwardIconIndex = _excellentIconIndex; + attackerAwardData->_lastAwardTime = multiplayerManager.getTime(); + } + + // Modify some of the attacker's stats + + attackerAwardData->_lastKillTime = level.time; + attackerAwardData->_killStreak++; + + attackerAwardData->_numKills++; + + if ( meansOfDeath == MOD_EXPLOSION ) + { + attackerAwardData->_numKillsWithExplosives++; + } + + // See if this is the highest kill streak for this player + + if ( attackerAwardData->_killStreak > attackerAwardData->_highestKillStreak) + attackerAwardData->_highestKillStreak = attackerAwardData->_killStreak; + + // See if we should give a kill streak award + + if ( attackerAwardData->_killStreak == _minKillsForChampion ) + { + multiplayerManager.centerPrint( attackingPlayer->entnum, "$$Champion$$\n", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_champ.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + attackerAwardData->_numChampionAwards++; + + attackerAwardData->_lastAwardIconIndex = _championIconIndex; + attackerAwardData->_lastAwardTime = multiplayerManager.getTime(); + } + else if ( attackerAwardData->_killStreak == _minKillsForMaster ) + { + multiplayerManager.centerPrint( attackingPlayer->entnum, "$$Master$$\n", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_master.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + attackerAwardData->_numMasterAwards++; + + attackerAwardData->_lastAwardIconIndex = _masterIconIndex; + attackerAwardData->_lastAwardTime = multiplayerManager.getTime(); + } + else if ( attackerAwardData->_killStreak == _minKillsForExpert ) + { + multiplayerManager.centerPrint( attackingPlayer->entnum, "$$Expert$$\n", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_expert.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + attackerAwardData->_numExpertAwards++; + + attackerAwardData->_lastAwardIconIndex = _expertIconIndex; + attackerAwardData->_lastAwardTime = multiplayerManager.getTime(); + } + else if ( attackerAwardData->_killStreak == _minKillsForAce ) + { + multiplayerManager.centerPrint( attackingPlayer->entnum, "$$Ace$$\n", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_ace.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + attackerAwardData->_numAceAwards++; + + attackerAwardData->_lastAwardIconIndex = _aceIconIndex; + attackerAwardData->_lastAwardTime = multiplayerManager.getTime(); + } + + // Keep track of which weapons this player killed things with + + attackingPlayer->getActiveWeaponName( WEAPON_ANY, weaponName ); + + itemIndex = getItemIndex( _weapons, weaponName ); + itemIndex -= 1; + + if ( ( itemIndex >= 0 ) && ( itemIndex < 32 ) ) + { + attackerAwardData->_weaponsKilledWith |= ( 1 << itemIndex ); + } +} + +void AwardSystem::pickedupItem( Player *player, MultiplayerItemType itemType, const char *itemName ) +{ + int itemIndex; + int i; + Player *playerToCheck; + Vector diff; + float distance; + + // We only care about powerups + + if ( itemType != MP_ITEM_TYPE_POWERUP ) + return; + + // Mark that we have used this powerup + + itemIndex = getItemIndex( _powerups, itemName ); + itemIndex -= 1; + + if ( ( itemIndex >= 0 ) && ( itemIndex < 32 ) ) + { + _playerAwardData[ player->entnum ]._powerupsUsed |= ( 1 << itemIndex ); + } + + // See if the player just denied someone else this powerup + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + playerToCheck = multiplayerManager.getPlayer( i ); + + if ( !playerToCheck ) + continue; + + // Make sure not to check the player that picked up the item + + if ( playerToCheck == player ) + continue; + + // Make sure the player isn't a spectator + + if ( multiplayerManager.isPlayerSpectator( playerToCheck ) ) + continue; + + // Make sure the players' aren't on the same team + + if ( multiplayerManager.getPlayersTeam( player ) == multiplayerManager.getPlayersTeam( playerToCheck ) ) + continue; + + // See if the player is really close by + + diff = playerToCheck->origin - player->origin; + distance = diff.length(); + + if ( distance < _deniedDistance ) + { + // Denied award (really a taunt) + + multiplayerManager.centerPrint( playerToCheck->entnum, "$$Denied$$\n", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.playerSound( playerToCheck->entnum, "localization/sound/dialog/dm/comp_denied.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + _playerAwardData[ i ]._numDeniedAwards++; + + _playerAwardData[ i ]._lastAwardIconIndex = _deniedIconIndex; + _playerAwardData[ i ]._lastAwardTime = multiplayerManager.getTime(); + } + } +} + +void AwardSystem::playerFired( Player *attackingPlayer ) +{ + _playerAwardData[ attackingPlayer->entnum ]._shotsFired++; +} + +void AwardSystem::playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ) +{ + if ( !attackingPlayer ) + return; + + // Make sure this is a new valid hit + + if ( ( damagedPlayer != attackingPlayer ) && + ( _playerAwardData[ attackingPlayer->entnum ]._lastHitTime != multiplayerManager.getTime() ) ) + { + // Player actually hit someone, record it as a shot hit + + _playerAwardData[ attackingPlayer->entnum ]._shotsHit++; + _playerAwardData[ attackingPlayer->entnum ]._lastHitTime = multiplayerManager.getTime(); + } +} + +void AwardSystem::addPlayer( Player *player ) +{ + _playerAwardData[ player->entnum ].reset(); + _playerAwardData[ player->entnum ]._playing = true; +} + +void AwardSystem::removePlayer( Player *player ) +{ + _playerAwardData[ player->entnum ].reset(); +} + +int AwardSystem::getStat( Player *player, int statNum, int value ) +{ + if ( statNum == STAT_MP_AWARD_COUNT ) + { + if ( _playerAwardData[ player->entnum ]._lastAwardTime + 5.0f >= multiplayerManager.getTime() ) + { + return getLastAwardCount( player ); + } + } + + return value; +} + +int AwardSystem::getIcon( Player *player, int statNum, int value ) +{ + if ( statNum == STAT_MP_AWARD_ICON ) + { + int icon; + + icon = getLastAward( player ); + + if ( icon > 0 ) + return icon; + else + return value; + } + + return value; +} + +int AwardSystem::getInfoIcon( Player *player ) +{ + return getLastAward( player ); +} + +int AwardSystem::getLastAward( Player *player ) +{ + if ( _playerAwardData[ player->entnum ]._lastAwardTime + 5.0f >= multiplayerManager.getTime() ) + { + return _playerAwardData[ player->entnum ]._lastAwardIconIndex; + } + else + { + return 0; + } +} + +int AwardSystem::getLastAwardCount( Player *player ) +{ + int lastAward; + + if ( _playerAwardData[ player->entnum ]._lastAwardTime + 5.0f >= multiplayerManager.getTime() ) + { + lastAward = getLastAward( player ); + + if ( lastAward == _firstStrikeIconIndex ) + return _playerAwardData[ player->entnum ]._numFirstStrikeAwards; + else if ( lastAward == _impressiveIconIndex ) + return _playerAwardData[ player->entnum ]._numImpressiveAwards; + else if ( lastAward == _excellentIconIndex ) + return _playerAwardData[ player->entnum ]._numExcellentAwards; + else if ( lastAward == _aceIconIndex ) + return _playerAwardData[ player->entnum ]._numAceAwards; + else if ( lastAward == _expertIconIndex ) + return _playerAwardData[ player->entnum ]._numExpertAwards; + else if ( lastAward == _masterIconIndex ) + return _playerAwardData[ player->entnum ]._numMasterAwards; + else if ( lastAward == _championIconIndex ) + return _playerAwardData[ player->entnum ]._numChampionAwards; + else if ( lastAward == _deniedIconIndex ) + return _playerAwardData[ player->entnum ]._numDeniedAwards; + } + + return 0; +} + diff --git a/dlls/game/mp_awardsystem.hpp b/dlls/game/mp_awardsystem.hpp new file mode 100644 index 0000000..cef8689 --- /dev/null +++ b/dlls/game/mp_awardsystem.hpp @@ -0,0 +1,168 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/multiplayerArena.cpp $ +// $Revision:: 48 $ +// $Author:: Steven $ +// $Date:: 7/23/02 2:55p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// Description: +// + +#ifndef __MP_AWARDSYSTEM_HPP__ +#define __MP_AWARDSYSTEM_HPP__ + +#include "g_local.h" // common game stuff +#include "player.h" // for Player +#include "item.h" +#include "mp_shared.hpp" + +/* typedef enum +{ + AFTERMATCH_AWARD1, + AFTERMATCH_AWARD2, + AFTERMATCH_AWARD3, + AFTERMATCH_AWARD4, + AFTERMATCH_AWARD5, + AFTERMATCH_AWARD6, + MAX_AFTER_MATCH_AWARDS +} AfterMatchAwardType; */ + +typedef enum +{ + AFTERMATCH_TEAM_AWARD_MVP, + AFTERMATCH_TEAM_AWARD_DEFENDER, + AFTERMATCH_TEAM_AWARD_WARRIOR, + AFTERMATCH_TEAM_AWARD_CARRIER, + AFTERMATCH_TEAM_AWARD_INTERCEPTOR, + AFTERMATCH_TEAM_AWARD_BRAVERY +} AfterMatchTeamAwardType; + +class MultiplayerPlayerAwardData +{ +public: + float _lastKillTime; + int _killStreak; + int _highestKillStreak; + int _numImpressives; + int _numDeaths; + int _shotsFired; + int _shotsHit; + float _lastHitTime; + unsigned int _weaponsKilledWith; + unsigned int _powerupsUsed; + int _numFlagCaptures; + int _numFlagReturns; + int _numFlagGuardingKills; + + int _numKills; + int _numKillsWithExplosives; + int _numVaporizedKills; + + int _entnum; + + bool _playing; + + int _numFirstStrikeAwards; + int _numImpressiveAwards; + int _numExcellentAwards; + int _numAceAwards; + int _numExpertAwards; + int _numMasterAwards; + int _numChampionAwards; + int _numDeniedAwards; + + int _lastAwardIconIndex; + float _lastAwardTime; + + int _afterMatchAwardIndexes[ MAX_SCORE_ICONS ]; + + MultiplayerPlayerAwardData(); + void init( void ); + void reset( void ); +}; + +class AwardSystem : public Class +{ +private: + static const float _minEfficiencyForEfficiencyAward; + static const int _minImpressivesForSharpshooter; + static const float _minPercentForDemolitionist; + static const float _minPercentForVaporizer; + static const int _minKillsForChampion; + static const int _minKillsForMaster; + static const int _minKillsForExpert; + static const int _minKillsForAce; + static const float _maxExcellentTime; + static const float _deniedDistance; + + int _maxPlayers; + bool _hadFirstStrike; + MultiplayerPlayerAwardData *_playerAwardData; + + int _efficiencyIconIndex; + int _sharpshooterIconIndex; + int _untouchableIconIndex; + + int _logisticsIconIndex; + int _tacticianIconIndex; + int _demolitionistIconIndex; + + int _aceIconIndex; + int _expertIconIndex; + int _masterIconIndex; + int _championIconIndex; + + int _mvpIconIndex; + int _defenderIconIndex; + int _warriorIconIndex; + int _carrierIconIndex; + int _interceptorIconIndex; + int _braveryIconIndex; + + int _firstStrikeIconIndex; + int _impressiveIconIndex; + int _excellentIconIndex; + int _deniedIconIndex; + + Container _powerups; + Container _weapons; + + int getItemIndex( Container &itemContainer, const str &itemName ); + int getNumItems( Container &itemContainer ); + int getNumItemBits( unsigned int bits ); + + void awardTeamAward( AfterMatchTeamAwardType teamAward, const char *teamName ); + int getLastAward( Player *player ); + int getLastAwardCount( Player *player ); + +public: + AwardSystem(); + ~AwardSystem(); + void init( int maxPlayers ); + void initItems( void ); + virtual void playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ); + virtual void playerFired( Player *attackingPlayer ); + virtual void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); + virtual void addPlayer( Player *player ); + virtual void removePlayer( Player *player ); + virtual void update( float frameTime ) {}; + + virtual int getStat( Player *player, int statNum, int value ); + virtual int getIcon( Player *player, int statNum, int value ); + virtual int getInfoIcon( Player *player ); + int getAfterMatchAward( Player *player, int index ); + + void pickedupItem( Player *player, MultiplayerItemType itemType, const char *itemName ); + + void playerEventNotification( const char *eventName, const char *eventItemName, Player *eventPlayer ); + + virtual void matchOver( void ); +}; + +#endif // __MP_AWARDSYSTEM_HPP__ diff --git a/dlls/game/mp_manager.cpp b/dlls/game/mp_manager.cpp new file mode 100644 index 0000000..77b78b8 --- /dev/null +++ b/dlls/game/mp_manager.cpp @@ -0,0 +1,3960 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/mp_manager.cpp $ +// $Revision:: 143 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 3:53p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" + +#include "player.h" +#include "mp_manager.hpp" +#include "mp_modeDm.hpp" +#include "mp_modeTeamDm.hpp" +#include "mp_modeCtf.hpp" +#include "powerups.h" + +MultiplayerManager multiplayerManager; + +str playersLastTeam[ MAX_CLIENTS ]; + +// Setup constants + +const int MultiplayerManager::_playerFreezeTime = 100; +//const int MultiplayerManager::_maxVoteCount = 3; +const float MultiplayerManager::_maxVoteTime = 30.0f; +const int MultiplayerManager::_maxSayStringLength = 150; +const float MultiplayerManager::_inBetweenMatchTime = 3.0f; + +const int MultiplayerPlayerData::_maxDialogs = 16; + +MultiplayerPlayerData::MultiplayerPlayerData() +{ + reset(); + + _dialogData = new MultiplayerDialogData[ _maxDialogs ]; +} + +MultiplayerPlayerData::~MultiplayerPlayerData() +{ + delete [] _dialogData; +} + +void MultiplayerPlayerData::reset( void ) +{ + _votecount = 0; + _voted = false; + _spectator = false; + _spectatorByChoice = false; + _valid = false; + _waitingForRespawn = false; + _teamHud = ""; + _named = false; + + _nextDialogSendSpot = 0; + _nextDialogAddSpot = 0; + _nextDialogSendTime = 0.0f; + _nextTauntTime = 0.0f; +} + +CLASS_DECLARATION( Class, MultiplayerManager, NULL ) +{ + { NULL, NULL } +}; + +MultiplayerManager::MultiplayerManager() +{ + _inMultiplayerGame = false; + + _multiplayerGame = NULL; + + _playerData = NULL; + + _awardSystem = NULL; + + _gameStarted = false; + _gameOver = false; + + _needToAddBots = true; + + _inMatch = false; + + _talkingIconIndex = 0; + _waitingToRespawnIconIndex = 0; + + _restartMatchTime = 0.0f; + + _voteTime = 0.0f; +} + +MultiplayerManager::~MultiplayerManager() +{ + cleanup( false ); +} + +// +// Interface for game dll +// + +void MultiplayerManager::cleanup( qboolean restart ) +{ + _inMultiplayerGame = false; + + _gameStarted = false; + _gameOver = false; + + if ( !restart ) + { + _needToAddBots = true; + } + + // Clean up the game + + delete _multiplayerGame; + _multiplayerGame = NULL; + + // Clean up the player data + + delete [] _playerData; + _playerData = NULL; + + // Clean up the award system + + delete _awardSystem; + _awardSystem = NULL; + + // Clean up all the modifiers + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + delete modifier; + } + + _modifiers.FreeObjectList(); + + _voteTime = 0.0f; +} + +void MultiplayerManager::init( void ) +{ + _inMultiplayerGame = false; + + resetRespawnTime(); + + checkModifiedCvars( false ); +} + +void MultiplayerManager::start( void ) +{ + if ( !_inMultiplayerGame ) + return; + + _gameStarted = true; + _gameOver = false; + + _talkingIconIndex = gi.imageindex( "sysimg/icons/mp/talking" ); + _waitingToRespawnIconIndex = gi.imageindex( "sysimg/icons/mp/elimination_eliminated" ); + + // Start the modifiers + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->start(); + } + + // Start the game + + _multiplayerGame->start(); +} + +void MultiplayerManager::update( float frameTime ) +{ + int i; + + + if ( !_inMultiplayerGame ) + return; + + // Restart the match if time + + if ( !_inMatch && !_gameOver && ( _restartMatchTime > 0.0f ) && ( _restartMatchTime < getTime() ) ) + { + restartMatch(); + } + + // Update the respawn time + + if ( mp_respawnTime->modified ) + { + if ( mp_respawnTime->value >= 0.0f ) + { + setRespawnTime( mp_respawnTime->value ); + } + + mp_respawnTime->modified = false; + } + + // Start the game if not started already + + if ( !_gameStarted ) + start(); + + if ( _needToAddBots ) + addBots(); + + // Update all of the modifiers + + if ( _multiplayerGame->inMatch() ) + { + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->update( frameTime ); + } + } + + // Update the game + + _multiplayerGame->update( frameTime ); + + // Check for voting conditions + + checkVote(); + + // Check for modified cvars + + checkModifiedCvars( true ); + + // Update cvars + + if ( strlen( password->string ) ) + { + if ( !g_needpass->integer ) + { + gi.cvar_set( "g_needpass", "1" ); + } + } + else + { + if ( g_needpass->integer ) + { + gi.cvar_set( "g_needpass", "0" ); + } + } + + // Update dialogs + + for ( i = 0 ; i < maxclients->integer ; i++ ) + { + Player *player; + + // Get the player + + player = getPlayer( i ); + + if ( !player ) + continue; + + sendNextPlayerSound( player ); + } + + // Check for end of intermission + + if ( level.intermissiontime ) + { + G_CheckIntermissionExit(); + + if ( !_declaredWinner && ( _declareWinnerTime < getTime() ) ) + { + _multiplayerGame->declareWinner(); + + _declaredWinner = true; + } + return; + } + + // Check for end of match + + if ( _multiplayerGame->isEndOfMatch() ) + { + _gameOver = true; + + centerPrintAllClients( "$$MatchOver$$\n", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_matover.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + + _multiplayerGame->EndMatch(); + G_BeginIntermission2(); + matchOver(); + + _declareWinnerTime = getTime() + 2.0f; + _declaredWinner = false; + + setNextMap(); + } + + // Update the players + + for ( i = 0 ; i < maxclients->integer ; i++ ) + { + Player *player; + Player *playerFollowing; + + // Get the player + + player = getPlayer( i ); + + if ( !player ) + continue; + + if ( isPlayerSpectator( player, SPECTATOR_TYPE_FOLLOW ) ) + { + playerFollowing = getPlayer( _playerData[ player->entnum ]._spectatorPlayerNum ); + + if ( !playerFollowing || playerFollowing->deadflag == DEAD_DEAD ) + { + makePlayerSpectateNextPlayer( player ); + + if ( !isPlayerSpectator( player, SPECTATOR_TYPE_FOLLOW ) ) + continue; + + playerFollowing = getPlayer( _playerData[ player->entnum ]._spectatorPlayerNum ); + } + + if ( playerFollowing ) + { + player->setOrigin( playerFollowing->origin ); + player->setAngles( playerFollowing->angles ); + player->SetViewAngles( playerFollowing->getViewAngles() ); + } + } + + if ( _playerData[ player->entnum ]._waitingForRespawn ) + { + int lastTimeLeft; + int timeLeft; + str printString; + + timeLeft = (int)( _playerData[ player->entnum ]._respawnTime - getTime() + 1.0f + ( frameTime / 2.0f ) ); + lastTimeLeft = (int)( _playerData[ player->entnum ]._respawnTime - getTime() + frameTime + 1.0f + ( frameTime / 2.0f ) ); + + if ( ( lastTimeLeft != timeLeft ) && ( timeLeft > 0 ) ) + { + printString = "$$RespawnIn$$ "; + printString += timeLeft; + + centerPrint( player->entnum, printString, CENTERPRINT_IMPORTANCE_HIGH ); + } + + if ( _playerData[ player->entnum ]._respawnTime <= getTime() ) + { + respawnPlayer( player, true ); + } + } + } +} + +void MultiplayerManager::addBots( void ) +{ + cvar_t *mp_botcommands; + + // If this is not a dedicated server make sure the player enters the game first + + if ( !dedicated->integer && ( !g_entities[ 0 ].inuse || !g_entities[ 0 ].client || !g_entities[ 0 ].entity ) ) + return; + + // Add the bot commands + + mp_botcommands = gi.cvar( "mp_botcommands", "", 0 ); + + if ( strlen( mp_botcommands->string ) > 0 ) + { + gi.SendConsoleCommand( mp_botcommands->string ); + //gi.cvar_set( "mp_botcommands", "" ); + } + + _needToAddBots = false; +} + +extern int gametype; // for BOTLIB +void MultiplayerManager::initMultiplayerGame( void ) +{ + cvar_t *mp_modifier_Destruction; + int i; + + // Automatically turn off diffusion, it will be turned back on if needed + + gi.cvar_set( "mp_modifier_diffusion", "0" ); + + // Create the correct game type + + switch ( mp_gametype->integer ) // Todo : switch off of a something better than a hardcoded index + { + case 0 : + _multiplayerGame = new ModeDeathmatch(); + gametype = GT_FFA; + break; + case 1 : + _multiplayerGame = new ModeTeamDeathmatch(); + gametype = GT_TEAM; + break; + case 2 : + _multiplayerGame = new ModeCaptureTheFlag(); + gametype = GT_CTF; + break ; + case 3 : + // This is bomb diffusion mode + + _multiplayerGame = new ModeTeamDeathmatch(); + + gi.cvar_set( "mp_modifier_diffusion", "1" ); + /* gi.cvar_set( "mp_modifier_specialties", "1" ); + gi.cvar_set( "mp_modifier_elimination", "1" ); */ + + //gi.cvar_set( "mp_gametype", "1" ); + + gametype = GT_TEAM; + break ; + + default: + _multiplayerGame = new ModeDeathmatch(); + gametype = GT_FFA; + break ; + } + + // Setup some stuff for bots + + mp_modifier_Destruction = gi.cvar( "mp_modifier_Destruction", "0", 0 ); + + if ( mp_modifier_Destruction->integer ) + { + gametype = GT_OBELISK; + } + + if ( !_multiplayerGame ) + return; + + // Create the player data + + _playerData = new MultiplayerPlayerData[ maxclients->integer ]; + + // Initialize the game + + _multiplayerGame->init( maxclients->integer ); + + _multiplayerGame->setPointLimit( mp_pointlimit->integer ); + _multiplayerGame->setTimeLimit( mp_timelimit->value * 60.0f ); + + _inMultiplayerGame = true; + + // Add all of the needed modifiers + + addModifiers(); + + // Initialize all of the needed modifiers + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->init( maxclients->integer ); + } + + // Initialize the award system + + _awardSystem = new AwardSystem; + _awardSystem->init( maxclients->integer ); + + // Initialize anything needed that is outside of the multiplayer system + + // Make sure no tricorder modes are available except those explicitly set by the script + + Event *event = new Event( "addAvailableViewMode" ); + event->AddString( "BogusMode" ); + world->ProcessEvent( event ); +} + +void MultiplayerManager::initItems( void ) +{ + int i; + int j; + bool shouldKeep; + MultiplayerItem *item; + Item *normalItem; + + if ( !_inMultiplayerGame ) + return; + + // Tell the game to initialize items + + _multiplayerGame->initItems(); + + // Tell all of the modifiers to initialize items + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->initItems(); + } + + // Tell the award system to initialize items + + _awardSystem->initItems(); + + // Find all of the multiplayer items, tell everyone about each of them and see if we need to keep them + + // TODO: move this to a seperate proc + + for ( i = 0; i < MAX_GENTITIES; i++ ) + { + if ( g_entities[ i ].inuse && g_entities[ i ].entity ) + { + if ( g_entities[ i ].entity->isSubclassOf( MultiplayerItem ) ) + { + item = (MultiplayerItem *)g_entities[ i ].entity; + + shouldKeep = false; + + // Tell modifiers about the item and see if they want to keep it + + for ( j = 1 ; j <= _modifiers.NumObjects() ; j++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( j ); + + if ( modifier ) + { + if ( modifier->shouldKeepItem( item ) ) + shouldKeep = true; + } + } + + // Tell the mode about the item and see if it wants to keep it + + if ( _multiplayerGame->shouldKeepItem( item ) ) + shouldKeep = true; + + // If no one said to keep the item get rid of it + + if ( shouldKeep ) + { + // Tell all of the modifiers that the item was kept + + for ( j = 1 ; j <= _modifiers.NumObjects() ; j++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( j ); + + if ( modifier ) + { + modifier->itemKept( item ); + } + } + + // Tell the mode that the item was kept + + _multiplayerGame->itemKept( item ); + } + else + { + // No one told us to keep this multiplayer item, so get rid of it + + item->PostEvent( EV_Remove, FRAMETIME ); + } + } + else if ( g_entities[ i ].entity->isSubclassOf( Item ) ) + { + normalItem = (Item *)g_entities[ i ].entity; + + shouldKeep = true; + + // Tell modifiers about the item and see if they want to keep it + + for ( j = 1 ; j <= _modifiers.NumObjects() ; j++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( j ); + + if ( modifier ) + { + if ( !modifier->shouldKeepNormalItem( normalItem ) ) + shouldKeep = false; + } + } + + // Tell the mode about the item and see if it wants to keep it + + if ( !_multiplayerGame->shouldKeepNormalItem( normalItem ) ) + shouldKeep = false; + + // If no one said to keep the item get rid of it + + if ( !shouldKeep ) + { + // Someone told us to get rid of this item + + normalItem->PostEvent( EV_Remove, FRAMETIME ); + } + } + } + } +} + +void MultiplayerManager::resetItems( void ) +{ + int i; + Item *item; + + // Setup all of the items again + + for ( i = 0; i < MAX_GENTITIES; i++ ) + { + if ( g_entities[ i ].inuse && g_entities[ i ].entity && g_entities[ i ].entity->isSubclassOf( Item ) ) + { + item = (Item *)g_entities[ i ].entity; + + // Ignore runes and multiplayer items + + if ( item->isSubclassOf( Rune ) || item->isSubclassOf( MultiplayerItem ) ) + continue; + + if ( item->Respawnable() ) + { + // Item is a normal respawnable item, so respawn it again + + item->CancelEventsOfType( EV_Item_Respawn ); + item->PostEvent( EV_Item_Respawn, 0.0f ); + } + else + { + // Item is a temporary item, so get rid of it + + item->PostEvent( EV_Remove, 0.0f ); + } + } + } +} + +bool MultiplayerManager::inMultiplayer( void ) +{ + return _inMultiplayerGame; +} + +bool MultiplayerManager::checkFlag( unsigned int flag ) +{ + if ( !_inMultiplayerGame ) + return false; + + + if ( mp_flags->integer & flag ) + return true; + else + return false; +} + + +bool MultiplayerManager::fullCollision( void ) +{ + if ( !_inMultiplayerGame ) + return true; + + if ( checkFlag( MP_FLAG_FULL_COLLISION ) ) + return true; + else + return false; +} + +bool MultiplayerManager::isFightingAllowed( void ) +{ + if ( !_inMultiplayerGame ) + return true; + + if ( !_inMatch ) + return false; + + return _allowFighting; +} + +void MultiplayerManager::addPlayer( Player *player ) +{ + if ( !_inMultiplayerGame ) + return; + + assert( player ); + + if ( !player ) + return; + + // See if the player has already been added + + if ( _playerData[ player->entnum ]._valid ) + return; + + // Initialize the player data + + _playerData[ player->entnum ].reset(); + _playerData[ player->entnum ]._valid = true; + + // Inform the game about the new player + + _multiplayerGame->AddPlayer( player ); + + // Inform all of the modifiers about the new player + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->addPlayer( player ); + } + + // Inform the award system about the new player + + _awardSystem->addPlayer ( player ); + + // Inform all of the players that the player has joined the game + + multiplayerManager.HUDPrintAllClients( va( "%s $$JoinedGame$$\n", player->client->pers.netname ) ); +} + +void MultiplayerManager::removePlayer( Player *player ) +{ + if ( !_inMultiplayerGame ) + return; + + assert( player ); + + if ( !player ) + return; + + if ( !_playerData[ player->entnum ]._valid ) + return; + + // Inform the game about the player being removed + + _multiplayerGame->RemovePlayer( player ); + + // Reset the player's data + + _playerData[ player->entnum ].reset(); + + // Inform all of the modifiers about the player being removed + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->removePlayer( player ); + } + + // Inform the award system about the player being removed + + _awardSystem->removePlayer ( player ); + + // Inform all of the players that the player has left the game + + multiplayerManager.HUDPrintAllClients( va( "%s $$LeftGame$$\n", player->client->pers.netname ) ); +} + +void MultiplayerManager::changePlayerModel( Player *player, const char *modelName, bool force ) +{ + str modelToUse; + + if ( !_inMultiplayerGame || !player ) + return; + + if ( ( player->model != modelName ) || ( force ) ) + { + modelToUse = modelName; + + // Verify that this is an acceptable model to use + + if ( !isValidPlayerModel( player, modelToUse ) ) + { + centerPrint( player->entnum, va( "%s is not an acceptable model to use", modelName ), CENTERPRINT_IMPORTANCE_NORMAL ); + modelToUse = getDefaultPlayerModel( player ); + } + + if ( checkFlag( MP_FLAG_FORCE_DEFAULT_MODEL ) ) + { + modelToUse = getDefaultPlayerModel( player ); + } + + // Setup the default backup model + + player->setBackupModel( getDefaultPlayerModel( player ) ); + + // Setup the model + + player->InitModel( modelToUse ); + + player->CancelEventsOfType( EV_ProcessInitCommands ); + player->ProcessInitCommands( player->edict->s.modelindex ); + + resetPlayerStateMachine( player ); + + if ( multiplayerManager.isPlayerSpectator( player ) ) + { + player->hideModel(); + player->setSolidType( SOLID_NOT ); + } + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->playerChangedModel( player ); + } + + _multiplayerGame->playerChangedModel( player ); + + player->modelChanged(); + } +} + +void MultiplayerManager::resetPlayerStateMachine( Player *player ) +{ + if ( !_inMultiplayerGame ) + return; + + if ( player ) + { + player->SetAnim( "stand_idle", legs, true ); + player->SetAnim( "stand_idle", torso, true ); + player->LoadStateTable(); + } + +} + +void MultiplayerManager::changePlayerName( Player *player, const str &playerName ) +{ + if ( !_inMultiplayerGame ) + return; + + if ( player ) + { + if ( _playerData[ player->entnum ]._named && ( _playerData[ player->entnum ]._name != playerName ) ) + { + // Inform all of the players that the player has changed his name + + multiplayerManager.HUDPrintAllClients( va( "%s $$ChangedName$$ %s\n", _playerData[ player->entnum ]._name.c_str(), playerName.c_str() ) ); + } + + _playerData[ player->entnum ]._named = true; + _playerData[ player->entnum ]._name = playerName; + } + +} + +void MultiplayerManager::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + if ( !_inMultiplayerGame ) + return; + + // Inform all of the modifiers about the player being killed + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->playerKilled( killedPlayer, attackingPlayer, inflictor, meansOfDeath ); + } + + // Inform the game about the player being killed + + _multiplayerGame->playerKilled( killedPlayer, attackingPlayer, inflictor, meansOfDeath ); + + // Inform the award system about the player being killed + + _awardSystem->playerKilled( killedPlayer, attackingPlayer, inflictor, meansOfDeath ); + + // Tell the players what happened + + if ( killedPlayer == attackingPlayer || !attackingPlayer ) + { + centerPrint( killedPlayer->entnum, va( "^%c$$YouKilledYourself$$^%c", COLOR_RED, COLOR_NONE ), CENTERPRINT_IMPORTANCE_NORMAL ); + } + else + { + str printString; + + centerPrint( killedPlayer->entnum, va( "^%c$$KilledByPlayer$$^%c %s", COLOR_RED, COLOR_NONE, attackingPlayer->client->pers.netname, COLOR_WHITE + 1 ), CENTERPRINT_IMPORTANCE_NORMAL ); + + printString = va( "^%c$$KilledPlayer$$^%c %s", COLOR_GREEN, COLOR_NONE, killedPlayer->client->pers.netname ); + + if ( checkRule( "usingIndividualScore", false, attackingPlayer ) ) + { + str placeName = getPlaceName( attackingPlayer ); + int points = getPoints( attackingPlayer ); + + if ( points == 1 ) + printString += va( "\n$$PlaceString1$$ ^%c %s^%c $$PlaceString2$$ ^%c %d^%c $$Point$$", COLOR_CYAN, placeName.c_str(), COLOR_NONE, COLOR_CYAN, points, COLOR_NONE ); + else + printString += va( "\n$$PlaceString1$$ ^%c %s^%c $$PlaceString2$$ ^%c %d^%c $$Points$$", COLOR_CYAN, placeName.c_str(), COLOR_NONE, COLOR_CYAN, points, COLOR_NONE ); + } + + centerPrint( attackingPlayer->entnum, printString, CENTERPRINT_IMPORTANCE_NORMAL ); + + multiplayerManager.instantPlayerSound( attackingPlayer->entnum, "snd_mp_killedsomeone", CHAN_COMBAT4 ); + } +} + +void MultiplayerManager::matchOver( void ) +{ + if ( !_inMultiplayerGame ) + return; + + int i; + + // Inform all of the modifiers about the match being over + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->matchOver(); + } + + // Inform the game about the match being over + + _multiplayerGame->matchOver(); + + // Inform the award system about the match being over + + _awardSystem->matchOver(); + + _inMatch = false; + + // Do some solo match work (EF2 specific) + + cvar_t *mp_solomatch; + cvar_t *mp_mapAward; + cvar_t *mp_botskilllevel; + str cvarName; + + // Get all the cvars we need + + cvarName = level.mapname + "_award"; + + mp_solomatch = gi.cvar( "mp_solomatch", "0", 0 ); + mp_mapAward = gi.cvar( cvarName.c_str(), "0", CVAR_ARCHIVE ); + mp_botskilllevel = gi.cvar( "mp_botskilllevel", "0", CVAR_ARCHIVE ); + + // Make sure we are in solomatch + + if ( mp_solomatch->integer ) + { + int playersScore; + int botsScore; + bool wonMatch = true; + Player *player; + + // See if the player won the match + + player = getPlayer( 0 ); + playersScore = getPoints( player ); + + for ( i = 1 ; i < maxclients->integer ; i++ ) + { + player = getPlayer( i ); + + if ( player ) + { + botsScore = getPoints( player ); + + if ( botsScore >= playersScore ) + { + wonMatch = false; + break; + } + } + } + + if ( wonMatch ) + { + // See if this is the highest bot skill level the player has won on + + if ( mp_mapAward->integer < mp_botskilllevel->integer ) + { + // Save cvar + + gi.cvar_set( cvarName.c_str(), mp_botskilllevel->string ); + } + } + } +} + +int MultiplayerManager::getScoreIcon( Player *player, int index ) +{ + int icon = 0; + + if ( _inMatch ) + { + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + icon = modifier->getScoreIcon( player, index, icon ); + } + } + + // Allow the game to apply speed modifiers + + icon = _multiplayerGame->getScoreIcon( player, index, icon ); + } + else + { + icon = _awardSystem->getAfterMatchAward( player, index ); + } + + if ( ( index == SCOREICON3 ) && ( icon == 0 ) && _playerData[ player->entnum ]._waitingForRespawn ) + { + icon = _waitingToRespawnIconIndex; + } + + return icon; +} + +int MultiplayerManager::getAfterMatchAward( Player *player, int index ) +{ + return _awardSystem->getAfterMatchAward( player, index ); +} + +void MultiplayerManager::playerDead( Player *player ) +{ + assert(player); + + if ( !player ) + return; + + // Inform the game that the player is dead + + _multiplayerGame->playerDead( player ); +} + +void MultiplayerManager::applySpeedModifiers( Player *player, int *moveSpeed ) +{ + if ( !_inMultiplayerGame ) + return; + + // Allow all of the modifiers to apply speed modifiers + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->applySpeedModifiers( player, moveSpeed ); + } + + // Allow the game to apply speed modifiers + + _multiplayerGame->applySpeedModifiers( player, moveSpeed ); +} + +void MultiplayerManager::applyJumpModifiers( Player *player, int *jumpSpeed ) +{ + if ( !_inMultiplayerGame ) + return; + + // Allow all of the modifiers to apply jump modifiers + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->applyJumpModifiers( player, jumpSpeed ); + } + + // Allow the game to apply jump modifiers + + _multiplayerGame->applyJumpModifiers( player, jumpSpeed ); +} + +void MultiplayerManager::applyAirAccelerationModifiers( Player *player, int *airAcceleration ) +{ + if ( !_inMultiplayerGame ) + return; + + // Allow all of the modifiers to apply air acceleration modifiers + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->applyAirAccelerationModifiers( player, airAcceleration ); + } + + // Allow the game to apply air acceleration modifiers + + _multiplayerGame->applyAirAccelerationModifiers( player, airAcceleration ); +} + +bool MultiplayerManager::canPickup( Player *player, MultiplayerItemType itemType, const char *item_name ) +{ + if ( !_inMultiplayerGame ) + return true; + + // See if all of the modifiers will allow the player to pickup this item + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + if ( !modifier->canPickup( player, itemType, item_name ) ) + return false; + } + } + + // See if the game will allow the player to pickup this item + + if ( !_multiplayerGame->canPickup( player, itemType, item_name ) ) + return false; + + // The player is allowed to pickup this item + + return true; +} + +void MultiplayerManager::pickedupItem( Player *player, MultiplayerItemType itemType, const char *itemName ) +{ + if ( !_inMultiplayerGame ) + return; + + // Tell modifiers that an item was picked up + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + modifier->pickedupItem( player, itemType, itemName ); + } + } + + // Tell game that an item was picked up + + _multiplayerGame->pickedupItem( player, itemType, itemName ); + + // Tell the award system that an item was picked up + + _awardSystem->pickedupItem( player, itemType, itemName ); +} + +int MultiplayerManager::getPointsForKill( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath, int points ) +{ + int realPoints; + + realPoints = points; + + // Allow all of the modifiers to change the points given for this kill + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + realPoints = modifier->getPointsForKill( killedPlayer, attackingPlayer, inflictor, meansOfDeath, realPoints ); + } + + return realPoints; +} + +void MultiplayerManager::respawnPlayer( Player *player, bool forced ) +{ + if ( !_inMultiplayerGame ) + return; + + assert(player); + + if ( !player ) + return; + + if ( multiplayerManager.isPlayerSpectator( player ) && multiplayerManager.isPlayerSpectatorByChoice( player ) ) + return; + + if ( getRespawnTime() > 0.0f && !forced ) + { + if ( !_playerData[ player->entnum ]._waitingForRespawn ) + { + _playerData[ player->entnum ]._waitingForRespawn = true; + _playerData[ player->entnum ]._respawnTime = getTime() + getRespawnTime(); + } + } + else + { + _playerData[ player->entnum ]._waitingForRespawn = false; + _multiplayerGame->respawnPlayer( player ); + } +} + +void MultiplayerManager::respawnAllPlayers( void ) +{ + int i; + Player *player; + + if ( !_inMultiplayerGame ) + return; + + for( i = 0 ; i < maxclients->integer ; i++ ) + { + player = getPlayer( i ); + + if ( !player ) + continue; + + if ( multiplayerManager.isPlayerSpectatorByChoice( player ) && !( player->edict->svflags & SVF_BOT ) ) + continue; + + respawnPlayer( player, true ); + } +} + +Entity *MultiplayerManager::getSpawnPoint( Player *player ) +{ + if ( !_inMultiplayerGame ) + return NULL; + + assert( player ); + + if ( !player ) + return NULL; + + // Get a spawn point for this player from the game + + return _multiplayerGame->getSpawnPoint( player ); +} + +float MultiplayerManager::playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ) +{ + float realDamage; + + realDamage = damage; + + if ( !_inMultiplayerGame ) + return realDamage; + + // Change damage based on cvar + + realDamage *= mp_damageMultiplier->value; + + // Change damage based on modifiers + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + realDamage = modifier->playerDamaged( damagedPlayer, attackingPlayer, realDamage, meansOfDeath ); + } + + // Change damage based on the mode + + realDamage = _multiplayerGame->playerDamaged( damagedPlayer, attackingPlayer, realDamage, meansOfDeath ); + + // Inform the award system that someone was damaged + + _awardSystem->playerDamaged( damagedPlayer, attackingPlayer, realDamage, meansOfDeath ); + + return realDamage; +} + +void MultiplayerManager::playerTookDamage( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ) +{ + _multiplayerGame->playerTookDamage( damagedPlayer, attackingPlayer, damage, meansOfDeath ); +} + +void MultiplayerManager::playerFired( Player *attackingPlayer ) +{ + if ( !_inMultiplayerGame ) + return; + + // Inform the modifiers that the player shot + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->playerFired( attackingPlayer ); + } + + // Inform the game that the player shot + + _multiplayerGame->playerFired( attackingPlayer ); + + // Inform the award system that the player shot + + _awardSystem->playerFired( attackingPlayer ); +} + +float MultiplayerManager::getModifiedKnockback( Player *damagedPlayer, Player *attackingPlayer, float knockback ) +{ + if ( !_inMultiplayerGame ) + return knockback; + + // Change damage based on cvar + + return knockback * mp_knockbackMultiplier->value; +} + +void MultiplayerManager::itemTouched( Player *player, MultiplayerItem *item ) +{ + if ( !_inMultiplayerGame ) + return; + + // Make sure the player is alive and playing + + if ( isPlayerSpectator( player ) || ( player->deadflag != DEAD_NO ) || ( player->getHealth() <= 0.0f ) ) + return; + + // Tell all of the modifiers that this item was touched + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->itemTouched( player, item ); + } + + // Tell the mode that this item was touched + + _multiplayerGame->itemTouched( player, item ); +} + +float MultiplayerManager::itemDamaged( MultiplayerItem *item, Player *attackingPlayer, float damage, int meansOfDeath ) +{ + float realDamage; + + if ( !_inMultiplayerGame ) + return damage; + + realDamage = damage; + + // Tell all of the modifiers that this item was damaged + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + realDamage = modifier->itemDamaged( item, attackingPlayer, realDamage, meansOfDeath ); + } + } + + // Tell the mode that this item was destroyed + + return _multiplayerGame->itemDamaged( item, attackingPlayer, realDamage, meansOfDeath ); +} + +void MultiplayerManager::itemDestroyed( Player *player, MultiplayerItem *item ) +{ + if ( !_inMultiplayerGame ) + return; + + // Tell all of the modifiers that this item was destroyed + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->itemDestroyed( player, item ); + } + + // Tell the mode that this item was destroyed + + _multiplayerGame->itemDestroyed( player, item ); +} + +void MultiplayerManager::itemUsed( Entity *entity, MultiplayerItem *item ) +{ + if ( !_inMultiplayerGame ) + return; + + // Tell all of the modifiers that this item was destroyed + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->itemUsed( entity, item ); + } + + // Tell the mode that this item was destroyed + + _multiplayerGame->itemUsed( entity, item ); +} + +void MultiplayerManager::playerUsed( Player *usedPlayer, Player *usingPlayer, Equipment *equipment ) +{ + if ( !_inMultiplayerGame ) + return; + + // Tell all of the modifiers that this item was destroyed + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->playerUsed( usedPlayer, usingPlayer, equipment ); + } + + // Tell the mode that this item was destroyed + + _multiplayerGame->playerUsed( usedPlayer, usingPlayer, equipment ); +} + +bool MultiplayerManager::checkRule( const char *rule, bool defaultValue, Player *player ) +{ + bool value; + + if ( !_inMultiplayerGame ) + return defaultValue; + + // Default the value to the value passed in + + value = defaultValue; + + // Check this rule with all of the modifiers + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + value = modifier->checkRule( rule, value, player ); + } + } + + // Check this rule with the mode + + value = _multiplayerGame->checkRule( rule, value, player ); + + return value; +} + +bool MultiplayerManager::checkGameType( const char *gameType ) +{ + if ( !_inMultiplayerGame ) + return false; + + // Check for the gametype with the game first + + if ( _multiplayerGame->checkGameType( gameType ) ) + return true; + + // Check for the gametype with all of the modifiers + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + if ( modifier->checkGameType( gameType ) ) + return true; + } + } + + return false; +} + +bool MultiplayerManager::doesPlayerHaveItem( Player *player, const char *itemName ) +{ + if ( !_inMultiplayerGame ) + return false; + + if ( !player ) + return false; + + // Check to see if the player has the item with the game first + + if ( _multiplayerGame->doesPlayerHaveItem( player, itemName ) ) + return true; + + // Check to see if the player has the item with all of the modifiers + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + if ( modifier->doesPlayerHaveItem( player, itemName ) ) + return true; + } + } + + return false; +} + +void MultiplayerManager::score( Player *player ) +{ + assert(player); + + if ( !player ) + return; + + if ( !_inMultiplayerGame ) + return; + + // Tell the game to send the current score to the player + + _multiplayerGame->score( player ); +} + +int MultiplayerManager::getPoints( Player *player ) +{ + assert(player); + + if ( !player ) + return 0; + + if ( !_inMultiplayerGame ) + return 0; + + // Get the player's current points from the game + + return _multiplayerGame->getPoints( player ); +} + +int MultiplayerManager::getKills( Player *player ) +{ + assert(player); + + if ( !player ) + return 0; + + if ( !_inMultiplayerGame ) + return 0; + + // Get the player's current kills from the game + + return _multiplayerGame->getKills( player ); +} + +int MultiplayerManager::getDeaths( Player *player ) +{ + assert(player); + + if ( !player ) + return 0; + + if ( !_inMultiplayerGame ) + return 0; + + // Get the player's current deaths from the game + + return _multiplayerGame->getDeaths( player ); +} + +int MultiplayerManager::getTeamPoints( Player *player ) +{ + assert(player); + + if ( !player ) + return 0; + + if ( !_inMultiplayerGame ) + return 0; + + // Get the player's team's current points from the game + + return _multiplayerGame->getTeamPoints( player ); +} + +int MultiplayerManager::getTeamPoints( const str &teamName ) +{ + if ( !_inMultiplayerGame ) + return 0; + + // Get the player's team's current points from the game + + return _multiplayerGame->getTeamPoints( teamName ); +} + +void MultiplayerManager::addTeamPoints( const str &teamName, int points ) +{ + if ( !_inMultiplayerGame ) + return; + + // Add team's current points + + _multiplayerGame->addTeamPoints( teamName, points ); +} + +int MultiplayerManager::getStat( Player *player, int statNum ) +{ + int value = 0; + + assert(player); + + if ( !player ) + return 0; + + if ( !_inMultiplayerGame ) + return 0; + + // See if this is one of the stats that the manager cares about + + if ( ( statNum == STAT_TIMELEFT_SECONDS ) ) + { + value = 0; + + if ( mp_timelimit->integer ) + { + value = (int) (mp_timelimit->integer * 60.0f - level.time); + } + } + + if ( statNum == STAT_MP_SPECTATING_ENTNUM ) + { + value = _playerData[ player->entnum ]._spectatorPlayerNum; + } + + // Allow all of the modifiers to set the stat + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + value = modifier->getStat( player, statNum, value ); + } + } + + // Allow the game to set the stat + + value = _multiplayerGame->getStat( player, statNum, value ); + + value = _awardSystem->getStat( player, statNum, value ); + + return value; +} + +int MultiplayerManager::getIcon( Player *player, int statNum ) +{ + int value = -1; + + assert(player); + + if ( !player ) + return -1; + + if ( !_inMultiplayerGame ) + return -1; + + // Allow all of the modifiers to set the icon + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + value = modifier->getIcon( player, statNum, value ); + } + } + + // Allow the game to set the icon + + value = _multiplayerGame->getIcon( player, statNum, value ); + + value = _awardSystem->getIcon( player, statNum, value ); + + return value; +} + +Team* MultiplayerManager::getPlayersTeam( const Player *player ) +{ + assert(player); + + if ( !player ) + return NULL; + + if ( !_inMultiplayerGame ) + return NULL; + + return _multiplayerGame->getPlayersTeam( player ); +} + + +void MultiplayerManager::callVote( Player *player, const str &command, const str &arg ) +{ + int i; + int count; + + + if ( !_inMultiplayerGame ) + return; + + // Player wants to call a vote + + if ( checkFlag( MP_FLAG_DONT_ALLOW_VOTE ) ) + { + HUDPrint( player->entnum, "$$VotingNotAllowed$$.\n" ); + return; + } + + // If voteTime is set, then a vote has already been called + + if ( _voteTime ) + { + HUDPrint( player->entnum, "$$AlreadyVoting$$.\n" ); + return; + } + + // Make sure the player hasn't called too many votes + + if ( _playerData[ player->entnum ]._votecount >= mp_maxVotes->integer ) + { + HUDPrint( player->entnum, va( "$$MaxVotes$$ (%d).\n", mp_maxVotes->integer ) ); + return; + } + + // Make sure everything is ok + + if( strchr( command.c_str(), ';' ) || strchr( arg.c_str(), ';' ) ) + { + HUDPrint( player->entnum, "$$InvalidVote$$\n" ); + return; + } + + if ( ( stricmp( command.c_str(), "restart" ) != 0 ) && + ( stricmp( command.c_str(), "nextmap" ) != 0 ) && + ( stricmp( command.c_str(), "map" ) != 0 ) && + ( stricmp( command.c_str(), "g_gametype" ) != 0 ) && + ( stricmp( command.c_str(), "kick" ) != 0 ) ) + { + HUDPrint( player->entnum, "$$InvalidVote$$\n" ); + HUDPrint( player->entnum, "$$VoteCommands$$: restart, nextmap, map , g_gametype and kick .\n" ); + return; + } + + // If a map command, make sure the map actually exists + + if ( stricmp( command.c_str(), "map" ) == 0 ) + { + str fullMapName; + str printString; + + fullMapName = "maps/"; + fullMapName += arg; + fullMapName += ".bsp"; + + if ( !gi.FS_Exists( fullMapName.c_str() ) ) + { + printString = fullMapName + " $$NotFoundOnServer$$\n"; + HUDPrint( player->entnum, printString.c_str() ); + return; + } + } + + // Build the vote string for later use and to tell all clients about + + if ( stricmp( command.c_str(), "map" ) == 0 ) + { + // If a map command was issued, preserve the nextmap cvar so we don't lose it + if ( strlen( sv_nextmap->string ) ) + { + _voteString = va( "%s %s; set nextmap \"%s\"", command.c_str(), arg.c_str(), sv_nextmap->string ); + } + else + { + _voteString = va( "%s %s", command.c_str(), arg.c_str() ); + } + } + else + { + _voteString = va( "%s %s", command.c_str(), arg.c_str() ); + } + + // Print out a message to everyone + + multiplayerManager.HUDPrintAllClients( va( "%s ^4$$CalledVote$$:^8 %s\n", player->client->pers.netname, _voteString.c_str() ) ); + + // Play a sound to announce a new vote + + broadcastInstantSound( "sound/ships/attrexian/att-beepreject.wav" ); + + // Start the voting, the caller automatically votes yes + + _voteTime = level.time; + _voteYes = 1; + _voteNo = 0; + + _playerData[ player->entnum ]._voted = true; + _playerData[ player->entnum ]._votecount++; + + // Clear all the other player's voteflags + + count = 1; + + for( i = 0; i < maxclients->integer; i++ ) + { + Player *currentPlayer; + gentity_t *ent = g_entities + i; + + if ( !ent->inuse || !ent->client || !ent->entity ) + continue; + + if ( ent->svflags & SVF_BOT ) + continue; + + if ( i == player->entnum ) + continue; + + _playerData[ i ]._voted = false; + + currentPlayer = getPlayer( i ); + + if ( currentPlayer ) + { + currentPlayer->setVoteText( va( "$$NewVote$$: %s (F1 = $$Yes$$, F2 = $$No$$)\n", _voteString.c_str() ) ); + } + + count++; + } + + _numVoters = count; +} + +void MultiplayerManager::vote( Player *player, const str &vote ) +{ + // Player is voting on the current vote + + // Make sure the is a vote current going on + + if ( !_voteTime ) + { + HUDPrint( player->entnum, "$$NoVote$$\n" ); + return; + } + + // Make sure this player hasn't already voted on this vote + + if ( _playerData[ player->entnum ]._voted ) + { + HUDPrint( player->entnum, "$$VoteAlreadyCast$$\n" ); + return; + } + + // Make sure the vote is valid + + if ( vote.length() < 1 ) + return; + + // Vote + + _playerData[ player->entnum ]._voted = true; + + player->clearVoteText(); + + if ( ( vote[0] == 'y' ) || ( vote[0] == 'Y' ) || ( vote[0] == '1' ) ) + { + _voteYes++; + + HUDPrint( player->entnum, "$$VoteCast$$ - $$Yes$$\n" ); + } + else + { + _voteNo++; + + HUDPrint( player->entnum, "$$VoteCast$$ - $$No$$\n" ); + } + + // NOTE: a majority will be determined in checkVote +} + +void MultiplayerManager::checkVote( void ) +{ + int i; + + // Check if a vote is active + + if ( !_voteTime ) + { + return; + } + + // Make sure time hasn't run out for this vote + + if ( level.time - _voteTime >= _maxVoteTime ) + { + multiplayerManager.HUDPrintAllClients( "$$VoteFailed$$\n" ); + } + else + { + // See if we have a majority vote yet + + if ( _voteYes > ( _numVoters / 2.0f ) ) + { + // Vote passed - execute the command, then remove the vote + + multiplayerManager.HUDPrintAllClients( "$$VotePassed$$\n" ); + gi.SendConsoleCommand( va("%s\n", _voteString.c_str() ) ); + } + else if ( _voteNo >= ( _numVoters / 2.0f ) ) + { + // Vote failed - same behavior as a timeout + multiplayerManager.HUDPrintAllClients( "$$VoteFailed$$\n" ); + } + else + { + // still waiting for a majority + return; + } + } + + _voteTime = 0.0f; + + // Clear all the player's vote text + + for( i = 0; i < maxclients->integer; i++ ) + { + Player *currentPlayer; + + currentPlayer = getPlayer( i ); + + if ( currentPlayer ) + { + currentPlayer->clearVoteText(); + } + } +} + +void MultiplayerManager::joinTeam( Player *player, const str &teamName ) +{ + str realTeamName; + + int i; + + // Make sure everything is ok + + if ( !player || !_inMultiplayerGame ) + return; + + // Fix up the teamName + + if ( stricmp( teamName.c_str(), "Blue" ) == 0 ) + realTeamName = "Blue"; + else if ( stricmp( teamName.c_str(), "Red" ) == 0 ) + realTeamName = "Red"; + else + realTeamName = teamName; + + if ( ( realTeamName == "Red" ) || ( realTeamName == "Blue" ) || ( realTeamName == "normal" ) ) + { + _playerData[ player->entnum ]._spectatorByChoice = false; + } + + // See if we can change to the specified team + + if ( !_multiplayerGame->canJoinTeam( player, realTeamName ) ) + return; + + // Tell the modifiers that player left game + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + modifier->removePlayer( player ); + } + } + + // Tell mode that I'm joining the specified team + + _multiplayerGame->joinTeam( player, realTeamName ); + + // Tell all of the modifiers that I'm joining the specified team + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + modifier->addPlayer( player ); + + modifier->joinedTeam( player, realTeamName ); + } + } +} + +void MultiplayerManager::say( Player *player, const str &text, bool team ) +{ + str realText; + str tempText; + int i; + str name; + + + if ( player ) + { + name = player->client->pers.netname; + } + else + { + name = "$$ServerName$$"; + } + + realText = name; + realText += ":"; + + // Color the say (team - yellow, normal - cyan) + + if ( player ) + { + realText += "^"; + + if ( team ) + realText += COLOR_YELLOW; + else + realText += COLOR_CYAN; + + realText += " "; + } + + // Get rid of all color in the say so our defaults will stay + + tempText = text; + + for ( i = 0 ; i < tempText.length() ; i++ ) + { + if ( tempText[ i ] == '^' ) + { + tempText[ i ] = '*'; + } + } + + // Add the say text + + realText += tempText; + + // Finish up with the text + + if ( player ) + { + realText += "^"; + realText += COLOR_NONE; + } + + realText += "\n"; + + // Strip out any bad characters + + for ( i = 0 ; i < realText.length() ; i++ ) + { + if ( realText[ i ] == '%' ) + { + realText[ i ] = '.'; + } + } + + // Don't let text be too long for malicious reasons + + if ( realText.length() > _maxSayStringLength ) + { + HUDPrint( player->entnum, "$$SayTooLong$$\n" ); + return; + } + + // Send say to console if in a dedicated server + + if ( dedicated->integer ) + { + gi.Printf( "%s: %s\n", name.c_str(), text.c_str() ); + } + + // Send the say to appropriate clients + + if ( player && isPlayerSpectator( player ) ) + HUDPrintSpectators( player, realText, team ); + else if ( player && team ) + HUDPrintTeamClients( player, realText ); + else + HUDPrintAllClients( realText ); +} + +void MultiplayerManager::tell( Player *player, const str &text, int entnum ) +{ + str realText; + str tempText; + int i; + Player *otherPlayer; + + + realText = player->client->pers.netname; + realText += ":"; + + // Color the say (team - yellow, normal - cyan) + + realText += "^"; + realText += COLOR_MAGENTA; + realText += " "; + + // Get rid of all color in the say so our defaults will stay + + tempText = text; + + for ( i = 0 ; i < tempText.length() ; i++ ) + { + if ( tempText[ i ] == '^' ) + { + tempText[ i ] = '*'; + } + } + + // Add the say text + + realText += tempText; + + // Finish up with the text + + realText += "^"; + realText += COLOR_NONE; + realText += "\n"; + + // Don't let text be too long for malicious reasons + + if ( realText.length() > _maxSayStringLength ) + { + HUDPrint( player->entnum, "$$SayTooLong$$\n" ); + return; + } + + // Get other player + + otherPlayer = getPlayer( entnum ); + + if ( !otherPlayer ) + return; + + + // Send the say to appropriate clients + + if ( isPlayerSpectator( player ) && !isPlayerSpectator( otherPlayer ) ) + return; + + HUDSay( otherPlayer->entnum, realText ); +} + +// +// Modifier stuff +// + +void MultiplayerManager::addModifiers( void ) +{ + tryAddModifier( "InstantKill" ); + tryAddModifier( "ActionHero" ); + tryAddModifier( "AutoHandicap" ); + tryAddModifier( "PointsPerWeapon" ); + tryAddModifier( "ControlPoints" ); + tryAddModifier( "Destruction" ); + tryAddModifier( "OneFlag" ); + tryAddModifier( "Elimination" ); + tryAddModifier( "Diffusion" ); + tryAddModifier( "Specialties" ); +} + +void MultiplayerManager::tryAddModifier( const str &modifierName ) +{ + cvar_t *mp_modifier; + str cvarName; + + cvarName = "mp_modifier_"; + cvarName += modifierName; + + mp_modifier = gi.cvar( cvarName.c_str(), "0", 0 ); + + if ( mp_modifier->integer ) + { + addModifier( modifierName ); + } +} + +void MultiplayerManager::addModifier( const str &modifierName ) +{ + MultiplayerModifier *newModifier = NULL; + + if ( modifierName == "InstantKill" ) + { + newModifier = new ModifierInstantKill; + } + else if ( modifierName == "ActionHero" ) + { + newModifier = new ModifierActionHero; + } + else if ( modifierName == "AutoHandicap" ) + { + newModifier = new ModifierAutoHandicap; + } + else if ( modifierName == "PointsPerWeapon" ) + { + newModifier = new ModifierPointsPerWeapon; + } + else if ( modifierName == "ControlPoints" ) + { + newModifier = new ModifierControlPoints; + } + else if ( modifierName == "Destruction" ) + { + newModifier = new ModifierDestruction; + } + else if ( modifierName == "OneFlag" ) + { + newModifier = new ModifierOneFlag; + } + else if ( modifierName == "Elimination" ) + { + newModifier = new ModifierElimination; + } + else if ( modifierName == "Diffusion" ) + { + newModifier = new ModifierDiffusion; + } + else if ( modifierName == "Specialties" ) + { + newModifier = new ModifierSpecialties; + } + + if ( newModifier ) + { + _modifiers.AddObject( newModifier ); + } +} + +void MultiplayerManager::addPoints( int entnum, int points ) +{ + if ( !_inMultiplayerGame ) + return; + + _multiplayerGame->addPoints( entnum, points ); +} + +// +// Interface for modes +// + +int MultiplayerManager::getClientNum( int entnum ) +{ + gclient_s *client; + + // Get the client + + client = getClient( entnum ); + + if ( client ) + return client->ps.clientNum; + else + return -1; +} + +int MultiplayerManager::getClientPing( int entnum ) +{ + gclient_s *client; + + // Get the client + + client = getClient( entnum ); + + if ( client ) + return client->ps.ping; + else + return -1; +} + +float MultiplayerManager::getTime( void ) +{ + return level.time; +} + +void MultiplayerManager::centerPrint( int entnum, const str &string, CenterPrintImportance importance ) +{ + gentity_t *gentity; + + if ( ( entnum >= 0 ) && ( entnum < maxclients->integer ) ) + { + gentity = &g_entities[ entnum ]; + + if ( gentity->inuse && gentity->entity && gentity->client ) + { + gi.centerprintf( gentity, importance, string.c_str() ); + } + } +} + +Player *MultiplayerManager::getPlayer( int entnum ) +{ + gentity_t *gentity; + Player *player = NULL; + + // Make sure everything is ok + + if ( ( entnum >= 0 ) && ( entnum < maxclients->integer ) ) + { + gentity = &g_entities[ entnum ]; + + if ( gentity->inuse && gentity->entity && gentity->client && gentity->entity->isSubclassOf( Player ) ) + { + player = (Player *)gentity->entity; + } + } + + return player; +} + +int MultiplayerManager::getMaxPlayers( void ) +{ + return maxclients->integer; +} + +int MultiplayerManager::getTotalPlayers( bool countSpectators ) +{ + int i; + Player *player; + int numPlayers; + + numPlayers = 0; + + for( i = 0 ; i < maxclients->integer ; i++ ) + { + player = getPlayer( i ); + + if ( player ) + { + if ( !isPlayerSpectator( player ) || !countSpectators ) + { + numPlayers++; + } + } + + } + return numPlayers; +} + +gclient_s *MultiplayerManager::getClient( int entnum ) +{ + gentity_t *gentity; + + // Make sure everything is ok + + if ( ( entnum >= 0 ) && ( entnum < maxclients->integer ) ) + { + gentity = &g_entities[ entnum ]; + + if ( gentity->inuse && gentity->entity && gentity->client && gentity->entity->isSubclassOf( Player ) ) + { + return gentity->client; + } + } + + return NULL; +} + +void MultiplayerManager::HUDSay( int entnum, const str &string ) +{ + str command; + Player *player; + + // Get the player + + player = getPlayer( entnum ); + + if ( player ) + { + // Build the hud print command to send to the client + + command = "hudsay \""; + command += string; + command += "\"\n"; + + if ( gi.GetNumFreeReliableServerCommands( player->edict - g_entities ) > 32 ) + { + gi.SendServerCommand( player->edict - g_entities, command.c_str() ); + } + } +} + +void MultiplayerManager::HUDPrint( int entnum, const str &string ) +{ + Player *player; + + player = getPlayer( entnum ); + + if ( player ) + { + player->hudPrint( string ); + } +} + +void MultiplayerManager::statusPrint( int entnum, const str &string ) +{ + str command; + Player *player; + + // Get the player + + player = getPlayer( entnum ); + + if ( player ) + { + // Build the status print command to send to the client + + command = "status \""; + command += string; + command += "\"\n"; + + gi.SendServerCommand( player->edict - g_entities, command.c_str() ); + } +} + +void MultiplayerManager::playerSound( int entnum, const str &soundName, int channel, float volume, float minDist, float time ) +{ + Player *player; + + // Get the playaer + + player = getPlayer( entnum ); + + // Tell the player to play a sound + + if ( player ) + { + addSoundToQueue( player, soundName, channel, volume, minDist, time ); + //player->Sound( soundName, channel, volume, minDist, NULL, 1.0f, true ); + } +} + +void MultiplayerManager::instantPlayerSound( int entnum, const str &soundName, int channel, float volume, float minDist ) +{ + Player *player; + + // Get the playaer + + player = getPlayer( entnum ); + + // Tell the player to play a sound + + if ( player ) + { + player->Sound( soundName, channel, volume, minDist, NULL, 1.0f, true ); + } +} + +void MultiplayerManager::broadcastInstantSound( const str &soundName, int channel, float volume, float minDist, Player *except ) +{ + int i; + Player *player; + + // Go through all of the clients + + for( i = 0; i < maxclients->integer; i++ ) + { + // Get the player + + player = getPlayer( i ); + + // Tell the player to play a sound + + if ( player && ( player != except ) ) + { + instantPlayerSound( player->entnum, soundName, channel, volume, minDist ); + } + } +} + +void MultiplayerManager::teamSound( Team *team, const str &soundName, int channel, float volume, float minDist, float time ) +{ + int i; + Player *player; + + // Go through all of the clients + + for( i = 0; i < maxclients->integer; i++ ) + { + // Get the player + + player = getPlayer( i ); + + // Tell the player to play a sound + + if ( player && ( getPlayersTeam( player ) == team ) ) + { + playerSound( player->entnum, soundName, channel, volume, minDist, time ); + } + } +} + +void MultiplayerManager::broadcastSound( const str &soundName, int channel, float volume, float minDist, Player *except, float time ) +{ + int i; + Player *player; + + // Go through all of the clients + + for( i = 0; i < maxclients->integer; i++ ) + { + // Get the player + + player = getPlayer( i ); + + // Tell the player to play a sound + + if ( player && ( player != except ) ) + { + playerSound( player->entnum, soundName, channel, volume, minDist, time ); + } + } +} + +bool MultiplayerManager::isPlayerSpectator( Player *player, SpectatorTypes spectatorType ) +{ + if ( !_inMultiplayerGame ) + return false; + + if ( spectatorType == SPECTATOR_TYPE_ANY ) + return _playerData[ player->entnum ]._spectator; + else if ( spectatorType == SPECTATOR_TYPE_NONE ) + return !_playerData[ player->entnum ]._spectator; + else + return ( _playerData[ player->entnum ]._spectator && _playerData[ player->entnum ]._spectatorType == spectatorType ); +} + +bool MultiplayerManager::isPlayerSpectatorByChoice( Player *player ) +{ + if ( !_inMultiplayerGame ) + return false; + + if ( _playerData[ player->entnum ]._spectator && _playerData[ player->entnum ]._spectatorByChoice ) + return true; + else + return false; +} + +Player *MultiplayerManager::getPlayerSpectating( Player *player ) +{ + if ( isPlayerSpectator( player, SPECTATOR_TYPE_FOLLOW ) ) + { + return getPlayer( _playerData[ player->entnum ]._spectatorPlayerNum ); + } + + return NULL; +} + +void MultiplayerManager::makePlayerSpectator( Player *player, SpectatorTypes spectatorType, bool byChoice ) +{ + Team *team; + + // Make the player into a spectator + + if ( player ) + { + initPlayer( player ); + + _playerData[ player->entnum ]._spectator = true; + _playerData[ player->entnum ]._spectatorType = spectatorType; + _playerData[ player->entnum ]._spectatorTime = getTime(); + + if ( byChoice ) + { + _playerData[ player->entnum ]._spectatorByChoice = true; + } + + _playerData[ player->entnum ]._spectatorPlayerNum = 0; + + // Print something useful to the player + + if ( spectatorType == SPECTATOR_TYPE_FOLLOW ) + centerPrint( player->entnum, "$$SpectatorFollow$$", CENTERPRINT_IMPORTANCE_NORMAL ); + else if ( spectatorType == SPECTATOR_TYPE_FREEFORM ) + centerPrint( player->entnum, "$$SpectatorFreeForm$$", CENTERPRINT_IMPORTANCE_NORMAL ); + else + centerPrint( player->entnum, "$$SpectatorNormal$$", CENTERPRINT_IMPORTANCE_NORMAL ); + + if ( spectatorType == SPECTATOR_TYPE_FOLLOW ) + { + Player *playerToSpectate = getLastKillerOfPlayer( player ); + + if ( playerToSpectate ) + makePlayerSpectatePlayer( player, playerToSpectate ); + else + makePlayerSpectateNextPlayer( player ); + } + + player->takedamage = DAMAGE_NO; + player->client->ps.feetfalling = false; + player->deadflag = DEAD_NO; + + player->flags &= ~FL_IMMOBILE; + player->flags &= ~FL_STUNNED; + + //player->movecontrol = MOVECONTROL_USER; + + // Hide the player model + + player->hideModel(); + + // Force them to the stand state + + player->SetState( "STAND", "STAND" ); + + // Go not solid + + player->setSolidType( SOLID_NOT ); + + // Move in normal walking mode + + //player->setMoveType( MOVETYPE_NOCLIP ); + player->setMoveType( MOVETYPE_WALK ); + + // Get rid of the inventory + + player->FreeInventory(); + player->disableInventory(); + + team = getPlayersTeam( player ); + + if ( !team ) + setTeamHud( player, "mp_teamspec" ); + else if ( team->getName() == "Red" ) + setTeamHud( player, "mp_teamredspec" ); + else + setTeamHud( player, "mp_teambluespec" ); + + player->clearItemText(); + } +} + +void MultiplayerManager::makePlayerSpectateNextPlayer( Player *player ) +{ + int startIndex; + int currentIndex; + Player *testPlayer; + + if ( !isPlayerSpectator( player, SPECTATOR_TYPE_FOLLOW ) ) + return; + + startIndex = _playerData[ player->entnum ]._spectatorPlayerNum; + + currentIndex = startIndex + 1; + + if ( currentIndex >= maxclients->integer ) + currentIndex = 0; + + while ( currentIndex != startIndex ) + { + testPlayer = getPlayer( currentIndex ); + + if ( testPlayer && !isPlayerSpectator( testPlayer ) ) + { + _playerData[ player->entnum ]._spectatorPlayerNum = currentIndex; + return; + } + + currentIndex++; + + if ( currentIndex >= maxclients->integer ) + currentIndex = 0; + } + + testPlayer = getPlayer( currentIndex ); + + if ( testPlayer && !isPlayerSpectator( testPlayer ) ) + { + _playerData[ player->entnum ]._spectatorPlayerNum = currentIndex; + return; + } + else + { + // If we get here there isn't anyone to spectate + + makePlayerSpectator( player, SPECTATOR_TYPE_NORMAL ); + } +} + +void MultiplayerManager::makePlayerSpectatePrevPlayer( Player *player ) +{ + int startIndex; + int currentIndex; + Player *testPlayer; + + if ( !isPlayerSpectator( player, SPECTATOR_TYPE_FOLLOW ) ) + return; + + startIndex = _playerData[ player->entnum ]._spectatorPlayerNum; + + currentIndex = startIndex - 1; + + if ( currentIndex < 0 ) + currentIndex = maxclients->integer - 1; + + while ( currentIndex != startIndex ) + { + testPlayer = getPlayer( currentIndex ); + + if ( testPlayer && !isPlayerSpectator( testPlayer ) ) + { + _playerData[ player->entnum ]._spectatorPlayerNum = currentIndex; + return; + } + + currentIndex--; + + if ( currentIndex < 0 ) + currentIndex = maxclients->integer - 1; + } + + // If we get here there isn't anyone to spectate + + makePlayerSpectator( player, SPECTATOR_TYPE_NORMAL ); +} + +void MultiplayerManager::makePlayerSpectatePlayer( Player *player, Player *playerToSpectate ) +{ + if ( !isPlayerSpectator( player, SPECTATOR_TYPE_FOLLOW ) ) + return; + + if ( playerToSpectate && !isPlayerSpectator( playerToSpectate ) ) + { + _playerData[ player->entnum ]._spectatorPlayerNum = playerToSpectate->entnum; + return; + } + + // PlayerToSpectate wasn't valid, try the next player instead + + makePlayerSpectateNextPlayer( player ); +} + +void MultiplayerManager::playerEnterArena( int entnum, float health ) +{ + Player *player; + Team *team; + + player = getPlayer( entnum ); + + if ( !player ) + return; + + _playerData[ entnum ]._spectator = false; + _playerData[ entnum ]._spectatorByChoice = false; + + // Make sure player is solid and can take damage + + player->takedamage = DAMAGE_YES; + player->setSolidType( SOLID_BBOX ); + + // Show the player + + player->showModel(); + + // Get rid of our inventory + + player->FreeInventory(); + + player->dropRune(); + player->dropPowerup(); + player->removeHoldableItem(); + + player->ProcessEvent( EV_Sentient_StopOnFire ); + player->stopStasis(); + + player->removeAttachedModelByTargetname( "attachedSpecialityBackpack" ); + player->removeAttachedModelByTargetname( "attachedDiffusionBomb" ); + player->removeAttachedModelByTargetname( "actionHero" ); + + player->clearTempAttachments(); + + player->enableInventory(); + + // Init(); + + // Set the player's health to the specified value + + player->health = health; + + // hold in place briefly + player->client->ps.pm_time = 100; + player->client->ps.pm_flags |= PMF_TIME_TELEPORT; + + Event *newEvent = new Event( EV_DisplayEffect ); + newEvent->AddString( "TransportIn" ); + newEvent->AddString( "Multiplayer" ); + player->PostEvent( newEvent, 0.0f ); + + changePlayerModel( player, player->model, true ); + + team = getPlayersTeam( player ); + + if ( !team ) + setTeamHud( player, "" ); + else if ( team->getName() == "Red" ) + setTeamHud( player, "mp_teamred" ); + else + setTeamHud( player, "mp_teamblue" ); +} + +void MultiplayerManager::playerSpawned( Player *player ) +{ + // Tell all of the modifiers that the player has respawned + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + modifier->playerSpawned( player ); + } + } +} + +void MultiplayerManager::allowFighting( bool allowFighting ) +{ + _allowFighting = allowFighting; +} + +void MultiplayerManager::centerPrintAllClients( const str &string, CenterPrintImportance importance ) +{ + int i; + gentity_t *ent; + + // Go through all of the clients + + for( i = 0; i < maxclients->integer; i++ ) + { + ent = g_entities + i; + + // Make sure this is a valid client + + if ( !ent->inuse || !ent->client || !ent->entity ) + continue; + + // Send centerprint command to the client + + centerPrint( ent->entity->entnum, string, importance ); + } +} + +void MultiplayerManager::HUDPrintAllClients( const str &string ) +{ + int i; + gentity_t *ent; + + // Go through all of the clients + + for( i = 0; i < maxclients->integer; i++ ) + { + ent = g_entities + i; + + // Make sure this is a valid client + + if ( !ent->inuse || !ent->client || !ent->entity ) + continue; + + // Send hud print command to the client + + HUDSay( ent->entity->entnum, string ); + } +} + +void MultiplayerManager::centerPrintTeamClients( Player *player, const str &string, CenterPrintImportance importance ) +{ + int i; + gentity_t *ent; + Player *otherPlayer; + + // Go through all of the clients + + for( i = 0; i < maxclients->integer; i++ ) + { + ent = g_entities + i; + + // Make sure this is a valid client + + if ( !ent->inuse || !ent->client || !ent->entity ) + continue; + + if ( !ent->entity->isSubclassOf( Player ) ) + continue; + + otherPlayer = (Player *)ent->entity; + + // Make sure this player is on the same team + + if ( getPlayersTeam( player ) == getPlayersTeam( otherPlayer ) ) + { + // Send centerprint command to the client + + centerPrint( otherPlayer->entnum, string, importance ); + } + } +} + +void MultiplayerManager::HUDPrintTeamClients( Player *player, const str &string ) +{ + int i; + gentity_t *ent; + Player *otherPlayer; + + // Go through all of the clients + + for( i = 0; i < maxclients->integer; i++ ) + { + ent = g_entities + i; + + // Make sure this is a valid client + + + if ( !ent->inuse || !ent->client || !ent->entity ) + continue; + + if ( !ent->entity->isSubclassOf( Player ) ) + continue; + + otherPlayer = (Player *)ent->entity; + + // Make sure this player is on the same team + + if ( getPlayersTeam( player ) == getPlayersTeam( otherPlayer ) ) + { + // Send the hud print command to the client + + HUDSay( otherPlayer->entnum, string ); + } + } +} + +void MultiplayerManager::HUDPrintSpectators( Player *player, const str &string, bool team ) +{ + int i; + gentity_t *ent; + Player *otherPlayer; + + // Go through all of the clients + + for( i = 0 ; i < maxclients->integer ; i++ ) + { + ent = g_entities + i; + + // Make sure this is a valid client + + if ( !ent->inuse || !ent->client || !ent->entity ) + continue; + + if ( !ent->entity->isSubclassOf( Player ) ) + continue; + + otherPlayer = (Player *)ent->entity; + + // Make sure this player is a spectator + + if ( !isPlayerSpectator( otherPlayer ) ) + continue; + + // Make sure the players are on the same team (if requested) + + if ( team && ( getPlayersTeam( player ) != getPlayersTeam( otherPlayer ) ) ) + continue; + + // Send the hud print command to the client + + HUDSay( otherPlayer->entnum, string ); + } +} + +void MultiplayerManager::addPlayerHealth( int entnum, float healthToAdd ) +{ + Player *player; + + player = getPlayer( entnum ); + + if ( player ) + { + player->AddHealth( healthToAdd ); + } +} + +void MultiplayerManager::givePlayerItem( int entnum, const str &itemName ) +{ + Player *player; + Event *event; + bool canGive; + + + if ( !_inMatch ) + return; + + player = getPlayer( entnum ); + + if ( !player ) + return; + + canGive = canGivePlayerItem( entnum, itemName ); + + if ( canGive ) + { + event = new Event( EV_Player_GiveCheat ); + event->AddString( itemName ); + player->ProcessEvent( event ); + } +} + +void MultiplayerManager::usePlayerItem( int entnum, const str &itemName ) +{ + Player *player; + + player = getPlayer( entnum ); + + if ( !player ) + return; + + // Use the specified item + + Event *ev = new Event( "use" ); + ev->AddString( itemName ); + player->ProcessEvent( ev ); +} + +bool MultiplayerManager::canGivePlayerItem( int entnum, const str &itemName ) +{ + int i; + Player *player; + + player = getPlayer( entnum ); + + if ( !player ) + return false; + + if ( player->FindItem( itemName ) ) + return false; + + // Make sure all of the modifiers allow this item to be given to the player + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + if ( !modifier->canGivePlayerItem( entnum, itemName ) ) + return false; + } + } + + // Make sure the game allows this item to be given to the player + + if ( !_multiplayerGame->canGivePlayerItem( entnum, itemName ) ) + return false; + + return true; +} + +float MultiplayerManager::getItemRespawnMultiplayer( void ) +{ + if ( !_inMultiplayerGame ) + return 1.0f; + else + return mp_itemRespawnMultiplier->value; +} + +float MultiplayerManager::getWeaponRespawnMultiplayer( void ) +{ + if ( !_inMultiplayerGame ) + return 1.0f; + else + return mp_weaponRespawnMultiplier->value; +} + +float MultiplayerManager::getPowerupRespawnMultiplayer( void ) +{ + if ( !_inMultiplayerGame ) + return 1.0f; + else + return mp_powerupRespawnMultiplier->value; +} + +void MultiplayerManager::playerEventNotification( const char *eventName, const char *eventItemName, Player *eventPlayer ) +{ + int i; + gentity_t *ent; + Player *player; + + if ( !_inMultiplayerGame ) + return; + + // Notify all of the modifiers about this event + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + modifier->playerEventNotification( eventName, eventItemName, eventPlayer ); + } + + // Notify the award system about this event + + _awardSystem->playerEventNotification( eventName, eventItemName, eventPlayer ); + + // Notify all the player's about the event + + for( i = 0; i < maxclients->integer; i++ ) + { + ent = g_entities + i; + + // Make sure this is a valid client + + if ( !ent->inuse || !ent->client || !ent->entity || !ent->entity->isSubclassOf( Player ) ) + continue; + + player = (Player *)ent->entity; + + player->notifyPlayerOfMultiplayerEvent( eventName, eventItemName, eventPlayer ); + } + + // Notify the game about the event + + _multiplayerGame->playerEventNotification( eventName, eventItemName, eventPlayer ); +} + +void MultiplayerManager::startMatch( void ) +{ + int i; + + _inMatch = true; + + // Inform all of the modifiers that the match is starting + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + modifier->matchStarting(); + } + } + + // Inform the game that the match has started + + _multiplayerGame->startMatch(); + + // Inform all of the modifiers that the match has started + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + modifier->matchStarted(); + } + } +} + +void MultiplayerManager::restartMatch( void ) +{ + // Inform the game that the match has ended + + _multiplayerGame->endMatch(); + + // Inform all of the modifiers that the match has started + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + modifier->matchEnded(); + } + } + + resetItems(); + + // Inform the game that the match has restarted + + _multiplayerGame->restartMatch(); + + // Inform all of the modifiers that the match has started + + /* for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + modifier->matchRestarted(); + } + } */ + + _inMatch = true; +} + +void MultiplayerManager::endMatch( void ) +{ + _inMatch = false; + + _restartMatchTime = getTime() + _inBetweenMatchTime; +} + +void MultiplayerManager::initPlayer( Player *player ) +{ + Vector savedAngles; + Vector savedViewAngles; + qboolean saved_teleport_bit; + + // Cancel all events that have been posted for the future + + player->CancelPendingEvents(); + + // Save off necessary stuff + + saved_teleport_bit = player->edict->s.eFlags & EF_TELEPORT_BIT; + savedAngles = player->client->cmd_angles; + savedViewAngles = player->getViewAngles(); + + // Initialize the player + + player->Init(); + + // Restore necessary stuff + + player->edict->s.eFlags |= saved_teleport_bit; + savedAngles.copyTo( player->client->cmd_angles ); + player->SetViewAngles( savedViewAngles ); +} + +Player *MultiplayerManager::getLastKilledByPlayer( Player *player, int *meansOfdeath ) +{ + return _multiplayerGame->getLastKilledByPlayer( player, meansOfdeath ); +} + +Player *MultiplayerManager::getLastKillerOfPlayer( Player *player, int *meansOfdeath ) +{ + return _multiplayerGame->getLastKillerOfPlayer( player, meansOfdeath ); +} + +void MultiplayerManager::playerCommand( Player *player, const char *command, const char *parm ) +{ + if ( !_inMultiplayerGame ) + return; + + // Pass the player command to all of the modifiers + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + modifier->playerCommand( player, command, parm ); + } + } + + // Pass the player command to the game + + _multiplayerGame->playerCommand( player, command, parm ); +} + +void MultiplayerManager::playerInput( Player *player, int newButtons ) +{ + if ( isPlayerSpectator( player ) ) + { + if ( newButtons & BUTTON_USE ) + { + // Change spectator type + + if ( isPlayerSpectator( player, SPECTATOR_TYPE_FOLLOW ) ) + makePlayerSpectator( player, SPECTATOR_TYPE_NORMAL ); + else if ( isPlayerSpectator( player, SPECTATOR_TYPE_NORMAL ) ) + makePlayerSpectator( player, SPECTATOR_TYPE_FREEFORM ); + else + makePlayerSpectator( player, SPECTATOR_TYPE_FOLLOW ); + } + + if ( isPlayerSpectator( player, SPECTATOR_TYPE_FOLLOW ) && ( _playerData[ player->entnum ]._spectatorTime != getTime() ) ) + { + if ( newButtons & BUTTON_ATTACKRIGHT ) + { + // Follow the next player + + makePlayerSpectateNextPlayer( player ); + } + + if ( newButtons & BUTTON_ATTACKLEFT ) + { + // Follow the prev player + + makePlayerSpectatePrevPlayer( player ); + } + } + } +} + +int MultiplayerManager::getInfoIcon( Player *player, int buttons ) +{ + int icon; + + // See if the play is talking + + if ( buttons & BUTTON_TALK ) + return _talkingIconIndex; + + // See if the game has anything + + icon = _multiplayerGame->getInfoIcon( player ); + + if ( icon > 0 ) + return icon; + + // See if the award system has anything + + icon = _awardSystem->getInfoIcon( player ); + + if ( icon > 0 ) + return icon; + + // See if any modifiers have anything + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + icon = modifier->getInfoIcon( player ); + + if ( icon > 0 ) + return icon; + } + } + + // No one has anything to display + + return 0; +} + +void MultiplayerManager::teamPointsChanged( Team *team, int oldPoints, int newPoints ) +{ + if ( !_inMultiplayerGame ) + return; + + // Tell the game that the team points have changed + + _multiplayerGame->teamPointsChanged( team, oldPoints, newPoints ); +} + +void MultiplayerManager::cacheMultiplayerFiles( const str &cacheFileName ) +{ + str fileToCache; + + // Build the name of the file to cache + + fileToCache = "precache/server/"; + fileToCache += cacheFileName; + fileToCache += ".txt"; + + // Cache everything in the file (if it is there) + + if ( gi.FS_ReadFile( fileToCache, NULL, true ) != -1 ) + { + level.consoleThread->Parse( fileToCache ); + } +} + +str MultiplayerManager::getPlaceName( Player *player ) +{ + int place; + str placeName; + + place = _multiplayerGame->getPlace( player ); + + placeName = "$$Place"; + placeName += place; + placeName += "$$"; + + return placeName; +} + +void MultiplayerManager::setTeamHud( Player *player, const str &teamHudName ) +{ + str command; + + if ( teamHudName == _playerData[ player->entnum ]._teamHud ) + return; + + // Remove the old team hud + + if ( _playerData[ player->entnum ]._teamHud.length() > 0 ) + { + command = va( "stufftext \"ui_removehud %s\"\n", _playerData[ player->entnum ]._teamHud.c_str() ); + gi.SendServerCommand( player->entnum, command.c_str() ); + } + + // Add the new team hud + + if ( teamHudName.length() > 0 ) + { + command = va( "stufftext \"ui_addhud %s\"\n", teamHudName.c_str() ); + gi.SendServerCommand( player->entnum, command.c_str() ); + } + + _playerData[ player->entnum ]._teamHud = teamHudName; +} + +str MultiplayerManager::getSpawnPointType( Player *player ) +{ + str spawnPointName; + MultiplayerModifier *modifier; + int i; + float priority; + float highestPriority = 0.0f; + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + priority = modifier->getSpawnPointPriority( player ); + + if ( priority > highestPriority ) + { + spawnPointName = modifier->getSpawnPointType( player ); + + highestPriority = priority; + } + } + } + + return spawnPointName; +} + +bool MultiplayerManager::isValidPlayerModel( Player *player, str modelToUse ) +{ + MultiplayerModifier *modifier; + int i; + bool validPlayerModel = false; + int modelIndex; + tiki_cmd_t tikicmds; + + + // Check to see if the model itself is ok first + + modelIndex = gi.modelindex( modelToUse ); + + if ( gi.InitCommands( modelIndex, &tikicmds ) ) + { + for( i = 0; i < tikicmds.num_cmds; i++ ) + { + if ( stricmp( tikicmds.cmds[ i ].args[ 0 ], "validPlayerModel" ) == 0 ) + { + validPlayerModel = true; + break; + } + } + } + + // Check with the game to see if the model is ok + + validPlayerModel = _multiplayerGame->isValidPlayerModel( player, modelToUse, validPlayerModel ); + + // Check with all of the modifiers to see if the model is ok + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + validPlayerModel = modifier->isValidPlayerModel( player, modelToUse, validPlayerModel ); + } + } + + return validPlayerModel; +} + +str MultiplayerManager::getDefaultPlayerModel( Player *player ) +{ + MultiplayerModifier *modifier; + int i; + str modelName; + + + modelName = _multiplayerGame->getDefaultPlayerModel( player, "models/char/munro.tik" ); + + for ( i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + modelName = modifier->getDefaultPlayerModel( player, modelName ); + } + } + + return modelName; +} + +void MultiplayerManager::setNextMap( void ) +{ + str nextMapName; + str fullMapName; + + if ( ( strlen( sv_nextmap->string ) == 0 ) && ( mp_useMapList->integer ) && ( strlen( mp_mapList->string ) > 0 ) ) + { + nextMapName = getNextMap(); + + fullMapName = "maps/"; + fullMapName += nextMapName; + fullMapName += ".bsp"; + + if ( gi.FS_Exists( fullMapName ) ) + { + gi.cvar_set( "nextmap", nextMapName.c_str() ); + } + else + { + gi.Printf( "%s map not found\n", fullMapName.c_str() ); + } + + gi.cvar_set( "mp_currentPosInMapList", va( "%d", mp_currentPosInMapList->integer + 1 ) ); + + } +} + +str MultiplayerManager::getNextMap( void ) +{ + str nextMapName; + str mapList; + int numMaps; + const char *currentPlaceInMapList; + int realCurrentPos; + int i; + str tempString; + const char *nextSpace; + int diff; + + + mapList = mp_mapList->string; + + // Get the number of maps in the list + + numMaps = 0; + currentPlaceInMapList = mapList.c_str(); + + while( 1 ) + { + currentPlaceInMapList = strstr( currentPlaceInMapList, ";" ); + + if ( currentPlaceInMapList ) + numMaps++; + else + break; + + currentPlaceInMapList++; + } + + if ( mapList.length() && ( mapList[ mapList.length() - 1 ] != ';' ) ) + numMaps++; + + if ( numMaps == 0 ) + return ""; + + // Get the position in the list + + realCurrentPos = mp_currentPosInMapList->integer % numMaps; + + // Get the next map string + + currentPlaceInMapList = mapList.c_str(); + + for ( i = 0 ; i < realCurrentPos ; i++ ) + { + currentPlaceInMapList = strstr( currentPlaceInMapList, ";" ); + currentPlaceInMapList++; + } + + nextMapName = currentPlaceInMapList; + + currentPlaceInMapList = nextMapName.c_str(); + + currentPlaceInMapList = strstr( currentPlaceInMapList, ";" ); + + if ( currentPlaceInMapList ) + { + nextMapName.CapLength( currentPlaceInMapList - nextMapName.c_str() ); + } + + // Remove spaces from the beginning of the map name + + while ( ( nextMapName.length() > 0 ) && ( nextMapName[ 0 ] == ' ' ) ) + { + tempString = nextMapName.c_str() + 1; + nextMapName = tempString; + } + + // Remove spaces from the end of the map name + + nextSpace = strstr( nextMapName.c_str(), " " ); + + if ( nextSpace ) + { + diff = nextSpace - nextMapName.c_str(); + nextMapName.CapLength( diff ); + } + + return nextMapName; +} + +float MultiplayerManager::getRespawnTime( void ) +{ + return _respawnTime; +} + +void MultiplayerManager::setRespawnTime( float time ) +{ + _respawnTime = time; +} + +void MultiplayerManager::resetRespawnTime( void ) +{ + setRespawnTime( mp_respawnTime->value ); + mp_respawnTime->modified = false; +} + +void MultiplayerManager::checkModifiedCvars( bool informPlayers ) +{ + // Inform players about cvar changes (if any ) + + if ( informPlayers ) + { + if ( hasFlagChanged( MP_FLAG_WEAPONS_STAY ) ) + checkCvar( mp_flags, "$$WeaponsStay$$", MP_CVAR_TYPE_BOOL, MP_FLAG_WEAPONS_STAY ); + if ( hasFlagChanged( MP_FLAG_NO_FALLING ) ) + checkCvar( mp_flags, "$$NoFalling$$", MP_CVAR_TYPE_BOOL, MP_FLAG_NO_FALLING ); + if ( hasFlagChanged( MP_FLAG_FRIENDLY_FIRE ) ) + checkCvar( mp_flags, "$$AllowFriendlyFire$$", MP_CVAR_TYPE_BOOL, MP_FLAG_FRIENDLY_FIRE ); + if ( hasFlagChanged( MP_FLAG_FORCE_RESPAWN ) ) + checkCvar( mp_flags, "$$ForceRespawn$$", MP_CVAR_TYPE_BOOL, MP_FLAG_FORCE_RESPAWN ); + if ( hasFlagChanged( MP_FLAG_INFINITE_AMMO ) ) + checkCvar( mp_flags, "$$InfiniteAmmo$$", MP_CVAR_TYPE_BOOL, MP_FLAG_INFINITE_AMMO ); + if ( hasFlagChanged( MP_FLAG_FIXED_FOV ) ) + checkCvar( mp_flags, "$$FixedFOV$$", MP_CVAR_TYPE_BOOL, MP_FLAG_FIXED_FOV ); + if ( hasFlagChanged( MP_FLAG_NO_DROP_WEAPONS ) ) + checkCvar( mp_flags, "$$DontDropWeapons$$", MP_CVAR_TYPE_BOOL, MP_FLAG_NO_DROP_WEAPONS ); + if ( hasFlagChanged( MP_FLAG_NO_FOOTSTEPS ) ) + checkCvar( mp_flags, "$$NoFootstepSounds$$", MP_CVAR_TYPE_BOOL, MP_FLAG_NO_FOOTSTEPS ); + if ( hasFlagChanged( MP_FLAG_DONT_ALLOW_VOTE ) ) + checkCvar( mp_flags, "$$DontAllowVoting$$", MP_CVAR_TYPE_BOOL, MP_FLAG_DONT_ALLOW_VOTE ); + if ( hasFlagChanged( MP_FLAG_FULL_COLLISION ) ) + checkCvar( mp_flags, "$$FullCollision$$", MP_CVAR_TYPE_BOOL, MP_FLAG_FULL_COLLISION ); + + checkCvar( mp_pointlimit, "$$PointLimit$$", MP_CVAR_TYPE_INTEGER ); + checkCvar( mp_timelimit, "$$TimeLimit$$", MP_CVAR_TYPE_INTEGER ); + + checkCvar( mp_itemRespawnMultiplier, "$$ItemRespawnMultiplier$$", MP_CVAR_TYPE_FLOAT ); + checkCvar( mp_weaponRespawnMultiplier, "$$WeaponRespawnMultiplier$$", MP_CVAR_TYPE_FLOAT ); + checkCvar( mp_powerupRespawnMultiplier, "$$PowerupRespawnMultiplier$$", MP_CVAR_TYPE_FLOAT ); + checkCvar( mp_knockbackMultiplier, "$$KnocbackMultiplier$$", MP_CVAR_TYPE_INTEGER ); + checkCvar( mp_damageMultiplier, "$$DamageMultiplier$$", MP_CVAR_TYPE_FLOAT ); + checkCvar( mp_respawnInvincibilityTime, "$$RespawnInvincibilityTime$$", MP_CVAR_TYPE_FLOAT ); + + checkCvar( mp_warmUpTime, "$$WarmUpTime$$", MP_CVAR_TYPE_INTEGER ); + + checkCvar( sv_maxspeed, "$$PlayerSpeed$$", MP_CVAR_TYPE_INTEGER ); + + checkCvar( mp_bigGunMode, "$$OptionBigGunMode$$", MP_CVAR_TYPE_INTEGER ); + + checkCvar( mp_respawnTime, "$$OptionRespawnTime$$", MP_CVAR_TYPE_INTEGER ); + checkCvar( mp_bombTime, "$$OptionBombTime$$", MP_CVAR_TYPE_INTEGER ); + checkCvar( mp_maxVotes, "$$OptionMaxVotes$$", MP_CVAR_TYPE_INTEGER ); + } + + // Mark everything as not modified + + mp_flags->modified = false; + mp_pointlimit->modified = false; + mp_timelimit->modified = false; + mp_itemRespawnMultiplier->modified = false; + mp_weaponRespawnMultiplier->modified = false; + mp_powerupRespawnMultiplier->modified = false; + mp_knockbackMultiplier->modified = false; + mp_damageMultiplier->modified = false; + mp_respawnInvincibilityTime->modified = false; + mp_warmUpTime->modified = false; + sv_maxspeed->modified = false; + mp_bigGunMode->modified = false; + mp_respawnTime->modified = false; + mp_bombTime->modified = false; + mp_maxVotes->modified = false; + + // Save off the flags + + _oldFlags = mp_flags->integer; +} + + +void MultiplayerManager::checkCvar( cvar_t *mp_cvarToCheck, str optionName, MPCvarType cvarType, int bitToCheck ) +{ + str stringToPrint; + + if ( mp_cvarToCheck->modified ) + { + stringToPrint = "$$ServerOptionChanged$$ : "; + stringToPrint += optionName; + stringToPrint += " "; + + if ( cvarType == MP_CVAR_TYPE_INTEGER ) + { + stringToPrint += mp_cvarToCheck->integer; + } + else if ( cvarType == MP_CVAR_TYPE_FLOAT ) + { + stringToPrint += mp_cvarToCheck->value; + } + else if ( cvarType == MP_CVAR_TYPE_BOOL ) + { + int value; + + if ( bitToCheck >= 0 ) + { + value = mp_cvarToCheck->integer & bitToCheck; + } + else + { + value = mp_cvarToCheck->integer; + } + + if ( value == 0 ) + stringToPrint += "$$Off$$"; + else + stringToPrint += "$$On$$"; + } + + stringToPrint += "\n"; + + multiplayerManager.HUDPrintAllClients( stringToPrint ); + } +} + +bool MultiplayerManager::hasFlagChanged( int bitToCheck ) +{ + if ( ( _oldFlags & bitToCheck ) != ( mp_flags->integer & bitToCheck ) ) + return true; + else + return false; +} + +void MultiplayerManager::addSoundToQueue( Player *player, str soundName, int channel, float volume, float minDist, float time ) +{ + MultiplayerDialogData * dialogData; + int nextDialogAddSpot; + MultiplayerPlayerData * playerData; + + + playerData = &_playerData[ player->entnum ]; + + nextDialogAddSpot = playerData->getNextDialogAddSpot(); + + dialogData = &playerData->_dialogData[ nextDialogAddSpot ]; + + // Add the dialog to the queue + + dialogData->_soundName = soundName; + dialogData->_channel = channel; + dialogData->_volume = volume; + dialogData->_minDist = minDist; + dialogData->_time = time; + + // Move to the next spot + + playerData->_nextDialogAddSpot++; +} + +void MultiplayerManager::sendNextPlayerSound( Player *player ) +{ + MultiplayerDialogData * dialogData; + MultiplayerPlayerData * playerData; + int nextDialogSendSpot; + + playerData = &_playerData[ player->entnum ]; + + // Make sure we haven't sent anything too recently + + if ( playerData->_nextDialogSendTime > multiplayerManager.getTime() ) + return; + + // Make sure we have something to send + + if ( playerData->_nextDialogSendSpot >= playerData->_nextDialogAddSpot ) + return; + + // Send the sound + + nextDialogSendSpot = playerData->getNextDialogSendSpot(); + + dialogData = &playerData->_dialogData[ nextDialogSendSpot ]; + + player->Sound( dialogData->_soundName, dialogData->_channel, dialogData->_volume, dialogData->_minDist, NULL, 1.0f, true ); + + // Move to the next slot + + playerData->_nextDialogSendSpot++; + + // Don't send another sound for a little while + + playerData->_nextDialogSendTime = multiplayerManager.getTime() + dialogData->_time; +} + +bool MultiplayerManager::skipWeaponReloads( void ) +{ + if ( !_inMultiplayerGame ) + return false; + + if ( mp_skipWeaponReloads->integer ) + return true; + + if ( _multiplayerGame->skipWeaponReloads() ) + return true; + + for ( int i = 1 ; i <= _modifiers.NumObjects() ; i++ ) + { + MultiplayerModifier *modifier; + + modifier = _modifiers.ObjectAt( i ); + + if ( modifier ) + { + if ( modifier->skipWeaponReloads() ) + return true; + } + } + + return false; +} diff --git a/dlls/game/mp_manager.hpp b/dlls/game/mp_manager.hpp new file mode 100644 index 0000000..5244fa7 --- /dev/null +++ b/dlls/game/mp_manager.hpp @@ -0,0 +1,337 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/dm_manager.h $ +// $Revision:: 31 $ +// $Author:: Steven $ +// $Date:: 7/18/02 10:45a $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +// Forward declaration +class Player; +//class DM_Team ; + +#ifndef __MP_MANAGER_HPP__ +#define __MP_MANAGER_HPP__ + +// Typedefs +typedef SafePtr PlayerPtr; + +#include "g_local.h" +#include "mp_modeBase.hpp" +#include "mp_modifiers.hpp" +#include "mp_awardsystem.hpp" +#include "mp_shared.hpp" +#include "equipment.h" + +class MultiplayerManager; + +extern MultiplayerManager multiplayerManager; + +extern str playersLastTeam[ MAX_CLIENTS ]; + +typedef enum +{ + MP_CVAR_TYPE_INTEGER, + MP_CVAR_TYPE_FLOAT, + MP_CVAR_TYPE_BOOL +} MPCvarType; + +class MultiplayerDialogData +{ +public: + str _soundName; + int _channel; + float _volume; + float _minDist; + float _time; +}; + +class MultiplayerPlayerData +{ +public: + static const int _maxDialogs; + + bool _valid; + int _votecount; + bool _voted; + bool _spectator; + bool _spectatorByChoice; + SpectatorTypes _spectatorType; + float _spectatorTime; + int _spectatorPlayerNum; + bool _waitingForRespawn; + float _respawnTime; + str _teamHud; + + bool _named; + str _name; + + MultiplayerDialogData* _dialogData; + int _nextDialogSendSpot; + int _nextDialogAddSpot; + float _nextDialogSendTime; + float _nextTauntTime; + + MultiplayerPlayerData(); + ~MultiplayerPlayerData(); + void reset( void ); + + int getNextDialogAddSpot() { return _nextDialogAddSpot % _maxDialogs; } + int getNextDialogSendSpot() { return _nextDialogSendSpot % _maxDialogs; } +}; + +class MultiplayerManager : public Class + { + private: + static const int _playerFreezeTime; + //static const int _maxVoteCount; + static const float _maxVoteTime; + static const int _maxSayStringLength; + static const float _inBetweenMatchTime; + + bool _inMultiplayerGame; + bool _gameStarted; + bool _gameOver; + MultiplayerModeBase *_multiplayerGame; + bool _allowFighting; + Container _modifiers; + MultiplayerPlayerData *_playerData; + AwardSystem *_awardSystem; + + float _voteTime; + str _voteString; + int _voteYes; + int _voteNo; + int _numVoters; + + float _declareWinnerTime; + bool _declaredWinner; + + bool _needToAddBots; + + bool _inMatch; + + int _talkingIconIndex; + int _waitingToRespawnIconIndex; + + float _restartMatchTime; + + float _respawnTime; + + int _oldFlags; + + gclient_s * getClient( int entnum ); + + void start( void ); + void addBots( void ); + + void matchOver( void ); + void restartMatch( void ); + + void setNextMap( void ); + str getNextMap( void ); + + void addSoundToQueue( Player *player, str soundName, int channel, float volume, float minDist, float time ); + void sendNextPlayerSound( Player *player ); + + protected: + + public: + CLASS_PROTOTYPE( MultiplayerManager ); + + MultiplayerManager(); + ~MultiplayerManager(); + + // + // Interface for game dll + // + + void init( void ); + void cleanup( qboolean restart ); + void update( float frameTime ); + bool inMultiplayer( void ); + bool checkFlag( unsigned int flag ); + bool isFightingAllowed( void ); + bool fullCollision( void ); + + void initMultiplayerGame( void ); + void initItems( void ); + void resetItems( void ); + void addPlayer( Player *player ); + void removePlayer( Player *player ); + + void applySpeedModifiers( Player *player, int *moveSpeed ); + void applyJumpModifiers( Player *player, int *jumpSpeed ); + void applyAirAccelerationModifiers( Player *player, int *airAcceleration ); + + bool canPickup( Player *player, MultiplayerItemType itemType, const char *item_name ); + void pickedupItem( Player *player, MultiplayerItemType itemType, const char *item_name ); + + void changePlayerModel( Player *player, const char *modelName, bool force = false ); + void resetPlayerStateMachine( Player *player ); + void changePlayerName( Player *player, const str &PlayerName ); + void playerDead( Player *player ); + void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); + float playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ); + void playerTookDamage( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ); + void playerFired( Player *attackingPlayer ); + float getModifiedKnockback( Player *damagedPlayer, Player *attackingPlayer, float knockback ); + + void itemTouched( Player *player, MultiplayerItem *item ); + void itemDestroyed( Player *player, MultiplayerItem *item ); + float itemDamaged( MultiplayerItem *item, Player *attackingPlayer, float damage, int meansOfDeath ); + void itemUsed( Entity *entity, MultiplayerItem *item ); + + void playerUsed( Player *usedPlayer, Player *usingPlayer, Equipment *equipment ); + + void score( Player *player ); + + void respawnPlayer( Player *player, bool forced ); + void respawnAllPlayers( void ); + Entity * getSpawnPoint( Player *player ); + + bool isPlayerSpectator( Player *player, SpectatorTypes spectatorType = SPECTATOR_TYPE_ANY ); + bool isPlayerSpectatorByChoice( Player *player ); + Player * getPlayerSpectating( Player *player ); + void makePlayerSpectator( Player *player, SpectatorTypes spectatorType = SPECTATOR_TYPE_FOLLOW, bool byChoice = false ); + void makePlayerSpectateNextPlayer( Player *player ); + void makePlayerSpectatePrevPlayer( Player *player ); + void makePlayerSpectatePlayer( Player *player, Player *playerToSpectate ); + void playerEnterArena( int entnum, float health ); + void playerSpawned( Player *player ); + + int getPoints( Player *player ); + int getKills( Player *player ); + int getDeaths( Player *player ); + int getTeamPoints( Player *player ); + int getTeamPoints( const str & teamName ); + void addTeamPoints( const str & teamName, int points ); + + int getStat( Player *player, int statNum ); + int getIcon( Player *player, int statNum ); + int getScoreIcon( Player *player, int index ); + int getInfoIcon( Player *player, int buttons ); + + Player * getLastKilledByPlayer( Player *player, int *meansOfDeath = NULL ); + Player * getLastKillerOfPlayer( Player *player, int *meansOfDeath = NULL ); + + Team* getPlayersTeam( const Player *player ); + + int getPointsForKill( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath, int points ); + + void callVote( Player *player, const str &command, const str &arg ); + void vote( Player *player, const str &vote ); + void checkVote( void ); + + void joinTeam( Player *player, const str &teamName ); + + void say( Player *player, const str &text, bool team ); + void tell( Player *player, const str &text, int entnum ); + + float getItemRespawnMultiplayer( void ); + float getWeaponRespawnMultiplayer( void ); + float getPowerupRespawnMultiplayer( void ); + + bool checkGameType( const char *rule ); + bool doesPlayerHaveItem( Player *player, const char *itemName ); + + void playerCommand( Player *player, const char *command, const char *parm ); + void playerInput( Player *player, int newButtons ); + + // + // Modifier stuff + // + + void addModifiers( void ); + void tryAddModifier( const str &modifierName ); + void addModifier( const str &modifierName ); + + void addPoints( int entnum, int points ); + + // + // Interface for modes + // + + Player * getPlayer( int entnum ); + int getMaxPlayers( void ); + int getTotalPlayers( bool countSpectators = false ); + + int getClientNum( int entnum ); + int getClientPing( int entnum ); + float getTime( void ); + void centerPrint( int entnum, const str &string, CenterPrintImportance importance ); + void HUDSay( int entnum, const str &string ); + void HUDPrint( int entnum, const str &string ); + void statusPrint( int entnum, const str &string ); + void instantPlayerSound( int entnum, const str &soundName, int channel = CHAN_AUTO, float volume = DEFAULT_VOL, float minDist = DEFAULT_MIN_DIST ); + void broadcastInstantSound( const str &soundName, int channel = CHAN_AUTO, float volume = DEFAULT_VOL, float minDist = DEFAULT_MIN_DIST, Player *except = NULL ); + void playerSound( int entnum, const str &soundName, int channel = CHAN_AUTO, float volume = DEFAULT_VOL, float minDist = DEFAULT_MIN_DIST, float time = 2.5f ); + void teamSound( Team *team, const str &soundName, int channel = CHAN_AUTO, float volume = DEFAULT_VOL, float minDist = DEFAULT_MIN_DIST, float time = 2.5f ); + void broadcastSound( const str &soundName, int channel = CHAN_AUTO, float volume = DEFAULT_VOL, float minDist = DEFAULT_MIN_DIST, Player *except = NULL, float time = 2.5f ); + + void addPlayerHealth( int entnum, float healthToAdd ); + + void allowFighting( bool allowFighting ); + + bool checkRule( const char *rule, bool defaultValue, Player *player = NULL ); + + void playerEventNotification( const char *eventName, const char *eventItemName, Player *eventPlayer ); + + void initPlayer( Player *player ); + + void teamPointsChanged( Team *team, int oldPoints, int newPoints ); + + void cacheMultiplayerFiles( const str &cacheFileName ); + + void setTeamHud( Player *player, const str &teamHudName ); + + str getSpawnPointType( Player *player ); + + // + // Shared Interface (game dll and modes) + // + + void centerPrintAllClients( const str &string, CenterPrintImportance importance ); + void HUDPrintAllClients( const str &string ); + + void centerPrintTeamClients( Player *player, const str &string, CenterPrintImportance importance ); + void HUDPrintTeamClients( Player *player, const str &string ); + + void HUDPrintSpectators( Player *player, const str &string, bool team ); + + bool canGivePlayerItem( int entnum, const str &itemName ); + void givePlayerItem( int entnum, const str &itemName ); + void usePlayerItem( int entnum, const str &itemName ); + + int getAfterMatchAward( Player *player, int index ); + + void startMatch( void ); + void endMatch( void ); + + str getPlaceName( Player *player ); + + bool isValidPlayerModel( Player *player, str modelToUse ); + str getDefaultPlayerModel( Player *player ); + + float getRespawnTime( void ); + void setRespawnTime( float time ); + void resetRespawnTime( void ); + + void checkModifiedCvars( bool informPlayers ); + void checkCvar( cvar_t *mp_cvarToCheck, str optionName, MPCvarType cvarType, int bitToCheck = -1 ); + bool hasFlagChanged( int bitToCheck ); + + bool skipWeaponReloads( void ); + void playerTaunt( Player *player, const str &tauntName ); + }; + +#endif // __MP_MANAGER_HPP__ diff --git a/dlls/game/mp_modeBase.cpp b/dlls/game/mp_modeBase.cpp new file mode 100644 index 0000000..bfaeca3 --- /dev/null +++ b/dlls/game/mp_modeBase.cpp @@ -0,0 +1,1952 @@ + +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/mp_modeBase.cpp $ +// $Revision:: 91 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// Description: +// + +#include "_pch_cpp.h" + +#include "mp_manager.hpp" +#include "mp_modeBase.hpp" +#include "equipment.h" +#include "powerups.h" +#include "weaputils.h" + +// Setup constants +const float MultiplayerModeBase::_defaultStartinghealth = 100.0f; +const int MultiplayerModeBase::_defaultPointsPerKill = 1; +const int MultiplayerModeBase::_defaultPointsPerTakenAwayForSuicide = 1; +const float MultiplayerModeBase::_spectatorMoveSpeedModifier = 1.5f; + +MultiplayerPlayerGameData::MultiplayerPlayerGameData() +{ + init(); +} + +void MultiplayerPlayerGameData::init( void ) +{ + _numDeaths = 0; + _numKills = 0; + _points = 0; + + _entnum = 0; + + _playing = false; + + _nextHitSoundTime = 0.0f; + + _currentTeam = NULL; + + _lastKilledByPlayer = -1; + _lastKillerOfPlayer = -1; + + _lastKilledByPlayerMOD = MOD_NONE; + _lastKillerOfPlayerMOD = MOD_NONE; + + _lastPlace = -1; + _lastTied = false; +} + +void MultiplayerPlayerGameData::reset( void ) +{ + _numDeaths = 0; + _numKills = 0; + _points = 0; + + _lastKilledByPlayer = -1; + _lastKillerOfPlayer = -1; + + _lastKilledByPlayerMOD = MOD_NONE; + _lastKillerOfPlayerMOD = MOD_NONE; + + _nextHitSoundTime = 0.0f; + + _lastPlace = -1; + _lastTied = false; +} + +CLASS_DECLARATION( Class, MultiplayerModeBase, NULL ) +{ + { NULL, NULL } +}; + + +//================================================================ +// Name: MultiplayerModeBase +// Class: MultiplayerModeBase +// +// Description: Constructor +// +// Parameters: const str& -- name of the arena +// +// Returns: None +// +//================================================================ +MultiplayerModeBase::MultiplayerModeBase() +{ + _maxPlayers = 20; + _startingHealth = (unsigned int) _defaultStartinghealth; + + _fightInProgress = false; + + _activePlayers = 0; + _spawncounter = 0; + + _pointLimit = 0; + _timeLimit = 0.0f; + + _playerGameData = NULL; + + _spectatorIconIndex = gi.imageindex( "sysimg/icons/mp/spectator" ); + + _warmUpTextIndex = G_FindConfigstringIndex( "$$WarmUp$$", CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ) + CS_GENERAL_STRINGS;; + _waitingForMinPlayersTextIndex = G_FindConfigstringIndex( "$$WaitMinPlayers$$", CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ) + CS_GENERAL_STRINGS;; + _playingTextIndex = G_FindConfigstringIndex( "$$Playing$$", CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ) + CS_GENERAL_STRINGS;; + + _gameStarted = false; + _lastTimeRemaining = 0; + + _lastHighestPoints = 0; +} + +//================================================================ +// Name: ~MultiplayerModeBase +// Class: MultiplayerModeBase +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//================================================================ +MultiplayerModeBase::~MultiplayerModeBase() +{ + int idx = 0 ; + + // Free up all of the ammos + + for (idx = 1; idx <= _ammoList.NumObjects(); idx++) + { + SimpleAmmoType* ammo = _ammoList.ObjectAt(idx); + delete ammo; + ammo = NULL; + } + + // Free up all of the player data + + delete [] _playerGameData; + _playerGameData = NULL; +} + +void MultiplayerModeBase::init( int maxPlayers ) +{ + _maxPlayers = maxPlayers; + _playerGameData = new MultiplayerPlayerGameData[ _maxPlayers ]; + + multiplayerManager.cacheMultiplayerFiles( "mp_general" ); +} + +void MultiplayerModeBase::initItems( void ) +{ + // Setup spawn points + + getSpawnpoints(); + + resetSpawnpoints(); + + // Setup the start time + + _matchStartTime = multiplayerManager.getTime(); + _gameStartTime = multiplayerManager.getTime(); + + _played5MinWarning = false; + _played2MinWarning = false; + _played1MinWarning = false; +} + +int MultiplayerModeBase::findPlayer( const Player *player ) +{ + int i; + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + if ( _playerGameData[ i ]._playing && _playerGameData[ i ]._entnum == player->entnum ) + return i; + } + + return -1; +} + +int MultiplayerModeBase::getPoints( Player *player ) +{ + int index; + + index = findPlayer( player ); + + if ( index >= 0 ) + { + return _playerGameData[ index ]._points; + } + + return 0; +} + +int MultiplayerModeBase::getKills( Player *player ) +{ + int index; + + index = findPlayer( player ); + + if ( index >= 0 ) + { + return _playerGameData[ index ]._numKills; + } + + return 0; +} + +int MultiplayerModeBase::getDeaths( Player *player ) +{ + int index; + + index = findPlayer( player ); + + if ( index >= 0 ) + { + return _playerGameData[ index ]._numDeaths; + } + + return 0; +} + +Team* MultiplayerModeBase::getPlayersTeam( const Player *player ) +{ + int index; + + index = findPlayer( player ); + + if ( index >= 0 ) + { + return _playerGameData[ index ]._currentTeam; + } + + return NULL; +} + +void MultiplayerModeBase::update( float frameTime ) +{ + if ( !_gameStarted ) + { + if ( shouldStartMatch() ) + { + multiplayerManager.startMatch(); + } + } + + // Tell players if timelimit is nearing + + if ( getTimeLimit() > 0.0f ) + { + float timeLimit; + float timeRemaining; + float fiveMinutes = 5 * 60; + float twoMinutes = 2 * 60; + float oneMinute = 1 * 60; + + timeLimit = getTimeLimit(); + timeRemaining = getTimeLimit() - ( multiplayerManager.getTime() - _gameStartTime ); + + if ( ( timeLimit > fiveMinutes ) && ( timeRemaining < fiveMinutes ) && ( !_played5MinWarning ) ) + { + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_5mins.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + _played5MinWarning = true; + } + else if ( ( timeLimit > twoMinutes ) && ( timeRemaining < twoMinutes ) && ( !_played2MinWarning ) ) + { + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_2mins.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + _played2MinWarning = true; + } + else if ( ( timeLimit > oneMinute ) && ( timeRemaining < oneMinute ) && ( !_played1MinWarning ) ) + { + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_1mins.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + _played1MinWarning = true; + } + } + + // Tell players if pointlimit is nearing + + if ( ( getPointLimit() > 0 ) && ( !isEndOfMatch() ) ) + { + int pointLimit; + int pointsRemaining; + int highestPoints; + int lastPointsRemaining; + + highestPoints = getHighestPoints(); + + if ( highestPoints > _lastHighestPoints ) + { + pointLimit = getPointLimit(); + pointsRemaining = pointLimit - highestPoints; + lastPointsRemaining = pointLimit - _lastHighestPoints; + + if ( ( pointLimit > 1 ) && ( pointsRemaining <= 1 ) && ( lastPointsRemaining > 1 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_1pointsleft.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else if ( ( pointLimit > 2 ) && ( pointsRemaining <= 2 ) && ( lastPointsRemaining > 2 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_2pointsleft.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else if ( ( pointLimit > 3 ) && ( pointsRemaining <= 3 ) && ( lastPointsRemaining > 3 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_3pointsleft.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else if ( ( pointLimit > 4 ) && ( pointsRemaining <= 4 ) && ( lastPointsRemaining > 4 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_4pointsleft.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else if ( ( pointLimit > 5 ) && ( pointsRemaining <= 5 ) && ( lastPointsRemaining > 5 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_5pointsleft.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else if ( ( pointLimit > 10 ) && ( pointsRemaining <= 10 ) && ( lastPointsRemaining > 10 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_10pointsleft.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else if ( ( pointLimit > 25 ) && ( pointsRemaining <= 25 ) && ( lastPointsRemaining > 25 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_25pointsleft.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else if ( ( pointLimit > 100 ) && ( pointsRemaining <= 100 ) && ( lastPointsRemaining > 100 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_100pointsleft.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else if ( ( pointLimit > 500 ) && ( pointsRemaining <= 500 ) && ( lastPointsRemaining > 500 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_500pointsleft.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + + _lastHighestPoints = highestPoints; + } +} + +//================================================================ +// Name: isEndOfMatch +// Class: MultiplayerModeBase +// +// Description: Checks to see if the end of match conditions have been +// met. Only condition in the base class is a single +// player exceeding the frag limit (if set). +// +// Parameters: None +// +// Returns: bool -- true if the match should now end +// +//================================================================ +bool MultiplayerModeBase::isEndOfMatch( void ) +{ + int i; + + // See if we have a gone over the point limit + + if ( getPointLimit() > 0 ) + { + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + if ( _playerGameData[ i ]._playing ) + { + if ( _playerGameData[ i ]._points >= getPointLimit() ) + return true; + } + } + } + + // See if we have a gone over the time limit + + if ( ( getTimeLimit() > 0.0f ) && ( multiplayerManager.getTime() - _gameStartTime > getTimeLimit() ) ) + return true; + + return false; +} + +void MultiplayerModeBase::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + bool goodKill; + + if ( attackingPlayer && ( killedPlayer != attackingPlayer ) ) + goodKill = true; + else + goodKill = false; + + handleKill( killedPlayer, attackingPlayer, inflictor, meansOfDeath, goodKill ); +} + +void MultiplayerModeBase::handleKill( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath, bool goodKill ) +{ + // Record the death + + _playerGameData[ killedPlayer->entnum ]._numDeaths++; + _playerGameData[ killedPlayer->entnum ]._lastKillerOfPlayerMOD = meansOfDeath; + + // Modify the points and kills + + if ( goodKill ) + { + // Player killed someone, so increment kills and points + + _playerGameData[ attackingPlayer->entnum ]._numKills++; + + addPoints( attackingPlayer->entnum, multiplayerManager.getPointsForKill( killedPlayer, attackingPlayer, + inflictor, meansOfDeath, _defaultPointsPerKill ) ); + //_playerGameData[ attackingPlayer->entnum ]._points += multiplayerManager.getPointsForKill( killedPlayer, + // attackingPlayer, inflictor, meansOfDeath, _defaultPointsPerKill ); + + _playerGameData[ killedPlayer->entnum ]._lastKillerOfPlayer = attackingPlayer->entnum; + } + else + { + // Player killed himself or a teammate, so decrement kills and points + + if ( attackingPlayer ) + { + //_playerGameData[ attackingPlayer->entnum ]._numKills--; + + addPoints( attackingPlayer->entnum, -_defaultPointsPerTakenAwayForSuicide ); + //_playerGameData[ attackingPlayer->entnum ]._points -= _defaultPointsPerTakenAwayForSuicide; + } + else + { + //_playerGameData[ killedPlayer->entnum ]._numKills--; + + addPoints( killedPlayer->entnum, -_defaultPointsPerTakenAwayForSuicide ); + //_playerGameData[ killedPlayer->entnum ]._points -= _defaultPointsPerTakenAwayForSuicide; + } + + _playerGameData[ killedPlayer->entnum ]._lastKillerOfPlayer = killedPlayer->entnum; + } + + // Print out an obituary + + obituary( killedPlayer, attackingPlayer, meansOfDeath ); + + // Save off some important info + + if ( attackingPlayer ) + { + _playerGameData[ attackingPlayer->entnum ]._lastKilledByPlayer = killedPlayer->entnum; + _playerGameData[ attackingPlayer->entnum ]._lastKilledByPlayerMOD = meansOfDeath; + } +} + +void MultiplayerModeBase::addPoints( int entnum, int points ) +{ + _playerGameData[ entnum ]._points += points; +} + +void MultiplayerModeBase::obituary( Player *killedPlayer, Player *attackingPlayer, int meansOfDeath ) +{ + const char *s1=NULL, *s2=NULL; + str printString; + bool suicide; + bool printSomething; + int i; + char color; + bool sameTeam; + + // Client killed himself + + suicide = false; + printSomething = false; + + sameTeam = false; + + if ( attackingPlayer && ( killedPlayer != attackingPlayer ) ) + { + Team *killedPlayersTeam; + Team *attackingPlayersTeam; + + killedPlayersTeam = multiplayerManager.getPlayersTeam( killedPlayer ); + + attackingPlayersTeam = multiplayerManager.getPlayersTeam( attackingPlayer ); + + if ( killedPlayersTeam && attackingPlayersTeam && ( killedPlayersTeam == attackingPlayersTeam ) ) + { + sameTeam = true; + } + } + + if ( killedPlayer == attackingPlayer || !attackingPlayer ) + { + suicide = true; + printSomething = true; + + switch( meansOfDeath ) + { + case MOD_SUICIDE: + s1 = "$$MOD_SUICIDE$$"; + break; + case MOD_DROWN: + s1 = "$$MOD_DROWN$$"; + break; + case MOD_LAVA: + s1 = "$$MOD_LAVA$$"; + break; + case MOD_SLIME: + s1 = "$$MOD_SLIME$$"; + break; + case MOD_FALLING: + s1 = "$$MOD_FALLING$$"; + break; + default: + s1 = "$$MOD_SUICIDE$$"; + break; + } + } + + // Killed by another player + + if ( attackingPlayer && attackingPlayer->isClient() && ( killedPlayer != attackingPlayer ) ) + { + printSomething = true; + + switch( meansOfDeath ) + { + case MOD_CRUSH: + case MOD_CRUSH_EVERY_FRAME: + s1 = "$$MOD_CRUSH$$"; + break; + case MOD_TELEFRAG: + s1 = "$$MOD_TELEFRAG$$"; + break; + case MOD_EXPLODEWALL: + case MOD_EXPLOSION: + case MOD_POO_EXPLOSION: + s1 = "$$MOD_EXPLOSION$$"; + break; + case MOD_ELECTRICWATER: + case MOD_ELECTRIC: + case MOD_CIRCLEOFPROTECTION: + s1 = "$$MOD_ELECTRIC$$"; + break; + case MOD_IMPACT: + case MOD_THROWNOBJECT: + s1 = "$$MOD_IMPACT$$"; + s2 = "$$MOD_IMPACT2$$"; + break; + case MOD_BEAM: + s1 = "$$MOD_BEAM$$"; + break; + case MOD_ROCKET: + s1 = "$$MOD_ROCKET$$"; + s2 = "$$MOD_ROCKET2$$"; + break; + case MOD_GAS_BLOCKABLE: + case MOD_GAS: + s1 = "$$MOD_GAS$$"; + break; + case MOD_ACID: + s1 = "$$MOD_ACID$$"; + break; + case MOD_SWORD: + s1 = "$$MOD_SWORD$$"; + break; + case MOD_PLASMA: + case MOD_PLASMABEAM: + case MOD_PLASMASHOTGUN: + s1 = "$$MOD_ASSULT_RIFLE$$"; + break; + case MOD_RADIATION: + s1 = "$$MOD_PLASMA$$"; + break; + case MOD_STING: + case MOD_STING2: + s1 = "$$MOD_STING$$"; + break; + case MOD_BULLET: + case MOD_FAST_BULLET: + s1 = "$$MOD_BULLET$$"; + break; + case MOD_VEHICLE: + s1 = "$$MOD_VEHICLE$$"; + break; + case MOD_FIRE: + case MOD_FIRE_BLOCKABLE: + case MOD_ON_FIRE: + s1 = "$$MOD_FIRE$$"; + break; + case MOD_LIFEDRAIN: + s1 = "$$MOD_LIFEDRAIN$$"; + break; + case MOD_FLASHBANG: + s1 = "$$MOD_FLASHBANG$$"; + break; + case MOD_AXE: + s1 = "$$MOD_AXE$$"; + s2 = "$$MOD_AXE2$$"; + break; + case MOD_CHAINSWORD: + s1 = "$$MOD_CHAINSWORD$$"; + break; + case MOD_FIRESWORD: + s1 = "$$MOD_FIRESWORD$$"; + break; + case MOD_ELECTRICSWORD: + s1 = "$$MOD_ELECTRICSWORD$$"; + s2 = "$$MOD_ELECTRICSWORD2$$"; + break; + case MOD_LIGHTSWORD: + s1 = "$$MOD_LIGHTSWORD$$"; + s2 = "$$MOD_LIGHTSWORD2$$"; + break; + case MOD_IMPALE: + s1 = "$$MOD_IMPALE$$"; + break; + case MOD_UPPERCUT: + s1 = "$$MOD_UPPERCUT$$"; + break; + case MOD_POISON: + s1 = "$$MOD_POISON$$"; + break; + case MOD_PHASER: + s1 = "$$MOD_PHASER$$"; + break; + case MOD_COMP_RIFLE: + s1 = "$$MOD_COMP_RIFLE$$"; + break; + //case MOD_ASSULT_RIFLE: + + //case MOD_IMOD: + // s1 = "$$MOD_IMOD$$"; + // break; + + case MOD_VAPORIZE: + case MOD_VAPORIZE_COMP: + case MOD_VAPORIZE_DISRUPTOR: + case MOD_VAPORIZE_PHOTON: + s1 = "$$MOD_VAPORIZE$$"; + break; + default: + s1 = "$$MOD_DEFAULT$$"; + break; + } + } + + if ( printSomething ) + { + Player *currentPlayer; + + // Print to the dedicated console + + if ( dedicated->integer ) + { + if ( suicide ) + { + printString = va( "%s %s", killedPlayer->client->pers.netname, s1 ); + } + else if ( s2 ) + { + printString = va( "%s %s %s %s", killedPlayer->client->pers.netname, s1, attackingPlayer->client->pers.netname, s2 ); + } + else + { + printString = va( "%s %s %s", killedPlayer->client->pers.netname, s1, attackingPlayer->client->pers.netname ); + } + + if ( sameTeam ) + { + printString += " ($$SameTeam$$)"; + } + + printString += "\n"; + + gi.Printf( printString.c_str() ); + } + + // Print to all of the players + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + currentPlayer = multiplayerManager.getPlayer( i ); + + if ( !currentPlayer ) + continue; + + // Figure out which color to use + + if ( killedPlayer && killedPlayer->entnum == i ) + color = COLOR_RED; + else if ( attackingPlayer && attackingPlayer->entnum == i ) + color = COLOR_GREEN; + else + color = COLOR_NONE; + + // Build the death string + + if ( suicide ) + { + printString = va( "%s ^%c%s^8", killedPlayer->client->pers.netname, color, s1 ); + } + else if ( s2 ) + { + printString = va( "%s ^%c%s^8 %s ^%c%s^8", killedPlayer->client->pers.netname, color, s1, attackingPlayer->client->pers.netname, color, s2 ); + } + else + { + printString = va( "%s ^%c%s^8 %s", killedPlayer->client->pers.netname, color, s1, attackingPlayer->client->pers.netname ); + } + + if ( sameTeam ) + { + printString += " (^"; + printString += COLOR_RED; + printString += "$$SameTeam$$^8)"; + } + + printString += "\n"; + + // Print out the death string + + if ( gi.GetNumFreeReliableServerCommands( currentPlayer->edict - g_entities ) > 32 ) + { + multiplayerManager.HUDPrint( currentPlayer->entnum, printString.c_str() ); + } + + //multiplayerManager.HUDPrintAllClients( printString.c_str() ); + } + } +} + +bool MultiplayerModeBase::needToAddPlayer( Player *player ) +{ + MultiplayerPlayerGameData *playerGameData; + + playerGameData = &_playerGameData[ player->entnum ]; + + if ( playerGameData->_playing ) + return false; + + return true; +} + + +//================================================================ +// Name: AddPlayer +// Class: MultiplayerModeBase +// +// Description: Adds the specified player to the list of players +// +// Parameters: Player* -- player to add +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::AddPlayer( Player *player ) +{ + MultiplayerPlayerGameData *playerGameData; + + // Make sure player is not already added + + if ( !needToAddPlayer( player ) ) + return; + + // Setup the player's data + + playerGameData = &_playerGameData[ player->entnum ]; + + playerGameData->reset(); + playerGameData->_entnum = player->entnum; + playerGameData->_playing = true; + playerGameData->_startTime = multiplayerManager.getTime(); + + // Setup the correct ui on the client + + setupMultiplayerUI( player ); +} + +bool MultiplayerModeBase::canJoinTeam( Player *player, const str &teamName ) +{ + if ( teamName == "normal" && multiplayerManager.isPlayerSpectator( player ) ) + return true; + //else if ( teamName == "spectator" && !multiplayerManager.isPlayerSpectator( player ) ) + else if ( teamName == "spectator" ) + return true; + else + return false; +} + +void MultiplayerModeBase::joinTeam( Player *player, const str &teamName ) +{ + if ( teamName == "normal" ) + { + multiplayerManager.setTeamHud( player, "" ); + RemovePlayer( player ); + AddPlayer( player ); + } + else if ( teamName == "spectator" ) + { + multiplayerManager.makePlayerSpectator( player, SPECTATOR_TYPE_FOLLOW, true ); + //multiplayerManager.setTeamHud( player, "mp_teamspec" ); + } +} + +void MultiplayerModeBase::setupMultiplayerUI( Player *player ) +{ + gi.SendServerCommand( player->entnum, "stufftext \"ui_removehuds all\"\n" ); + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_console\"\n" ); + + + if(mp_timelimit->integer) + { + gi.SendServerCommand( player->entnum, "stufftext \"globalwidgetcommand dmTimer enable\"\n"); + } + else + { + gi.SendServerCommand( player->entnum, "stufftext \"globalwidgetcommand dmTimer disable\"\n"); + } + //gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_dmhud\"\n" ); +} + +//================================================================ +// Name: RemovePlayer +// Class: MultiplayerModeBase +// +// Description: Removes a player from this arena. +// +// Parameters: Player* -- player to remove +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::RemovePlayer( Player *player ) +{ + _playerGameData[ player->entnum ]._playing = false; +} + +float MultiplayerModeBase::playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ) +{ + return damage; +} + +void MultiplayerModeBase::playerTookDamage( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ) +{ + if ( attackingPlayer && ( attackingPlayer != damagedPlayer ) ) + { + // Play the hurt someone sound + + if ( multiplayerManager.getTime() >= _playerGameData[ attackingPlayer->entnum ]._nextHitSoundTime && damagedPlayer->health > 0.0f ) + { + multiplayerManager.instantPlayerSound( attackingPlayer->entnum, "snd_mp_hurtsomeone", CHAN_COMBAT4 ); + + _playerGameData[ attackingPlayer->entnum ]._nextHitSoundTime = level.time + 0.25; + } + } +} + +void MultiplayerModeBase::readMultiplayerConfig( const char *configName ) +{ + Script buffer; + const char *token; + + // Make sure thee file exists + + if ( !gi.FS_Exists( configName ) ) + return; + + // Load the file + + buffer.LoadFile( configName ); + + // Parse the file + + while ( buffer.TokenAvailable( true ) ) + { + token = buffer.GetToken( true ); + + // Parse the current token (this should be parsed by all child classes also) + + if ( !parseConfigToken( token, &buffer ) ) + { + gi.DPrintf( "Token %s from %s not handled by anyone\n", token, configName ); + } + } +} + +bool MultiplayerModeBase::parseConfigToken( const char *key, Script *buffer ) +{ + const char *token; + + if ( stricmp( key, "giveWeapon" ) == 0 ) + { + if ( buffer->TokenAvailable( false ) ) + { + token = buffer->GetToken( false ); + + AddStartingWeapon( token ); + + return true; + } + } + else if ( stricmp( key, "startingWeapon" ) == 0 ) + { + if ( buffer->TokenAvailable( false ) ) + { + token = buffer->GetToken( false ); + + SetStartingWeapon( token ); + + return true; + } + } + + return false; +} + +int MultiplayerModeBase::getIcon( Player *player, int statNum, int value ) +{ + /* if ( statNum == STAT_MP_TEAMHUD_ICON && multiplayerManager.isPlayerSpectator( player ) ) + return _spectatorIconIndex; + else */ + return value; +} + +bool MultiplayerModeBase::shouldStartMatch( void ) +{ + int timeRemaining; + int numPlayers; + int i; + + if ( _gameStarted ) + return false; + + if ( isEndOfMatch() ) + return false; + + // Print time remaining (if changed) + + timeRemaining = (int)(_matchStartTime + mp_warmUpTime->value - multiplayerManager.getTime() + 1.0f); + + if ( ( timeRemaining > 0 ) && ( timeRemaining < 6 ) && ( timeRemaining != _lastTimeRemaining ) ) + { + _lastTimeRemaining = timeRemaining; + + multiplayerManager.centerPrintAllClients( va( "%d", _lastTimeRemaining ), CENTERPRINT_IMPORTANCE_NORMAL ); + + switch( timeRemaining ) + { + case 1 : + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_1.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, NULL, 0.5f ); + break; + case 2 : + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_2.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, NULL, 0.5f ); + break; + case 3 : + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_3.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, NULL, 0.5f ); + break; + case 4 : + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_4.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, NULL, 0.5f ); + break; + case 5 : + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_5.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, NULL, 0.5f ); + break; + } + } + + // Make sure we have done our warm up already + + if ( multiplayerManager.getTime() < _matchStartTime + mp_warmUpTime->value ) + return false; + + // Make sure we have enough players + + numPlayers = 0; + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + if ( _playerGameData[ i ]._playing ) + { + numPlayers++; + } + } + + if ( numPlayers < mp_minPlayers->integer ) + return false; + + return true; +} + +int MultiplayerModeBase::getStat( Player *player, int statNum, int value ) +{ + if ( statNum == STAT_MP_STATE ) + { + int numPlayers; + int i; + Player *player; + + if ( _gameStarted ) + { + if ( !value ) + return _playingTextIndex; + else + return value; + } + + if ( multiplayerManager.getTime() < _matchStartTime + mp_warmUpTime->value ) + return _warmUpTextIndex; + + // Make sure we have enough players + + numPlayers = 0; + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + player = multiplayerManager.getPlayer( i ); + + if ( player && !multiplayerManager.isPlayerSpectatorByChoice( player ) ) + { + numPlayers++; + } + } + + if ( numPlayers < mp_minPlayers->integer ) + return _waitingForMinPlayersTextIndex; + + } + + return value; +} + +void MultiplayerModeBase::startMatch( void ) +{ + int i; + Player *player; + + _gameStarted = true; + multiplayerManager.allowFighting( true ); + + // Make everyone not a spectator and spawn them into the world + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + player = getPlayer( i ); + + if ( player && ( ( player->edict->svflags & SVF_BOT ) || !multiplayerManager.isPlayerSpectatorByChoice( player ) ) ) + { + multiplayerManager.playerEnterArena( player->entnum, player->health ); + + respawnPlayer( player ); + + multiplayerManager.playerSpawned( player ); + } + } + + // Tell everyone the match started + + multiplayerManager.centerPrintAllClients( "$$MatchStarted$$", CENTERPRINT_IMPORTANCE_HIGH ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_mats.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); +} + +void MultiplayerModeBase::restartMatch( void ) +{ + _gameStarted = false; + + _matchStartTime = multiplayerManager.getTime(); + _lastTimeRemaining = 0; +} + +void MultiplayerModeBase::endMatch( void ) +{ + int i; + Player *player; + + _gameStarted = false; + + // Make everyone a spectator + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + if ( _playerGameData[ i ]._playing ) + { + player = getPlayer( i ); + + if ( player ) + { + multiplayerManager.makePlayerSpectator( player ); + } + } + } +} + +bool MultiplayerModeBase::inMatch( void ) +{ + return _gameStarted; +} + +Player *MultiplayerModeBase::getPlayer( int entnum ) +{ + // Make sure everything is ok + + if ( ( entnum < 0 ) || ( entnum >= _maxPlayers ) ) + return NULL; + + if ( !g_entities[ entnum ].inuse || !g_entities[ entnum ].entity ) + return NULL; + + if ( !g_entities[ entnum ].entity->isSubclassOf( Player ) ) + return NULL; + + // Return the referenced player + + return (Player *)g_entities[ entnum ].entity; +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//================================================================ +// Name: score +// Class: MultiplayerModeBase +// +// Description: Sends the current score to the specified player. +// +// Parameters: Player* -- player to send score to. +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::score( const Player *player ) +{ + char string[1400]; + char entry[1024]; + int i; + int tempStringlength; + int count = 0; + int stringlength = 0; + Player *currentPlayer; + int spectator; + + assert( player ); + if ( !player ) + { + warning( "MultiplayerModeBase::score", "Null Player specified.\n" ); + return; + } + + string[0] = 0; + entry[0] = 0; + + // This for loop builds a string containing all the players scores. + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + currentPlayer = multiplayerManager.getPlayer( i ); + + if ( !currentPlayer ) + continue; + + if ( multiplayerManager.isPlayerSpectator( currentPlayer ) && multiplayerManager.isPlayerSpectatorByChoice( currentPlayer ) ) + spectator = true; + else + spectator = false; + + Com_sprintf( entry, sizeof( entry ), "%i %i %i %i %i %i %i %d %d %d %d %d %d ", + multiplayerManager.getClientNum( _playerGameData[ i ]._entnum ), + _playerGameData[ i ]._points, + _playerGameData[ i ]._numKills, + _playerGameData[ i ]._numDeaths, + spectator, + //0 /*pl->GetMatchesWon() */, + //0 /*pl->GetMatchesLost()*/, + (int)(multiplayerManager.getTime() - _playerGameData[ i ]._startTime ), + multiplayerManager.getClientPing( _playerGameData[ i ]._entnum ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON1 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON2 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON3 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON4 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON5 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON6 ) ); + + tempStringlength = strlen( entry ); + + // Make sure the string is not too big (take into account other stuff that gets prepended below also) + + if ( stringlength + tempStringlength > 1000 ) + break; + + strcpy( string + stringlength, entry ); + + stringlength += tempStringlength; + count++; + } + + gi.SendServerCommand( player->edict-g_entities, "scores 0 %i %s", count, string ); +} + +//================================================================ +// Name: playerDead +// Class: MultiplayerModeBase +// +// Description: Do appropriate thing when player has been killed. +// Base class puts up a dead body and hides the player +// model. +// +// Parameters: Player* -- player that was killed. +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::playerDead( Player *player ) +{ + assert(player); + + if (!player) + { + warning("MultiplayerModeBase::playerDead", "NULL Player\n"); + return ; + } + + player->ProcessEvent( EV_Player_DeadBody ); + player->hideModel(); +} + +//================================================================ +// Name: SetStartingWeapon +// Class: MultiplayerModeBase +// +// Description: Sets the name of the weapon the players start with out +// +// Parameters: const str& -- weaponName +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::SetStartingWeapon( const str &weaponName ) +{ + _startingWeaponName = weaponName; +} + +//================================================================ +// Name: AddStartingWeapon +// Class: MultiplayerModeBase +// +// Description: Adds the specified weapon to the list of weapons +// the player starts with. The string must be a .tik +// file. +// +// Parameters: const str& -- viewmodel name (eg. viewmodel_peacemaker.tik); +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::AddStartingWeapon( const str& weaponViewmodel ) +{ + _weaponList.AddObject(weaponViewmodel); +} + +//================================================================ +// Name: ActivatePlayer +// Class: MultiplayerModeBase +// +// Description: Activates the specified player. Activation includes +// loading skin and giving appropriate weapons and ammo. +// +// Parameters: Player* -- player to be activated +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::ActivatePlayer( Player* player ) +{ + assert( player ); + + if ( !player ) + { + warning("MultiplayerModeBase::ActivatePlayer", "NULL Player\n"); + return ; + } + + // Make the player enter the game + + if ( !_gameStarted ) + { + multiplayerManager.makePlayerSpectator( player ); + return; + + } + + multiplayerManager.playerEnterArena( player->entnum, _startingHealth ); + + multiplayerManager.changePlayerModel( player, player->client->pers.mp_playermodel ); + _giveInitialConditions( player ); + + // Make player invunerable for a little bit + + if ( mp_respawnInvincibilityTime->value > 0.0f ) + { + Powerup *powerup; + Event *event; + + powerup = Powerup::CreatePowerup( "ProtectionTemp", "models/item/powerup_protection.tik", player ); + + if ( powerup ) + { + powerup->CancelEventsOfType( EV_ProcessInitCommands ); + powerup->ProcessInitCommands( powerup->edict->s.modelindex ); + + event = new Event( EV_Item_SetAmount ); + event->AddFloat( mp_respawnInvincibilityTime->value ); + powerup->ProcessEvent( event ); + + player->setPowerup( powerup ); + } + } + + multiplayerManager.allowFighting( true ); + player->takedamage = DAMAGE_YES; + + multiplayerManager.playerSpawned( player ); +} + +//================================================================ +// Name: BeginMatch +// Class: MultiplayerModeBase +// +// Description: Begins the match. Resets the spawn points and calls +// _beginMatch so subclasses can do their special thing. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::BeginMatch( void ) +{ + _fightInProgress = true ; + resetSpawnpoints(); + _beginMatch(); +} + +//================================================================ +// Name: EndMatch +// Class: MultiplayerModeBase +// +// Description: Ends the match. Resets the spawn points and calls +// _endMatch so subclasses can do their special thing. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::EndMatch( void ) +{ + _fightInProgress = false ; + resetSpawnpoints(); + _endMatch(); +} + +//================================================================ +// Name: resetSpawnpoints +// Class: MultiplayerModeBase +// +// Description: Resets the spawnpoints in the arena. This list +// of spawnpoints is doled out as people enter the arena. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::resetSpawnpoints( void ) +{ + // This builds a list of all the spawnpoints in the arena, which will be + // removed as each one is used at start + _unusedSpawnpointList.ClearObjectList(); + + for( int idx = 1; idx <= _spawnpointList.NumObjects(); idx++ ) + { + _unusedSpawnpointList.AddObject( _spawnpointList.ObjectAt( idx ) ); + } +} + +void MultiplayerModeBase::getSpawnpoints( void ) +{ + PlayerDeathmatchStart *deathmatchStart; + + deathmatchStart = ( PlayerDeathmatchStart * )G_FindClass( NULL, "info_player_deathmatch" ); + + while ( deathmatchStart ) + { + _spawnpointList.AddObject( deathmatchStart ); + + deathmatchStart = ( PlayerDeathmatchStart * )G_FindClass( deathmatchStart, "info_player_deathmatch" ); + } +} + +//---------------------------------------------------------------- +// P R O T E C T E D M E T H O D S +//---------------------------------------------------------------- + + +//================================================================ +// Name: _beginMatch +// Class: MultiplayerModeBase +// +// Description: Begins the match. All players in the arena have +// BeginFight() called on them to enable fighting. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::_beginMatch() +{ + for(int idx=1; idx <= _playerList.NumObjects(); idx++ ) + { + Player *player = _playerList.ObjectAt( idx ); + + assert( player ); + if ( !player ) + { + continue; + } + + multiplayerManager.allowFighting( true ); + } +} + +//================================================================ +// Name: _endMatch +// Class: MultiplayerModeBase +// +// Description: Ends the match. All players in the arena have +// EndFight() called on them to disable fighting. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::_endMatch() +{ +} + +void MultiplayerModeBase::declareWinner( void ) +{ + int i; + Player *player; + int place; + bool tied; + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + player = getPlayer( i ); + + if ( !player ) + continue; + + place = getPlace( player, &tied ); + + if ( ( place == 1 ) && tied ) + multiplayerManager.playerSound( player->entnum, "localization/sound/dialog/dm/comp_tiedfirst.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.5f ); + else if ( place == 1 ) + multiplayerManager.playerSound( player->entnum, "localization/sound/dialog/dm/comp_winn.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.5f ); + else if ( place == 2 ) + multiplayerManager.playerSound( player->entnum, "localization/sound/dialog/dm/comp_second.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.5f ); + else if ( place == 3 ) + multiplayerManager.playerSound( player->entnum, "localization/sound/dialog/dm/comp_third.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.5f ); + else + multiplayerManager.playerSound( player->entnum, "localization/sound/dialog/dm/comp_didnotrank.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.5f ); + } +} + +//================================================================ +// Name: _giveInitialConditions +// Class: MultiplayerModeBase +// +// Description: Gives the specified player the weapons and ammo +// designated for this arena. +// +// Parameters: Player* -- player to receive weapons +// +// Returns: None +// +//================================================================ +void MultiplayerModeBase::_giveInitialConditions( Player *player ) +{ + int idx ; + + assert( player ); + + if ( !player ) + { + warning( "MultiplayerModeBase::_giveInitialConditions", "NULL player specified.\n" ); + return; + } + + // Give all the weapons to the player + + for ( idx=1; idx <= _weaponList.NumObjects(); idx++ ) + { + multiplayerManager.givePlayerItem( player->entnum, _weaponList.ObjectAt( idx ) ); + /* Event *ev = new Event( "weapon" ); + ev->AddToken( _weaponList.ObjectAt( idx ) ); + player->ProcessEvent( ev ); // deletes the created Event */ + } + + // Give all the ammo to the player + + for ( idx=1; idx <= _ammoList.NumObjects(); idx++ ) + { + Event *ev = new Event( "ammo" ); + ev->AddString( _ammoList.ObjectAt( idx )->type ); + ev->AddInteger( _ammoList.ObjectAt( idx )->amount ); + player->ProcessEvent( ev ); // deletes the created Event + } + + // Start the player with the appropriate weapon + + if ( _startingWeaponName.length() ) + { + Event *ev = new Event( "use" ); + ev->AddString( _startingWeaponName ); + player->ProcessEvent( ev ); // deletes the created Event + } +} + +int MultiplayerModeBase::getNumSpawnpoints( void ) +{ + return _spawnpointList.NumObjects(); +} + +Entity* MultiplayerModeBase::getSpawnpointbyIndex( int index ) +{ + int numPoints; + + // Make sure everything is ok + + numPoints = getNumSpawnpoints(); + + if ( !numPoints ) + { + warning( "MultiplayerModeBase::getSpawnpointbyIndex", "No spawnpoints found in arena\n" ); + return NULL; + } + + if ( ( index < 0 ) || ( index >= numPoints ) ) + return NULL; + + // Get the spawn point + + return _spawnpointList.ObjectAt( index + 1 ); +} + +Entity* MultiplayerModeBase::getRandomSpawnpoint( bool useCounter ) +{ + Entity *spot = NULL; + + int numPoints = _spawnpointList.NumObjects(); + + // Make sure everything is ok + + if ( !numPoints ) + { + warning( "MultiplayerModeBase::GetRandomSpawnpoint", "No spawnpoints found in arena\n" ); + return NULL; + } + + // Get the selected index (either in order or random) + + if ( useCounter ) + { + if ( _spawncounter > numPoints ) + { + _spawncounter = 1; // reuse spawn points + } + + spot = ( Entity * )_spawnpointList.ObjectAt( _spawncounter ); + _spawncounter++; + return spot; + } + + int selection = ( (int)( G_Random() * numPoints ) ); + + // Get the spawn point + + return getSpawnpointbyIndex( selection ); +} + +int MultiplayerModeBase::getNumNamedSpawnpoints( const str &spawnpointType ) +{ + int numPoints; + int numNamedPoints; + int i; + PlayerDeathmatchStart *spawnpoint; + + numPoints = _spawnpointList.NumObjects(); + + if ( !numPoints ) + { + warning( "MultiplayerModeBase::getNumNamedSpawnpoint", "No spawnpoints found in map\n" ); + return 0; + } + + // Find out how many spawnpoints match this type + + numNamedPoints = 0; + + for ( i = 1 ; i <= numPoints ; i++ ) + { + spawnpoint = _spawnpointList.ObjectAt( i ); + + if ( spawnpoint->_type == spawnpointType ) + { + numNamedPoints++; + } + } + + return numNamedPoints; +} + +Entity* MultiplayerModeBase::getNamedSpawnpointbyIndex( const str &spawnpointType, int index ) +{ + int i; + int numNamedPoints; + int numPoints; + PlayerDeathmatchStart *spawnpoint; + + + numNamedPoints = getNumNamedSpawnpoints( spawnpointType ); + + // Make sure we found at least 1 spawnpoint matching the type + + if ( !numNamedPoints || ( index < 0 ) || ( index >= numNamedPoints ) ) + return NULL; + + numNamedPoints = 0; + + // Find the spawn point picked + + numPoints = getNumSpawnpoints(); + + for ( i = 1 ; i <= numPoints ; i++ ) + { + spawnpoint = _spawnpointList.ObjectAt( i ); + + if ( spawnpoint->_type == spawnpointType ) + { + if ( numNamedPoints == index ) + { + return spawnpoint; + } + + numNamedPoints++; + } + } + + return NULL; +} + +Entity* MultiplayerModeBase::getRandomNamedSpawnpoint( const str &spawnpointType ) +{ + int numNamedPoints; + int selection; + + numNamedPoints = getNumNamedSpawnpoints( spawnpointType ); + + selection = ( (int)( G_Random() * numNamedPoints ) ); + + return getNamedSpawnpointbyIndex( spawnpointType, selection ); +} + +Entity* MultiplayerModeBase::getFarthestNamedSpawnpoint( const Vector &origin, const str &spawnpointType ) +{ + int i; + int numNamedPoints; + int numPoints; + PlayerDeathmatchStart *spawnpoint; + PlayerDeathmatchStart *farthestSpawnpoint; + float farthestDistance; + Vector dir; + float dist; + + + farthestSpawnpoint = NULL; + farthestDistance = 0.0f; + + numNamedPoints = getNumNamedSpawnpoints( spawnpointType ); + + // Make sure we found at least 1 spawnpoint matching the type + + if ( !numNamedPoints ) + return NULL; + + // Go through all of the spawn points and find the farthest one from the specified point + + numPoints = getNumSpawnpoints(); + + for ( i = 1 ; i <= numPoints ; i++ ) + { + spawnpoint = _spawnpointList.ObjectAt( i ); + + if ( spawnpoint->_type == spawnpointType ) + { + dir = origin - spawnpoint->origin; + dist = dir.length(); + + if ( !farthestSpawnpoint || ( dist > farthestDistance ) ) + { + farthestSpawnpoint = spawnpoint; + farthestDistance = dist; + } + } + } + + return farthestSpawnpoint; +} + +Entity *MultiplayerModeBase::getSpawnPoint( Player *player ) +{ + Entity *spawnPoint = NULL; + int randomStartingSpot; + int i; + int spawnPointIndex; + int numSpawnPoints; + bool useAnySpawnPoint; + + numSpawnPoints = getNumNamedSpawnpoints( "" ); + + if ( numSpawnPoints == 0 ) + { + useAnySpawnPoint = true; + + numSpawnPoints = getNumSpawnpoints(); + } + else + { + useAnySpawnPoint = false; + } + + randomStartingSpot = ( (int)( G_Random() * numSpawnPoints ) ); + + for( i = 0 ; i < numSpawnPoints ; i++ ) + { + spawnPointIndex = ( randomStartingSpot + i ) % numSpawnPoints; + + if ( useAnySpawnPoint ) + spawnPoint = getSpawnpointbyIndex( spawnPointIndex ); + else + spawnPoint = getNamedSpawnpointbyIndex( "", spawnPointIndex ); + + if ( spawnPoint ) + { + int j; + int num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + Vector min; + Vector max; + bool badSpot; + + min = spawnPoint->origin + player->mins + Vector( 0, 0, 1 ); + max = spawnPoint->origin + player->maxs + Vector( 0, 0, 1 ); + + num = gi.AreaEntities( min, max, touch, MAX_GENTITIES, qfalse ); + + badSpot = false; + + for( j = 0 ; j < num ; j++ ) + { + hit = &g_entities[ touch[ j ] ]; + + if ( !hit->inuse || ( hit->entity == player ) || !hit->entity || ( hit->entity == world ) || ( !hit->entity->edict->solid ) ) + { + continue; + } + + if ( hit->entity->isSubclassOf( Player ) ) + { + Player *hitPlayer; + + hitPlayer = (Player *)hit->entity; + + badSpot = true; + break; + } + } + + if ( !badSpot ) + { + break; + } + } + + } + + return spawnPoint; +} + +Player *MultiplayerModeBase::getLastKilledByPlayer( Player *player, int *meansOfDeath ) +{ + int entnum; + + entnum = _playerGameData[ player->entnum ]._lastKilledByPlayer; + + if ( entnum < 0 ) + return NULL; + + if ( meansOfDeath ) + *meansOfDeath = _playerGameData[ player->entnum ]._lastKilledByPlayerMOD; + + return GetPlayer( entnum ); +} + +Player *MultiplayerModeBase::getLastKillerOfPlayer( Player *player, int *meansOfDeath ) +{ + int entnum; + + entnum = _playerGameData[ player->entnum ]._lastKillerOfPlayer; + + if ( entnum < 0 ) + return NULL; + + if ( meansOfDeath ) + *meansOfDeath = _playerGameData[ player->entnum ]._lastKillerOfPlayerMOD; + + return GetPlayer( entnum ); +} + +void MultiplayerModeBase::applySpeedModifiers( Player *player, int *moveSpeed ) +{ + if ( multiplayerManager.isPlayerSpectator( player ) ) + { + *moveSpeed *= (int)_spectatorMoveSpeedModifier; + } +} + +int MultiplayerModeBase::comparePlayerScore( MultiplayerPlayerGameData &player1Data, MultiplayerPlayerGameData &player2Data ) +{ + // Check points first + + if ( player1Data._points > player2Data._points ) + return 1; + else if ( player1Data._points < player2Data._points ) + return -1; + + // Check kills second + + if ( player1Data._numKills > player2Data._numKills ) + return 1; + else if ( player1Data._numKills < player2Data._numKills ) + return -1; + + // Check deaths third + + if ( player1Data._numDeaths > player2Data._numDeaths ) + return -1; + else if ( player1Data._numDeaths < player2Data._numDeaths ) + return 1; + + // If we get here the player's are tied + + return 0; +} + +int MultiplayerModeBase::getPlace( Player *player, bool *tied ) +{ + int place = 1; + bool isTied = false; + int i; + Player *currentPlayer; + int scoreDiff; + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + // Get this player and make sure everything is ok + + currentPlayer = getPlayer( i ); + + if ( !currentPlayer || ( currentPlayer == player ) ) + continue; + + // See how the player compares with the current player + + scoreDiff = comparePlayerScore( _playerGameData[ player->entnum ], _playerGameData[ currentPlayer->entnum ] ); + + if ( scoreDiff < 0 ) + { + // The current player has a higher score so we bump the place one higher + + place++; + } + else if ( scoreDiff == 0 ) + { + // The current player has a same score so they are tied + + isTied = true; + } + } + + if ( tied ) + { + *tied = isTied; + } + + return place; +} + +int MultiplayerModeBase::getHighestPoints( void ) +{ + int i; + int highestPoints = -999999999; + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + if ( _playerGameData[ i ]._points > highestPoints ) + { + highestPoints = _playerGameData[ i ]._points; + } + } + + return highestPoints; +} + +bool MultiplayerModeBase::shouldKeepNormalItem( Item *item ) +{ + if ( multiplayerManager.checkFlag( MP_FLAG_NO_POWERUPS ) && item->isSubclassOf( PowerupBase ) ) + return false; + + return true; +} diff --git a/dlls/game/mp_modeBase.hpp b/dlls/game/mp_modeBase.hpp new file mode 100644 index 0000000..b7219de --- /dev/null +++ b/dlls/game/mp_modeBase.hpp @@ -0,0 +1,267 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/multiplayerArena.h $ +// $Revision:: 38 $ +// $Author:: Steven $ +// $Date:: 7/23/02 3:55p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// Description: +// + +#ifndef __MP_MODEBASE_HPP__ +#define __MP_MODEBASE_HPP__ + +#include "g_local.h" // common game stuff +#include "player.h" // for Player +#include "item.h" +#include "PlayerStart.h" // for PlayerDeathmatchStart +#include "container.h" // for Container +#include "str.h" // for str +#include "mp_team.hpp" +#include "mp_shared.hpp" +#include "equipment.h" + +class MultiplayerPlayerGameData +{ +public: + int _numDeaths; + int _numKills; + int _points; + + int _entnum; + + bool _playing; + + float _nextHitSoundTime; + + float _startTime; + + Team *_currentTeam; + + int _lastKilledByPlayer; + int _lastKillerOfPlayer; + + int _lastKilledByPlayerMOD; + int _lastKillerOfPlayerMOD; + + int _lastPlace; + bool _lastTied; + + MultiplayerPlayerGameData(); + void init( void ); + void reset( void ); +}; + +//----------------------------------------------------------------------- +// MultiplayerModeBase -- Abstract base class providing common functionality +// to all multiplayer game types. Tracks players +// entering the arena, starting the match, etc. +//----------------------------------------------------------------------- +class MultiplayerModeBase : public Class + { + protected: + static const float _defaultStartinghealth; + static const int _defaultPointsPerKill; + static const int _defaultPointsPerTakenAwayForSuicide; + static const float _spectatorMoveSpeedModifier; + + Container _playerList; + Container _spawnpointList; + Container _unusedSpawnpointList; + Container _weaponList; + Container _ammoList; + unsigned int _activePlayers ; + unsigned int _spawncounter ; + unsigned int _startingHealth ; + bool _fightInProgress ; + + str _startingWeaponName; + + MultiplayerPlayerGameData *_playerGameData; + unsigned int _maxPlayers; + int _pointLimit; + + float _matchStartTime; + float _gameStartTime; + float _timeLimit; + + bool _gameStarted; + int _lastTimeRemaining; + + int _spectatorIconIndex; + + int _warmUpTextIndex; + int _waitingForMinPlayersTextIndex; + int _playingTextIndex; + + int _lastHighestPoints; + + bool _played5MinWarning; + bool _played2MinWarning; + bool _played1MinWarning; + + // Abstract constructor + MultiplayerModeBase(); + + int getNumSpawnpoints( void ); + Entity * getSpawnpointbyIndex( int index ); + Entity * getRandomSpawnpoint( bool useCounter = false ); + + int getNumNamedSpawnpoints( const str &spawnpointType ); + Entity * getNamedSpawnpointbyIndex( const str &spawnpointType, int index ); + Entity * getRandomNamedSpawnpoint( const str &spawnpointType ); + Entity * getFarthestNamedSpawnpoint( const Vector &origin, const str &spawnpointType ); + + virtual void _endMatch(); + virtual void _beginMatch(); + virtual void _giveInitialConditions(Player* player); + + void handleKill( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath, bool goodKill ); + + Player * getPlayer( int entnum ); + + public: + CLASS_PROTOTYPE( MultiplayerModeBase ); + virtual ~MultiplayerModeBase(); + + virtual void init( int maxPlayers ); + virtual void initItems( void ); + virtual void start( void ) {}; + virtual bool shouldKeepItem( MultiplayerItem *item ) { return false; } + virtual bool shouldKeepNormalItem( Item *item ); + virtual void itemKept( MultiplayerItem *item ) {}; + virtual void update( float frameTime ); + + virtual void matchOver( void ) {}; + virtual void declareWinner( void ); + + void getSpawnpoints( void ); + virtual Entity * getSpawnPoint( Player *player ); + + int findPlayer( const Player *player ); + + virtual bool canGivePlayerItem( int entnum, const str &itemName ) { return true; } + + // Queries + virtual bool isEndOfMatch( void ); + bool isFightInProgress() { return _fightInProgress ; } + + // Gets + //inline int getID( void ) { return _id; } + inline unsigned int getStartingHealth( void ) { return _startingHealth ; } + inline unsigned int getMaxPlayers( void ) { return _maxPlayers ; } + inline unsigned int getActivePlayers( void ) { return _activePlayers ; } + + inline int getPointLimit( void ) { return _pointLimit ; } + inline unsigned int getTimeLimit( void ) { return (unsigned int) _timeLimit ; } + + int getPoints( Player *player ); + int getKills( Player *player ); + int getDeaths( Player *player ); + Team* getPlayersTeam( const Player *player ); + virtual int getTeamPoints( Player *player ) { return 0; } + virtual int getTeamPoints( const str & teamName ) { return 0; } + virtual void addTeamPoints( const str & teamName, int points ) { return; } + + virtual int getStat( Player *player, int statNum, int value ); + virtual int getIcon( Player *player, int statNum, int value ); + virtual int getScoreIcon( Player *player, int index, int value ) { return value; } + virtual int getInfoIcon( Player *player ) { return 0; } + + virtual Player * getLastKilledByPlayer( Player *player, int *meansOfDeath ); + virtual Player * getLastKillerOfPlayer( Player *player, int *meansOfDeath ); + + int comparePlayerScore( MultiplayerPlayerGameData &player1Data, MultiplayerPlayerGameData &player2Data ); + int getPlace( Player *player, bool *tied = NULL ); + + // Sets + //void setID( int id ); + void setStartingHealth( unsigned int startingHealth) { _startingHealth = startingHealth ; } + void setPointLimit( int pointLimit ) { _pointLimit = pointLimit; } + void setTimeLimit( float timeLimit ) { _timeLimit = timeLimit; } + + // Arena wide functions + virtual void resetSpawnpoints( void ); + virtual void BeginMatch( void ); + virtual void EndMatch( void ); + virtual bool inMatch( void ); + + // Player specific functions + virtual void ActivatePlayer( Player *player ); + virtual void AddPlayer( Player *player ); + virtual void RemovePlayer( Player *player ); + bool needToAddPlayer( Player *player ); + + virtual bool canJoinTeam( Player *player, const str &teamName ); + virtual void joinTeam( Player *player, const str &teamName ); + + virtual bool canPickup( Player *player, MultiplayerItemType itemType, const char *item_name ) { return true; } + virtual void pickedupItem( Player *player, MultiplayerItemType itemType, const char *itemName ) {}; + + virtual void applySpeedModifiers( Player *player, int *moveSpeed ); + virtual void applyJumpModifiers( Player *player, int *jumpSpeed ) {}; + virtual void applyAirAccelerationModifiers( Player *player, int *airAcceleration ) {}; + + virtual void setupMultiplayerUI( Player *player ); + + virtual void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); + virtual float playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ); + virtual void playerTookDamage( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ); + virtual void playerFired( Player *attackingPlayer ) {}; + virtual void obituary( Player *killedPlayer, Player *attackingPlayer, int meansOfDeath ); + virtual void playerDead( Player *player ); + + virtual void itemTouched( Player *player, MultiplayerItem *item ) {}; + virtual void itemDestroyed( Player *player, MultiplayerItem *item ) {}; + virtual float itemDamaged( MultiplayerItem *item, Player *attackingPlayer, float damage, int meansOfDeath ) { return damage; } + virtual void itemUsed( Entity *entity, MultiplayerItem *item ) {}; + + virtual void playerUsed( Player *usedPlayer, Player *usingPlayer, Equipment *equipment ) {}; + + virtual void score( const Player *player ); + + virtual void respawnPlayer( Player *player ) {}; + + virtual void playerEventNotification( const char *eventName, const char *eventItemName, Player *eventPlayer ) {}; + + // Utility functions + void AddStartingWeapon(const str& weaponViewmodel); + void SetStartingWeapon( const str& weaponName ); + + virtual void addPoints( int entnum, int points ); + + void readMultiplayerConfig( const char *configName ); + virtual bool parseConfigToken( const char *key, Script *buffer ); + + bool shouldStartMatch( void ); + void startMatch( void ); + void endMatch( void ); + void restartMatch( void ); + + virtual bool checkGameType( const char *rule ) { return false; } + virtual bool doesPlayerHaveItem( Player *player, const char *itemName ) { return false; } + + virtual void playerCommand( Player *player, const char *command, const char *parm ) {}; + + virtual void teamPointsChanged( Team *team, int oldPoints, int newPoints ) {}; + + virtual bool checkRule( const char *rule, bool defaultValue, Player *player = NULL ) { return defaultValue; } + + virtual int getHighestPoints( void ); + + virtual bool isValidPlayerModel( Player *player, str modelToUse, bool defaultValue ) { return defaultValue; } + virtual str getDefaultPlayerModel( Player *player, str modelName ) { return modelName; } + + virtual void playerChangedModel( Player *player ) {}; + + virtual bool skipWeaponReloads( void ) { return false; } + }; + +#endif // __MP_MODEBASE_HPP__ diff --git a/dlls/game/mp_modeCtf.cpp b/dlls/game/mp_modeCtf.cpp new file mode 100644 index 0000000..af84781 --- /dev/null +++ b/dlls/game/mp_modeCtf.cpp @@ -0,0 +1,1188 @@ +//------------------------------------------------------------------------------ +// +// $Logfile:: /EF2/Code/DLLs/game/mp_modeCtf.cpp $ +// $Revision:: 60 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// Description: +// Implementation of Team and TeamArena classes. These classes provide +// Team-based multiplayer support. +// + +#include "_pch_cpp.h" +#include "mp_modeCtf.hpp" +#include "mp_manager.hpp" + + +//---------------------------------------------------------------- +// C A P T U R E T H E F L A G A R E N A +//---------------------------------------------------------------- + +// Setup constants + +const float ModeCaptureTheFlag::_maxGuardingDist = 1000.0f; +const float ModeCaptureTheFlag::_ctfCapturePoints = 100.0f; +const float ModeCaptureTheFlag::_maxDroppedFlagTime = 15.0f; + +const int ModeCaptureTheFlag::_pointsForFlagReturn = 5; +const int ModeCaptureTheFlag::_pointsForKillingFlagCarrier = 20; +const int ModeCaptureTheFlag::_pointsForAssist = 15; +const int ModeCaptureTheFlag::_pointsForDefense = 15; +const int ModeCaptureTheFlag::_pointsForTakingTheFlag = 15; +const int ModeCaptureTheFlag::_pointsForCapturingTheFlag = 85; + + +CLASS_DECLARATION( ModeTeamBase, ModeCaptureTheFlag, NULL ) +{ + { NULL, NULL } +}; + +//REGISTER_ARENA( ModeCaptureTheFlag ); + +//================================================================ +// Name: ModeCaptureTheFlag +// Class: ModeCaptureTheFlag +// +// Description: Constructor +// +// Parameters: const str& -- name of the arena +// +// Returns: None +// +//================================================================ +ModeCaptureTheFlag::ModeCaptureTheFlag() +{ + _redTeam = AddTeam("Red"); + _blueTeam = AddTeam("Blue"); + + _playerCtfData = NULL; + + _useTeamSpawnpoints = true; + + _flagCarrierIconIndex = gi.imageindex( "sysimg/icons/mp/ctf_flagcarrier" ); + + _oneFlagTakenIconIndex = gi.imageindex( "sysimg/icons/mp/ctf_oneFlagTaken" ); + _oneFlagMissingIconIndex = gi.imageindex( "sysimg/icons/mp/ctf_oneFlagMissing" ); + _oneFlagInBaseIconIndex = gi.imageindex( "sysimg/icons/mp/ctf_oneFlagInBase" ); + + _redFlagTakenIconIndex = gi.imageindex( "sysimg/icons/mp/ctf_redFlagTaken" ); + _redFlagMissingIconIndex = gi.imageindex( "sysimg/icons/mp/ctf_redFlagMissing" ); + _redFlagInBaseIconIndex = gi.imageindex( "sysimg/icons/mp/ctf_redFlagInBase" ); + + _blueFlagTakenIconIndex = gi.imageindex( "sysimg/icons/mp/ctf_blueFlagTaken" ); + _blueFlagMissingIconIndex = gi.imageindex( "sysimg/icons/mp/ctf_blueFlagMissing" ); + _blueFlagInBaseIconIndex = gi.imageindex( "sysimg/icons/mp/ctf_blueFlagInBase" ); +} + + +//================================================================ +// Name: ~ModeCaptureTheFlag +// Class: ModeCaptureTheFlag +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None, dammit +// +//================================================================ +ModeCaptureTheFlag::~ModeCaptureTheFlag() +{ + delete [] _playerCtfData; + _playerCtfData = NULL; + + _flags.FreeObjectList(); +} + +void ModeCaptureTheFlag::init( int maxPlayers ) +{ + // Initialize all of our data + + ModeTeamBase::init( maxPlayers ); + + _playerCtfData = new MultiplayerPlayerCtfData[ _maxPlayers ]; + + _flags.FreeObjectList(); + + // Read in the ctf data + + readMultiplayerConfig( "global/mp_ctf.cfg" ); + + multiplayerManager.cacheMultiplayerFiles( "mp_ctf" ); +} + +bool ModeCaptureTheFlag::shouldKeepItem( MultiplayerItem *item ) +{ + // See if we want to keep this item + + if ( strnicmp( item->getName().c_str(), "ctfflag", 7 ) == 0 ) + { + if ( !multiplayerManager.checkRule ( "keepflags", true ) ) + return false; + + if ( ( stricmp( item->getName().c_str() + 8, "red" ) == 0 ) || + ( stricmp( item->getName().c_str() + 8, "blue" ) == 0 ) || + ( stricmp( item->getName().c_str() + 8, "base" ) == 0 ) ) + { + // It is a red or blue flag so keep it + + return true; + } + } + + return false; +} + +void ModeCaptureTheFlag::itemKept( MultiplayerItem *item ) +{ + // See if we care about this item + + if ( strnicmp( item->getName().c_str(), "ctfflag", 7 ) == 0 ) + { + CtfFlag ctfFlag; + + // It is a flag so keep track of it + + ctfFlag._realFlag = item; + ctfFlag._tempFlag = NULL; + + if ( strnicmp( item->getName().c_str() + 8, "red", 3 ) == 0 ) + { + ctfFlag._teamName = "Red"; + } + else if ( strnicmp( item->getName().c_str() + 8, "blue", 4 ) == 0 ) + { + ctfFlag._teamName = "Blue"; + } + + _flags.AddObject ( ctfFlag ); + } +} + +void ModeCaptureTheFlag::addPlayerToTeam( Player *player, Team *team ) +{ + ModeTeamBase::addPlayerToTeam( player, team ); + + _playerCtfData[ player->entnum ].init(); +} + +void ModeCaptureTheFlag::RemovePlayer( Player *player ) +{ + ModeTeamBase::RemovePlayer( player ); + + putFlagBack( player ); +} + +void ModeCaptureTheFlag::setupMultiplayerUI( Player *player ) +{ + // Todo: make this a function in MultiplayerModeBase, each subclass just sets a string to the hud name + + gi.SendServerCommand( player->entnum, "stufftext \"ui_removehuds all\"\n" ); + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_console\"\n" ); + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_teamhud\"\n" ); + + if ( multiplayerManager.checkRule ( "flagpickup-enemyflag", true ) ) + { + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_flagstatus\"\n" ); + } + + if(mp_timelimit->integer) + { + gi.SendServerCommand( player->entnum, "stufftext \"globalwidgetcommand dmTimer enable\"\n"); + } + else + { + gi.SendServerCommand( player->entnum, "stufftext \"globalwidgetcommand dmTimer disable\"\n"); + } +} + +void ModeCaptureTheFlag::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + Team *team; + //int points; + float distance; + str printString; + bool goodKill; + + if ( attackingPlayer && ( killedPlayer != attackingPlayer ) && + ( _playerGameData[ attackingPlayer->entnum ]._currentTeam != _playerGameData[ killedPlayer->entnum ]._currentTeam ) ) + goodKill = true; + else + goodKill = false; + + handleKill( killedPlayer, attackingPlayer, inflictor, meansOfDeath, goodKill ); + //MultiplayerModeBase::playerKilled( killedPlayer, attackingPlayer, inflictor, meansOfDeath ); + + if ( !attackingPlayer ) + attackingPlayer = killedPlayer; + + team = _playerGameData[ attackingPlayer->entnum ]._currentTeam; + + /* if ( team ) + { + if ( multiplayerManager.checkRule( "team-pointsForKills", true, attackingPlayer ) ) + { + // Award a point to the attacking player's team (if victim's not on same team) + // Lose a point if on the same team + + if ( _playerGameData[ killedPlayer->entnum ]._currentTeam != _playerGameData[ attackingPlayer->entnum ]._currentTeam ) + { + points = _defaultPointsPerKill; + + points = multiplayerManager.getPointsForKill( killedPlayer, attackingPlayer, inflictor, meansOfDeath, points ); + team->addPoints( attackingPlayer, points ); + } + } + } */ + + // See if the killed player was carrying the flag + + if ( _playerCtfData[ killedPlayer->entnum ]._hasFlag ) + { + if ( _playerGameData[ killedPlayer->entnum ]._currentTeam != _playerGameData[ attackingPlayer->entnum ]._currentTeam ) + { + CtfFlag *ctfFlag; + str printString; + + printString = "$$FlagCarrierKilled$$ "; + printString += attackingPlayer->client->pers.netname; + printString += " ($$"; + printString += _playerGameData[ attackingPlayer->entnum ]._currentTeam->getName(); + printString += "$$ $$Team$$)!"; + + multiplayerManager.centerPrintAllClients( printString, CENTERPRINT_IMPORTANCE_NORMAL ); + + ctfFlag = findFlag( _playerCtfData[ killedPlayer->entnum ]._flag ); + + if ( ctfFlag ) + { + if ( ctfFlag->_teamName == "Red" ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_rfk.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else if ( ctfFlag->_teamName == "Blue" ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bfk.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_flagcarrierkilled.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + + // Give points to the player + + multiplayerManager.addPoints( attackingPlayer->entnum, _pointsForKillingFlagCarrier ); + } + + dropFlag( killedPlayer ); + } + + // See if the player was guarding the flag (either he or the killed player was close to his flag) + + if ( _playerGameData[ killedPlayer->entnum ]._currentTeam != _playerGameData[ attackingPlayer->entnum ]._currentTeam ) + { + bool flagGuarded; + + distance = findNearestTeamFlagDist( team->getName(), attackingPlayer->origin ); + + flagGuarded = false; + + if ( ( distance > 0 ) && ( distance < _maxGuardingDist ) ) + { + flagGuarded = true; + } + else + { + distance = findNearestTeamFlagDist( team->getName(), killedPlayer->origin ); + + if ( ( distance > 0 ) && ( distance < _maxGuardingDist ) ) + { + flagGuarded = true; + } + } + + if ( flagGuarded ) + { + multiplayerManager.playerEventNotification( "flag-guarded", "", attackingPlayer ); + + printString = "$$FlagGuarded$$ "; + printString += attackingPlayer->client->pers.netname; + + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_flagguarded.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + + multiplayerManager.centerPrintTeamClients( attackingPlayer, printString, CENTERPRINT_IMPORTANCE_HIGH ); + + // Give points to the player + + multiplayerManager.addPoints( attackingPlayer->entnum, _pointsForDefense ); + } + } + + // See if the player was guarding his team's flag carrier (either he or the killed player was close to the flag carrier) + + if ( ( _playerGameData[ killedPlayer->entnum ]._currentTeam != _playerGameData[ attackingPlayer->entnum ]._currentTeam ) && + !_playerCtfData[ attackingPlayer->entnum ]._hasFlag ) + { + bool flagCarrierGuarded; + + distance = findNearestTeamFlagCarrierDist( team->getName(), attackingPlayer->origin ); + + flagCarrierGuarded = false; + + if ( ( distance > 0 ) && ( distance < _maxGuardingDist ) ) + { + flagCarrierGuarded = true; + } + else + { + distance = findNearestTeamFlagCarrierDist( team->getName(), killedPlayer->origin ); + + if ( ( distance > 0 ) && ( distance < _maxGuardingDist ) ) + { + flagCarrierGuarded = true; + } + } + + if ( flagCarrierGuarded ) + { + //multiplayerManager.playerEventNotification( "flag-carrierguarded", "", attackingPlayer ); + + printString = "$$FlagAssist$$ "; + printString += attackingPlayer->client->pers.netname; + + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_flagassist.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + + multiplayerManager.centerPrintTeamClients( attackingPlayer, printString, CENTERPRINT_IMPORTANCE_HIGH ); + + // Give points to the player + + multiplayerManager.addPoints( attackingPlayer->entnum, _pointsForAssist ); + } + } +} + +CtfFlag *ModeCaptureTheFlag::findFlag( MultiplayerItem *item ) +{ + int i; + CtfFlag *ctfFlag; + + for ( i = 1 ; i <= _flags.NumObjects() ; i++ ) + { + ctfFlag = &_flags.ObjectAt( i ); + + if ( ( ctfFlag->_realFlag == item ) || ( ctfFlag->_tempFlag == item ) ) + return ctfFlag; + } + + return NULL; +} + +float ModeCaptureTheFlag::findNearestTeamFlagDist( const str &teamName, const Vector &position ) +{ + int i; + CtfFlag *ctfFlag; + CtfFlag *nearestCtfFlag = NULL; + float nearestDistance = -1.0f; + Vector diff; + float distance; + + // Go through all of the flags + + for ( i = 1 ; i <= _flags.NumObjects() ; i++ ) + { + ctfFlag = &_flags.ObjectAt( i ); + + // Make sure everything is ok + + if ( !ctfFlag->_realFlag ) + continue; + + if ( ctfFlag->_teamName != teamName ) + continue; + + // Get the distance to the flag + + diff = position - ctfFlag->_realFlag->origin; + distance = diff.length(); + + // See if this is the nearest flag + + if ( !nearestCtfFlag || ( distance < nearestDistance ) ) + { + nearestCtfFlag = ctfFlag; + nearestDistance = distance; + } + } + + return nearestDistance; +} + +float ModeCaptureTheFlag::findNearestTeamFlagCarrierDist( const str &teamName, const Vector &position ) +{ + int i; + Player *currentPlayer; + Player *nearestFlagCarrier = NULL; + float nearestDistance = -1.0f; + Team *team; + Vector diff; + float distance; + + // Go through all of the flags + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + if ( !_playerGameData[ i ]._playing ) + continue; + + currentPlayer = (Player *)g_entities[ _playerGameData[ i ]._entnum ].entity; + + team = multiplayerManager.getPlayersTeam( currentPlayer ); + + // Make sure this player is one the correct team and has the flag + + if ( !team ) + continue; + + if ( team->getName() != teamName ) + continue; + + if ( !_playerCtfData[ currentPlayer->entnum ]._hasFlag ) + continue; + + // Get the distance to the flag carrier + + diff = position - currentPlayer->origin; + distance = diff.length(); + + // See if this is the nearest flag carrier + + if ( !nearestFlagCarrier || ( distance < nearestDistance ) ) + { + nearestFlagCarrier = currentPlayer; + nearestDistance = distance; + } + } + + return nearestDistance; +} + +void ModeCaptureTheFlag::itemTouched( Player *player, MultiplayerItem *item ) +{ + CtfFlag *ctfFlag; + bool myFlag; + bool teamFlag; + bool tempFlag; + + // Make sure this item is one of the flags + + ctfFlag = findFlag( item ); + + if ( !ctfFlag ) + return; + + // Determine if this is my team's flag or not + + if ( !_playerGameData[ player->entnum ]._currentTeam ) + return; + + if ( ( ( ctfFlag->_teamName == "Red" ) && ( _playerGameData[ player->entnum ]._currentTeam == _redTeam ) ) || + ( ( ctfFlag->_teamName == "Blue" ) && ( _playerGameData[ player->entnum ]._currentTeam == _blueTeam ) ) ) + myFlag = true; + else + myFlag = false; + + // Determine if this is one of the team flags (red or blue) + + if ( ( ctfFlag->_teamName == "Red" ) || ( ctfFlag->_teamName == "Blue" ) ) + teamFlag = true; + else + teamFlag = false; + + // Determine if this is a temporary flag (one dropped) + + if ( item == ctfFlag->_tempFlag ) + tempFlag = true; + else + tempFlag = false; + + // Determine what to do about touching the flag + + if ( myFlag && teamFlag && tempFlag && multiplayerManager.checkRule ( "flagreturn-teamflag", true ) ) + { + // Return this temporary flag back to the flag position + + returnFlag( item, player ); + } + else if ( _playerCtfData[ player->entnum ]._hasFlag ) + { + // The player has a flag, see if we score or not + + if ( !tempFlag && myFlag && multiplayerManager.checkRule ( "flagscore-teamflag", true ) ) + { + score( player ); + } + else if ( !tempFlag && !myFlag && teamFlag && multiplayerManager.checkRule ( "flagscore-enemyflag", false ) ) + { + score( player ); + } + else if ( !tempFlag && !teamFlag ) + { + str rule; + + rule = "flagscore-otherflag-"; + rule += item->getName(); + + if ( multiplayerManager.checkRule ( rule.c_str(), false ) ) + { + score( player ); + } + } + } + else + { + // The player does not have a flag, see if we can grab the flag + + if ( !myFlag && teamFlag && multiplayerManager.checkRule ( "flagpickup-enemyflag", true ) ) + { + grabTheFlag( player, item ); + } + else if ( myFlag && teamFlag && multiplayerManager.checkRule ( "flagpickup-teamflag", false ) ) + { + grabTheFlag( player, item ); + } + else if ( !teamFlag ) + { + str rule; + + rule = "flagpickup-otherflag-"; + rule += ctfFlag->_realFlag->getName(); + + if ( multiplayerManager.checkRule ( rule.c_str(), false ) ) + { + grabTheFlag( player, item ); + } + } + } +} + +void ModeCaptureTheFlag::score( Player *player ) +{ + Team *team; + str printString; + CtfFlag *ctfFlag; + + ctfFlag = findFlag( _playerCtfData[ player->entnum ]._flag ); + + if ( !ctfFlag ) + return; + + // Put the flag back where it is suppose to be + + putFlagBack( player ); + + // Give the player's team points + + // Todo: hide all team stuff in ModeTeamBase ? + + team = _playerGameData[ player->entnum ]._currentTeam; + + // Tell everyone the flag was captured + + printString = "$$FlagCaptured$$ "; + printString += player->client->pers.netname; + printString += " ($$"; + printString += team->getName(); + printString += "$$ $$Team$$)!"; + + multiplayerManager.centerPrintAllClients( printString, CENTERPRINT_IMPORTANCE_NORMAL ); + + if ( strnicmp( ctfFlag->_realFlag->getName().c_str(), "ctfflag-red", strlen( "ctfflag-red" ) ) == 0 ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_frc.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else if ( strnicmp( ctfFlag->_realFlag->getName().c_str(), "ctfflag-blue", strlen( "ctfflag-blue" ) ) == 0 ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bfc.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else + { + if ( team->getName() == "Red" ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_fcrt.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_fcbt.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + + // Tell the other multiplayer systems that the flag was captured + + multiplayerManager.playerEventNotification( "flag-captured", ctfFlag->_realFlag->getName().c_str(), player ); + + // Give the team and the player points + + team->addPoints( player,(int) _ctfCapturePoints ); + + multiplayerManager.addPoints( player->entnum, _pointsForCapturingTheFlag ); +} + +void ModeCaptureTheFlag::dropFlag( Player *player ) +{ + CtfFlag *ctfFlag; + MultiplayerItem *newFlag; + + // Make sure everything is ok + + if ( !_playerCtfData[ player->entnum ]._hasFlag ) + return; + + ctfFlag = findFlag( _playerCtfData[ player->entnum ]._flag ); + + if ( !ctfFlag ) + return; + + // Create a new flag to put on the ground + + newFlag = new MultiplayerItem; + + newFlag->setModel( ctfFlag->_realFlag->model ); + + newFlag->angles = player->angles; + newFlag->setAngles(); + + newFlag->setOrigin( player->origin ); + + newFlag->CancelEventsOfType( EV_ProcessInitCommands ); + newFlag->ProcessInitCommands( newFlag->edict->s.modelindex ); + + newFlag->setName( "ctfflag-temp" ); + + // Save off some info about the dropped flag + + ctfFlag->_tempFlag = newFlag; + ctfFlag->_tempFlagTime = multiplayerManager.getTime(); + + // Remove the flag from the player + + if ( _playerCtfData[ player->entnum ]._carriedFlag ) + { + _playerCtfData[ player->entnum ]._carriedFlag->PostEvent( EV_Detach, 0.0f ); + _playerCtfData[ player->entnum ]._carriedFlag->PostEvent( EV_Remove, FRAMETIME ); + _playerCtfData[ player->entnum ]._carriedFlag = NULL; + } + + _playerCtfData[ player->entnum ]._hasFlag = false; + + multiplayerManager.playerEventNotification( "flag-dropped", ctfFlag->_realFlag->getName().c_str(), player ); +} + +void ModeCaptureTheFlag::returnFlag( MultiplayerItem *item, Player *player ) +{ + CtfFlag *ctfFlag; + str printString; + + ctfFlag = findFlag( item ); + + if ( !ctfFlag ) + return; + + // Remove the dropped flag from the ground + + if ( ctfFlag->_tempFlag ) + { + ctfFlag->_tempFlag->PostEvent( EV_Remove, FRAMETIME ); + + ctfFlag->_tempFlag = NULL; + } + + // Put the flag back to where it belongs (really just show it again) + + if ( ctfFlag->_realFlag ) + { + ctfFlag->_realFlag->showModel(); + ctfFlag->_realFlag->setSolidType( SOLID_TRIGGER ); + } + + if ( player ) + { + // Tell everyone that the player returned the flag + + printString = "$$FlagReturnedBy$$ "; + printString += player->client->pers.netname; + printString += " ($$"; + printString += _playerGameData[ player->entnum ]._currentTeam->getName(); + printString += "$$ $$Team$$)!"; + + multiplayerManager.centerPrintAllClients( printString, CENTERPRINT_IMPORTANCE_NORMAL ); + + if ( strnicmp( ctfFlag->_realFlag->getName().c_str(), "ctfflag-red", strlen( "ctfflag-red" ) ) == 0 ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_rfr.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + else if ( strnicmp( ctfFlag->_realFlag->getName().c_str(), "ctfflag-blue", strlen( "ctfflag-blue" ) ) == 0 ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bfr.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + else + { + if ( _playerGameData[ player->entnum ]._currentTeam->getName() == "Red" ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_frbrt.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + else + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_frbbt.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + } + + // Tell all of the other multiplayer systems that the flag was returned + + multiplayerManager.playerEventNotification( "flag-returned", ctfFlag->_realFlag->getName().c_str(), player ); + + // Give points to the player + + multiplayerManager.addPoints( player->entnum, _pointsForFlagReturn ); + } + else + { + printString = "$$FlagReturned$$"; + + multiplayerManager.centerPrintAllClients( printString, CENTERPRINT_IMPORTANCE_NORMAL ); + + if ( strnicmp( ctfFlag->_realFlag->getName().c_str(), "ctfflag-red", strlen( "ctfflag-red" ) ) == 0 ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_rfr.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + else if ( strnicmp( ctfFlag->_realFlag->getName().c_str(), "ctfflag-blue", strlen( "ctfflag-blue" ) ) == 0 ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bfr.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + + } +} + +void ModeCaptureTheFlag::putFlagBack( Player *player ) +{ + CtfFlag *ctfFlag; + MultiplayerItem *flag; + + // Make sure everything is ok + + if ( !_playerCtfData[ player->entnum ]._hasFlag ) + return; + + ctfFlag = findFlag( _playerCtfData[ player->entnum ]._flag ); + + if ( !ctfFlag ) + return; + + flag = ctfFlag->_realFlag; + + // Put the flag back + + if ( flag ) + { + flag->showModel(); + flag->setSolidType( SOLID_TRIGGER ); + } + + // Remove flag from the player + + if ( _playerCtfData[ player->entnum ]._carriedFlag ) + { + _playerCtfData[ player->entnum ]._carriedFlag->PostEvent( EV_Detach, 0.0f ); + _playerCtfData[ player->entnum ]._carriedFlag->PostEvent( EV_Remove, FRAMETIME ); + _playerCtfData[ player->entnum ]._carriedFlag = NULL; + } + + _playerCtfData[ player->entnum ]._hasFlag = false; +} + +void ModeCaptureTheFlag::grabTheFlag( Player *player, MultiplayerItem *item ) +{ + CtfFlag *ctfFlag; + Entity *obj; + str printString; + Team *team; + MultiplayerItem *flag; + bool tempFlag; + + ctfFlag = findFlag( item ); + + flag = ctfFlag->_realFlag; + + // Get rid of the dropped flag (if any) + + if ( ctfFlag->_tempFlag ) + { + ctfFlag->_tempFlag->PostEvent( EV_Remove, FRAMETIME ); + + ctfFlag->_tempFlag = NULL; + + tempFlag = true; + } + else + { + tempFlag = false; + } + + // Give the flag to the player + + obj = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + obj->setModel( flag->model ); + + obj->animate->RandomAnimate( "idle" ); + + attachFlag( player, obj ); + + _playerCtfData[ player->entnum ]._hasFlag = true; + _playerCtfData[ player->entnum ]._carriedFlag = obj; + _playerCtfData[ player->entnum ]._flag = flag; + + // Hide the real flag and make it not solid + + flag->setSolidType( SOLID_NOT ); + flag->hideModel(); + + // Give the player some points for taking the flag (only if it was taken from the base) + + if ( !tempFlag ) + { + multiplayerManager.addPoints( player->entnum, _pointsForTakingTheFlag ); + } + + // Tell everyone that the flag was taken + + team = _playerGameData[ player->entnum ]._currentTeam; + + printString = "$$FlagTaken$$ "; + printString += player->client->pers.netname; + printString += " ($$"; + printString += team->getName(); + printString += "$$ $$Team$$)!"; + + multiplayerManager.centerPrintAllClients( printString, CENTERPRINT_IMPORTANCE_NORMAL ); + + if ( strnicmp( ctfFlag->_realFlag->getName().c_str(), "ctfflag-red", strlen( "ctfflag-red" ) ) == 0 ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_rft.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + else if ( strnicmp( ctfFlag->_realFlag->getName().c_str(), "ctfflag-blue", strlen( "ctfflag-blue" ) ) == 0 ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bft.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + else + { + if ( team->getName() == "Red" ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_ftbrt.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + else + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_ftbbt.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + } + + multiplayerManager.playerEventNotification( "flag-taken", ctfFlag->_realFlag->getName().c_str(), player ); +} + +void ModeCaptureTheFlag::update( float frameTime ) +{ + CtfFlag *ctfFlag; + int i; + + MultiplayerModeBase::update( frameTime ); + + // Return any flags that have been sitting on the ground too long + + for ( i = 1 ; i <= _flags.NumObjects() ; i++ ) + { + ctfFlag = &_flags.ObjectAt( i ); + + if ( ( ctfFlag->_tempFlag ) && ( ctfFlag->_tempFlagTime + _maxDroppedFlagTime < multiplayerManager.getTime() ) ) + { + returnFlag( ctfFlag->_tempFlag, NULL ); + } + } +} + +bool ModeCaptureTheFlag::isEndOfMatch( void ) +{ + // See if we have a gone over the point limit + + if ( getPointLimit() > 0 ) + { + // Check team points + + if ( _redTeam->getPoints() >= getPointLimit() ) + return true; + else if ( _blueTeam->getPoints() >= getPointLimit() ) + return true; + } + + // See if we have a gone over the time limit + + if ( ( getTimeLimit() > 0.0f ) && ( multiplayerManager.getTime() - _gameStartTime > getTimeLimit() ) ) + return true; + + return false; +} + +int ModeCaptureTheFlag::getIcon( Player *player, int statNum, int value ) +{ + if ( statNum == STAT_MP_MODE_ICON ) + { + if ( _playerCtfData[ player->entnum ]._hasFlag ) + return _flagCarrierIconIndex; + else + return -1; + } + + return ModeTeamBase::getIcon( player, statNum, value ); +} + +int ModeCaptureTheFlag::getScoreIcon( Player *player, int index, int value ) +{ + if ( index == SCOREICON1 ) + { + if ( _playerCtfData[ player->entnum ]._hasFlag ) + return _flagCarrierIconIndex; + else + return 0; + } + + return value; +} + +void ModeCaptureTheFlag::score( const Player *player ) +{ + char string[1400]; + char entry[1024]; + int i; + int tempStringlength; + int count = 0; + int stringlength = 0; + Team *team; + str teamName; + //int teamPoints; + Player *currentPlayer; + int redScore; + int blueScore; + + assert( player ); + if ( !player ) + { + warning( "MultiplayerModeBase::score", "Null Player specified.\n" ); + return; + } + + string[0] = 0; + entry[0] = 0; + + // This for loop builds a string containing all the players scores. + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + currentPlayer = multiplayerManager.getPlayer( i ); + + if ( !currentPlayer ) + continue; + + team = multiplayerManager.getPlayersTeam( currentPlayer ); + + if ( team ) + { + teamName = team->getName(); + //teamPoints = team->getPoints(); + } + else + { + teamName = "spectator"; + //teamPoints = 0; + } + + + Com_sprintf( entry, sizeof( entry ), "%i %i %i %i %s %i %i %d %d %d %d %d %d ", + multiplayerManager.getClientNum( _playerGameData[ i ]._entnum ), + _playerGameData[ i ]._points, + _playerGameData[ i ]._numKills, + _playerGameData[ i ]._numDeaths, + //0 /*pl->GetMatchesWon() */, + //0 /*pl->GetMatchesLost()*/, + teamName.c_str(), + //teamPoints, + (int)(multiplayerManager.getTime() - _playerGameData[i]._startTime), + multiplayerManager.getClientPing( _playerGameData[ i ]._entnum ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON1 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON2 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON3 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON4 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON5 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON6 ) ); + + tempStringlength = strlen( entry ); + + // Make sure the string is not too big (take into account other stuff that gets prepended below also) + + if ( stringlength + tempStringlength > 975 ) + break; + + strcpy( string + stringlength, entry ); + + stringlength += tempStringlength; + count++; + } + + redScore = multiplayerManager.getTeamPoints( "Red" ); + blueScore = multiplayerManager.getTeamPoints( "Blue" ); + + gi.SendServerCommand( player->edict-g_entities, "scores 2 %i %d %d %s", count, redScore, blueScore, string ); +} + +//---------------------------------------------------------------- +// P R O T E C T E D M E T H O D S +//---------------------------------------------------------------- +/* void ModeCaptureTheFlag::_endMatch( void ) +{ + multiplayerManager.centerPrintAllClients( "$$MatchOver$$\n", CENTERPRINT_IMPORTANCE_NORMAL ); + + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_matover.mp3" ); +} */ + + +bool ModeCaptureTheFlag::checkGameType( const char *gameType ) +{ + if ( stricmp( gameType, "ctf" ) == 0 ) + return true; + else + return false; +} + +bool ModeCaptureTheFlag::doesPlayerHaveItem( Player *player, const char *itemName ) +{ + // We only care about flags + + if ( strnicmp( itemName, "ctfflag", 7 ) == 0 ) + { + // See if the player has a flag + + if ( _playerCtfData[ player->entnum ]._hasFlag ) + { + CtfFlag *ctfFlag; + + // Get the flag that the player has + + ctfFlag = findFlag( _playerCtfData[ player->entnum ]._flag ); + + if ( !ctfFlag ) + return false; + + if ( ctfFlag->_realFlag ) + { + // See if this is the flag in question + + if ( stricmp( ctfFlag->_realFlag->getName().c_str(), itemName ) == 0 ) + { + return true; + } + } + } + } + + return false; +} + +int ModeCaptureTheFlag::getStat( Player *player, int statNum, int value ) +{ + CtfFlag *ctfFlag; + int i; + str flagName; + bool oneFlag; + int takenIconIndex; + int missingIconIndex; + int inBaseIconIndex; + + if ( ( statNum == STAT_MP_GENERIC5 ) || ( statNum == STAT_MP_GENERIC6 ) ) + { + // Make sure not one flag + + oneFlag = !multiplayerManager.checkRule ( "flagpickup-enemyflag", true ); + + if ( oneFlag && statNum == STAT_MP_GENERIC6 ) + return value; + + if ( oneFlag ) + { + flagName = "ctfflag-one"; + takenIconIndex = _oneFlagTakenIconIndex; + missingIconIndex = _oneFlagMissingIconIndex; + inBaseIconIndex = _oneFlagInBaseIconIndex; + } + else if ( statNum == STAT_MP_GENERIC5 ) + { + flagName = "ctfflag-red"; + takenIconIndex = _redFlagTakenIconIndex; + missingIconIndex = _redFlagMissingIconIndex; + inBaseIconIndex = _redFlagInBaseIconIndex; + } + else + { + flagName = "ctfflag-blue"; + takenIconIndex = _blueFlagTakenIconIndex; + missingIconIndex = _blueFlagMissingIconIndex; + inBaseIconIndex = _blueFlagInBaseIconIndex; + } + + for ( i = 1 ; i <= _flags.NumObjects() ; i++ ) + { + ctfFlag = &_flags.ObjectAt( i ); + + if ( strnicmp( ctfFlag->_realFlag->getName().c_str(), flagName.c_str(), flagName.length() ) == 0 ) + { + if ( ctfFlag->_tempFlag ) + { + return missingIconIndex; + } + else if ( ctfFlag->_realFlag->getSolidType() == SOLID_NOT ) + { + return takenIconIndex; + } + else + { + return inBaseIconIndex; + } + } + } + } + else if ( statNum == STAT_MP_STATE ) + { + return MultiplayerModeBase::getStat( player, statNum, value ); + } + + return value; +} + +void ModeCaptureTheFlag::playerEventNotification( const char *eventName, const char *eventItemName, Player *eventPlayer ) +{ + if ( stricmp( eventName, "use-HoldableItem" ) == 0 ) + { + if ( stricmp( eventItemName, "Transporter" ) == 0 ) + { + dropFlag( eventPlayer ); + } + } +} + +void ModeCaptureTheFlag::playerChangedModel( Player *player ) +{ + Entity *flag; + + if ( _playerCtfData[ player->entnum ]._hasFlag && _playerCtfData[ player->entnum ]._carriedFlag ) + { + flag = _playerCtfData[ player->entnum ]._carriedFlag; + + flag->detach(); + + attachFlag( player, flag ); + } + + updatePlayerSkin( player ); +} + +void ModeCaptureTheFlag::attachFlag( Player *player, Entity *obj ) +{ + int tagnum; + + tagnum = gi.Tag_NumForName( player->edict->s.modelindex, "Bip01 spine2" ); + + if ( tagnum < 0 ) + tagnum = gi.Tag_NumForName( player->edict->s.modelindex, "Bip01 spine1" ); + + if ( tagnum < 0 ) + tagnum = gi.Tag_NumForName( player->edict->s.modelindex, "Bip01 Head" ); + + if ( ( tagnum < 0 ) || !obj->attach( player->entnum, tagnum, false, player->getFlagAttachOffset(), player->getFlagAttachAngles() ) ) + { + warning( "ModeCaptureTheFlag::attachFlag", "Could not attach model %s", obj->model.c_str() ); + delete obj; + return; + } +} + diff --git a/dlls/game/mp_modeCtf.hpp b/dlls/game/mp_modeCtf.hpp new file mode 100644 index 0000000..99a90c0 --- /dev/null +++ b/dlls/game/mp_modeCtf.hpp @@ -0,0 +1,136 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/teamArena.h $ +// $Revision:: 20 $ +// $Author:: Steven $ +// $Date:: 7/17/02 4:00p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// Description: +// + +#ifndef __MP_MODECTF_HPP__ +#define __MP_MODECTF_HPP__ + +#include "mp_modeTeamBase.hpp" + +class MultiplayerPlayerCtfData +{ +public: + int _hasFlag; + EntityPtr _carriedFlag; + MultiplayerItem * _flag; + + MultiplayerPlayerCtfData() { _hasFlag = false; } + void init( void ) { _hasFlag = false; } +}; + +class CtfFlag +{ +public: + MultiplayerItem * _tempFlag; + float _tempFlagTime; + MultiplayerItem * _realFlag; + str _teamName; +}; + +//--------------------------------------------------------------------- +// ModeCaptureTheFlag -- Capture the Flag Multiplayer. Classic CTF +// style gameplay. +//--------------------------------------------------------------------- +//CLASS_ARENA( ModeCaptureTheFlag ); +class ModeCaptureTheFlag : public ModeTeamBase + { + private: + static const float _maxGuardingDist; + static const float _ctfCapturePoints; + static const float _maxDroppedFlagTime; + + static const int _pointsForFlagReturn; + static const int _pointsForKillingFlagCarrier; + static const int _pointsForAssist; + static const int _pointsForDefense; + static const int _pointsForTakingTheFlag; + static const int _pointsForCapturingTheFlag; + + Team* _redTeam; + Team* _blueTeam; + + Container _flags; + + MultiplayerPlayerCtfData *_playerCtfData; + + int _flagCarrierIconIndex; + + int _oneFlagTakenIconIndex; + int _oneFlagMissingIconIndex; + int _oneFlagInBaseIconIndex; + + int _redFlagTakenIconIndex; + int _redFlagMissingIconIndex; + int _redFlagInBaseIconIndex; + + int _blueFlagTakenIconIndex; + int _blueFlagMissingIconIndex; + int _blueFlagInBaseIconIndex; + + protected: + ///* virtual */ void _endMatch(); // Notifies teams of end of match + + void grabTheFlag( Player *player, MultiplayerItem *item ); + void score( Player *player ); + void putFlagBack( Player *player ); + + CtfFlag * findFlag( MultiplayerItem *item ); + float findNearestTeamFlagDist( const str &teamName, const Vector &position ); + float findNearestTeamFlagCarrierDist( const str &teamName, const Vector &position ); + + void returnFlag( MultiplayerItem *item, Player *player ); + void dropFlag( Player *player ); + void attachFlag( Player *player, Entity *obj ); + + public: + CLASS_PROTOTYPE( ModeCaptureTheFlag ); + + ModeCaptureTheFlag(); + ~ModeCaptureTheFlag(); + + /* virtual */ void init( int maxPlayers ); + + /* virtual */ bool shouldKeepItem( MultiplayerItem *item ); + /* virtual */ void itemKept( MultiplayerItem *item ); + + /* virtual */ void RemovePlayer( Player *player ); + + /* virtual */ void addPlayerToTeam( Player *player, Team *team ); + + void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); + /* virtual */ void itemTouched( Player *player, MultiplayerItem *item ); + + bool isEndOfMatch( void ); + + virtual int getStat( Player *player, int statNum, int value ); + /* virtual */ int getIcon( Player *player, int statNum, int value ); + /* virtual */ int getScoreIcon( Player *player, int index, int value ); + + /* virtual */ void setupMultiplayerUI( Player *player ); + + /* virtual */ void score( const Player *player ); + + /* virtual */ void update( float frameTime ); + + /* virtual */ bool checkGameType( const char *rule ); + /* virtual */ bool doesPlayerHaveItem( Player *player, const char *itemName ); + + /* virtual */ void playerEventNotification( const char *eventName, const char *eventItemName, Player *eventPlayer ); + + /* virtual */ void playerChangedModel( Player *player ); + }; + +#endif // __MP_MODECTF_HPP__ diff --git a/dlls/game/mp_modeDm.cpp b/dlls/game/mp_modeDm.cpp new file mode 100644 index 0000000..4775a66 --- /dev/null +++ b/dlls/game/mp_modeDm.cpp @@ -0,0 +1,256 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/mp_modeDm.cpp $ +// $Revision:: 19 $ +// $Author:: Steven $ +// $Date:: 8/08/03 1:26p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// Description: +// + +#include "_pch_cpp.h" + +#include "mp_manager.hpp" +#include "mp_modeDm.hpp" + + +CLASS_DECLARATION( MultiplayerModeBase, ModeDeathmatch, NULL ) +{ + { NULL, NULL } +}; + +//================================================================ +// Name: ModeDeathmatch +// Class: ModeDeathmatch +// +// Description: Constructor +// +// Parameters: const str& -- name of the arena +// +// Returns: None +// +//================================================================ +ModeDeathmatch::ModeDeathmatch() +{ +} + +//================================================================ +// Name: ~ModeDeathmatch +// Class: ModeDeathmatch +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//================================================================ +ModeDeathmatch::~ModeDeathmatch() +{ +} + +void ModeDeathmatch::respawnPlayer( Player *player ) +{ + Entity *spawnPoint; + + assert(player); + + if ( !player ) + { + warning("ModeDeathmatch::respawnPlayer", "NULL player\n"); + return ; + } + + // Make sure we are allowed to respawn the player + + if ( !multiplayerManager.checkRule( "respawnPlayer", true, player ) ) + { + if ( multiplayerManager.checkRule( "allowSpectator", true ) ) + { + multiplayerManager.makePlayerSpectator( player ); + } + + return; + } + + // Respawn the player + + MultiplayerModeBase::respawnPlayer(player); + + multiplayerManager.initPlayer( player ); + + // Warp the player to a spawn point + + spawnPoint = getSpawnPoint( player ); + + if ( spawnPoint ) + { + player->WarpToPoint( spawnPoint ); + } + + KillBox( player ); + + ActivatePlayer(player); +} + + +//================================================================ +// Name: AddPlayer +// Class: ModeDeathmatch +// +// Description: Adds a player to the arena. In deathmatch, this +// also sets their spawn point and immediately sets +// them up for fighting. +// +// Parameters: Player* -- Player to add and prepare for fighting +// +// Returns: None +// +//================================================================ +void ModeDeathmatch::AddPlayer( Player *player ) +{ + Entity *spawnPoint; + + assert(player); + + if ( !player ) + { + warning("ModeDeathmatch::AddPlayer", "NULL Player\n"); + return ; + } + + if ( !needToAddPlayer( player ) ) + return; + + MultiplayerModeBase::AddPlayer(player); + + if ( !multiplayerManager.checkRule( "spawnPlayer", true, player ) ) + { + if ( multiplayerManager.checkRule( "allowSpectator", true ) ) + { + multiplayerManager.makePlayerSpectator( player ); + } + + return; + } + + // Warp the player to a spawn point + + spawnPoint = getSpawnPoint( player ); + + if ( spawnPoint ) + { + player->WarpToPoint( spawnPoint ); + } + + KillBox( player ); + + ActivatePlayer(player); + + // If the game hasn't started yet just make the player a spectator + + if ( !_gameStarted ) + { + multiplayerManager.makePlayerSpectator( player ); + } +} + +void ModeDeathmatch::init( int maxPlayers ) +{ + MultiplayerModeBase::init( maxPlayers ); + + readMultiplayerConfig( "global/mp_dm.cfg" ); + + multiplayerManager.cacheMultiplayerFiles( "mp_dm" ); +} + +bool ModeDeathmatch::checkGameType( const char *gameType ) +{ + if ( stricmp( gameType, "dm" ) == 0 ) + return true; + else + return false; +} + +int ModeDeathmatch::getHighestPoints( int entnum ) +{ + int i; + int highestPoints = -999999999; + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + if ( _playerGameData[ i ]._playing && ( _playerGameData[ i ]._entnum != entnum ) ) + { + if ( _playerGameData[ i ]._points > highestPoints ) + { + highestPoints = _playerGameData[ i ]._points; + } + } + } + + return highestPoints; +} + +bool ModeDeathmatch::checkRule( const char *rule, bool defaultValue, Player *player ) +{ + if ( stricmp( rule, "usingIndividualScore" ) == 0 ) + return true; + else + return defaultValue; +} + +void ModeDeathmatch::update( float frameTime ) +{ + int i; + Player *player; + int place; + bool tied; + + MultiplayerModeBase::update( frameTime ); + + // Update the players about their ranking + + if ( _gameStarted ) + { + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + player = multiplayerManager.getPlayer( i ); + + if ( !player ) + continue; + + place = getPlace( player, &tied ); + + if ( ( place != _playerGameData[ i ]._lastPlace ) || ( tied != _playerGameData[ i ]._lastTied ) ) + { + if ( _playerGameData[ i ]._lastPlace != -1 ) + { + if ( ( place == 1 ) && ( !tied ) ) + { + // Now in first place + multiplayerManager.playerSound( _playerGameData[ i ]._entnum, "localization/sound/dialog/dm/comp_1stplace.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + } + else if ( ( place == 1 ) && ( tied ) ) + { + // Now tied for the lead + multiplayerManager.playerSound( _playerGameData[ i ]._entnum, "localization/sound/dialog/dm/comp_tiedlead.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + } + else if ( ( _playerGameData[ i ]._lastPlace == 1 ) && ( ( place != 1 ) || ( tied ) ) ) + { + // No longer in the lead + multiplayerManager.playerSound( _playerGameData[ i ]._entnum, "localization/sound/dialog/dm/comp_nolead.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + } + } + + _playerGameData[ i ]._lastPlace = place; + _playerGameData[ i ]._lastTied = tied; + } + } + } +} diff --git a/dlls/game/mp_modeDm.hpp b/dlls/game/mp_modeDm.hpp new file mode 100644 index 0000000..21e2545 --- /dev/null +++ b/dlls/game/mp_modeDm.hpp @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/multiplayerArena.h $ +// $Revision:: 38 $ +// $Author:: Steven $ +// $Date:: 7/23/02 3:55p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// Description: +// + +#ifndef __MP_MODEDM_HPP__ +#define __MP_MODEDM_HPP__ + +#include "mp_modeBase.hpp" + +//----------------------------------------------------------------------- +// ModeDeathmatch -- Implements plain-old deathmatch (PODM). +//----------------------------------------------------------------------- +//CLASS_ARENA( ModeDeathmatch ); +class ModeDeathmatch : public MultiplayerModeBase + { + public: + CLASS_PROTOTYPE( ModeDeathmatch ); + ModeDeathmatch(); + virtual ~ModeDeathmatch(); + + /* virtual */ void AddPlayer( Player *player ); + + /* virtual */ void respawnPlayer( Player *player ); + + /* virtual */ void init( int maxPlayers ); + + /* virtual */ bool checkGameType( const char *rule ); + + /* virtual */ void update( float frameTime ); + + /* virtual */ bool checkRule( const char *rule, bool defaultValue, Player *player = NULL ); + + private: + + int getHighestPoints( int entnum ); + }; + +#endif // __MP_MODEDM_HPP__ + diff --git a/dlls/game/mp_modeTeamBase.cpp b/dlls/game/mp_modeTeamBase.cpp new file mode 100644 index 0000000..3ae51fd --- /dev/null +++ b/dlls/game/mp_modeTeamBase.cpp @@ -0,0 +1,1164 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/mp_modeTeamBase.cpp $ +// $Revision:: 59 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// Description: +// + +#include "_pch_cpp.h" + +#include "mp_manager.hpp" +#include "mp_modeBase.hpp" +#include "mp_modeTeamBase.hpp" + + +CLASS_DECLARATION( MultiplayerModeBase, ModeTeamBase, NULL ) +{ + { NULL, NULL } +}; + + +//----------------------------------------------------------------- +// T E A M A R E N A +//----------------------------------------------------------------- + +//================================================================ +// Name: ModeTeamBase +// Class: ModeTeamBase +// +// Description: Constructor +// +// Parameters: const str& -- name of the arena +// +// Returns: None +// +//================================================================ +ModeTeamBase::ModeTeamBase() +{ + _maxTeams = 2; + + _leadTeam = NULL; + + _useTeamSpawnpoints = false; + + _redTeamIconIndex = gi.imageindex( "sysimg/icons/mp/team_red" ); + _blueTeamIconIndex = gi.imageindex( "sysimg/icons/mp/team_blue" ); + + _redTeamHudIconIndex = gi.imageindex( "sysimg/icons/mp/team_red_hud" ); + _blueTeamHudIconIndex = gi.imageindex( "sysimg/icons/mp/team_blue_hud" ); + + _redTeamSpectatorHudIconIndex = gi.imageindex( "sysimg/icons/mp/team_red_spectator" ); + _blueTeamSpectatorHudIconIndex = gi.imageindex( "sysimg/icons/mp/team_blue_spectator" ); +} + + +//================================================================ +// Name: ~ModeTeamBase +// Class: ModeTeamBase +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//================================================================ + +ModeTeamBase::~ModeTeamBase() +{ + _deleteTeams(); +} + +void ModeTeamBase::init( int maxPlayers ) +{ + MultiplayerModeBase::init( maxPlayers ); + + multiplayerManager.cacheMultiplayerFiles( "mp_teamBase" ); +} + +//================================================================ +// Name: isEndOfMatch +// Class: ModeTeamBase +// +// Description: Determines if either team has met the frag limit. +// If so, announces a winner (should the base team +// arena be so bold?). +// +// If there is no frag limit set, no check is made. +// +// Parameters: None +// +// Returns: bool -- true if the match has ended based on fraglimit +// +//================================================================ +bool ModeTeamBase::isEndOfMatch( void ) +{ + if (!getPointLimit()) + { + return false ; + } + + for (int idx=1; idx <= _teamList.NumObjects(); idx++) + { + Team* team = _teamList.ObjectAt(idx); + if (team->getDeaths() > getPointLimit()) + { + multiplayerManager.centerPrintAllClients(va("$$%s$$ $$TeamLoses$$\n", team->getName().c_str() ), CENTERPRINT_IMPORTANCE_NORMAL ); + + if ( team->getName() == "Red" ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_redtlose.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + else + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bltlose.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + + return true ; + } + } + return false ; +} + +//================================================================ +// Name: AddPlayer +// Class: ModeTeamBase +// +// Description: Adds a player to the arena. If force join is on, +// this player will be force to join with the fewest +// players. +// +// Parameters: Player* -- player to add +// +// Returns: None +// +//================================================================ +void ModeTeamBase::AddPlayer( Player *player ) +{ + // Make sure everything is ok + + assert(player); + + if ( !player ) + { + warning("ModeDeathmatch::AddPlayer", "NULL Player\n"); + return ; + } + + // Make sure player hasn't alrady been added + + if ( !needToAddPlayer( player ) ) + return; + + MultiplayerModeBase::AddPlayer(player); + + // Add player to a team or make him a spectator + + if ( playersLastTeam[ player->entnum ].length() ) + { + addPlayerToTeam( player, getTeam( playersLastTeam[ player->entnum ] ) ); + } + else if ( !multiplayerManager.checkFlag( MP_FLAG_NO_AUTO_JOIN_TEAM ) || multiplayerManager.checkFlag( MP_FLAG_AUTO_BALANCE_TEAMS ) || ( player->edict->svflags & SVF_BOT ) ) + { + Team* pickedTeam = 0 ; + Team* team = 0 ; + int minimumPlayers = 0 ; + + // Automatically add this player to one of the teams + + for (int idx = 1; idx <= _teamList.NumObjects(); idx++) + { + team = _teamList.ObjectAt(idx); + + assert(team); + if (!team) + { + continue ; + } + + int players = team->getActivePlayers(); + if ( (players < minimumPlayers) || (!pickedTeam) ) + { + pickedTeam = team ; + minimumPlayers = players ; + } + } + + if (pickedTeam) + { + addPlayerToTeam( player, pickedTeam ); + } + } + else + { + // Not force joining of teams, start as a spectator + + addPlayerToTeam( player, NULL ); + } + + //if ( _playerGameData[ player->entnum ]._currentTeam ) + // addPlayerToTeam( player, _playerGameData[ player->entnum ]._currentTeam ); + + // If the game hasn't started yet just make the player a spectator + + if ( !_gameStarted ) + { + multiplayerManager.makePlayerSpectator( player ); + } +} + +void ModeTeamBase::addPlayerToTeam( Player *player, Team *team ) +{ + Team *oldTeam; + Entity *spawnPoint; + + + MultiplayerModeBase::AddPlayer(player); + + oldTeam = _playerGameData[ player->entnum ]._currentTeam; + + if ( team && ( oldTeam != team ) ) + { + // Inform all of the players that the player has changed teams + + multiplayerManager.HUDPrintAllClients(va("%s $$Joined$$ $$%s$$ $$Team$$.\n", player->client->pers.netname, team->getName().c_str() ) ); + } + + if ( oldTeam ) + { + _playerGameData[ player->entnum ]._currentTeam = NULL; + + player->SurfaceCommand( "all", "-skin1" ); + player->SurfaceCommand( "all", "-skin2" ); + + oldTeam->RemovePlayer(player); + } + + if ( team ) + { + // Since the player is now on a team add him to the game + + if ( _gameStarted ) + multiplayerManager.playerEnterArena( player->entnum, player->health ); + else + multiplayerManager.makePlayerSpectator( player ); + + // Add the player to the team + + team->AddPlayer(player); + + _playerGameData[ player->entnum ]._currentTeam = team; + + updatePlayerSkin( player ); + + if ( team->getName() == "Red" ) + { + if ( multiplayerManager.isPlayerSpectator( player ) ) + multiplayerManager.setTeamHud( player, "mp_teamredspec" ); + else + multiplayerManager.setTeamHud( player, "mp_teamred" ); + } + else + { + if ( multiplayerManager.isPlayerSpectator( player ) ) + multiplayerManager.setTeamHud( player, "mp_teambluespec" ); + else + multiplayerManager.setTeamHud( player, "mp_teamblue" ); + } + + multiplayerManager.playerSpawned( player ); + + playersLastTeam[ player->entnum ] = team->getName(); + } + else + { + // No team selected so make the player a spectator + + multiplayerManager.makePlayerSpectator( player, SPECTATOR_TYPE_FOLLOW, true ); + + /* team = _playerGameData[ player->entnum ]._currentTeam; + + if ( !team ) + multiplayerManager.setTeamHud( player, "mp_teamspec" ); + else if ( team->getName() == "Red" ) + multiplayerManager.setTeamHud( player, "mp_teamredspec" ); + else + multiplayerManager.setTeamHud( player, "mp_teambluespec" ); */ + } + + // Warp player to a spawn point + + spawnPoint = getSpawnPoint( player ); + + if ( spawnPoint ) + { + player->WarpToPoint( spawnPoint ); + } + + if ( team && _gameStarted ) + { + KillBox( player ); + + ActivatePlayer( player ); + } +} + +void ModeTeamBase::changeTeams( Player *player, Team *team ) +{ + RemovePlayer( player ); + addPlayerToTeam( player, team ); +} + +void ModeTeamBase::respawnPlayer( Player *player ) +{ + Entity *spawnPoint; + + if ( !player ) + { + assert(player); + warning("ModeDeathmatch::respawnPlayer", "NULL player\n"); + return; + } + + // Make sure we are allowed to respawn the player + + if ( !multiplayerManager.checkRule( "respawnPlayer", true, player ) ) + { + if ( multiplayerManager.checkRule( "allowSpectator", true ) ) + { + multiplayerManager.makePlayerSpectator( player ); + } + + return; + } + + MultiplayerModeBase::respawnPlayer( player ); + + multiplayerManager.initPlayer( player ); + + spawnPoint = getSpawnPoint( player ); + + if ( spawnPoint ) + { + player->WarpToPoint( spawnPoint ); + } + + KillBox( player ); + + ActivatePlayer( player ); +} + +Entity *ModeTeamBase::getSpawnPoint( Player *player ) +{ + Entity *spawnPoint = NULL; + int i; + int numSpawnPoints = 0; + str spawnpointName; + int randomStartingSpot; + int spawnPointIndex; + + + // Determine what kind of spawn point we want to use + + if ( multiplayerManager.checkRule( "spawnpoints-special", false, player ) ) + { + spawnpointName = multiplayerManager.getSpawnPointType( player ); + + numSpawnPoints = getNumNamedSpawnpoints( spawnpointName ); + } + + if ( !numSpawnPoints && multiplayerManager.checkRule( "spawnpoints-team", _useTeamSpawnpoints ) && _playerGameData[ player->entnum ]._currentTeam ) + { + if ( stricmp( _playerGameData[ player->entnum ]._currentTeam->getName(), "red" ) == 0 ) + spawnpointName = "red"; + else + spawnpointName = "blue"; + + numSpawnPoints = getNumNamedSpawnpoints( spawnpointName ); + } + + if ( !numSpawnPoints ) + { + spawnpointName = ""; + + numSpawnPoints = getNumNamedSpawnpoints( spawnpointName ); + } + + randomStartingSpot = ( (int)( G_Random() * numSpawnPoints ) ); + + for( i = 0 ; i < numSpawnPoints ; i++ ) + { + spawnPointIndex = ( randomStartingSpot + i ) % numSpawnPoints; + + spawnPoint = getNamedSpawnpointbyIndex( spawnpointName, spawnPointIndex ); + + // Make sure we don't telefrag someone on our team + + if ( spawnPoint ) + { + int j; + int num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + Vector min; + Vector max; + bool badSpot; + + min = spawnPoint->origin + player->mins + Vector( 0, 0, 1 ); + max = spawnPoint->origin + player->maxs + Vector( 0, 0, 1 ); + + num = gi.AreaEntities( min, max, touch, MAX_GENTITIES, qfalse ); + + badSpot = false; + + for( j = 0 ; j < num ; j++ ) + { + hit = &g_entities[ touch[ j ] ]; + + if ( !hit->inuse || ( hit->entity == player ) || !hit->entity || ( hit->entity == world ) || ( !hit->entity->edict->solid ) ) + { + continue; + } + + if ( hit->entity->isSubclassOf( Player ) ) + { + badSpot = true; + break; + } + } + + if ( badSpot ) + { + continue; + } + } + + // This is a good spawn point so use it + + return spawnPoint; + } + + // Just return the last spawn point found + + return spawnPoint; +} + +//================================================================ +// Name: AddTeam +// Class: ModeTeamBase +// +// Description: Adds a team to the arena. Checks to ensure that the +// max number of teams has not yet been exceeded. +// +// Parameters: const str& -- new name of team +// +// Returns: Team* -- Created team (NULL if failed to create) +// +//================================================================ +Team* ModeTeamBase::AddTeam( const str& teamName ) +{ + Team* team = _findTeamByName(teamName); + + if (team) + { + warning( "ModeTeamBase::AddTeam", va("Team %s already exists\n", teamName.c_str() ) ); + return NULL ; + } + + team = new Team(teamName); + team->setMaxPlayers(getMaxPlayers() / getMaxTeams()); + _teamList.AddObject( team ); + + return team ; +} + +Team *ModeTeamBase::getTeam( const str & teamName ) +{ + int i; + Team *team; + + for ( i = 1 ; i <= _teamList.NumObjects() ; i++ ) + { + team = _teamList.ObjectAt( i ); + + if ( team->getName() == teamName ) + { + return team; + } + } + + return NULL; +} + + +//================================================================ +// Name: RemoveTeam +// Class: ModeTeamBase +// +// Description: Removes and deletes the team specified by name from +// the arena. Calls _deleteTeam to do actual removal. +// +// Parameters: +// +// Returns: +// +//================================================================ +void ModeTeamBase::RemoveTeam( const str& teamName ) +{ + Team* team = _findTeamByName(teamName); + _deleteTeam(team); +} + + + +//================================================================ +// Name: AddTeamStartingAmmo +// Class: ModeTeamBase +// +// Description: Adds team-specific ammo. This enables a particular +// team to receive a specific amount and/or type of ammo. +// Calls _addTeamStartingAmmo() to actually give it to the team. +// +// Parameters: const str& teamName -- team to receive the ammo +// const str& ammoName -- name of ammo to give +// int amount -- amount of ammo to give +// +// Returns: None +// +//================================================================ +void ModeTeamBase::AddTeamStartingAmmo( const str& teamName, const str& ammoName, int amount ) +{ + Team* team = _findTeamByName(teamName); + SimpleAmmoType ammoType(ammoName, amount); + _addTeamStartingAmmo(team, ammoType); +} + +//================================================================ +// Name: AddTeamStartingWeapon +// Class: ModeTeamBase +// +// Description: Adds team-specific weapon. This enables a particular +// team to receive a specific weapon. Calls _addTeamStartingWeapon() +// to actually give it to the team. +// +// Parameters: const str& teamName -- team to receive the ammo +// const str& weaponName -- name of weapon to give +// +// Returns: None +// +//================================================================ +void ModeTeamBase::AddTeamStartingWeapon( const str& teamName, const str& weaponName ) +{ + Team* team = _findTeamByName(teamName); + _addTeamStartingWeapon(team, weaponName); +} + +//================================================================ +// Name: setTeamStartingHealth +// Class: ModeTeamBase +// +// Description: Sets the starting health for the team. Calls +// _setTeamStartingHealth to do actual work. +// +// Parameters: const str& -- team name +// unsigned int -- amount of health +// +// Returns: None +// +//================================================================ +void ModeTeamBase::SetTeamStartingHealth( const str &teamName, unsigned int startingHealth ) +{ + Team* team = _findTeamByName(teamName); + _setTeamStartingHealth(team, startingHealth); +} + +//---------------------------------------------------------------- +// P R O T E C T E D M E T H O D S +//---------------------------------------------------------------- + + +//================================================================ +// Name: _addTeamStartingAmmo +// Class: ModeTeamBase +// +// Description: Adds the specified ammo to the specified team. +// +// Parameters: Team* -- team to receive the ammo +// SimpleAmmoType -- ammo to receive +// +// Returns: None +// +//================================================================ +void ModeTeamBase::_addTeamStartingAmmo( Team* team, const SimpleAmmoType &ammo ) +{ + // Check for valid team + assert(team); + if (!team) + { + warning("ModeTeamBase::_addTeamStartingAmmo", "NULL team passed\n"); + return ; + } + + // Check for valid ammo type + assert(ammo.type.length()); + if (!ammo.type.length()) + { + warning("ModeTeamBase::_addTeamStartingAmmo", "No ammo type specified\n"); + return ; + } + + // Check for valid amount + assert(ammo.amount > 0); + if (ammo.amount < 0) + { + warning("ModeTeamBase::_addTeamStartingAmmo", "Negative ammo amount specified\n"); + return ; + } + + team->AddStartingAmmo(ammo); +} + + +//================================================================ +// Name: _addTeamStartingWeapon +// Class: ModeTeamBase +// +// Description: Adds the specified weapon to the specified team. +// +// Parameters: Team* -- team to receive the ammo +// const str& -- weapon to receive +// +// Returns: None +// +//================================================================ +void ModeTeamBase::_addTeamStartingWeapon( Team* team, const str& weaponName ) +{ + // Check for valid team + assert(team); + if (!team) + { + warning("ModeTeamBase::_addTeamStartingWeapon", "NULL team passed\n"); + return ; + } + + // Check for valid weaponName + assert(weaponName.length()); + if (!weaponName.length()) + { + warning("ModeTeamBase::_addTeamStartingWeapon", "No weapon specified\n"); + return ; + } + + team->AddStartingWeapon(weaponName); +} + + +//================================================================ +// Name: _setTeamStartingHealth +// Class: ModeTeamBase +// +// Description: Sets the specified team's starting health to the +// specified value. A starting health of 0 means to +// use the arena's default value (typicaly 100). +// +// Parameters: Team* -- team to affect +// unsigned int -- amount of starting health (must be >= 0) +// +// Returns: None +// +//================================================================ +void ModeTeamBase::_setTeamStartingHealth( Team* team, unsigned int startingHealth ) +{ + // Check for valid team + assert(team); + if (!team) + { + warning("ModeTeamBase::_addTeamStartingWeapon", "NULL team passed\n"); + return ; + } + + /* assert(startingHealth >= 0); + if (startingHealth < 0) + { + warning("ModeTeamBase::_setTeamStartingHealth", va("Cannot set health to %d\n", startingHealth)); + return ; + } */ + + team->setStartingHealth(startingHealth); +} + +//================================================================ +// Name: _deleteTeams +// Class: ModeTeamBase +// +// Description: Deletes all the teams currently in the arena. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void ModeTeamBase::_deleteTeams() +{ + for (int idx = 1; idx <= _teamList.NumObjects(); idx++) + { + Team* team = _teamList.ObjectAt(idx); + _deleteTeam(team); + } +} + +//================================================================ +// Name: _deleteTeam +// Class: ModeTeamBase +// +// Description: Removes the specified team from the arena and deletes it. +// Minimal error checking since it is an internal function. +// +// Parameters: Team* -- team to remove and delete +// +// Returns: None +// +//================================================================ +void ModeTeamBase::_deleteTeam( Team* team ) +{ + assert(team); + if (!team) + { + warning("ModeTeamBase::_removeTeam", "NULL Team passed\n"); + return ; + } + + _teamList.RemoveObject(team); + delete team ; +} + + + +//================================================================ +// Name: _beginMatch +// Class: ModeTeamBase +// +// Description: Called when a match begins. Calls BeginMatch on +// every team in the arena. The teams are responsible +// for preparing the participants for battle. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void ModeTeamBase::_beginMatch() +{ + for (int idx = 1; idx <= _teamList.NumObjects(); idx++) + { + Team* team = _teamList.ObjectAt(idx); + team->BeginMatch(); + } +} + + +//================================================================ +// Name: _endMatch +// Class: ModeTeamBase +// +// Description: Called when a match ends. Calls EndMatch on +// every team in the arena. The teams are responsible +// for removing the players from battle-readiness. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void ModeTeamBase::_endMatch() +{ + for (int idx = 1; idx <= _teamList.NumObjects(); idx++) + { + Team* team = _teamList.ObjectAt(idx); + team->EndMatch(); + } +} + +void ModeTeamBase::declareWinner( void ) +{ + int redPoints; + int bluePoints; + + redPoints = getTeamPoints( "Red" ); + bluePoints = getTeamPoints( "Blue" ); + + if ( redPoints > bluePoints ) + { + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_rtwins.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + else if ( bluePoints > redPoints ) + { + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_btwins.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + /* else + { + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_matover.mp3" ); + } */ +} + + +//================================================================ +// Name: _findTeamByName +// Class: ModeTeamBase +// +// Description: Retrieves a pointer to the team having the specified name. +// +// Parameters: const str& -- name of the team to retrieve +// +// Returns: Team* -- pointer to team found (NULL if not found) +// +//================================================================ +Team* ModeTeamBase::_findTeamByName( const str& teamName ) +{ + assert(teamName.length()); + if (!teamName.length()) + { + warning("ModeTeamBase::_findTeamByName", "Team name not specified\n"); + return NULL ; + } + + Team* team = NULL ; + for (int idx = 1; idx <= _teamList.NumObjects(); idx++) + { + team = _teamList.ObjectAt(idx); + if ( stricmp( team->getName().c_str(), teamName.c_str() ) == 0 ) + { + return team ; + } + } + + return NULL ; +} + +int ModeTeamBase::getIcon( Player *player, int statNum, int value ) +{ + /* if ( statNum == STAT_MP_TEAMHUD_ICON ) + { + Team *team; + + team = _playerGameData[ player->entnum ]._currentTeam; + + if ( team ) + { + // Player is on a team so return the appropriate team icon + + if ( multiplayerManager.isPlayerSpectator( player ) ) + { + if ( team->getName() == "Red" ) + return _redTeamSpectatorHudIconIndex; + else if ( team->getName() == "Blue" ) + return _blueTeamSpectatorHudIconIndex; + } + else + { + if ( team->getName() == "Red" ) + return _redTeamHudIconIndex; + else if ( team->getName() == "Blue" ) + return _blueTeamHudIconIndex; + } + } + } + else */ if ( statNum == STAT_MP_TEAM_ICON ) + { + Team *team; + + team = _playerGameData[ player->entnum ]._currentTeam; + + if ( team ) + { + // Player is on a team so return the appropriate team icon + + if ( team->getName() == "Red" ) + return _redTeamIconIndex; + else if ( team->getName() == "Blue" ) + return _blueTeamIconIndex; + } + } + else if ( statNum == STAT_MP_OTHERTEAM_ICON ) + { + Team *team; + + team = _playerGameData[ player->entnum ]._currentTeam; + + if ( team ) + { + // Player is on a team so return the appropriate team icon + + if ( team->getName() == "Red" ) + return _blueTeamIconIndex; + else if ( team->getName() == "Blue" ) + return _redTeamIconIndex; + } + } + + // We didn't return an icon so let the base mode have a chance + + return MultiplayerModeBase::getIcon( player, statNum, value ); +} + +int ModeTeamBase::getInfoIcon( Player *player ) +{ + Team *team; + + // Make sure entity is not invisible + + if ( player->_affectingViewModes & gi.GetViewModeMask( "forcevisible" ) ) + return 0; + + team = _playerGameData[ player->entnum ]._currentTeam; + + if ( team ) + { + if ( team->getName() == "Red" ) + return _redTeamIconIndex; + else if ( team->getName() == "Blue" ) + return _blueTeamIconIndex; + } + + return 0; +} + +bool ModeTeamBase::canJoinTeam( Player *player, const str &teamName ) +{ + Team *team; + + if ( multiplayerManager.checkFlag( MP_FLAG_AUTO_BALANCE_TEAMS ) ) + { + Team *team; + Team *redTeam; + Team *blueTeam; + int redTeamPlayers; + int blueTeamPlayers; + + // Get all of the teams + + team = getTeam( teamName ); + redTeam = getTeam( "Red" ); + blueTeam = getTeam( "Blue" ); + + // Get the number of players + + redTeamPlayers = redTeam->getActivePlayers(); + blueTeamPlayers = blueTeam->getActivePlayers(); + + if ( _playerGameData[ player->entnum ]._currentTeam == redTeam ) + redTeamPlayers -= 1; + else if ( _playerGameData[ player->entnum ]._currentTeam == blueTeam ) + blueTeamPlayers -= 1; + + // Don't allow switch unless it's to the team that has less people + + if ( ( redTeamPlayers > blueTeamPlayers ) && ( team == redTeam ) ) + { + multiplayerManager.centerPrint( player->entnum, "$$CannotJoinAutoBalanceRed$$", CENTERPRINT_IMPORTANCE_HIGH ); + return false; + } + else if ( ( blueTeamPlayers > redTeamPlayers ) && ( team == blueTeam ) ) + { + multiplayerManager.centerPrint( player->entnum, "$$CannotJoinAutoBalanceBlue$$", CENTERPRINT_IMPORTANCE_HIGH ); + return false; + } + } + + if ( !multiplayerManager.checkRule( "respawnPlayer", true, player ) ) + { + return false; + } + + team = _findTeamByName( teamName ); + + if ( _playerGameData[ player->entnum ]._currentTeam == team ) + return false; + else + return true; +} + +void ModeTeamBase::joinTeam( Player *player, const str &teamName ) +{ + Team *team; + + team = _findTeamByName( teamName ); + + changeTeams( player, team ); +} + +float ModeTeamBase::playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ) +{ + // Always take telefrag damage + + if ( meansOfDeath == MOD_TELEFRAG ) + return damage; + + // Player can always hurt himself + + if ( damagedPlayer == attackingPlayer ) + return damage; + + // If on same team and not allowing team damage + + if ( ( _playerGameData[ damagedPlayer->entnum ]._currentTeam == _playerGameData[ attackingPlayer->entnum ]._currentTeam ) && + ( !multiplayerManager.checkFlag( MP_FLAG_FRIENDLY_FIRE ) ) ) + return 0; + else + return damage; +} + +int ModeTeamBase::getTeamPoints( Player *player ) +{ + Team *team; + + // Return the points for this team + + team = _playerGameData[ player->entnum ]._currentTeam; + + if ( team ) + return team->getPoints(); + else + return 0; +} + +int ModeTeamBase::getTeamPoints( const str & teamName ) +{ + int i; + Team *team; + + // Return the points for this team + + for ( i = 1 ; i <= _teamList.NumObjects() ; i++ ) + { + team = _teamList.ObjectAt( i ); + + if ( team->getName() == teamName ) + { + return team->getPoints(); + } + } + + return 0; +} + +void ModeTeamBase::addTeamPoints( const str & teamName, int points ) +{ + int i; + Team *team; + + // Return the points for this team + + for ( i = 1 ; i <= _teamList.NumObjects() ; i++ ) + { + team = _teamList.ObjectAt( i ); + + if ( team->getName() == teamName ) + { + team->addPoints( NULL, points ); + } + } +} + +void ModeTeamBase::teamPointsChanged( Team *team, int oldPoints, int newPoints ) +{ + Team *otherTeam; + int otherTeamPoints; + + if ( team->getName() == "Red" ) + otherTeam = getTeam( "Blue" ); + else + otherTeam = getTeam( "Red" ); + + otherTeamPoints = otherTeam->getPoints(); + + // Play a dialog for this team if necessary + + if ( ( newPoints == otherTeamPoints ) && ( oldPoints != otherTeamPoints ) ) + { + // Now tied for the lead + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_teamstied.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.0f ); + } + else if ( ( newPoints > otherTeamPoints ) && ( oldPoints <= otherTeamPoints ) ) + { + // Now in first place + multiplayerManager.teamSound( team, "localization/sound/dialog/dm/comp_team1stplace.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + + // No longer in the lead + multiplayerManager.teamSound( otherTeam, "localization/sound/dialog/dm/comp_teamnolead.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + } + else if ( ( newPoints < otherTeamPoints ) && ( oldPoints >= otherTeamPoints ) ) + { + // No longer in the lead + multiplayerManager.teamSound( team, "localization/sound/dialog/dm/comp_teamnolead.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + + // Now in first place + multiplayerManager.teamSound( otherTeam, "localization/sound/dialog/dm/comp_team1stplace.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + } +} + +void ModeTeamBase::RemovePlayer( Player *player ) +{ + Team *team; + + team = _playerGameData[ player->entnum ]._currentTeam; + + if ( team ) + { + team->RemovePlayer( player ); + } + + MultiplayerModeBase::RemovePlayer( player ); +} + +int ModeTeamBase::getHighestPoints( void ) +{ + int i; + int highestPoints = -999999999; + Team *team; + + for ( i = 1 ; i <= _teamList.NumObjects() ; i++ ) + { + team = _teamList.ObjectAt( i ); + + if ( team->getPoints() > highestPoints ) + { + highestPoints = team->getPoints(); + } + } + + return highestPoints; +} + +void ModeTeamBase::updatePlayerSkin( Player *player ) +{ + Team *team; + + team = getPlayersTeam( player ); + + player->SurfaceCommand( "all", "-skin1" ); + player->SurfaceCommand( "all", "-skin2" ); + + if ( !team ) + return; + + if ( team->getName() == "Red" ) + { + player->SurfaceCommand( "all", "+skin1" ); + } + else + { + player->SurfaceCommand( "all", "+skin2" ); + } +} + +void ModeTeamBase::playerChangedModel( Player *player ) +{ + updatePlayerSkin( player ); +} + diff --git a/dlls/game/mp_modeTeamBase.hpp b/dlls/game/mp_modeTeamBase.hpp new file mode 100644 index 0000000..8938d0b --- /dev/null +++ b/dlls/game/mp_modeTeamBase.hpp @@ -0,0 +1,119 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/multiplayerArena.h $ +// $Revision:: 38 $ +// $Author:: Steven $ +// $Date:: 7/23/02 3:55p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// Description: +// + + +#ifndef __MP_MODETEAMBASE_HPP__ +#define __MP_MODETEAMBASE_HPP__ + +#include "mp_modeBase.hpp" + +//----------------------------------------------------------------------- +// ModeTeamBase -- Abstract base class providing common team-based +// multiplayer functionality. Uses the Team class to +// compose multiple teams of players. Tracks team +// kills and assigns skins and such. +//----------------------------------------------------------------------- +class ModeTeamBase : public MultiplayerModeBase + { + protected: + Container _teamList; // list of teams + unsigned int _maxTeams ; // maximum number of teams allowed (default is 2) + Team* _leadTeam ; // If set, this team is currently "winning" + bool _useTeamSpawnpoints; + + int _redTeamIconIndex; + int _blueTeamIconIndex; + + int _redTeamHudIconIndex; + int _blueTeamHudIconIndex; + + int _redTeamSpectatorHudIconIndex; + int _blueTeamSpectatorHudIconIndex; + + // Abstract constructor + ModeTeamBase(); + + /* virtual */ void init( int maxPlayers ); + + /* virtual */ void declareWinner( void ); + + /* virtual */ void _endMatch(); // Notifies teams of end of match + /* virtual */ void _beginMatch(); // Notifies teams of start of match + + // Utility functions + Team* _findTeamByName(const str& teamName); + void _deleteTeams(); + void _deleteTeam(Team* team); + void _addTeamStartingAmmo(Team* team, const SimpleAmmoType &ammo); + void _addTeamStartingWeapon(Team* team, const str& weapon); + void _setTeamStartingHealth(Team* team, unsigned int startingHealth); + + void updatePlayerSkin( Player *player ); + + public: + CLASS_PROTOTYPE( ModeTeamBase ); + virtual ~ModeTeamBase(); + + // Queries + /* virtual */ bool isEndOfMatch(); // Checks for team frag limits met + + // Gets + unsigned int getMaxTeams() { return _maxTeams ; } + Team* getLeadTeam() { return _leadTeam ; } + + /* virtual */ int getIcon( Player *player, int statNum, int value ); + /* virtual */ int getInfoIcon( Player *player ); + + // Sets + virtual void SetMaxTeams(unsigned int maxTeams) { _maxTeams = maxTeams ; } + virtual void SetTeamStartingHealth(const str& teamName, unsigned int startingHealth); + + /* virtual */ bool canJoinTeam( Player *player, const str &teamName ); + /* virtual */ void joinTeam( Player *player, const str &teamName ); + + + // Player specific functions + /* virtual */ void AddPlayer( Player *player ); // if force join is set, will stick on team with fewest members + /* virtual */ void RemovePlayer( Player *player ); + + /* virtual */ float playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ); + + virtual void addPlayerToTeam( Player *player, Team *team ); + void changeTeams( Player *player, Team *team ); + + /* virtual */ void respawnPlayer( Player *player ); + /* virtual */ Entity * getSpawnPoint( Player *player ); + + // Team specific functions + virtual void AddTeamStartingWeapon(const str& teamName, const str& startingWeapon); + virtual void AddTeamStartingAmmo(const str& teamName, const str& ammoName, int amount); + virtual Team* AddTeam(const str& name); + Team * getTeam( const str & teamName ); + virtual void RemoveTeam(const str& name); + + int getTeamPoints( Player *player ); + int getTeamPoints( const str & teamName ); + void addTeamPoints( const str & teamName, int points ); + + void teamPointsChanged( Team *team, int oldPoints, int newPoints ); + + /* virtual */ int getHighestPoints( void ); + + /* virtual */ void playerChangedModel( Player *player ); + }; + +#endif // __MP_MODETEAMBASE_HPP__ diff --git a/dlls/game/mp_modeTeamDm.cpp b/dlls/game/mp_modeTeamDm.cpp new file mode 100644 index 0000000..abe49b5 --- /dev/null +++ b/dlls/game/mp_modeTeamDm.cpp @@ -0,0 +1,233 @@ +//------------------------------------------------------------------------------ +// +// $Logfile:: /Code/DLLs/game/mp_modeTeamDm.cpp $ +// $Revision:: 26 $ +// $Author:: Singlis $ +// $Date:: 5/17/03 6:41p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// Description: +// + +#include "_pch_cpp.h" +#include "mp_modeTeamDm.hpp" +#include "mp_manager.hpp" + +CLASS_DECLARATION( ModeTeamBase, ModeTeamDeathmatch, NULL ) +{ + { NULL, NULL } +}; + +ModeTeamDeathmatch::ModeTeamDeathmatch() +{ + _redTeam = AddTeam("Red"); + _blueTeam = AddTeam("Blue"); +} + +ModeTeamDeathmatch::~ModeTeamDeathmatch() +{ +} + +void ModeTeamDeathmatch::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + Team *team; + int points; + bool goodKill; + + if ( attackingPlayer && ( killedPlayer != attackingPlayer ) && + ( _playerGameData[ attackingPlayer->entnum ]._currentTeam != _playerGameData[ killedPlayer->entnum ]._currentTeam ) ) + goodKill = true; + else + goodKill = false; + + handleKill( killedPlayer, attackingPlayer, inflictor, meansOfDeath, goodKill ); + //MultiplayerModeBase::playerKilled( killedPlayer, attackingPlayer, inflictor, meansOfDeath ); + + if ( !attackingPlayer ) + attackingPlayer = killedPlayer; + + team = _playerGameData[ attackingPlayer->entnum ]._currentTeam; + + if ( team ) + { + if ( multiplayerManager.checkRule( "team-pointsForKills", true, attackingPlayer ) ) + { + // Setup the default points for this kill - 1 point to the attacking player's team (if victim's not + // on same team), or lose 1 point if on the same team + + if ( _playerGameData[ killedPlayer->entnum ]._currentTeam != _playerGameData[ attackingPlayer->entnum ]._currentTeam ) + { + points = _defaultPointsPerKill; + + // Get the real points to give (the modifiers can change it) + + points = multiplayerManager.getPointsForKill( killedPlayer, attackingPlayer, inflictor, meansOfDeath, points ); + + // Give points to the team + + team->addPoints( attackingPlayer, points ); + } + } + } +} + +bool ModeTeamDeathmatch::isEndOfMatch( void ) +{ + // TODO: move this to ModeTeamBase ? + + // See if we have a gone over the point limit + + if ( getPointLimit() > 0 ) + { + // Check team points + + if ( _redTeam->getPoints() >= getPointLimit() ) + return true; + else if ( _blueTeam->getPoints() >= getPointLimit() ) + return true; + } + + // See if we have a gone over the time limit + + if ( ( getTimeLimit() > 0.0f ) && ( multiplayerManager.getTime() - _gameStartTime > getTimeLimit() ) ) + return true; + + return false; +} + +int ModeTeamDeathmatch::getTeamPoints( Player *player ) +{ + Team *team; + + // Return the points for this team + + team = _playerGameData[ player->entnum ]._currentTeam; + + if ( team ) + return team->getPoints(); + else + return 0; +} + +void ModeTeamDeathmatch::setupMultiplayerUI( Player *player ) +{ + // Todo: make this a function in MultiplayerModeBase, each subclass just sets a string to the hud name + + gi.SendServerCommand( player->entnum, "stufftext \"ui_removehuds all\"\n" ); + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_console\"\n" ); + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_teamhud\"\n" ); + + if(mp_timelimit->integer) + { + gi.SendServerCommand( player->entnum, "stufftext \"globalwidgetcommand dmTimer enable\"\n"); + } + else + { + gi.SendServerCommand( player->entnum, "stufftext \"globalwidgetcommand dmTimer disable\"\n"); + } +} + +void ModeTeamDeathmatch::init( int maxPlayers ) +{ + ModeTeamBase::init( maxPlayers ); + + readMultiplayerConfig( "global/mp_team.cfg" ); +} + +void ModeTeamDeathmatch::score( const Player *player ) +{ + char string[1400]; + char entry[1024]; + int i; + int tempStringlength; + int count = 0; + int stringlength = 0; + Team *team; + str teamName; + //int teamPoints; + Player *currentPlayer; + int redScore; + int blueScore; + + assert( player ); + if ( !player ) + { + warning( "MultiplayerModeBase::score", "Null Player specified.\n" ); + return; + } + + string[0] = 0; + entry[0] = 0; + + // This for loop builds a string containing all the players scores. + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + currentPlayer = multiplayerManager.getPlayer( i ); + + if ( !currentPlayer ) + continue; + + team = multiplayerManager.getPlayersTeam( currentPlayer ); + + if ( team ) + { + teamName = team->getName(); + //teamPoints = team->getPoints(); + } + else + { + teamName = "spectator"; + //teamPoints = 0; + } + + Com_sprintf( entry, sizeof( entry ), "%i %i %i %i %s %i %i %d %d %d %d %d %d ", + multiplayerManager.getClientNum( _playerGameData[ i ]._entnum ), + _playerGameData[ i ]._points, + _playerGameData[ i ]._numKills, + _playerGameData[ i ]._numDeaths, + //0 /*pl->GetMatchesWon() */, + //0 /*pl->GetMatchesLost()*/, + teamName.c_str(), + //teamPoints, + (int)(multiplayerManager.getTime() - _playerGameData[ i ]._startTime ), + multiplayerManager.getClientPing( _playerGameData[ i ]._entnum ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON1 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON2 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON3 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON4 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON5 ), + multiplayerManager.getScoreIcon( currentPlayer, SCOREICON6 ) ); + + tempStringlength = strlen( entry ); + + // Make sure the string is not too big (take into account other stuff that gets prepended below also) + + if ( stringlength + tempStringlength > 975 ) + break; + + strcpy( string + stringlength, entry ); + + stringlength += tempStringlength; + count++; + } + + redScore = multiplayerManager.getTeamPoints( "Red" ); + blueScore = multiplayerManager.getTeamPoints( "Blue" ); + + gi.SendServerCommand( player->edict-g_entities, "scores 1 %i %d %d %s", count, redScore, blueScore, string ); +} + +bool ModeTeamDeathmatch::checkGameType( const char *gameType ) +{ + if ( stricmp( gameType, "teamdm" ) == 0 ) + return true; + else + return false; +} diff --git a/dlls/game/mp_modeTeamDm.hpp b/dlls/game/mp_modeTeamDm.hpp new file mode 100644 index 0000000..d5d7b57 --- /dev/null +++ b/dlls/game/mp_modeTeamDm.hpp @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/teamArena.h $ +// $Revision:: 20 $ +// $Author:: Steven $ +// $Date:: 7/17/02 4:00p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// Description: +// + +#ifndef __MP_MODETEAMDM_HPP__ +#define __MP_MODETEAMDM_HPP__ + +#include "mp_modeTeamBase.hpp" // for base class MultiplayerModeBase and dependent headers + +class ModeTeamDeathmatch : public ModeTeamBase + { + private: + Team* _redTeam ; + Team* _blueTeam ; + + protected: + + public: + CLASS_PROTOTYPE( ModeTeamDeathmatch ); + ModeTeamDeathmatch(); + ~ModeTeamDeathmatch(); + + /* virtual */ void init( int maxPlayers ); + + void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); + bool isEndOfMatch( void ); + /* virtual */ int getTeamPoints( Player *player ); + + /* virtual */ void setupMultiplayerUI( Player *player ); + + /* virtual */ void score( const Player *player ); + + /* virtual */ bool checkGameType( const char *rule ); + }; + +#endif // __MP_MODETEAMDM_HPP__ diff --git a/dlls/game/mp_modifiers.cpp b/dlls/game/mp_modifiers.cpp new file mode 100644 index 0000000..5cb1589 --- /dev/null +++ b/dlls/game/mp_modifiers.cpp @@ -0,0 +1,4155 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/mp_modifiers.cpp $ +// $Revision:: 172 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// Description: +// + +#include "_pch_cpp.h" +#include "mp_manager.hpp" +#include "mp_modifiers.hpp" +#include "equipment.h" +#include "powerups.h" +#include "weaputils.h" +#include "health.h" +#include "armor.h" + +// Setup constants + +const float ModifierInstantKill::_instantKillDamage = 1000.0f; + +const float ModifierInstantKill::_regenTime = 0.1f; +const int ModifierInstantKill::_regenAmount = 1; + +ModifierInstantKill::ModifierInstantKill() +{ + _lastRegenTime = 0.0f; +} + +void ModifierInstantKill::init( int maxPlayers ) +{ + multiplayerManager.cacheMultiplayerFiles( "mp_instantKill" ); +} + +float ModifierInstantKill::playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ) +{ + float realDamage = 0.0f; + + if ( damage > 0.0f ) + { + switch( meansOfDeath ) + { + case MOD_COMP_RIFLE: + case MOD_IMOD_PRIMARY: + case MOD_IMOD_SECONDARY: + case MOD_DISRUPTOR: + case MOD_SNIPER: + case MOD_MELEE: + realDamage = _instantKillDamage; + break; + default: + realDamage = damage; + break; + } + } + + return realDamage; +} + +bool ModifierInstantKill::canGivePlayerItem( int entnum, const str &itemName ) +{ + if ( strstr( itemName.c_str(), "phaser.tik" ) ) + return false; + else if ( strstr( itemName.c_str(), "batleth.tik" ) ) + return false; + else if ( strstr( itemName.c_str(), "compressionrifle.tik" ) ) + return false; + + return true; +} + +bool ModifierInstantKill::checkRule( const char *rule, bool defaultValue, Player *player ) +{ + if ( stricmp( rule, "dropWeapons" ) == 0 ) + return false; + else + return defaultValue; +} + +void ModifierInstantKill::playerSpawned( Player *player ) +{ + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-sniperrifle.tik" ); + multiplayerManager.usePlayerItem( player->entnum, "FederationSniperRifle" ); + + //multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-compressionrifle.tik" ); + //multiplayerManager.usePlayerItem( player->entnum, "CompressionRifle" ); + + //multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-imod.tik" ); + //multiplayerManager.usePlayerItem( player->entnum, "I-Mod" ); + + player->GiveAmmo( "Fed", 400, false ); +} + +bool ModifierInstantKill::shouldKeepNormalItem( Item *item ) +{ + if ( item->isSubclassOf( Weapon ) ) + return false; + else if ( item->isSubclassOf( Health ) ) + return false; + else if ( item->isSubclassOf( Armor ) ) + return false; + else if ( item->isSubclassOf( AmmoEntity ) ) + return false; + else + return true; +} + +void ModifierInstantKill::update( float frameTime ) +{ + int i; + Player *player; + + if ( _lastRegenTime + _regenTime < multiplayerManager.getTime() ) + { + for ( i = 0 ; i < multiplayerManager.getMaxPlayers() ; i++ ) + { + player = multiplayerManager.getPlayer( i ); + + if ( !player ) + continue; + + player->GiveAmmo( "Fed", _regenAmount, false ); + //player->GiveAmmo( "Plasma", _regenAmount, false ); + } + + _lastRegenTime = multiplayerManager.getTime(); + } +} + +// Setup constants + +const float ModifierDestruction::_defaultObjectHealth = 500.0f; +const int ModifierDestruction::_pointsForDestroyingObject = 100; +const float ModifierDestruction::_objectHealRate = 75.0f; +const float ModifierDestruction::_maxGuardingDist = 1000.0f; + +const float ModifierDestruction::_minDamageForPoints = 100.0f; +const float ModifierDestruction::_minHealingForPoints = 100.0f; + +const int ModifierDestruction::_pointsForDamage = 5; +const int ModifierDestruction::_pointsForHealing = 5; +const int ModifierDestruction::_pointsForDestroying = 25; +const int ModifierDestruction::_pointsForGuarding = 10; + +ModifierDestruction::ModifierDestruction() +{ + _redDestructionObject = NULL; + _blueDestructionObject = NULL; + + _destructionPlayerData = NULL; + + _redLastDamageSoundTime = 0.0f; + _blueLastDamageSoundTime = 0.0f; + + _respawnTime = 0.0f; + + _blueObjectDestroyed = false; + _redObjectDestroyed = false; +} + +ModifierDestruction::~ModifierDestruction() +{ + delete [] _destructionPlayerData; +} + +void ModifierDestruction::init( int maxPlayers ) +{ + _maxPlayers = maxPlayers; + + _destructionPlayerData = new DestructionPlayerData[ _maxPlayers ]; + + multiplayerManager.cacheMultiplayerFiles( "mp_destruction" ); +} + +void ModifierDestruction::addPlayer( Player *player ) +{ + // Put the destruction ui on the player's screen + + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_destruction\"\n" ); + + _destructionPlayerData[ player->entnum ].reset(); +} + +void ModifierDestruction::playerSpawned( Player *player ) +{ + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-tricorder.tik" ); +} + +void ModifierDestruction::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + Team *victimsTeam; + Team *killersTeam; + float distance; + str printString; + bool objectGuarded; + + if ( !attackingPlayer || ( killedPlayer == attackingPlayer ) ) + return; + + victimsTeam = multiplayerManager.getPlayersTeam( killedPlayer ); + killersTeam = multiplayerManager.getPlayersTeam( attackingPlayer ); + + if ( victimsTeam && killersTeam && ( victimsTeam == killersTeam ) ) + return; + + // See if the player was guarding a object (either he or the killed player was close to his object) + + objectGuarded = false; + + distance = findDistanceToTeamsObject( killersTeam->getName(), attackingPlayer->origin ); + + if ( ( distance > 0 ) && ( distance < _maxGuardingDist ) ) + { + objectGuarded = true; + } + else + { + distance = findDistanceToTeamsObject( killersTeam->getName(), killedPlayer->origin ); + + if ( ( distance > 0 ) && ( distance < _maxGuardingDist ) ) + { + objectGuarded = true; + } + } + + if ( objectGuarded ) + { + //multiplayerManager.playerEventNotification( "controlpoint-guarded", "", attackingPlayer ); + + printString = "$$ObjectGuarded$$ "; + printString += attackingPlayer->client->pers.netname; + + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_objectguarded.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.2f ); + + multiplayerManager.centerPrintTeamClients( attackingPlayer, printString, CENTERPRINT_IMPORTANCE_HIGH ); + + // Give points to the player for guarding his teams objects + + multiplayerManager.addPoints( attackingPlayer->entnum, _pointsForGuarding ); + } +} + +float ModifierDestruction::findDistanceToTeamsObject( const str &teamName, const Vector &position ) +{ + Vector diff; + float distance = -1.0f; + MultiplayerItem *destructionObject = NULL; + + + // Figure out which object to use + + if ( teamName == "Red" ) + destructionObject = _redDestructionObject; + else if ( teamName == "Blue" ) + destructionObject = _blueDestructionObject; + + // Get the distance from the object to the specified point + + if ( destructionObject ) + { + diff = position - destructionObject->origin; + distance = diff.length(); + } + + // Return the distance + + return distance; +} + +int ModifierDestruction::getStat( Player *player, int statNum, int value ) +{ + float floatRealValue; + + if ( statNum == STAT_MP_GENERIC1 ) + { + // Return the health of the red team's object + + if ( _redDestructionObject ) + { + floatRealValue = ( _redDestructionObject->health / _redDestructionObject->max_health ) * 100.0f; + + if ( ( floatRealValue < 1.0f ) && ( _redDestructionObject->health > 1.0f ) ) + { + floatRealValue = 1.0f; + } + + return ( (int)floatRealValue ); + + } + } + else if ( statNum == STAT_MP_GENERIC2 ) + { + // Return the health of the blue team's object + + if ( _blueDestructionObject ) + { + floatRealValue = ( _blueDestructionObject->health / _blueDestructionObject->max_health ) * 100.0f; + + if ( ( floatRealValue < 1.0f ) && ( _blueDestructionObject->health > 1.0f ) ) + { + floatRealValue = 1.0f; + } + + return ( (int)floatRealValue ); + + } + } + + return value; +} + +bool ModifierDestruction::shouldKeepItem( MultiplayerItem *item ) +{ + Event *event; + + // See if we care about this item + + if ( strnicmp( item->getName().c_str(), "DestructionObject", sizeof( "DestructionObject" ) - 1 ) == 0 ) + { + // It's a destruction object + + if ( stricmp( item->getName().c_str(), "DestructionObject-red" ) == 0 ) + _redDestructionObject = item; + else if ( stricmp( item->getName().c_str(), "DestructionObject-blue" ) == 0 ) + _blueDestructionObject = item; + + // Change the multiplayer item to suit our needs + + item->setSolidType( SOLID_BBOX ); + item->setContents( CONTENTS_SOLID ); + item->takedamage = DAMAGE_YES; + + event = new Event( EV_Trigger_SetDestructible ); + event->AddInteger( true ); + item->ProcessEvent( event ); + + if ( item->health == 0 ) + { + item->setMaxHealth( _defaultObjectHealth ); + item->setHealth( _defaultObjectHealth ); + } + + if ( stricmp( item->getName().c_str(), "DestructionObject-red" ) == 0 ) + _redObjectLasthealth = item->health; + else if ( stricmp( item->getName().c_str(), "DestructionObject-blue" ) == 0 ) + _blueObjectLasthealth = item->health; + + + // Tell the manager to keep the item + + return true; + } + + return false; +} + +float ModifierDestruction::itemDamaged( MultiplayerItem *item, Player *attackingPlayer, float damage, int meansOfDeath ) +{ + Team *team; + + team = multiplayerManager.getPlayersTeam( attackingPlayer ); + + if ( !team ) + return damage; + + if ( ( ( item == _redDestructionObject ) && ( team->getName() == "Red" ) ) || + ( ( item == _blueDestructionObject ) && ( team->getName() == "Blue" ) ) ) + { + // Can't hurt our own object + + return 0.0f; + } + + if ( ( _respawnTime > 0.0f ) && ( _respawnTime > multiplayerManager.getTime() ) ) + { + return 0.0f; + } + + if ( _blueObjectDestroyed || _redObjectDestroyed ) + return 0.0f; + + _destructionPlayerData[ attackingPlayer->entnum ]._damageDone += damage; + + while( _destructionPlayerData[ attackingPlayer->entnum ]._damageDone > _minDamageForPoints ) + { + _destructionPlayerData[ attackingPlayer->entnum ]._damageDone -= _minDamageForPoints; + + multiplayerManager.addPoints( attackingPlayer->entnum, _pointsForDamage ); + } + + // Play a sound to tell everyone the object was damaged + + if ( ( item == _redDestructionObject ) && ( multiplayerManager.getTime() > _redLastDamageSoundTime + 0.5 ) ) + { + item->Sound( "impact_singularity", CHAN_AUTO, 1.0f, 500 ); + _redLastDamageSoundTime = multiplayerManager.getTime(); + } + else if ( ( item == _blueDestructionObject ) && ( multiplayerManager.getTime() > _blueLastDamageSoundTime + 0.5 ) ) + { + item->Sound( "impact_singularity", CHAN_AUTO, 1.0f, 500 ); + _blueLastDamageSoundTime = multiplayerManager.getTime(); + } + + // Scale the damage done to the singularity inversely to the number of players in the game + + damage /= (float)multiplayerManager.getTotalPlayers( false ) + 2.0f; + + return damage; +} + +void ModifierDestruction::itemDestroyed( Player *player, MultiplayerItem *item ) +{ + Team *team; + + if ( strnicmp( item->getName().c_str(), "DestructionObject", sizeof( "DestructionObject" ) - 1 ) == 0 ) + { + // Snap health to 0 + + item->setHealth( 0.0f ); + + if ( item->animate && item->animate->HasAnim( "idle" ) ) + { + item->animate->RandomAnimate( "idle" ); + } + + if ( stricmp( item->getName().c_str(), "DestructionObject-red" ) == 0 ) + _redObjectLasthealth = item->health; + else if ( stricmp( item->getName().c_str(), "DestructionObject-blue" ) == 0 ) + _blueObjectLasthealth = item->health; + + item->SpawnEffect( "models/fx/fx-explosion-singularity.tik", item->origin, item->angles, 1.0f ); + + // Give the team points + + team = multiplayerManager.getPlayersTeam( player ); + + // Give the player points + + multiplayerManager.addPoints( player->entnum, _pointsForDestroying ); + + if ( ( ( team->getName() == "Red" ) && ( item == _blueDestructionObject ) ) || + ( ( team->getName() == "Blue" ) && ( item == _redDestructionObject ) ) ) + { + team->addPoints( NULL, _pointsForDestroyingObject ); + + if ( team->getName() == "Red" ) + { + multiplayerManager.centerPrintAllClients( "$$BlueObjectDestroyed$$!", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_btsestroy.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + _blueObjectDestroyed = true; + } + else + { + multiplayerManager.centerPrintAllClients( "$$RedObjectDestroyed$$!", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_rtsestroy.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + _redObjectDestroyed = true; + } + + multiplayerManager.playerEventNotification( "destruction", item->getName().c_str(), player ); + + // Respawn all of the players in a few seconds + + _respawnTime = multiplayerManager.getTime() + 3.0f; + } + } +} + +void ModifierDestruction::itemUsed( Entity *entity, MultiplayerItem *item ) +{ + Player *player; + Equipment *equipment; + Team * team; + + + // Can't heal singularity during respawn time + + if ( _blueObjectDestroyed || _redObjectDestroyed ) + return; + + if ( ( _respawnTime > 0.0f ) && ( _respawnTime >= multiplayerManager.getTime() ) ) + { + return; + } + + // We only care about equipment using stuff (tricorders to be exact) + + if ( entity && entity->isSubclassOf( Player ) ) + { + player = (Player *)entity; + + if ( !( player->edict->svflags & SVF_BOT ) ) + { + player->loadUseItem( "tricorder" ); + } + return; + } + + if ( !entity || entity->isSubclassOf( Player ) ) + return; + + if ( !entity->isSubclassOf( Equipment ) ) + return; + + equipment = (Equipment *)entity; + + if ( stricmp( equipment->getTypeName().c_str(), "tricorder" ) != 0 ) + { + return; + } + + // The tricorder must have a player as the owner + + if ( !equipment->GetOwner() || !equipment->GetOwner()->isSubclassOf( Player ) ) + return; + + player = (Player *)equipment->GetOwner(); + team = multiplayerManager.getPlayersTeam( player ); + + if ( !team ) + return; + + // Heal the destruction object if everything is ok + + if ( ( ( item == _redDestructionObject ) && ( team->getName() == "Red" ) ) || + ( ( item == _blueDestructionObject ) && ( team->getName() == "Blue" ) ) ) + { + if ( _destructionPlayerData[ player->entnum ]._lastHealTime != level.time ) + { + _destructionPlayerData[ player->entnum ]._lastHealTime = level.time; + + // Scale the healed damage done to the singularity inversely to the number of players in the game + + item->addHealth( _objectHealRate * level.frametime / ( multiplayerManager.getTotalPlayers( false ) + 2.0f ) ); + + // Give points to player for healing object + + _destructionPlayerData[ player->entnum ]._healthHealed += _objectHealRate * level.frametime; + + while( _destructionPlayerData[ player->entnum ]._healthHealed > _minHealingForPoints ) + { + _destructionPlayerData[ player->entnum ]._healthHealed -= _minHealingForPoints; + + multiplayerManager.addPoints( player->entnum, _pointsForHealing ); + } + } + } +} + +void ModifierDestruction::update( float frameTime ) +{ + float redHealth; + float redMaxHealth; + float blueHealth; + float blueMaxHealth; + int currentStage; + int lastStage; + + + if ( _redDestructionObject ) + { + redHealth = _redDestructionObject->health; + redMaxHealth = _redDestructionObject->max_health; + + updateObjectAnim( _redDestructionObject, redHealth, _redObjectLasthealth, redMaxHealth ); + + currentStage = getStage( redHealth, redMaxHealth ); + lastStage = getStage( _redObjectLasthealth, redMaxHealth ); + + if ( currentStage > lastStage ) + { + if ( currentStage == 4 ) + { + multiplayerManager.centerPrintAllClients( "$$RedObjectCritical$$", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_rtscl.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + else if ( currentStage == 3 ) + { + multiplayerManager.centerPrintAllClients( "$$RedObject25$$", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_rts25.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + else if ( currentStage == 2 ) + { + multiplayerManager.centerPrintAllClients( "$$RedObject50$$", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_rts50.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + } + + _redObjectLasthealth = redHealth; + } + + if ( _blueDestructionObject ) + { + blueHealth = _blueDestructionObject->health; + blueMaxHealth = _blueDestructionObject->max_health; + + updateObjectAnim( _blueDestructionObject, blueHealth, _blueObjectLasthealth, blueMaxHealth ); + + currentStage = getStage( blueHealth, blueMaxHealth ); + lastStage = getStage( _blueObjectLasthealth, blueMaxHealth ); + + if ( currentStage > lastStage ) + { + if ( currentStage == 4 ) + { + multiplayerManager.centerPrintAllClients( "$$BlueObjectCritical$$", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_btscl.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + else if ( currentStage == 3 ) + { + multiplayerManager.centerPrintAllClients( "$$BlueObject25$$", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bts25.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + else if ( currentStage == 2 ) + { + multiplayerManager.centerPrintAllClients( "$$BlueObject50$$", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bts50.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + } + + _blueObjectLasthealth = blueHealth; + } + + // See if we should respawn everyone + + if ( ( _respawnTime > 0.0f ) && ( _respawnTime < multiplayerManager.getTime() ) ) + { + multiplayerManager.respawnAllPlayers(); + _respawnTime = 0.0f; + + // Reset health back up to the max for which ever singularity was destroyed + + if ( _blueObjectDestroyed ) + { + _blueDestructionObject->setHealth( _blueDestructionObject->max_health ); + _blueObjectDestroyed = false; + } + + if ( _redObjectDestroyed ) + { + _redDestructionObject->setHealth( _redDestructionObject->max_health ); + _redObjectDestroyed = false; + } + + } + + // Regenerate some health + + /* if ( _redDestructionObject && ( _redDestructionObject->health < _redDestructionObject->max_health ) ) + { + _redDestructionObject->addHealth( 1 * frameTime ); + } + + if ( _blueDestructionObject && ( _blueDestructionObject->health < _blueDestructionObject->max_health ) ) + { + _blueDestructionObject->addHealth( 1 * frameTime ); + } */ +} + +void ModifierDestruction::updateObjectAnim( MultiplayerItem *destructionObject, float health, float lastHealth, float maxHealth ) +{ + int currentStage; + int lastStage; + str animName; + + + // Make sure everything is ok + + if ( !destructionObject ) + return; + + // Get the current and the last stage + + currentStage = getStage( health, maxHealth ); + lastStage = getStage( lastHealth, maxHealth ); + + // See if we have changed stages + + if ( currentStage != lastStage ) + { + // Figure out the correct animation to play + + if ( currentStage == 1 ) + animName = "idle"; + else if ( currentStage == 2 ) + animName = "stage2"; + else if ( currentStage == 3 ) + animName = "stage3"; + else if ( currentStage == 4 ) + animName = "stage4"; + + // Play the new animation if it exists + + if ( destructionObject->animate && destructionObject->animate->HasAnim( animName ) ) + { + destructionObject->animate->RandomAnimate( animName ); + } + } +} + +int ModifierDestruction::getStage( float health, float maxHealth ) +{ + if ( health < maxHealth * 0.1f ) + return 4; + else if ( health < maxHealth * 0.25f ) + return 3; + else if ( health < maxHealth * 0.5f ) + return 2; + else + return 1; +} + +bool ModifierDestruction::checkRule( const char *rule, bool defaultValue, Player *player ) +{ + // We want team spawnpoints + + if ( stricmp( rule, "spawnpoints-team" ) == 0 ) + return true; + else if ( stricmp( rule, "keepflags" ) == 0 ) + return false; + else + return defaultValue; +} + +bool ModifierDestruction::checkGameType( const char *gameType ) +{ + if ( stricmp( gameType, "destruction" ) == 0 ) + return true; + else + return false; +} + +bool ModifierOneFlag::shouldKeepItem( MultiplayerItem *item ) +{ + // We need to keep the one flag + + if ( ( stricmp( item->getName().c_str(), "ctfflag-one" ) == 0 ) || + ( stricmp( item->getName().c_str(), "ctfflag-baseone" ) == 0 ) ) + { + if ( !multiplayerManager.checkRule ( "keepflags", true ) ) + return false; + + return true; + } + else + { + return false; + } +} + +bool ModifierOneFlag::checkRule( const char *rule, bool defaultValue, Player *player ) +{ + // Check to see if we care about this rule + + if ( strnicmp( rule, "flagscore-", sizeof( "flagscore-" ) - 1 ) == 0 ) + { + // See if this flag touch should generate a flag score + + if ( stricmp( rule, "flagscore-teamflag" ) == 0 ) + return false; + else if ( stricmp( rule, "flagscore-enemyflag" ) == 0 ) + return true; + } + else if ( strnicmp( rule, "flagpickup-", sizeof( "flagpickup-" ) - 1 ) == 0 ) + { + // See if this flag touch should generate a flag pickup + + if ( stricmp( rule, "flagpickup-enemyflag" ) == 0 ) + return false; + else if ( stricmp( rule, "flagpickup-otherflag-ctfflag-one" ) == 0 ) + return true; + } + + return defaultValue; +} + +bool ModifierOneFlag::checkGameType( const char *gameType ) +{ + if ( stricmp( gameType, "oneflag" ) == 0 ) + return true; + else + return false; +} + +void ModifierOneFlag::addPlayer( Player *player ) +{ + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_oneflagstatus\"\n" ); +} + +const int ModifierElimination::_pointsForBeingLastAlive = 5; + +ModifierElimination::ModifierElimination() +{ + _respawning = false; + _playerEliminated = false; + + _playerEliminationData = NULL; + + _eliminatedIconIndex = gi.imageindex( "sysimg/icons/mp/elimination_eliminated" ); + + _needPlayers = true; + + _eliminatedTextIndex = G_FindConfigstringIndex( "$$EliminatedWait$$", CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ) + CS_GENERAL_STRINGS;; + _nextRoundTextIndex = G_FindConfigstringIndex( "$$NextRoundWait$$", CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ) + CS_GENERAL_STRINGS;; +} + +ModifierElimination::~ModifierElimination() +{ + delete [] _playerEliminationData; +} + +void ModifierElimination::init( int maxPlayers ) +{ + _maxPlayers = maxPlayers; + _playerEliminationData = new EliminationPlayerData[ maxPlayers ]; + + multiplayerManager.cacheMultiplayerFiles( "mp_elimination" ); +} + +void ModifierElimination::reset( void ) +{ + int i; + + _playerEliminated = false; + _matchOver = false; + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + _playerEliminationData[ i ].reset(); + } +} + +bool ModifierElimination::checkRule( const char *rule, bool defaultValue, Player *player ) +{ + // Only let players respawn between matches + + if ( ( stricmp( rule, "respawnPlayer" ) == 0 ) ) + { + if ( !player || _playerEliminationData[ player->entnum ]._eliminated ) + return false; + } + else if ( ( stricmp( rule, "spawnPlayer" ) == 0 ) || ( stricmp( rule, "changeTeams" ) == 0 ) ) + { + // Don't spawn him if he has been eliminated + + if ( player && _playerEliminationData[ player->entnum ]._eliminated ) + return false; + + // Ok if in first 30 seconds of match + + if ( _matchStartTime + 5 > multiplayerManager.getTime() ) + return defaultValue; + + // See if we still need players + + if ( _needPlayers ) + { + return defaultValue; + } + else + { + return false; + } + } + + return defaultValue; +} + +void ModifierElimination::addPlayer( Player *player ) +{ + _playerEliminationData[ player->entnum ].reset(); + + if ( multiplayerManager.checkGameType( "dm" ) ) + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_elimination\"\n" ); + else + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_eliminationteam\"\n" ); + + if ( multiplayerManager.isPlayerSpectator( player ) && !multiplayerManager.isPlayerSpectatorByChoice( player ) ) + { + _playerEliminationData[ player->entnum ]._eliminated = true; + } +} + +void ModifierElimination::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + Team *team; + + if ( !killedPlayer ) + return; + + _playerEliminated = true; + + _playerEliminationData[ killedPlayer->entnum ]._eliminated = true; + + team = multiplayerManager.getPlayersTeam( killedPlayer ); + + if ( !team || ( numPlayersAliveOnTeam( team->getName() ) > 0 ) ) + { + multiplayerManager.HUDPrintAllClients( va( "%s $$Eliminated$$\n", killedPlayer->client->pers.netname ) ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_pyelim.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.0f ); + } +} + +int ModifierElimination::getScoreIcon( Player *player, int index, int value ) +{ + if ( index == SCOREICON3 ) + { + if ( _playerEliminationData[ player->entnum ]._eliminated ) + return _eliminatedIconIndex; + else + return 0; + } + + return value; +} + +int ModifierElimination::getStat( Player *player, int statNum, int value ) +{ + int numAlive = 0; + Team *team; + Player *currentPlayer; + gentity_t *edict; + int i; + + if ( statNum == STAT_MP_GENERIC5 ) + { + if ( multiplayerManager.checkGameType( "dm" ) ) + { + for( i = 0 ; i < game.maxclients ; i++ ) + { + edict = &g_entities[ i ]; + + // Make sure this edict is a valid player + + if ( !edict->inuse || !edict->entity || !edict->entity->isSubclassOf( Player ) ) + continue; + + currentPlayer = ( Player * )edict->entity; + + if ( !_playerEliminationData[ currentPlayer->entnum ]._eliminated && !multiplayerManager.isPlayerSpectator( currentPlayer ) ) + numAlive++; + } + + return numAlive; + } + else + { + for( i = 0 ; i < game.maxclients ; i++ ) + { + edict = &g_entities[ i ]; + + // Make sure this edict is a valid player + + if ( !edict->inuse || !edict->entity || !edict->entity->isSubclassOf( Player ) ) + continue; + + currentPlayer = ( Player * )edict->entity; + + // Get the player's team + + team = multiplayerManager.getPlayersTeam( currentPlayer ); + + if ( team && team->getName() == "Red" && !_playerEliminationData[ currentPlayer->entnum ]._eliminated && + !multiplayerManager.isPlayerSpectator( currentPlayer ) ) + { + numAlive++; + } + } + + return numAlive; + } + } + else if ( statNum == STAT_MP_GENERIC6 ) + { + if ( !multiplayerManager.checkGameType( "dm" ) ) + { + for( i = 0 ; i < game.maxclients ; i++ ) + { + edict = &g_entities[ i ]; + + // Make sure this edict is a valid player + + if ( !edict->inuse || !edict->entity || !edict->entity->isSubclassOf( Player ) ) + continue; + + currentPlayer = ( Player * )edict->entity; + + // Get the player's team + + team = multiplayerManager.getPlayersTeam( currentPlayer ); + + if ( team && team->getName() == "Blue" && !_playerEliminationData[ currentPlayer->entnum ]._eliminated && + !multiplayerManager.isPlayerSpectator( currentPlayer ) ) + { + numAlive++; + } + } + + return numAlive; + } + } + else if ( statNum == STAT_MP_STATE ) + { + if ( _playerEliminationData[ player->entnum ]._eliminated ) + return _eliminatedTextIndex; + + if ( multiplayerManager.isPlayerSpectator( player ) && multiplayerManager.isFightingAllowed() ) + return _nextRoundTextIndex; + } + + return value; +} + +void ModifierElimination::matchStarting( void ) +{ + reset(); + + _matchStartTime = multiplayerManager.getTime(); + _needPlayers = true; +} + +int ModifierElimination::numPlayersAliveOnTeam( const str &teamName ) +{ + int i; + Player *player; + gentity_t *edict; + int numPlayers; + Team *team; + + + numPlayers = 0; + + for( i = 0 ; i < game.maxclients ; i++ ) + { + edict = &g_entities[ i ]; + + // Make sure this edict is a valid player + + if ( !edict->inuse || !edict->entity || edict->entity->isSubclassOf( Player ) ) + continue; + + player = ( Player * )edict->entity; + + // Get the player's team + + team = multiplayerManager.getPlayersTeam( player ); + + if ( team && ( team->getName() == teamName ) && !_playerEliminationData[ player->entnum ]._eliminated ) + { + numPlayers++; + } + } + + return numPlayers; +} + +void ModifierElimination::update( float frameTime ) +{ + int numAlive = 0; + int numRedTeamAlive = 0; + int numBlueTeamAlive = 0; + Team *team; + bool redTeam = false; + bool blueTeam = false; + bool usingTeams = false; + bool respawnEveryone; + Player *playerAlive = NULL; + Player *player; + Entity *entity; + gentity_t *edict; + int i; + + + if ( _matchOver ) + return; + + // Gather number of people alive and dead + + for( i = 0 ; i < game.maxclients ; i++ ) + { + edict = &g_entities[ i ]; + + // Make sure this edict is a valid player + + if ( !edict->inuse || !edict->entity ) + continue; + + entity = edict->entity; + + if ( !entity->isSubclassOf( Player ) ) + continue; + + player = ( Player * )entity; + + // Get the player's team + + team = multiplayerManager.getPlayersTeam( player ); + + redTeam = false; + blueTeam = false; + + if ( team ) + { + if ( stricmp( team->getName().c_str(), "red" ) == 0 ) + { + redTeam = true; + usingTeams = true; + } + else if ( stricmp( team->getName().c_str(), "blue" ) == 0 ) + { + blueTeam = true; + usingTeams = true; + } + } + + // Calculate number of people alive on each team + + if ( !_playerEliminationData[ player->entnum ]._eliminated && !multiplayerManager.isPlayerSpectator( player ) ) + { + numAlive++; + + playerAlive = player; + + if ( redTeam ) + numRedTeamAlive++; + else if ( blueTeam ) + numBlueTeamAlive++; + } + } + + // See if we need players + + if ( usingTeams ) + { + if ( numRedTeamAlive == 0 || numBlueTeamAlive == 0 ) + _needPlayers = true; + else + _needPlayers = false; + + } + else + { + if ( numAlive < 2 ) + _needPlayers = true; + else + _needPlayers = false; + } + + if ( !_playerEliminated ) + return; + + // See if the round is over + + respawnEveryone = false; + + if ( usingTeams ) + { + // Determine if only one team is alive + + if ( ( numRedTeamAlive > 0 ) && ( numBlueTeamAlive == 0 ) ) + { + multiplayerManager.centerPrintAllClients( "$$BlueTeamEliminated$$", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_btelim.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + + if ( multiplayerManager.checkRule ( "endmatch-elimination-blue", true, NULL ) ) + { + respawnEveryone = true; + multiplayerManager.addTeamPoints( "Red", 1 ); + } + else + { + _matchOver = true; + } + } + else if ( ( numBlueTeamAlive > 0 ) && ( numRedTeamAlive == 0 ) ) + { + multiplayerManager.centerPrintAllClients( "$$RedTeamEliminated$$", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_rtelim.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + + if ( multiplayerManager.checkRule ( "endmatch-elimination-red", true, NULL ) ) + { + respawnEveryone = true; + multiplayerManager.addTeamPoints( "Blue", 1 ); + } + else + { + _matchOver = true; + } + } + else if ( ( numBlueTeamAlive == 0 ) && ( numRedTeamAlive == 0 ) ) + { + multiplayerManager.centerPrintAllClients( "$$NoOneWinsRound$$!", CENTERPRINT_IMPORTANCE_NORMAL ); + + if ( multiplayerManager.checkRule ( "endmatch-elimination-both", true, NULL ) ) + { + respawnEveryone = true; + } + else + { + _matchOver = true; + } + } + } + else + { + // Determine if one person won the match + + if ( numAlive == 1 ) + { + str stringToPrint; + + stringToPrint = playerAlive->client->pers.netname; + stringToPrint += " $$WinsRound$$!"; + + multiplayerManager.centerPrintAllClients( stringToPrint.c_str(), CENTERPRINT_IMPORTANCE_NORMAL ); + respawnEveryone = true; + + multiplayerManager.addPoints( playerAlive->entnum, _pointsForBeingLastAlive ); + } + else if ( numAlive == 0 ) + { + multiplayerManager.centerPrintAllClients( "$$NoOneWinsRound$$!", CENTERPRINT_IMPORTANCE_NORMAL ); + respawnEveryone = true; + } + } + + if ( respawnEveryone ) + { + // The match has been won, restart the match + + multiplayerManager.endMatch(); + //multiplayerManager.restartMatch(); + + _matchOver = true; + } +} + +// Setup constants + +const float ModifierDiffusion::_timeNeededToArmBomb = 5.0f; +const float ModifierDiffusion::_timeNeededToDisarmBomb = 5.0f; +const float ModifierDiffusion::_maxGuardingDist = 750.0f; + +const float ModifierDiffusion::_maxArmingPause = 1.0f; +const float ModifierDiffusion::_maxDisarmingPause = 1.0f; + +const float ModifierDiffusion::_maxBombOnGroundTime = 30.0f; + +const int ModifierDiffusion::_pointsForArmingBomb = 25; +const int ModifierDiffusion::_pointsForExplodingBomb = 50; +const int ModifierDiffusion::_pointsForDisarmingBomb = 50; +const int ModifierDiffusion::_pointsForGuardingBase = 5; +const int ModifierDiffusion::_pointsForGuardingTheBomber = 5; +const int ModifierDiffusion::_pointsForGuardingBomb = 5; +const int ModifierDiffusion::_pointsForKillingTheBomber = 10; + +const int ModifierDiffusion::_teamPointsForBombing = 250; + +DiffusionBombPlace::DiffusionBombPlace() +{ + _item = NULL; + reset(); +} + +void DiffusionBombPlace::reset() +{ + _armed = false; + + _totalArmingTime = 0.0f; + _totalDisarmingTime = 0.0f; + + _lastArmingTime = 0.0f; + _lastDisarmingTime = 0.0f; + + _totalArmedTime = 0.0f; +}; + +ModifierDiffusion::ModifierDiffusion() +{ + _bomber = -1; + _bombArmedByPlayer = -1; + + _lastBomber = -1; + _bombDroppedTime = 0.0f; + + _bomb = NULL; + _tempBombItem = NULL; + + _bomberIconIndex = gi.imageindex( "sysimg/icons/mp/diffusion_bomber" ); + + _redBombPlaceArmedIconIndex = gi.imageindex( "sysimg/icons/mp/diffusion_bombArmed-red" ); + _blueBombPlaceArmedIconIndex = gi.imageindex( "sysimg/icons/mp/diffusion_bombArmed-blue" ); + + _bombCarriedByRedTeamIconIndex = gi.imageindex( "sysimg/icons/mp/diffusion_bombTaken-red" ); + _bombCarriedByBlueTeamIconIndex = gi.imageindex( "sysimg/icons/mp/diffusion_bombTaken-blue" ); + + _bombInBaseIconIndex = gi.imageindex( "sysimg/icons/mp/diffusion_bombNormal" ); + _bombOnGroundIconIndex = gi.imageindex( "sysimg/icons/mp/diffusion_bombOnground" ); + + _respawnTime = 0.0f; +} + +ModifierDiffusion::~ModifierDiffusion() +{ + multiplayerManager.resetRespawnTime(); +} + +void ModifierDiffusion::init( int maxPlayers ) +{ + _maxPlayers = maxPlayers; + + _timeNeededForBombToExplode = mp_bombTime->value; + + if ( multiplayerManager.getRespawnTime() < 0.0f ) + { + multiplayerManager.setRespawnTime( 5.0f ); + } + + multiplayerManager.cacheMultiplayerFiles( "mp_diffusion" ); +} + +void ModifierDiffusion::playerSpawned( Player *player ) +{ + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-tricorder.tik" ); + + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-compressionrifle.tik" ); + multiplayerManager.usePlayerItem( player->entnum, "CompressionRifle" ); +} + +void ModifierDiffusion::playerEventNotification( const char *eventName, const char *eventItemName, Player *eventPlayer ) +{ + if ( stricmp( eventName, "use-HoldableItem" ) == 0 ) + { + if ( stricmp( eventItemName, "Transporter" ) == 0 ) + { + dropBomb( eventPlayer ); + } + } +} + +bool ModifierDiffusion::checkRule( const char *rule, bool defaultValue, Player *player ) +{ + // We want team spawnpoints + + if ( stricmp( rule, "spawnpoints-team" ) == 0 ) + { + return true; + } + + return defaultValue; +} + +bool ModifierDiffusion::shouldKeepItem( MultiplayerItem *item ) +{ + if ( strnicmp( item->getName().c_str(), "Diffusion-", strlen( "Diffusion-" ) ) == 0 ) + { + // This is a diffusion item so keep track of it + + if ( stricmp( item->getName().c_str(), "Diffusion-bombplace-red" ) == 0 ) + _redBombPlace._item = item; + else if ( stricmp( item->getName().c_str(), "Diffusion-bombplace-blue" ) == 0 ) + _blueBombPlace._item = item; + else if ( stricmp( item->getName().c_str(), "Diffusion-bomb" ) == 0 ) + _bomb = item; + else + return false; + + return true; + } + + return false; +} + +void ModifierDiffusion::itemTouched( Player *player, MultiplayerItem *item ) +{ + str printString; + + + // Can't pickup bomb during respawn period + + if ( _respawnTime > 0.0f ) + return; + + // Make sure this is a bomb + + if ( ( item != _bomb ) && ( item != _tempBombItem ) ) + return; + + // Make sure this isn't a bot + + if ( player->edict->svflags & SVF_BOT ) + return; + + // Make sure we can't pickup the bomb right after we dropped it + + if ( ( player->entnum == _lastBomber ) && ( _bombDroppedTime + 0.5 > multiplayerManager.getTime() ) ) + return; + + // Give the bomb to the player + + makeBomber( player ); + + // Tell everyone that the bomb has been picked up + + printString = "$$BombTaken$$ "; + printString += player->client->pers.netname; + + printString += " ($$"; + printString += multiplayerManager.getPlayersTeam( player )->getName(); + printString += "$$ $$Team$$)!"; + + multiplayerManager.centerPrintAllClients( printString, CENTERPRINT_IMPORTANCE_HIGH ); + + if ( multiplayerManager.getPlayersTeam( player )->getName() == "Red" ) + { + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bombtakenred.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, player, 1.5f ); + } + else + { + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bombtakenblue.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, player, 1.5f ); + } + + if ( item == _tempBombItem ) + { + // Get rid of the temporary bomb + + _tempBombItem->PostEvent( EV_Remove, 0.0f ); + _tempBombItem = NULL; + } + else + { + // Hide the item picked up + + _bomb->setSolidType( SOLID_NOT ); + _bomb->hideModel(); + } +} + +void ModifierDiffusion::dropBomb( Player *player ) +{ + MultiplayerItem *newBomb; + str printString; + + // Make sure everything is ok + + if ( player != getBomber() ) + return; + + // Create a new bomb to put on the ground + + newBomb = new MultiplayerItem; + + newBomb->setModel( _bomb->model ); + + newBomb->angles = player->angles; + newBomb->setAngles(); + + newBomb->setOrigin( player->origin ); + + newBomb->CancelEventsOfType( EV_ProcessInitCommands ); + newBomb->ProcessInitCommands( newBomb->edict->s.modelindex ); + + newBomb->setName( "Diffusion-bomb-temp" ); + + // Setup the _tempBombItem stuff + + _tempBombItem = newBomb; + _tempBombItemTime = multiplayerManager.getTime(); + + // Change the player back to not being a bomber + + clearBomber(); + _bombDroppedTime = multiplayerManager.getTime(); + + // Tell the team that the bomb was dropped + + printString = "$$BombDropped$$"; + + multiplayerManager.centerPrintAllClients( printString, CENTERPRINT_IMPORTANCE_HIGH ); +} + +void ModifierDiffusion::respawnBomb( bool quiet ) +{ + str printString; + + if ( _tempBombItem ) + { + _tempBombItem->PostEvent( EV_Remove, 0.0f ); + _tempBombItem = NULL; + } + + if ( _bomb ) + { + _bomb->showModel(); + _bomb->setSolidType( SOLID_TRIGGER ); + } + + if ( !quiet ) + { + printString = "$$BombReturnedToBase$$"; + multiplayerManager.centerPrintAllClients( printString, CENTERPRINT_IMPORTANCE_HIGH ); + + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bombbase.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } +} + +void ModifierDiffusion::itemUsed( Entity *entity, MultiplayerItem *item ) +{ + Equipment *equipment; + Sentient *owner; + Player *player; + bool offense; + bool defense; + Team *team; + DiffusionBombPlace *enemyBombPlace; + DiffusionBombPlace *teamBombPlace; + str printString; + + if ( entity && entity->isSubclassOf( Player ) ) + { + player = (Player *)entity; + + if ( !( player->edict->svflags & SVF_BOT ) ) + { + player->loadUseItem( "tricorder" ); + } + + return; + } + + if ( !entity || !entity->isSubclassOf( Equipment ) ) + return; + + // Get the use info + + equipment = (Equipment *)entity; + + owner = equipment->GetOwner(); + + if ( !owner || !owner->isSubclassOf( Player ) ) + return; + + player = (Player *)owner; + + if ( stricmp( equipment->getTypeName().c_str(), "tricorder" ) != 0 ) + { + return; + } + + // Figure out if the player is on offense or defense + + team = multiplayerManager.getPlayersTeam( player ); + + if ( !team ) + return; + + offense = false; + defense = false; + + enemyBombPlace = NULL; + teamBombPlace = NULL; + + if ( team->getName() == "Red" ) + { + if ( item == _redBombPlace._item ) + defense = true; + else if ( item == _blueBombPlace._item ) + offense = true; + + enemyBombPlace = &_blueBombPlace; + teamBombPlace = &_redBombPlace; + } + else if ( team->getName() == "Blue" ) + { + if ( item == _blueBombPlace._item ) + defense = true; + else if ( item == _redBombPlace._item ) + offense = true; + + enemyBombPlace = &_redBombPlace; + teamBombPlace = &_blueBombPlace; + } + + if ( offense ) + { + // Must be the bomber + + if ( player->entnum != _bomber ) + return; + + if ( !enemyBombPlace->_armed && ( enemyBombPlace->_lastArmingTime != level.time ) ) + { + enemyBombPlace->_lastArmingTime = level.time; + + enemyBombPlace->_totalArmingTime += level.frametime; + + if ( enemyBombPlace->_totalArmingTime > _timeNeededToArmBomb ) + { + SpawnArgs args; + int tagNum; + + _bombArmedByPlayer = player->entnum; + + enemyBombPlace->_armed = true; + + enemyBombPlace->_lastDisarmingTime = 0.0f; + enemyBombPlace->_totalDisarmingTime = 0.0f; + enemyBombPlace->_totalArmedTime = 0.0f; + + clearBomber(); + + // Tell everyone the bomb has been armed + + printString = "$$BombArmed$$ "; + printString += player->client->pers.netname; + printString += " ($$"; + printString += multiplayerManager.getPlayersTeam( player )->getName(); + printString += "$$ $$Team$$)!"; + + multiplayerManager.centerPrintAllClients( printString, CENTERPRINT_IMPORTANCE_NORMAL ); + + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bomba.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.0f ); + + // Spawn a bomb in the bomb place + + args.setArg( "model", "models/item/mp_diffusion_bomb.tik" ); + + _attachedBomb = args.Spawn(); + + if ( !_attachedBomb ) + return; + + tagNum = gi.Tag_NumForName( enemyBombPlace->_item->edict->s.modelindex, "tag_bomb" ); + + if ( tagNum >= 0 ) + { + _attachedBomb->attach( enemyBombPlace->_item->entnum, tagNum ); + } + else + { + _attachedBomb->setOrigin( enemyBombPlace->_item->origin ); + } + + _attachedBomb->ProcessPendingEvents(); + + _attachedBomb->setSolidType( SOLID_NOT ); + + _attachedBomb->SetTargetName( "attachedDiffusionBomb" ); + + if ( !_attachedBomb->animate ) + _attachedBomb->animate = new Animate( _attachedBomb ); + + _attachedBomb->animate->RandomAnimate( "idle" ); + } + } + } + else if ( defense ) + { + // Make sure the player's team's bomb place has been armed + + if ( !teamBombPlace->_armed ) + return; + + if ( teamBombPlace->_lastDisarmingTime != level.time ) + { + teamBombPlace->_lastDisarmingTime = level.time; + + teamBombPlace->_totalDisarmingTime += level.frametime; + + if ( teamBombPlace->_totalDisarmingTime > _timeNeededToDisarmBomb ) + { + // Disarmed the bomb + + teamBombPlace->_armed = false; + + teamBombPlace->_item->removeAttachedModelByTargetname( "attachedDiffusionBomb" ); + + makeBomber( player ); + + // Tell everyone the bomb has been armed + + printString = "$$BombDisarmed$$ "; + printString += player->client->pers.netname; + printString += " ($$"; + printString += multiplayerManager.getPlayersTeam( player )->getName(); + printString += "$$ $$Team$$)!"; + + multiplayerManager.centerPrintAllClients( printString, CENTERPRINT_IMPORTANCE_NORMAL ); + + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bombdisarmed.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, player, 1.0f ); + } + } + } +} + +void ModifierDiffusion::playerChangedModel( Player *player ) +{ + if ( player->entnum == _bomber ) + { + attachBomb( player ); + } +} + +void ModifierDiffusion::attachBomb( Player *player ) +{ + Event *attachEvent; + str tagName; + + player->removeAttachedModelByTargetname( "attachedDiffusionBomb" ); + + if ( gi.Tag_NumForName( player->edict->s.modelindex, "Bip01 spine2" ) >= 0 ) + tagName = "Bip01 spine2"; + else if ( gi.Tag_NumForName( player->edict->s.modelindex, "Bip01 spine1" ) >= 0 ) + tagName = "Bip01 spine1"; + else if ( gi.Tag_NumForName( player->edict->s.modelindex, "Bip01 Head" ) >= 0 ) + tagName = "Bip01 Head"; + + attachEvent = new Event( EV_AttachModel ); + + attachEvent->AddString( "models/item/mp_diffusion_bomb.tik" ); + attachEvent->AddString( tagName ); + attachEvent->AddFloat( 1.0f ); + attachEvent->AddString( "attachedDiffusionBomb" ); + attachEvent->AddInteger( 0 ); + attachEvent->AddFloat( -1.0f ); + attachEvent->AddFloat( 0.0f ); + attachEvent->AddFloat( -1.0f ); + attachEvent->AddFloat( -1.0f ); + attachEvent->AddVector( player->getBackpackAttachOffset() ); + attachEvent->AddVector( player->getBackpackAttachAngles() ); + + player->ProcessEvent( attachEvent ); +} + +int ModifierDiffusion::getStat( Player *player, int statNum, int value ) +{ + if ( statNum == STAT_MP_GENERIC1 ) + { + if ( _redBombPlace._armed ) + { + return (int)( 100.0f - ( _redBombPlace._totalDisarmingTime / _timeNeededToDisarmBomb ) * 100.0f ); + } + else + { + return (int)( ( _redBombPlace._totalArmingTime / _timeNeededToArmBomb ) * 100.0f ); + } + } + else if ( statNum == STAT_MP_GENERIC3 ) + { + if ( _blueBombPlace._armed ) + { + return (int)( 100.0f - ( _blueBombPlace._totalDisarmingTime / _timeNeededToDisarmBomb ) * 100.0f ); + } + else + { + return (int)( ( _blueBombPlace._totalArmingTime / _timeNeededToArmBomb ) * 100.0f ); + } + } + else if ( statNum == STAT_MP_GENERIC2 ) + { + + if ( _redBombPlace._armed ) + { + return (int)ceil( _timeNeededForBombToExplode - _redBombPlace._totalArmedTime ); + } + else if ( _blueBombPlace._armed ) + { + return (int)ceil( _timeNeededForBombToExplode - _blueBombPlace._totalArmedTime ); + } + else + { + return 999; + } + } + else if ( statNum == STAT_MP_GENERIC7 ) + { + Player *bomber = getBomber(); + str bomberTeamName; + + if ( bomber ) + { + bomberTeamName = multiplayerManager.getPlayersTeam( bomber )->getName(); + } + + if ( _redBombPlace._armed ) + return _redBombPlaceArmedIconIndex; + else if ( _blueBombPlace._armed ) + return _blueBombPlaceArmedIconIndex; + else if ( bomber && ( bomberTeamName == "Red" ) ) + return _bombCarriedByRedTeamIconIndex; + else if ( bomber && ( bomberTeamName == "Blue" ) ) + return _bombCarriedByBlueTeamIconIndex; + else if ( _bomb->getSolidType() != SOLID_NOT ) + return _bombInBaseIconIndex; + else + return _bombOnGroundIconIndex; + } + + return value; +} + +void ModifierDiffusion::addPlayer( Player *player ) +{ + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_diffusion\"\n" ); +} + +void ModifierDiffusion::removePlayer( Player *player ) +{ + // Put any items back that are necessary + + if ( player->entnum == _bomber ) + { + dropBomb( player ); + } + + if ( _bombArmedByPlayer == player->entnum ) + { + _bombArmedByPlayer = -1; + } +} + +void ModifierDiffusion::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + bool victimWasBomber; + + if ( killedPlayer->entnum == _bomber ) + { + dropBomb( killedPlayer ); + + victimWasBomber = true; + } + else + { + victimWasBomber = false; + } + + // Make sure this was a good kill + + if ( !attackingPlayer || ( attackingPlayer == killedPlayer ) ) + return; + + if ( multiplayerManager.getPlayersTeam( killedPlayer ) && multiplayerManager.getPlayersTeam( attackingPlayer ) && + ( multiplayerManager.getPlayersTeam( killedPlayer ) == multiplayerManager.getPlayersTeam( attackingPlayer ) ) ) + return; + + // Award extra points (if any) + + if ( victimWasBomber ) + { + // Give points to the player that killed the bomber + + multiplayerManager.addPoints( attackingPlayer->entnum, _pointsForKillingTheBomber ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bomberKilled.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.0f ); + } + + // Give more points possibly + + if ( playerGuardedBomber( attackingPlayer, killedPlayer ) ) + { + // Give points to the player that guarded the bomber + + multiplayerManager.addPoints( attackingPlayer->entnum, _pointsForGuardingTheBomber ); + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_bomberguarded.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.2f ); + } + else if ( playerGuardedBase( attackingPlayer, killedPlayer ) ) + { + // Give points to the player that guarded the armed bomb + + multiplayerManager.addPoints( attackingPlayer->entnum, _pointsForGuardingBase ); + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_baseguarded.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.2f ); + } + else if ( playerGuardedBomb( attackingPlayer, killedPlayer ) ) + { + // Give points to the player that guarded the armed bomb + + multiplayerManager.addPoints( attackingPlayer->entnum, _pointsForGuardingBomb ); + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_bombguarded.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.2f ); + } +} + +Player *ModifierDiffusion::getBomber( void ) +{ + if ( _bomber >= 0 ) + return multiplayerManager.getPlayer( _bomber ); + else + return NULL; +} + +bool ModifierDiffusion::playerGuardedBomber( Player *attackingPlayer, Player *killedPlayer ) +{ + Player *bomber; + + // Get the bomber + + bomber = getBomber(); + + if ( !bomber ) + return false; + + // Make sure bomber is on the same team as the attacker + + if ( multiplayerManager.getPlayersTeam( attackingPlayer ) != multiplayerManager.getPlayersTeam( bomber ) ) + return false; + + // See if the attacking or killed player is within the guarding distance of the bomber + + if ( withinGuardDistance( bomber->origin, attackingPlayer->origin ) || + withinGuardDistance( bomber->origin, killedPlayer->origin ) ) + return true; + else + return false; +} + +bool ModifierDiffusion::playerGuardedBase( Player *attackingPlayer, Player *killedPlayer ) +{ + MultiplayerItem *bombPlace; + Team *team; + + team = multiplayerManager.getPlayersTeam( attackingPlayer ); + + if ( !team ) + return false; + + // Get the player's bomb place + + if ( team->getName() == "Red" ) + bombPlace = _redBombPlace._item; + else + bombPlace = _blueBombPlace._item; + + if ( !bombPlace ) + return false; + + // See if the attacking or killed player is within the guarding distance of the bomb place + + if ( withinGuardDistance( bombPlace->origin, attackingPlayer->origin ) || + withinGuardDistance( bombPlace->origin, killedPlayer->origin ) ) + return true; + else + return false; +} + +bool ModifierDiffusion::playerGuardedBomb( Player *attackingPlayer, Player *killedPlayer ) +{ + DiffusionBombPlace *bombPlace; + Team *team; + + team = multiplayerManager.getPlayersTeam( attackingPlayer ); + + if ( !team ) + return false; + + // Get the player's bomb place + + if ( team->getName() == "Red" ) + bombPlace = &_blueBombPlace; + else + bombPlace = &_redBombPlace; + + if ( !bombPlace ) + return false; + + if ( !bombPlace->_armed ) + return false; + + // See if the attacking or killed player is within the guarding distance of the bomb + + if ( withinGuardDistance( bombPlace->_item->origin, attackingPlayer->origin ) || + withinGuardDistance( bombPlace->_item->origin, killedPlayer->origin ) ) + return true; + else + return false; +} + +bool ModifierDiffusion::withinGuardDistance( const Vector &origin1, const Vector &origin2 ) +{ + Vector diff; + float distance; + + diff = origin1 - origin2; + distance = diff.length(); + + if ( distance < _maxGuardingDist ) + return true; + else + return false; +} + + +void ModifierDiffusion::playerCommand( Player *player, const char *command, const char *parm ) +{ + if ( stricmp( command, "dropItem" ) == 0 ) + { + if ( player == getBomber() ) + { + dropBomb( player ); + } + } +} + +void ModifierDiffusion::makeBomber( Player *player ) +{ + _bomber = player->entnum; + _lastBomber = player->entnum; + + multiplayerManager.playerSound( player->entnum, "localization/sound/dialog/dm/comp_bomber.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.5f ); + + // Attach the bomb to the player + + attachBomb( player ); +} + +void ModifierDiffusion::clearBomber( void ) +{ + if ( _bomber >= 0 ) + { + Player *player; + + player = multiplayerManager.getPlayer( _bomber ); + + if ( player ) + { + player->removeAttachedModelByTargetname( "attachedDiffusionBomb" ); + } + } + + _bomber = -1; +} + +void ModifierDiffusion::update( float frameTime ) +{ + float currentTime; + + + currentTime = multiplayerManager.getTime(); + + // Make sure the red bomb place has been armed recently enough + + if ( _redBombPlace._lastArmingTime + _maxArmingPause < currentTime ) + { + _redBombPlace._lastArmingTime = 0.0f; + _redBombPlace._totalArmingTime = 0.0f; + } + + // Make sure the red bomb place has been diffused recently enough + + if ( _redBombPlace._lastDisarmingTime + _maxDisarmingPause < currentTime ) + { + _redBombPlace._lastDisarmingTime = 0.0f; + _redBombPlace._totalDisarmingTime = 0.0f; + } + + // Make sure the blue bomb place has been armed recently enough + + if ( _blueBombPlace._lastArmingTime + _maxArmingPause < currentTime ) + { + _blueBombPlace._lastArmingTime = 0.0f; + _blueBombPlace._totalArmingTime = 0.0f; + } + + // Make sure the blue bomb place has been diffused recently enough + + if ( _blueBombPlace._lastDisarmingTime + _maxDisarmingPause < currentTime ) + { + _blueBombPlace._lastDisarmingTime = 0.0f; + _blueBombPlace._totalDisarmingTime = 0.0f; + } + + // Return the bomb back to the base if it has been sitting on the ground too long + + if ( _tempBombItem && ( _tempBombItemTime + _maxBombOnGroundTime < multiplayerManager.getTime() ) ) + { + respawnBomb(); + } + + // See if the bomb has exploded + + if ( _redBombPlace._armed || _blueBombPlace._armed ) + { + int lastTimeLeft; + int timeLeft; + DiffusionBombPlace *armedBombPlace; + + if ( _redBombPlace._armed ) + armedBombPlace = &_redBombPlace; + else + armedBombPlace = &_blueBombPlace; + + lastTimeLeft = (int )( _timeNeededForBombToExplode - armedBombPlace->_totalArmedTime + 1 ); + + armedBombPlace->_totalArmedTime += frameTime; + + timeLeft = (int)( _timeNeededForBombToExplode - armedBombPlace->_totalArmedTime + 1 ); + + // Play computer dialog for time left (if any) + + if ( ( timeLeft <= 1 ) && ( lastTimeLeft > 1 ) && ( _timeNeededForBombToExplode > 1 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_1.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, NULL, 0.5f ); + else if ( ( timeLeft <= 2 ) && ( lastTimeLeft > 2 ) && ( _timeNeededForBombToExplode > 2 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_2.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, NULL, 0.5f ); + else if ( ( timeLeft <= 3 ) && ( lastTimeLeft > 3 ) && ( _timeNeededForBombToExplode > 3 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_3.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, NULL, 0.5f ); + else if ( ( timeLeft <= 4 ) && ( lastTimeLeft > 4 ) && ( _timeNeededForBombToExplode > 4 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_4.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, NULL, 0.5f ); + else if ( ( timeLeft <= 5 ) && ( lastTimeLeft > 5 ) && ( _timeNeededForBombToExplode > 5 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_5.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, NULL, 0.5f ); + else if ( ( timeLeft <= 7 ) && ( lastTimeLeft > 7 ) && ( _timeNeededForBombToExplode > 7 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bombtime.mp3", CHAN_AUTO, DEFAULT_VOL, + DEFAULT_MIN_DIST, NULL, 0.5f ); + else if ( ( timeLeft <= 15 ) && ( lastTimeLeft > 15 ) && ( _timeNeededForBombToExplode > 15 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bombtime15.mp3" ); + else if ( ( timeLeft <= 30 ) && ( lastTimeLeft > 30 ) && ( _timeNeededForBombToExplode > 30 ) ) + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bombtime30.mp3" ); + + if ( armedBombPlace->_totalArmedTime > _timeNeededForBombToExplode ) + { + str printString; + + // The bomb has exploded + + armedBombPlace->_item->SpawnEffect( "models/fx/fx-explosion-bomb.tik", armedBombPlace->_item->origin, armedBombPlace->_item->angles, 1.0f ); + + if ( _bombArmedByPlayer >= 0 ) + { + // Give points to the player that armed the bomb + + multiplayerManager.addPoints( _bombArmedByPlayer, _pointsForExplodingBomb ); + } + + // Give the bombing team a lot of points and tell everyone what happened + + if ( armedBombPlace == &_redBombPlace ) + { + multiplayerManager.addTeamPoints( "Blue", _teamPointsForBombing ); + + multiplayerManager.centerPrintAllClients( "$$RedBaseDestroyed$$", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bombexplodered.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 2.0f ); + } + else + { + multiplayerManager.addTeamPoints( "Red", _teamPointsForBombing ); + + multiplayerManager.centerPrintAllClients( "$$BlueBaseDestroyed$$", CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_bombexplodeblue.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 2.0f ); + } + + armedBombPlace->_armed = false; + + respawnBomb( true ); + + armedBombPlace->_item->removeAttachedModelByTargetname( "attachedDiffusionBomb" ); + _attachedBomb->PostEvent( EV_Remove, 0.0f ); + _attachedBomb = NULL; + + armedBombPlace->_totalArmedTime = 0.0f; + armedBombPlace->_lastArmingTime = 0.0f; + armedBombPlace->_lastDisarmingTime = 0.0f; + + // Respawn all of the players in a few seconds + + _respawnTime = multiplayerManager.getTime() + 3.0f; + } + } + + // See if we should respawn everyone + + if ( ( _respawnTime > 0.0f ) && ( _respawnTime < multiplayerManager.getTime() ) ) + { + multiplayerManager.respawnAllPlayers(); + _respawnTime = 0.0f; + } +} + +int ModifierDiffusion::getIcon( Player *player, int statNum, int value ) +{ + if ( statNum == STAT_MP_MODE_ICON ) + { + // Return the icon for the player's specialty + + if ( player == getBomber() ) + return _bomberIconIndex; + else + return -1; + } + + return value; +} + +int ModifierDiffusion::getScoreIcon( Player *player, int index, int value ) +{ + if ( index == SCOREICON5 ) + { + if ( player == getBomber() ) + return _bomberIconIndex; + else + return 0; + } + + return value; +} + +void ModifierDiffusion::matchStarted( void ) +{ + if ( _bomb ) + { + _bomb->showModel(); + _bomb->setSolidType( SOLID_TRIGGER ); + } + + if ( _redBombPlace._item ) + { + _redBombPlace._item->showModel(); + _redBombPlace._item->setSolidType( SOLID_BBOX ); + } + + if ( _blueBombPlace._item ) + { + _blueBombPlace._item->showModel(); + _blueBombPlace._item->setSolidType( SOLID_BBOX ); + } +} + +// Setup constants + +const float ModifierSpecialties::_infiltratorMinRespawnTime = 0.0f; +const float ModifierSpecialties::_medicMinRespawnTime = 30.0f; +const float ModifierSpecialties::_technicianMinRespawnTime = 30.0f; +const float ModifierSpecialties::_demolitionistMinRespawnTime = 15.0f; +const float ModifierSpecialties::_heavyweaponsMinRespawnTime = 0.0f; +const float ModifierSpecialties::_sniperMinRespawnTime = 0.0f; + +const float ModifierSpecialties::_startingInfiltratorHealth = 100.0f; +const float ModifierSpecialties::_startingInfiltratorMassModifier = 0.5f; +const float ModifierSpecialties::_startingMedicArmor = 75.0f; +const float ModifierSpecialties::_startingTechnicianArmor = 50.0f; +const float ModifierSpecialties::_startingDemolitionistArmor = 50.0f; +const float ModifierSpecialties::_startingHeavyWeaponsHealth = 200.0f; +const float ModifierSpecialties::_startingHeavyWeaponsArmor = 100.0f; +const float ModifierSpecialties::_startingHeavyWeaponsMassModifier = 2.0f; +const float ModifierSpecialties::_startingSniperArmor = 25.0f; +const float ModifierSpecialties::_startingNormalHealth = 100.0f; + +const float ModifierSpecialties::_medicHealOtherRate = 20.0f; +const float ModifierSpecialties::_medicHealSelfRate = 2.0f; +const float ModifierSpecialties::_infiltratorMoveSpeedModifier = 1.25f; +const float ModifierSpecialties::_heavyWeaponsMoveSpeedModifier = .75f; +const float ModifierSpecialties::_infiltratorJumpSpeedModifier = 1.1f; +const float ModifierSpecialties::_infiltratorAirAccelerationModifier = 1.5f; + +const float ModifierSpecialties::_technicianHoldableItemRegenTime = 20.0f; +const float ModifierSpecialties::_demolitionistHoldableItemRegenTime = 20.0f; + +const bool ModifierSpecialties::_medicCanSeeEnemyHealth = true; + +const float ModifierSpecialties::_amountOfHealingForPoints = 10.0f; +const int ModifierSpecialties::_pointsForHealing = 1; + +const bool ModifierSpecialties::_removeItems = false; + +const SpecialtyType SpecialtyPlayerData::_defaultSpecialty = SPECIALTY_INFILTRATOR; + +SpecialtyPlayerData::SpecialtyPlayerData() +{ + _forced = false; + reset(); +} +void SpecialtyPlayerData::reset( void ) +{ + if ( _forced ) + return; + + _specialty = _defaultSpecialty; + _item = NULL; + _lastUseTime = 0.0f; + _amountOfHealing = 0.0f; + _lastPlayer = -1; +} + +ModifierSpecialties::ModifierSpecialties() +{ + _playerSpecialtyData = NULL; + + _infiltratorIconIndex = gi.imageindex( "sysimg/icons/mp/specialty_infiltrator" ); + _medicIconIndex = gi.imageindex( "sysimg/icons/mp/specialty_medic" ); + _technicianIconIndex = gi.imageindex( "sysimg/icons/mp/specialty_technician" ); + _demolitionistIconIndex = gi.imageindex( "sysimg/icons/mp/specialty_demolitionist" ); + _heavyweaponsIconIndex = gi.imageindex( "sysimg/icons/mp/specialty_heavyweapons" ); + _sniperIconIndex = gi.imageindex( "sysimg/icons/mp/specialty_sniper" ); + + _specialtyItems.FreeObjectList(); +} + +ModifierSpecialties::~ModifierSpecialties() +{ + delete [] _playerSpecialtyData; + _playerSpecialtyData = NULL; + + _specialtyItems.FreeObjectList(); +} + +void ModifierSpecialties::init( int maxPlayers ) +{ + _maxPlayers = maxPlayers; + _playerSpecialtyData = new SpecialtyPlayerData[ maxPlayers ]; + + multiplayerManager.cacheMultiplayerFiles( "mp_specialties" ); +} + +bool ModifierSpecialties::checkRule( const char *rule, bool defaultValue, Player *player ) +{ + // We want team spawn points + + if ( stricmp( rule, "spawnpoints-team" ) == 0 ) + return true; + else if ( stricmp( rule, "dropWeapons" ) == 0 ) + return false; + else if ( stricmp( rule, "spawnpoints-special" ) == 0 ) + { + // We want special spawnpoints in this mode but not for bots + + if ( player->edict->svflags & SVF_BOT ) + return defaultValue; + else + return true; + } + else + return defaultValue; +} + +str ModifierSpecialties::getSpawnPointType( Player *player ) +{ + Team *team; + + team = multiplayerManager.getPlayersTeam( player ); + + if ( team && team->getName() == "Red" ) + return "specialty-red"; + else if ( team && team->getName() == "Blue" ) + return "specialty-blue"; + else + return "specialty"; +} + +void ModifierSpecialties::playerCommand( Player *player, const char *command, const char *parm ) +{ + if ( stricmp( command, "setSpecialty" ) == 0 ) + { + SpecialtyType specialty; + + // Figure out which specialty we want to set + + if ( stricmp( parm, "infiltrator" ) == 0 ) + specialty = SPECIALTY_INFILTRATOR; + else if ( stricmp( parm, "medic" ) == 0 ) + specialty = SPECIALTY_MEDIC; + else if ( stricmp( parm, "technician" ) == 0 ) + specialty = SPECIALTY_TECHNICIAN; + else if ( stricmp( parm, "demolitionist" ) == 0 ) + specialty = SPECIALTY_DEMOLITIONIST; + else if ( ( stricmp( parm, "heavyweapons" ) == 0 ) || ( stricmp( parm, "heavy" ) == 0 ) ) + specialty = SPECIALTY_HEAVY_WEAPONS; + else if ( stricmp( parm, "sniper" ) == 0 ) + specialty = SPECIALTY_SNIPER; + else + specialty = SPECIALTY_NONE; + + // Setup the specialty + + if ( ( specialty == SPECIALTY_NONE ) || ( player->edict->svflags & SVF_BOT ) ) + { + setupSpecialty( player, specialty, true ); + + _playerSpecialtyData[ player->entnum ]._specialty = specialty; + + if ( specialty == SPECIALTY_NONE ) + _playerSpecialtyData[ player->entnum ]._forced = false; + else + _playerSpecialtyData[ player->entnum ]._forced = true; + } + } +} + +bool ModifierSpecialties::shouldKeepItem( MultiplayerItem *item ) +{ + str itemName; + const char *dash1; + const char *dash2; + + itemName = item->getName(); + + if ( strnicmp( itemName.c_str(), "specialty-", strlen( "specialty-" ) ) == 0 ) + { + SpecialtyItem specialtyItem; + + // This is a specialty item so keep track of it + + specialtyItem._item = item; + specialtyItem._needToRespawn = false; + specialtyItem._respawnTime = 0.0f; + specialtyItem._pickedupTime = 0.0f; + + if ( strnicmp( itemName.c_str(), "specialty-infiltrator-", strlen( "specialty-infiltrator-" ) ) == 0 ) + { + specialtyItem._type = SPECIALTY_INFILTRATOR; + specialtyItem._minimumRespawnTime = _infiltratorMinRespawnTime; + } + else if ( strnicmp( itemName.c_str(), "specialty-medic-", strlen( "specialty-medic-" ) ) == 0 ) + { + specialtyItem._type = SPECIALTY_MEDIC; + specialtyItem._minimumRespawnTime = _medicMinRespawnTime; + } + else if ( strnicmp( itemName.c_str(), "specialty-technician-", strlen( "specialty-technician-" ) ) == 0 ) + { + specialtyItem._type = SPECIALTY_TECHNICIAN; + specialtyItem._minimumRespawnTime = _technicianMinRespawnTime; + } + else if ( strnicmp( itemName.c_str(), "specialty-demolitionist-", strlen( "specialty-demolitionist-" ) ) == 0 ) + { + specialtyItem._type = SPECIALTY_DEMOLITIONIST; + specialtyItem._minimumRespawnTime = _demolitionistMinRespawnTime; + } + else if ( strnicmp( itemName.c_str(), "specialty-heavyweapons-", strlen( "specialty-heavyweapons-" ) ) == 0 ) + { + specialtyItem._type = SPECIALTY_HEAVY_WEAPONS; + specialtyItem._minimumRespawnTime = _heavyweaponsMinRespawnTime; + } + else if ( strnicmp( itemName.c_str(), "specialty-sniper-", strlen( "specialty-sniper-" ) ) == 0 ) + { + specialtyItem._type = SPECIALTY_SNIPER; + specialtyItem._minimumRespawnTime = _sniperMinRespawnTime; + } + else + { + specialtyItem._type = SPECIALTY_NONE; + } + + // Get the team name + + dash1 = strstr( itemName.c_str(), "-" ); + + if ( dash1 ) + { + dash2 = strstr( dash1 + 1, "-" ); + + if ( dash2 ) + { + specialtyItem._teamName = dash2 + 1; + } + } + + // Add the item to the list + + _specialtyItems.AddObject( specialtyItem ); + + return true; + } + else + { + return false; + } +} + +bool ModifierSpecialties::shouldKeepNormalItem( Item *item ) +{ + if ( item->isSubclassOf( Weapon ) ) + return false; + else + return true; +} + +SpecialtyItem *ModifierSpecialties::findSpecialtyItem( MultiplayerItem *item ) +{ + int i; + SpecialtyItem *specialtyItem; + + // Go through the list and find the item + + for ( i = 1 ; i <= _specialtyItems.NumObjects() ; i++ ) + { + specialtyItem = &_specialtyItems.ObjectAt( i ); + + if ( specialtyItem->_item == item ) + { + return specialtyItem; + } + } + + return NULL; +} + +void ModifierSpecialties::addPlayer( Player *player ) +{ + putItemBack( player ); + + _playerSpecialtyData[ player->entnum ].reset(); + + if ( _playerSpecialtyData[ player->entnum ]._forced ) + { + setupSpecialty( player, _playerSpecialtyData[ player->entnum ]._specialty, false ); + } + + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_specialties\"\n" ); +} + +void ModifierSpecialties::itemTouched( Player *player, MultiplayerItem *item ) +{ + SpecialtyType specialty; + bool sameTeam = false; + Team *team; + const char *teamName; + SpecialtyItem *specialtyItem; + + // Make sure player doesn't already have a class + + //if ( _playerSpecialtyData[ player->entnum ]._specialty != SPECIALTY_NONE ) + // return; + + // Make sure this isn't a bot + + if ( player->edict->svflags & SVF_BOT ) + return; + + // Make sure this is a specialty item + + specialtyItem = findSpecialtyItem( item ); + + if ( !specialtyItem ) + return; + + specialty = specialtyItem->_type; + + // Only bother if the player isn't already the new specialty + + if ( _playerSpecialtyData[ player->entnum ]._specialty == specialty ) + return; + + // Make sure the item and the player are on the same team + + team = multiplayerManager.getPlayersTeam( player ); + + if ( !team ) + return; + + teamName = team->getName().c_str(); + + if ( stricmp( teamName, specialtyItem->_teamName ) == 0 ) + sameTeam = true; + + if ( !sameTeam ) + return; + + if ( !multiplayerManager.checkRule ( "specialty-pickup", true, player ) ) + return; + + if ( _playerSpecialtyData[ player->entnum ]._specialty != SPECIALTY_NONE ) + { + putItemBack( player ); + } + + // Pickup the item and change the player's specialty + + _playerSpecialtyData[ player->entnum ]._specialty = specialty; + _playerSpecialtyData[ player->entnum ]._item = item; + + specialtyItem->_pickedupTime = multiplayerManager.getTime(); + + setupSpecialty( player, specialty, true ); + + removeItem( item ); +} + +void ModifierSpecialties::setupSpecialty( Player *player, SpecialtyType specialty, bool chosen ) +{ + float armorToGive; + Event *event; + bool hadTricorder; + str specialtyName; + str specialtySoundName; + + armorToGive = 0.0f; + + // Change the player based on the specialty + + + // Take away holdable item + + player->removeHoldableItem(); + + // Take away all of the weapons except the tricorder + + if ( player->FindItemByModelname( "models/weapons/worldmodel-tricorder.tik" ) ) + hadTricorder = true; + else + hadTricorder = false; + + if ( !( player->edict->svflags & SVF_BOT ) ) + { + player->FreeInventory(); + } + + multiplayerManager.resetPlayerStateMachine( player ); + + if ( hadTricorder ) + { + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-tricorder.tik" ); + } + + // Take away any armor + + Event *armorEvent = new Event( EV_Sentient_SetMyArmorAmount ); + armorEvent->AddFloat( 0.0f ); + player->ProcessEvent( armorEvent ); + + _playerSpecialtyData[ player->entnum ]._regenHoldableItemTime = 0.0f; + + switch( specialty ) + { + case SPECIALTY_NONE : + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-phaser.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-batleth.tik" ); + multiplayerManager.usePlayerItem( player->entnum, "Phaser" ); + + player->setMaxHealth( _startingNormalHealth ); + + if ( player->getHealth() > _startingNormalHealth ) + player->setHealth( _startingNormalHealth ); + + break; + case SPECIALTY_INFILTRATOR : + + specialtyName = "$$Infiltrator$$\n"; + specialtySoundName = "localization/sound/dialog/dm/comp_infil.mp3"; + + player->setMaxHealth( _startingInfiltratorHealth ); + + if ( player->getHealth() > _startingInfiltratorHealth ) + player->setHealth( (int)_startingInfiltratorHealth ); + + player->mass *= (int)_startingInfiltratorMassModifier; + + event = new Event( EV_Sentient_AddResistance ); + event->AddString( "falling" ); + event->AddInteger( 100 ); + player->ProcessEvent( event ); + + player->setHoldableItem( HoldableItem::createHoldableItem( "Transporter", "models/item/holdable_transporter.tik", player ) ); + + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-phaser.tik" ); + //multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-batleth.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-compressionrifle.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "weapons/worldmodel-imod.tik" ); + multiplayerManager.usePlayerItem( player->entnum, "CompressionRifle" ); + + player->GiveAmmo( "Plasma", 400, false, 400 ); + + break; + case SPECIALTY_MEDIC : + + specialtyName = "$$Medic$$\n"; + specialtySoundName = "localization/sound/dialog/dm/comp_medic.mp3"; + + player->setMaxHealth( _startingNormalHealth ); + + if ( player->getHealth() > _startingNormalHealth ) + player->setHealth( _startingNormalHealth ); + + armorToGive = _startingMedicArmor; + + //player->setHoldableItem( HoldableItem::createHoldableItem( "Health", "models/item/holdable_medkit.tik", player ) ); + + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-phaser.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-tricorder.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-batleth.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-burstrifle.tik" ); + multiplayerManager.usePlayerItem( player->entnum, "BurstRifle" ); + + player->GiveAmmo( "Plasma", 400, false, 400 ); + + break; + case SPECIALTY_TECHNICIAN : + + specialtyName = "$$Technician$$\n"; + specialtySoundName = "localization/sound/dialog/dm/comp_tech.mp3"; + + player->setMaxHealth( _startingNormalHealth ); + + if ( player->getHealth() > _startingNormalHealth ) + player->setHealth( _startingNormalHealth ); + + armorToGive = _startingTechnicianArmor; + + //event = new Event( EV_Sentient_AddResistance ); + //event->AddString( "drown" ); + //event->AddInteger( 100 ); + //player->ProcessEvent( event ); + + //player->setHoldableItem( HoldableItem::createHoldableItem( "Protection", "models/item/holdable_forcefield.tik", player ) ); + + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-phaser.tik" ); + //multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-batleth.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-attrex-rifle.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-rom-disruptor.tik" ); + multiplayerManager.usePlayerItem( player->entnum, "AttrexianRifle" ); + + _playerSpecialtyData[ player->entnum ]._regenHoldableItemTime = multiplayerManager.getTime() + _technicianHoldableItemRegenTime; + + player->GiveAmmo( "Idryll", 400, false, 400 ); + + break; + case SPECIALTY_DEMOLITIONIST : + + specialtyName = "$$Demolitionist$$\n"; + specialtySoundName = "localization/sound/dialog/dm/comp_demoli.mp3"; + + player->setMaxHealth( _startingNormalHealth ); + + if ( player->getHealth() > _startingNormalHealth ) + player->setHealth( _startingNormalHealth ); + + armorToGive = _startingDemolitionistArmor; + + player->setHoldableItem( HoldableItem::createHoldableItem( "Explosive", "models/item/holdable_explosive.tik", player ) ); + + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-phaser.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-batleth.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-fieldassaultrifle.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-grenadelauncher.tik" ); + multiplayerManager.usePlayerItem( player->entnum, "GrenadeLauncher" ); + + player->GiveAmmo( "Fed", 400, false, 400 ); + + break; + case SPECIALTY_HEAVY_WEAPONS : + + specialtyName = "$$HeavyWeapons$$\n"; + specialtySoundName = "localization/sound/dialog/dm/comp_hw.mp3"; + + player->setMaxHealth( _startingHeavyWeaponsHealth ); + + if ( player->getHealth() > _startingHeavyWeaponsHealth ) + player->setHealth( _startingHeavyWeaponsHealth ); + + armorToGive = (int) _startingHeavyWeaponsArmor; + player->mass *= (int)_startingHeavyWeaponsMassModifier; + + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-phaser.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-batleth.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-tetryon.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-photon.tik" ); + multiplayerManager.usePlayerItem( player->entnum, "PhotonBurst" ); + + player->GiveAmmo( "Fed", 400, false, 400 ); + player->GiveAmmo( "Plasma", 100, false, 400 ); + + break; + case SPECIALTY_SNIPER : + + specialtyName = "$$Sniper$$\n"; + specialtySoundName = "localization/sound/dialog/dm/comp_sniper.mp3"; + + player->setMaxHealth( _startingNormalHealth ); + + if ( player->getHealth() > _startingNormalHealth ) + player->setHealth( _startingNormalHealth ); + + armorToGive = _startingSniperArmor; + + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-phaser.tik" ); + //multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-batleth.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-sniperrifle.tik" ); + multiplayerManager.givePlayerItem( player->entnum, "models/weapons/worldmodel-compressionrifle.tik" ); + multiplayerManager.usePlayerItem( player->entnum, "FederationSniperRifle" ); + + player->GiveAmmo( "Fed", 400, false, 400 ); + + // TODO decoy ? + //player->giveItem( "models/item/decoy.tik", 1, false ); + break; + } + + // Give the player some armor + + if ( armorToGive > 0.0f ) + { + Event *armorEvent; + + armorEvent = new Event( EV_Sentient_GiveArmor ); + armorEvent->AddString( "BasicArmor" ); + armorEvent->AddFloat( armorToGive ); + armorEvent->AddInteger( false ); + + player->ProcessEvent( armorEvent ); + } + + // Tell the player his specialty + + if ( !multiplayerManager.isPlayerSpectator( player ) ) + { + if ( specialtyName.length() > 0 ) + { + // Tell player his new specialty + + multiplayerManager.centerPrint( player->entnum, specialtyName.c_str(), CENTERPRINT_IMPORTANCE_HIGH ); + + // Tell teammates his new specialty + + if ( chosen ) + { + str stringToPrint; + + stringToPrint = player->client->pers.netname; + stringToPrint += " $$BecomesA$$ "; + stringToPrint += specialtyName; + + multiplayerManager.HUDPrintTeamClients( player, stringToPrint.c_str() ); + } + } + + if ( ( specialtySoundName.length() > 0 ) && ( chosen ) ) + { + multiplayerManager.playerSound( player->entnum, specialtySoundName, CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.0f ); + } + } + + attachBackpack( player ); +} + +void ModifierSpecialties::playerChangedModel( Player *player ) +{ + attachBackpack( player ); +} + +void ModifierSpecialties::attachBackpack( Player *player ) +{ + str attachModelName; + str tagName; + + player->removeAttachedModelByTargetname( "attachedSpecialityBackpack" ); + + switch( _playerSpecialtyData[ player->entnum ]._specialty ) + { + case SPECIALTY_INFILTRATOR : + attachModelName = "models/item/mp_specialty_infiltrator.tik"; + break; + case SPECIALTY_MEDIC : + attachModelName = "models/item/mp_specialty_medic.tik"; + break; + case SPECIALTY_TECHNICIAN : + attachModelName = "models/item/mp_specialty_technician.tik"; + break; + case SPECIALTY_DEMOLITIONIST : + attachModelName = "models/item/mp_specialty_demolitionist.tik"; + break; + case SPECIALTY_HEAVY_WEAPONS : + attachModelName = "models/item/mp_specialty_heavyweapons.tik"; + break; + case SPECIALTY_SNIPER : + attachModelName = "models/item/mp_specialty_sniper.tik"; + break; + } + + if ( gi.Tag_NumForName( player->edict->s.modelindex, "Bip01 spine2" ) >= 0 ) + tagName = "Bip01 spine2"; + else if ( gi.Tag_NumForName( player->edict->s.modelindex, "Bip01 spine1" ) >= 0 ) + tagName = "Bip01 spine1"; + else if ( gi.Tag_NumForName( player->edict->s.modelindex, "Bip01 Head" ) >= 0 ) + tagName = "Bip01 Head"; + + if ( attachModelName.length() ) + { + Event *attachEvent; + + attachEvent = new Event( EV_AttachModel ); + attachEvent->AddString( attachModelName.c_str() ); + attachEvent->AddString( tagName ); + attachEvent->AddFloat( 1.0f ); + attachEvent->AddString( "attachedSpecialityBackpack" ); + attachEvent->AddInteger( 0 ); + attachEvent->AddFloat( -1.0f ); + attachEvent->AddFloat( 0.0f ); + attachEvent->AddFloat( -1.0f ); + attachEvent->AddFloat( -1.0f ); + attachEvent->AddVector( player->getBackpackAttachOffset() ); + attachEvent->AddVector( player->getBackpackAttachAngles() ); + + player->ProcessEvent( attachEvent ); + } +} + +void ModifierSpecialties::playerUsed( Player *usedPlayer, Player *usingPlayer, Equipment *equipment ) +{ + // Make sure everything is ok + + if ( !usedPlayer || !usingPlayer || !equipment ) + return; + + // Give health to the player if everything is ok and this is a medic + + if ( ( _playerSpecialtyData[ usingPlayer->entnum ]._specialty == SPECIALTY_MEDIC ) && + ( _playerSpecialtyData[ usingPlayer->entnum ]._lastUseTime < multiplayerManager.getTime() ) && + ( multiplayerManager.getPlayersTeam( usedPlayer ) == multiplayerManager.getPlayersTeam( usingPlayer ) ) && + ( stricmp( equipment->getTypeName().c_str(), "tricorder" ) == 0 ) && + ( usedPlayer != usingPlayer ) ) + { + float healthToAdd; + float originalHealth; + float healthAdded; + + + _playerSpecialtyData[ usingPlayer->entnum ]._lastUseTime = multiplayerManager.getTime(); + + healthToAdd = _medicHealOtherRate * level.frametime; + + originalHealth = usedPlayer->getHealth(); + + usedPlayer->addHealth( healthToAdd, 100.0f ); + + healthAdded = usedPlayer->getHealth() - originalHealth; + + // Give the healing player some points if necessary + + _playerSpecialtyData[ usingPlayer->entnum ]._amountOfHealing += healthAdded; + + while ( _playerSpecialtyData[ usingPlayer->entnum ]._amountOfHealing >= _amountOfHealingForPoints ) + { + _playerSpecialtyData[ usingPlayer->entnum ]._amountOfHealing -= _amountOfHealingForPoints; + multiplayerManager.addPoints( usingPlayer->entnum, _pointsForHealing ); + } + } + /* else if ( ( _playerSpecialtyData[ usingPlayer->entnum ]._specialty == SPECIALTY_TECHNICIAN ) && + ( multiplayerManager.getPlayersTeam( usedPlayer ) == multiplayerManager.getPlayersTeam( usingPlayer ) ) ) + { + if ( _playerSpecialtyData[ usingPlayer->entnum ]._lastUseTime == 0.0f ) + { + Powerup *powerup; + + _playerSpecialtyData[ usingPlayer->entnum ]._lastUseTime = multiplayerManager.getTime(); + + powerup = Powerup::CreatePowerup( "Invisibility", "models/item/powerup_invisibility.tik", usedPlayer ); + + if ( powerup ) + { + powerup->CancelEventsOfType( EV_ProcessInitCommands ); + powerup->ProcessInitCommands( powerup->edict->s.modelindex ); + + usedPlayer->setPowerup( powerup ); + } + } + else + { + usingPlayer->Sound( "snd_noammo" ); + } + } */ +} + +void ModifierSpecialties::update( float frameTime ) +{ + Player *player; + Entity *entity; + gentity_t *edict; + int i; + SpecialtyItem *specialtyItem; + HoldableItem *holdableItem; + + // Update all of the players based on their specialty + + for( i = 0 ; i < game.maxclients ; i++ ) + { + edict = &g_entities[ i ]; + + if ( !edict->inuse || !edict->entity ) + continue; + + entity = edict->entity; + + if ( !entity->isSubclassOf( Player ) ) + continue; + + player = ( Player * )entity; + + if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_MEDIC ) + { + player->addHealth( _medicHealSelfRate * frameTime ); + } + else if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_TECHNICIAN ) + { + Event *newEvent = new Event( EV_Sentient_SetViewMode ); + newEvent->AddString( "forcevisible" ); + newEvent->AddInteger( 0 ); + player->ProcessEvent( newEvent ); + } + + // Respawn holdable items + + holdableItem = player->getHoldableItem(); + + if ( !holdableItem ) + { + if ( _playerSpecialtyData[ player->entnum ]._regenHoldableItemTime > 0.0f ) + { + if ( _playerSpecialtyData[ player->entnum ]._regenHoldableItemTime < multiplayerManager.getTime() ) + { + // Respawn holdable items + + if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_DEMOLITIONIST ) + { + player->setHoldableItem( HoldableItem::createHoldableItem( "Explosive", "models/item/holdable_explosive.tik", player ) ); + } + else if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_TECHNICIAN ) + { + float randomNum; + + randomNum = G_Random( 1 ); + + if ( randomNum < 0.25f ) + player->setHoldableItem( HoldableItem::createHoldableItem( "SpawnPowerup", "models/item/holdable_spawnInvis.tik", player ) ); + else if ( randomNum < 0.50f ) + player->setHoldableItem( HoldableItem::createHoldableItem( "SpawnPowerup", "models/item/holdable_spawnRegen.tik", player ) ); + else if ( randomNum < 0.75f ) + player->setHoldableItem( HoldableItem::createHoldableItem( "SpawnPowerup", "models/item/holdable_spawnSpeed.tik", player ) ); + else + player->setHoldableItem( HoldableItem::createHoldableItem( "SpawnPowerup", "models/item/holdable_spawnStrength.tik", player ) ); + } + + _playerSpecialtyData[ player->entnum ]._regenHoldableItemTime = 0.0f; + + player->Sound( "snd_holdableRespawn" ); + } + } + else + { + // The player has no holdable item and no timer is setup + + if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_DEMOLITIONIST ) + { + _playerSpecialtyData[ player->entnum ]._regenHoldableItemTime = multiplayerManager.getTime() + + _demolitionistHoldableItemRegenTime; + } + else if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_TECHNICIAN ) + { + _playerSpecialtyData[ player->entnum ]._regenHoldableItemTime = multiplayerManager.getTime() + + _technicianHoldableItemRegenTime; + } + } + } + } + + // Respawn any specialty items that need to be + + for ( i = 1 ; i <= _specialtyItems.NumObjects() ; i++ ) + { + specialtyItem = &_specialtyItems.ObjectAt( i ); + + if ( specialtyItem->_needToRespawn && ( specialtyItem->_respawnTime < multiplayerManager.getTime() ) ) + { + specialtyItem->_needToRespawn = false; + specialtyItem->_item->showModel(); + specialtyItem->_item->setSolidType( SOLID_TRIGGER ); + } + } +} + +void ModifierSpecialties::removePlayer( Player *player ) +{ + int i; + + putItemBack( player ); + + // Wipe out any saved data for the player + + for ( i = 0 ; i < _maxPlayers ; i++ ) + { + _playerSpecialtyData[ player->entnum ]._lastPlayer = -1; + } +} + +void ModifierSpecialties::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + putItemBack( killedPlayer ); +} + +void ModifierSpecialties::playerSpawned( Player *player ) +{ + if ( _playerSpecialtyData[ player->entnum ]._forced ) + { + setupSpecialty( player, _playerSpecialtyData[ player->entnum ]._specialty, false ); + } + else + { + putItemBack( player ); + + _playerSpecialtyData[ player->entnum ].reset(); + + setupSpecialty( player, _playerSpecialtyData[ player->entnum ]._specialty, false ); + } +} + +void ModifierSpecialties::applySpeedModifiers( Player *player, int *moveSpeed ) +{ + if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_INFILTRATOR ) + *moveSpeed *= (int) (_infiltratorMoveSpeedModifier); + else if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_HEAVY_WEAPONS ) + *moveSpeed = (int)( *moveSpeed * _heavyWeaponsMoveSpeedModifier ); +} + +void ModifierSpecialties::applyJumpModifiers( Player *player, int *jumpSpeed ) +{ + if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_INFILTRATOR ) + *jumpSpeed = (int)( *jumpSpeed * _infiltratorJumpSpeedModifier ); +} + +void ModifierSpecialties::applyAirAccelerationModifiers( Player *player, int *airAcceleration ) +{ + if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_INFILTRATOR ) + *airAcceleration = (int)( *airAcceleration * _infiltratorAirAccelerationModifier ); +} + +bool ModifierSpecialties::canPickup( Player *player, MultiplayerItemType itemType, const char *item_name ) +{ + // The infiltrator can't pickup the speed powerup + + if ( itemType == MP_ITEM_TYPE_ARMOR ) + { + if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_INFILTRATOR ) + { + // Infiltrator can't pick up armor + + return false; + } + } + else if ( itemType == MP_ITEM_TYPE_POWERUP ) + { + /* if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_INFILTRATOR ) + { + if ( stricmp( item_name, "Speed" ) == 0 ) + { + return false; + } + } + + // No one can pickup the strength powerup except the heavy weapons class + + if ( stricmp( item_name, "Strength" ) == 0 ) + { + if ( _playerSpecialtyData[ player->entnum ]._specialty != SPECIALTY_HEAVY_WEAPONS ) + { + return false; + } + } */ + } + else if ( itemType == MP_ITEM_TYPE_WEAPON ) + { + // Specialities can't pickup any weapons (they have already been given the weapons their allowed to have) + + if ( _playerSpecialtyData[ player->entnum ]._specialty != SPECIALTY_NONE ) + return false; + } + + return true; +} + +void ModifierSpecialties::removeItem( MultiplayerItem *item ) +{ + if ( !_removeItems ) + return; + + if ( item ) + { + item->setSolidType( SOLID_NOT ); + item->hideModel(); + } +} + +void ModifierSpecialties::putItemBack( Player *player ) +{ + MultiplayerItem *item; + SpecialtyItem *specialtyItem; + + if ( _playerSpecialtyData[ player->entnum ]._forced ) + return; + + if ( _removeItems ) + { + // Find the item in question + + item = _playerSpecialtyData[ player->entnum ]._item; + + specialtyItem = findSpecialtyItem( item ); + + // Put the item back + + if ( specialtyItem ) + { + HoldableItem* holdableItem; + bool respawnNow = false; + + holdableItem = player->getHoldableItem(); + + if ( holdableItem ) + { + if ( specialtyItem->_type == SPECIALTY_DEMOLITIONIST ) + { + if ( holdableItem->getName() == "Explosive" ) + respawnNow = true; + } + else if ( specialtyItem->_type == SPECIALTY_MEDIC ) + { + if ( ( holdableItem->getName() == "Health" ) && ( _playerSpecialtyData[ player->entnum ]._lastUseTime == 0.0f ) ) + respawnNow = true; + } + /* else if ( specialtyItem->_type == SPECIALTY_TECHNICIAN ) + { + if ( ( holdableItem->getName() == "Protection" ) && ( _playerSpecialtyData[ player->entnum ]._lastUseTime == 0.0f ) ) + respawnNow = true; + } */ + } + + if ( respawnNow ) + specialtyItem->_respawnTime = specialtyItem->_pickedupTime + 0.0f; + else + specialtyItem->_respawnTime = specialtyItem->_pickedupTime + specialtyItem->_minimumRespawnTime; + + specialtyItem->_needToRespawn = true; + } + } + + _playerSpecialtyData[ player->entnum ].reset(); +} + +int ModifierSpecialties::getStat( Player *player, int statNum, int value ) +{ + Entity *target; + Player *targetPlayer; + int lastPlayer; + float lastPlayerTime; + + if ( statNum == STAT_MP_GENERIC4 ) + { + if ( _playerSpecialtyData[ player->entnum ]._specialty == SPECIALTY_MEDIC ) + { + lastPlayer = _playerSpecialtyData[ player->entnum ]._lastPlayer; + lastPlayerTime = _playerSpecialtyData[ player->entnum ]._lastPlayerTime; + + target = player->GetTargetedEntity(); + + if ( target && target->isSubclassOf( Player ) && + ( ( multiplayerManager.getPlayersTeam( player ) == multiplayerManager.getPlayersTeam( (Player*)target ) ) || + ( _medicCanSeeEnemyHealth ) ) ) + { + _playerSpecialtyData[ player->entnum ]._lastPlayer = target->entnum; + _playerSpecialtyData[ player->entnum ]._lastPlayerTime = multiplayerManager.getTime(); + + return (int)( target->getHealth() + 0.99f ); + } + else if ( ( lastPlayer >= 0 ) && ( lastPlayerTime + 1.0f > multiplayerManager.getTime() ) ) + { + targetPlayer = multiplayerManager.getPlayer( lastPlayer ); + + if ( targetPlayer ) + { + return (int)( targetPlayer->getHealth() + 0.99f ); + } + } + else + { + _playerSpecialtyData[ player->entnum ]._lastPlayer = -1; + } + } + } + + return value; +} + +int ModifierSpecialties::getIcon( Player *player, int statNum, int value ) +{ + if ( statNum == STAT_MP_SPECIALTY_ICON ) + { + // Return the icon for the player's specialty + + switch( _playerSpecialtyData[ player->entnum ]._specialty ) + { + case SPECIALTY_NONE : + return value; + case SPECIALTY_INFILTRATOR : + return _infiltratorIconIndex; + case SPECIALTY_MEDIC : + return _medicIconIndex; + case SPECIALTY_TECHNICIAN : + return _technicianIconIndex; + case SPECIALTY_DEMOLITIONIST : + return _demolitionistIconIndex; + case SPECIALTY_HEAVY_WEAPONS : + return _heavyweaponsIconIndex; + case SPECIALTY_SNIPER : + return _sniperIconIndex; + } + + } + + return value; +} + +int ModifierSpecialties::getScoreIcon( Player *player, int index, int value ) +{ + if ( index == SCOREICON4 ) + { + // Return the icon for the player's specialty + + switch( _playerSpecialtyData[ player->entnum ]._specialty ) + { + case SPECIALTY_NONE : + return value; + case SPECIALTY_INFILTRATOR : + return _infiltratorIconIndex; + case SPECIALTY_MEDIC : + return _medicIconIndex; + case SPECIALTY_TECHNICIAN : + return _technicianIconIndex; + case SPECIALTY_DEMOLITIONIST : + return _demolitionistIconIndex; + case SPECIALTY_HEAVY_WEAPONS : + return _heavyweaponsIconIndex; + case SPECIALTY_SNIPER : + return _sniperIconIndex; + } + } + + return value; +} + +// Setup constants + +const float ModifierControlPoints::_timeNeededToControl = 5.0f; + +const int ModifierControlPoints::_maxNumberAwardsForPlayerCapture = 5; +const int ModifierControlPoints::_pointsForEachAwardForCapture = 5; +const int ModifierControlPoints::_pointsForProtectingControlPoint = 5; +const int ModifierControlPoints::_maxGuardingDist = 750; + +ModifierControlPoints::ModifierControlPoints() +{ + _alphaControlPointRedControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_alpha_red" ); + _alphaControlPointBlueControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_alpha_blue" ); + _alphaControlPointNeutralControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_alpha_neutral" ); + + _betaControlPointRedControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_beta_red" ); + _betaControlPointBlueControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_beta_blue" ); + _betaControlPointNeutralControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_beta_neutral" ); + + _deltaControlPointRedControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_delta_red" ); + _deltaControlPointBlueControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_delta_blue" ); + _deltaControlPointNeutralControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_delta_neutral" ); + + _gammaControlPointRedControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_gamma_red" ); + _gammaControlPointBlueControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_gamma_blue" ); + _gammaControlPointNeutralControlledIndex = gi.imageindex( "sysimg/icons/mp/cp_gamma_neutral" ); +} + +ModifierControlPoints::~ModifierControlPoints() +{ + _controlPoints.FreeObjectList(); +} + +void ModifierControlPoints::init( int maxPlayers ) +{ + multiplayerManager.cacheMultiplayerFiles( "mp_controlPoints" ); +} + +void ModifierControlPoints::addPlayer( Player *player ) +{ + gi.SendServerCommand( player->entnum, "stufftext \"ui_addhud mp_control\"\n" ); +} + +str ModifierControlPoints::getSpawnPointType( Player *player ) +{ + return "control"; +} + +bool ModifierControlPoints::checkRule( const char *rule, bool defaultValue, Player *player ) +{ + if ( stricmp( rule, "spawnpoints-special" ) == 0 ) + return true; + else + return defaultValue; +} + +/* int ModifierControlPoints::getStat( Player *player, int statNum, int value ) +{ + int redControlPoints; + int blueControlPoints; + int i; + ControlPointData *controlPoint; + + if ( statNum == STAT_MP_GENERIC1 ) + { + // Return the number of control points controlled by the red team + + redControlPoints = 0; + + for ( i = 1 ; i <= _controlPoints.NumObjects() ; i++ ) + { + controlPoint = _controlPoints.AddressOfObjectAt( i ); + + if ( controlPoint && controlPoint->_controllingTeam && controlPoint->_controllingTeam->getName() == "Red" ) + { + redControlPoints++; + } + } + + return redControlPoints; + } + else if ( statNum == STAT_MP_GENERIC2 ) + { + // Return the number of control points controlled by the blue team + + blueControlPoints = 0; + + for ( i = 1 ; i <= _controlPoints.NumObjects() ; i++ ) + { + controlPoint = _controlPoints.AddressOfObjectAt( i ); + + if ( controlPoint && controlPoint->_controllingTeam && controlPoint->_controllingTeam->getName() == "Blue" ) + { + blueControlPoints++; + } + } + + return blueControlPoints; + } + + return value; +} */ + +bool ModifierControlPoints::shouldKeepItem( MultiplayerItem *item ) +{ + ControlPointData controlPoint; + + if ( strnicmp( item->getName().c_str(), "controlpoint-", strlen( "controlpoint-" ) ) == 0 ) + //if ( strnicmp( item->getName().c_str(), "controlpoint", strlen( "controlpoint" ) ) == 0 ) + { + // This is a control point, add it to the list + + controlPoint._controlPoint = item; + controlPoint._controllingTeam = NULL; + controlPoint._lastControllingTeam = NULL; + controlPoint._lastTime = 0.0f; + + controlPoint._controlPointType = getControlPointType( item->getName() ); + //controlPoint._controlPointType = (ControlPointType)( _controlPoints.NumObjects() + 1); + + _controlPoints.AddObject( controlPoint ); + + return true; + } + else + return false; +} + +ControlPointType ModifierControlPoints::getControlPointType( const str &name ) +{ + if ( name == "controlpoint-alpha" ) + return CONTROL_POINT_ALPHA; + else if ( name == "controlpoint-beta" ) + return CONTROL_POINT_BETA; + else if ( name == "controlpoint-delta" ) + return CONTROL_POINT_DELTA; + else if ( name == "controlpoint-gamma" ) + return CONTROL_POINT_GAMMA; + else + return CONTROL_POINT_NONE; +} + +void ModifierControlPoints::itemTouched( Player *player, MultiplayerItem *item ) +{ + int i; + ControlPointData *controlPoint; + Team *team; + str animName; + str printString; + + // Find the control point + + for ( i = 1 ; i <= _controlPoints.NumObjects() ; i++ ) + { + controlPoint = _controlPoints.AddressOfObjectAt( i ); + + if ( controlPoint->_controlPoint == item ) + { + // This is the control point that was touched + + team = multiplayerManager.getPlayersTeam( player ); + + // Transfer control if a different team + + if ( team != controlPoint->_controllingTeam ) + { + controlPoint->_controllingTeam = team; + controlPoint->_lastTime = multiplayerManager.getTime(); + // TODO, clear this if the player quits + controlPoint->_lastPlayerToTouch = player->entnum; + controlPoint->_playerPointsAwardedForCapture = 0; + + // Change the control point based on who controls it + + animName = "control_"; + animName += controlPoint->_controllingTeam->getName(); + + controlPoint->_controlPoint->animate->RandomAnimate( animName ); + + multiplayerManager.playerSound( player->entnum, "localization/sound/dialog/dm/comp_ctrltaken.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.2f ); + } + } + } +} + +void ModifierControlPoints::update( float frameTime ) +{ + int i; + ControlPointData *controlPoint; + + // Loop through all control points + + for ( i = 1 ; i <= _controlPoints.NumObjects() ; i++ ) + { + controlPoint = _controlPoints.AddressOfObjectAt( i ); + + // See if this control point is controlled by a team + + if ( controlPoint->_controllingTeam ) + { + if ( multiplayerManager.getTime() > controlPoint->_lastTime + _timeNeededToControl ) + { + // The team has controlled it for long enough + + controlPoint->_controllingTeam->addPoints( NULL, 1 ); + + controlPoint->_lastTime = multiplayerManager.getTime(); + + if ( controlPoint->_playerPointsAwardedForCapture < _maxNumberAwardsForPlayerCapture ) + { + if ( controlPoint->_playerPointsAwardedForCapture == 0 ) + { + if ( controlPoint->_controllingTeam && ( controlPoint->_lastControllingTeam != controlPoint->_controllingTeam ) ) + { + str soundName; + + if ( controlPoint->_controllingTeam->getName() == "Red" ) + { + if ( controlPoint->_controlPointType == CONTROL_POINT_ALPHA ) + soundName = "localization/sound/dialog/dm/comp_ctrlredalpha.mp3"; + else if ( controlPoint->_controlPointType == CONTROL_POINT_BETA ) + soundName = "localization/sound/dialog/dm/comp_ctrlredbeta.mp3"; + else if ( controlPoint->_controlPointType == CONTROL_POINT_DELTA ) + soundName = "localization/sound/dialog/dm/comp_ctrlreddelta.mp3"; + else if ( controlPoint->_controlPointType == CONTROL_POINT_GAMMA ) + soundName = "localization/sound/dialog/dm/comp_ctrlredgamma.mp3"; + } + else + { + if ( controlPoint->_controlPointType == CONTROL_POINT_ALPHA ) + soundName = "localization/sound/dialog/dm/comp_ctrlbluealpha.mp3"; + else if ( controlPoint->_controlPointType == CONTROL_POINT_BETA ) + soundName = "localization/sound/dialog/dm/comp_ctrlbluebeta.mp3"; + else if ( controlPoint->_controlPointType == CONTROL_POINT_DELTA ) + soundName = "localization/sound/dialog/dm/comp_ctrlbluedelta.mp3"; + else if ( controlPoint->_controlPointType == CONTROL_POINT_GAMMA ) + soundName = "localization/sound/dialog/dm/comp_ctrlbluegamma.mp3"; + } + + multiplayerManager.broadcastSound( soundName, CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.5f ); + } + } + + controlPoint->_lastControllingTeam = controlPoint->_controllingTeam; + + controlPoint->_playerPointsAwardedForCapture++; + + if ( controlPoint->_lastPlayerToTouch >= 0 ) + { + multiplayerManager.addPoints( controlPoint->_lastPlayerToTouch, _pointsForEachAwardForCapture ); + } + } + } + } + } +} + +void ModifierControlPoints::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + Team *victimsTeam; + Team *killersTeam; + float distance; + str printString; + bool controlPointGuarded; + + if ( !attackingPlayer || ( killedPlayer == attackingPlayer ) ) + return; + + victimsTeam = multiplayerManager.getPlayersTeam( killedPlayer ); + killersTeam = multiplayerManager.getPlayersTeam( attackingPlayer ); + + if ( victimsTeam && killersTeam && ( victimsTeam == killersTeam ) ) + return; + + // See if the player was guarding a control point (either he or the killed player was close to his flag) + + controlPointGuarded = false; + + distance = findNearestControlledControlPoint( killersTeam->getName(), attackingPlayer->origin ); + + if ( ( distance > 0 ) && ( distance < _maxGuardingDist ) ) + { + controlPointGuarded = true; + } + else + { + distance = findNearestControlledControlPoint( killersTeam->getName(), killedPlayer->origin ); + + if ( ( distance > 0 ) && ( distance < _maxGuardingDist ) ) + { + controlPointGuarded = true; + } + } + + if ( controlPointGuarded ) + { + //multiplayerManager.playerEventNotification( "controlpoint-guarded", "", attackingPlayer ); + + printString = "$$ControlPointGuarded$$ "; + printString += attackingPlayer->client->pers.netname; + + multiplayerManager.playerSound( attackingPlayer->entnum, "localization/sound/dialog/dm/comp_ctrlguarded.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, 1.5f ); + + multiplayerManager.centerPrintTeamClients( attackingPlayer, printString, CENTERPRINT_IMPORTANCE_HIGH ); + + // Give points to the player + + multiplayerManager.addPoints( attackingPlayer->entnum, _pointsForProtectingControlPoint ); + } +} + +float ModifierControlPoints::findNearestControlledControlPoint( const str &teamName, const Vector &position ) +{ + int i; + float nearestDistance = -1.0f; + Vector diff; + float distance; + ControlPointData *controlPoint; + ControlPointData *nearestControlledControlPoint = NULL; + + // Loop through all of the control points + + for ( i = 1 ; i <= _controlPoints.NumObjects() ; i++ ) + { + controlPoint = _controlPoints.AddressOfObjectAt( i ); + + // See if this control point is controlled by the specified team + + if ( controlPoint->_controllingTeam && ( controlPoint->_controllingTeam->getName() == teamName ) && + ( controlPoint->_playerPointsAwardedForCapture > 0 ) ) + { + // Get the distance from the control point to the specified point + + diff = position - controlPoint->_controlPoint->origin; + distance = diff.length(); + + // See if this is the nearest controlled control point + + if ( !nearestControlledControlPoint || ( distance < nearestDistance ) ) + { + nearestControlledControlPoint = controlPoint; + nearestDistance = distance; + } + } + } + + return nearestDistance; +} + +int ModifierControlPoints::getStat( Player *player, int statNum, int value ) +{ + int redControlledIndex; + int blueControlledIndex; + int neutralControlledIndex; + ControlPointData *controlPoint; + int i; + ControlPointType controlPointType; + + + // Figure out which control point we are referring to (if any) and the set of icons to use + + if ( ( statNum == STAT_MP_GENERIC5 ) ) + { + controlPointType = CONTROL_POINT_ALPHA; + redControlledIndex = _alphaControlPointRedControlledIndex; + blueControlledIndex = _alphaControlPointBlueControlledIndex; + neutralControlledIndex = _alphaControlPointNeutralControlledIndex; + } + else if ( ( statNum == STAT_MP_GENERIC6 ) ) + { + controlPointType = CONTROL_POINT_BETA; + redControlledIndex = _betaControlPointRedControlledIndex; + blueControlledIndex = _betaControlPointBlueControlledIndex; + neutralControlledIndex = _betaControlPointNeutralControlledIndex; + } + else if ( ( statNum == STAT_MP_GENERIC7 ) ) + { + controlPointType = CONTROL_POINT_DELTA; + redControlledIndex = _deltaControlPointRedControlledIndex; + blueControlledIndex = _deltaControlPointBlueControlledIndex; + neutralControlledIndex = _deltaControlPointNeutralControlledIndex; + } + else if ( ( statNum == STAT_MP_GENERIC8 ) ) + { + controlPointType = CONTROL_POINT_GAMMA; + redControlledIndex = _gammaControlPointRedControlledIndex; + blueControlledIndex = _gammaControlPointBlueControlledIndex; + neutralControlledIndex = _gammaControlPointNeutralControlledIndex; + } + else + { + return value; + } + + // Find this control point + + for ( i = 1 ; i <= _controlPoints.NumObjects() ; i++ ) + { + controlPoint = _controlPoints.AddressOfObjectAt( i ); + + if ( controlPoint->_controlPointType == controlPointType ) + { + // Return the correct index based on which team controls this control point + + if ( !controlPoint->_controllingTeam ) + { + return neutralControlledIndex; + } + else if ( controlPoint->_controllingTeam->getName() == "Red" ) + { + return redControlledIndex; + } + else if ( controlPoint->_controllingTeam->getName() == "Blue" ) + { + return blueControlledIndex; + } + } + } + + return value; +} + +/* int ModifierControlPoints::getPointsForKill( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath, int points ) +{ + // Players do not get points for kills in control points mode + return 0; +} */ + +// Setup constants + +const float ModifierAutoHandicap::_attackerHandicapModifier = 1.05f; +const float ModifierAutoHandicap::_victimHandicapModifier = 1.05f; + +ModifierAutoHandicap::ModifierAutoHandicap() +{ + _playerHandicapData = NULL; +} + +ModifierAutoHandicap::~ModifierAutoHandicap() +{ + delete [] _playerHandicapData; + _playerHandicapData = NULL; +} + +void ModifierAutoHandicap::init( int maxPlayers ) +{ + _playerHandicapData = new HandicapPlayerData[ maxPlayers ]; +} + +void ModifierAutoHandicap::addPlayer( Player *player ) +{ + _playerHandicapData[ player->entnum ].reset(); +} + +float ModifierAutoHandicap::playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ) +{ + if ( damagedPlayer == attackingPlayer ) + return damage; + + // Change the damage based on the damaged and the attacking player's current handicap + + return damage * _playerHandicapData[ attackingPlayer->entnum ]._handicap / + _playerHandicapData[ damagedPlayer->entnum ]._handicap; +} + +void ModifierAutoHandicap::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + if ( killedPlayer == attackingPlayer || !attackingPlayer ) + return; + + // Make it harder for the attacker to get another kill + + _playerHandicapData[ attackingPlayer->entnum ]._handicap /= _attackerHandicapModifier; + + // Make it easier for the victim to get kills + + _playerHandicapData[ killedPlayer->entnum ]._handicap *= _victimHandicapModifier; +} + +// Setup constants + +const float ModifierActionHero::_actionHeroRegenRate = 5.0f; +const int ModifierActionHero::_extraPointsForKillingActionHero = 4; + +ModifierActionHero::ModifierActionHero() +{ + _actionHeroNum = -1; + _healthRegenRate = _actionHeroRegenRate; + + _actionHeroIconIndex = gi.imageindex( "sysimg/icons/mp/actionhero" ); + _actionHeroInfoIconIndex = gi.imageindex( "sysimg/icons/mp/actionhero_icon" ); +} + +void ModifierActionHero::init( int maxPlayers ) +{ + multiplayerManager.cacheMultiplayerFiles( "mp_actionhero" ); +} + +void ModifierActionHero::matchStarting( void ) +{ + _actionHeroNum = -1; +} + +void ModifierActionHero::removePlayer( Player *player ) +{ + if ( player->entnum == _actionHeroNum ) + { + // Action hero left the game so make action hero up for grabs + + multiplayerManager.centerPrintAllClients( "$$ActionHeroLeft$$", CENTERPRINT_IMPORTANCE_NORMAL ); + + _actionHeroNum = -1; + } +} + +void ModifierActionHero::update( float frameTime ) +{ + float healthToRegen; + Player *player; + + // Regen the action hero's health a little + + if ( _actionHeroNum != -1 ) + { + healthToRegen = _healthRegenRate * frameTime; + + player = multiplayerManager.getPlayer( _actionHeroNum ); + + if ( player ) + { + if ( player->canRegenerate() ) + { + multiplayerManager.addPlayerHealth( _actionHeroNum, healthToRegen ); + } + } + } +} + +void ModifierActionHero::playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) +{ + // Make sure everything is ok + + if ( !killedPlayer ) + return; + + // Always stop being the action hero if you die for any reason + + if ( killedPlayer->entnum == _actionHeroNum ) + { + Event *removeAttachedModel = new Event( EV_RemoveAttachedModelByTargetname ); + removeAttachedModel->AddString( "actionHero" ); + killedPlayer->ProcessEvent( removeAttachedModel ); + } + + if ( !attackingPlayer || ( killedPlayer == attackingPlayer ) ) + { + if ( killedPlayer->entnum == _actionHeroNum ) + { + _actionHeroNum = -1; + + multiplayerManager.centerPrintAllClients( "$$ActionHeroDead$$", CENTERPRINT_IMPORTANCE_NORMAL ); + } + + return; + } + + if ( ( _actionHeroNum == -1 ) || ( killedPlayer->entnum == _actionHeroNum ) ) + { + // This is either the first kill or the attacking player killed the action hero, either way we have + // a new actio hero + + // Give extra points if the last action hero was killed + + if ( _actionHeroNum != -1 ) + { + multiplayerManager.addPoints( attackingPlayer->entnum, _extraPointsForKillingActionHero ); + } + + // Make the killer the action hero + + _actionHeroNum = attackingPlayer->entnum; + + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-attrex-rifle.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-burstrifle.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-compressionrifle.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-drull-staff.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-fieldassaultrifle.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-grenadelauncher.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-imod.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-photon.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-rom-disruptor.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-rom-radgun.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-sniperrifle.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-tetryon.tik" ); + multiplayerManager.givePlayerItem( attackingPlayer->entnum, "models/weapons/worldmodel-tricorder.tik" ); + + attackingPlayer->GiveAmmo( "Plasma", 400, false, 400 ); + attackingPlayer->GiveAmmo( "Fed", 400, false, 400 ); + attackingPlayer->GiveAmmo( "Idryll", 400, false, 400 ); + + multiplayerManager.centerPrintAllClients( va( "%s $$NowActionHero$$", attackingPlayer->client->pers.netname ), CENTERPRINT_IMPORTANCE_NORMAL ); + multiplayerManager.broadcastSound( "localization/sound/dialog/dm/comp_nah.mp3", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.2f ); + + Event *attachModel = new Event( EV_AttachModel ); + attachModel->AddString( "models/fx/fx-actionHero.tik" ); + attachModel->AddString( "Bip01" ); + attachModel->AddFloat( 1.0f ); + attachModel->AddString( "actionHero" ); + attackingPlayer->ProcessEvent( attachModel ); + + // Max the player's health + + attackingPlayer->addHealth( 100.0f ); + + // Max the player's armor + + Event *armorEvent = new Event( EV_Sentient_GiveArmor ); + armorEvent->AddString( "BasicArmor" ); + armorEvent->AddFloat( 200.0f ); + armorEvent->AddInteger( false ); + attackingPlayer->ProcessEvent( armorEvent ); + } +} + +int ModifierActionHero::getIcon( Player *player, int statNum, int value ) +{ + if ( statNum == STAT_MP_SPECIALTY_ICON ) + { + // Return the action hero icon if this player is the action hero + + if ( _actionHeroNum == player->entnum ) + return _actionHeroIconIndex; + else + return -1; + } + + return value; +} + +int ModifierActionHero::getInfoIcon( Player *player ) +{ + if ( _actionHeroNum == player->entnum ) + return _actionHeroInfoIconIndex; + else + return 0; +} + +int ModifierActionHero::getScoreIcon( Player *player, int index, int value ) +{ + if ( index == SCOREICON2 ) + { + if ( _actionHeroNum == player->entnum ) + return _actionHeroIconIndex; + else + return 0; + } + + return value; +} + +ModifierPointsPerWeapon::~ModifierPointsPerWeapon() +{ + _weapons.FreeObjectList(); + _projectiles.FreeObjectList(); +} + +void ModifierPointsPerWeapon::init( int maxPlayers ) +{ + // Read in all of the projectile/weapon points data + + readMultiplayerConfig( "global/mp_PointsPerWeapon.cfg" ); +} + +void ModifierPointsPerWeapon::readMultiplayerConfig( const char *configName ) +{ + Script buffer; + const char *token; + + // Make sure the file exists + + if ( !gi.FS_Exists( configName ) ) + return; + + // Load the file + + buffer.LoadFile( configName ); + + // Parse the file + + while ( buffer.TokenAvailable( true ) ) + { + token = buffer.GetToken( true ); + + if ( !parseConfigToken( token, &buffer ) ) + { + gi.DPrintf( "Token %s from %s not handled by anyone\n", token, configName ); + } + } +} + +bool ModifierPointsPerWeapon::parseConfigToken( const char *key, Script *buffer ) +{ + PointsPerWeaponData pointsPerWeaponData; + + if ( stricmp( key, "projectile" ) == 0 ) + { + // Get projectile name + + if ( !buffer->TokenAvailable( false ) ) + return false; + + pointsPerWeaponData._name = buffer->GetToken( false ); + + // Get points for this projectile + + if ( !buffer->TokenAvailable( false ) ) + return false; + + pointsPerWeaponData._points = atoi( buffer->GetToken( false ) ); + + // Add projectile to the projectile list + + _projectiles.AddObject( pointsPerWeaponData ); + + return true; + } + else if ( stricmp( key, "weapon" ) == 0 ) + { + // Get weapon name + + if ( !buffer->TokenAvailable( false ) ) + return false; + + pointsPerWeaponData._name = buffer->GetToken( false ); + + // Get points for this projectile + + if ( !buffer->TokenAvailable( false ) ) + return false; + + pointsPerWeaponData._points = atoi( buffer->GetToken( false ) ); + + // Add weapon to the weapon list + + _weapons.AddObject( pointsPerWeaponData ); + + return true; + } + + return false; +} + +int ModifierPointsPerWeapon::getPointsForKill( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath, int points ) +{ + PointsPerWeaponData *pointsPerWeaponData; + int i; + + // Make sure everything is ok + + if ( killedPlayer == attackingPlayer ) + return points; + + if ( points <= 0 ) + return points; + + // See if the kill was by a projectile or directly from a weapon + + if ( inflictor && inflictor->isSubclassOf( Projectile ) ) + { + Projectile *proj; + + proj = (Projectile *)inflictor; + + // Determine the points based off of the projectile + + for ( i = 1 ; i <= _projectiles.NumObjects() ; i++ ) + { + pointsPerWeaponData = &_projectiles.ObjectAt( i ); + + if ( stricmp( proj->model.c_str(), pointsPerWeaponData->_name.c_str() ) == 0 ) + return pointsPerWeaponData->_points; + } + + // Didn't find it so just return the default + + return points; + } + else + { + str weaponName; + + // Get the name of the weapon + + attackingPlayer->getActiveWeaponName( WEAPON_ANY, weaponName ); + + // Determine the points based off of the weapon + + for ( i = 1 ; i <= _weapons.NumObjects() ; i++ ) + { + pointsPerWeaponData = &_weapons.ObjectAt( i ); + + if ( stricmp( weaponName.c_str(), pointsPerWeaponData->_name.c_str() ) == 0 ) + return pointsPerWeaponData->_points; + } + + // Didn't find it so just return the default + + return points; + } +} + diff --git a/dlls/game/mp_modifiers.hpp b/dlls/game/mp_modifiers.hpp new file mode 100644 index 0000000..d56e9f9 --- /dev/null +++ b/dlls/game/mp_modifiers.hpp @@ -0,0 +1,700 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/multiplayerArena.cpp $ +// $Revision:: 48 $ +// $Author:: Steven $ +// $Date:: 7/23/02 2:55p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// Description: +// + +#ifndef __MP_MODIFIERS_HPP__ +#define __MP_MODIFIERS_HPP__ + +#include "g_local.h" // common game stuff +#include "player.h" // for Player +#include "item.h" +#include "PlayerStart.h" // for PlayerDeathmatchStart +#include "container.h" // for Container +#include "str.h" // for str +#include "mp_shared.hpp" +#include "equipment.h" + +class MultiplayerModifier +{ +private: + +public: + MultiplayerModifier() {}; + virtual ~MultiplayerModifier() {}; + + virtual void init( int maxPlayers ) {}; + virtual void initItems( void ) {}; + virtual void start( void ) {}; + + virtual bool shouldKeepItem( MultiplayerItem *item ) { return false; } + virtual bool shouldKeepNormalItem( Item *item ) { return true; } + virtual void itemKept( MultiplayerItem *item ) {}; + virtual bool checkRule( const char *rule, bool defaultValue, Player *player = NULL ) { return defaultValue; } + virtual bool checkGameType( const char *rule ) { return false; } + virtual bool doesPlayerHaveItem( Player *player, const char *itemName ) { return false; } + + virtual int getStat( Player *player, int statNum, int value ) { return value; } + virtual int getIcon( Player *player, int statNum, int value ) { return value; } + virtual int getScoreIcon( Player *player, int index, int value ) { return value; } + virtual int getInfoIcon( Player *player ) { return 0; } + + virtual float playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ) { return damage; }; + virtual void playerFired( Player *attackingPlayer ) {}; + virtual void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ) {}; + virtual void playerSpawned( Player *player ) {}; + + virtual void matchOver( void ) {}; + + virtual void itemTouched( Player *player, MultiplayerItem *item ) {}; + virtual void itemDestroyed( Player *player, MultiplayerItem *item ) {}; + virtual float itemDamaged( MultiplayerItem *item, Player *attackingPlayer, float damage, int meansOfDeath ) { return damage; } + virtual void itemUsed( Entity *entity, MultiplayerItem *item ) {}; + + virtual void playerUsed( Player *usedPlayer, Player *usingPlayer, Equipment *equipment ) {}; + + virtual bool canGivePlayerItem( int entnum, const str &itemName ) { return true; } + + virtual void addPlayer( Player *player ) {}; + virtual void removePlayer( Player *player ) {}; + + virtual void joinedTeam( Player *player, const str &teamName ) {}; + + virtual void applySpeedModifiers( Player *player, int *moveSpeed ) {}; + virtual void applyJumpModifiers( Player *player, int *jumpSpeed ) {}; + virtual void applyAirAccelerationModifiers( Player *player, int *airAcceleration ) {}; + + virtual bool canPickup( Player *player, MultiplayerItemType itemType, const char *item_name ) { return true; } + virtual void pickedupItem( Player *player, MultiplayerItemType itemType, const char *itemName ) {}; + + virtual void update( float frameTime ) {}; + + virtual int getPointsForKill( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath, int points ) { return points; } + + virtual void playerEventNotification( const char *eventName, const char *eventItemName, Player *eventPlayer ) {}; + + virtual void playerCommand( Player *player, const char *command, const char *parm ) {}; + + virtual void matchStarted( void ) {}; + virtual void matchStarting( void ) {}; + virtual void matchRestarted( void ) {}; + virtual void matchEnded( void ) {}; + + virtual str getSpawnPointType( Player *player ) { return ""; } + virtual float getSpawnPointPriority( Player *player ) { return 0.0f; } + + virtual bool isValidPlayerModel( Player *player, str modelToUse, bool defaultValue ) { return defaultValue; } + virtual str getDefaultPlayerModel( Player *player, str modelName ) { return modelName; } + + virtual void playerChangedModel( Player *player ) {}; + virtual bool skipWeaponReloads( void ) { return false; } +}; + +class DestructionPlayerData +{ +public: + + float _lastHealTime; + float _damageDone; + float _healthHealed; + + DestructionPlayerData() { reset(); } + void reset( void ) { _lastHealTime = 0.0f; _damageDone = 0.0f; _healthHealed = 0.0f; } +}; + +class ModifierDestruction : public MultiplayerModifier +{ +private: + static const float _defaultObjectHealth; + static const int _pointsForDestroyingObject; + static const float _objectHealRate; + static const float _maxGuardingDist; + + static const float _minDamageForPoints; + static const float _minHealingForPoints; + + static const int _pointsForDamage; + static const int _pointsForHealing; + static const int _pointsForDestroying; + static const int _pointsForGuarding; + + MultiplayerItem * _redDestructionObject; + MultiplayerItem * _blueDestructionObject; + + float _redLastDamageSoundTime; + float _blueLastDamageSoundTime; + + int _maxPlayers; + DestructionPlayerData * _destructionPlayerData; + + float _redObjectLasthealth; + float _blueObjectLasthealth; + + bool _blueObjectDestroyed; + bool _redObjectDestroyed; + + float _respawnTime; + + float findDistanceToTeamsObject( const str &teamName, const Vector &position ); + void updateObjectAnim( MultiplayerItem *destructionObject, float health, float lastHealth, float maxHealth ); + int getStage( float health, float maxHealth ); + +public: + ModifierDestruction(); + ~ModifierDestruction(); + + /* virtual */ void init( int maxPlayers ); + + /* virtual */ int getStat( Player *player, int statNum, int value ); + + /* virtual */ void addPlayer( Player *player ); + /* virtual */ void playerSpawned( Player *player ); + /* virtual */ void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); + + /* virtual */ bool shouldKeepItem( MultiplayerItem *item ); + /* virtual */ void itemDestroyed( Player *player, MultiplayerItem *item ); + /* virtual */ float itemDamaged( MultiplayerItem *item, Player *attackingPlayer, float damage, int meansOfDeath ); + /* virtual */ void itemUsed( Entity *entity, MultiplayerItem *item ); + /* virtual */ void update( float frameTime ); + /* virtual */ bool checkRule( const char *rule, bool defaultValue, Player *player = NULL ); + /* virtual */ bool checkGameType( const char *rule ); +}; + +class ModifierOneFlag : public MultiplayerModifier +{ +private: + +public: + ModifierOneFlag() {}; + ~ModifierOneFlag() {}; + /* virtual */ bool shouldKeepItem( MultiplayerItem *item ); + /* virtual */ bool checkRule( const char *rule, bool defaultValue, Player *player = NULL ); + /* virtual */ bool checkGameType( const char *rule ); + /* virtual */ void addPlayer( Player *player ); +}; + +class EliminationPlayerData +{ +public: + + bool _eliminated; + + EliminationPlayerData() { reset(); } + void reset( void ) { _eliminated = false; } +}; + +class ModifierElimination : public MultiplayerModifier +{ +private: + static const int _pointsForBeingLastAlive; + + bool _respawning; + bool _playerEliminated; + int _maxPlayers; + + EliminationPlayerData* _playerEliminationData; + + bool _matchOver; + float _matchStartTime; + + int _eliminatedIconIndex; + + bool _needPlayers; + + int _eliminatedTextIndex; + int _nextRoundTextIndex; + + void reset( void ); + int numPlayersAliveOnTeam( const str &teamName ); + +public: + ModifierElimination(); + ~ModifierElimination(); + + /* virtual */ void init( int maxPlayers ); + /* virtual */ bool checkRule( const char *rule, bool defaultValue, Player *player = NULL ); + /* virtual */ void update( float frameTime ); + + /* virtual */ int getStat( Player *player, int statNum, int value ); + /* virtual */ int getScoreIcon( Player *player, int index, int value ); + + /* virtual */ void addPlayer( Player *player ); + /* virtual */ void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); + + /* virtual */ void matchStarting( void ); +}; + +/* typedef enum +{ + DIFFUSION_NONE, + DIFFUSION_BOMBER, + DIFFUSION_DIFFUSER +} DiffusionType; + +class DiffusionPlayerData +{ +public: + + DiffusionType _diffusionSpecialty; + MultiplayerItem * _item; + + DiffusionPlayerData() { reset(); } + void reset( void ) { _diffusionSpecialty = DIFFUSION_NONE; _item = NULL; } +}; */ + +class DiffusionBombPlace +{ +public: + + MultiplayerItem * _item; + + bool _armed; + + float _totalArmingTime; + float _totalDisarmingTime; + + float _lastArmingTime; + float _lastDisarmingTime; + + float _totalArmedTime; + + DiffusionBombPlace(); + void reset( void ); +}; + +class ModifierDiffusion : public MultiplayerModifier +{ +private: + static const float _timeNeededToArmBomb; + static const float _timeNeededToDisarmBomb; + static const float _maxGuardingDist; + static const float _maxArmingPause; + static const float _maxDisarmingPause; + static const float _maxBombOnGroundTime; + + static const int _pointsForArmingBomb; + static const int _pointsForExplodingBomb; + static const int _pointsForDisarmingBomb; + static const int _pointsForGuardingBase; + static const int _pointsForGuardingTheBomber; + static const int _pointsForGuardingBomb; + static const int _pointsForKillingTheBomber; + + static const int _teamPointsForBombing; + + + int _bomber; + int _bombArmedByPlayer; + + int _lastBomber; + float _bombDroppedTime; + + float _timeNeededForBombToExplode; + + DiffusionBombPlace _redBombPlace; + DiffusionBombPlace _blueBombPlace; + + MultiplayerItem * _bomb; + MultiplayerItem * _tempBombItem; + Entity * _attachedBomb; + + float _tempBombItemTime; + + //DiffusionPlayerData *_playerDiffusionData; + int _maxPlayers; + + int _bomberIconIndex; + int _diffuserIconIndex; + + int _bombNormalIconIndex; + int _bombPlacedIconIndex; + int _bombArmedIconIndex; + + int _redBombPlaceArmedIconIndex; + int _blueBombPlaceArmedIconIndex; + int _bombCarriedByRedTeamIconIndex; + int _bombCarriedByBlueTeamIconIndex; + int _bombInBaseIconIndex; + int _bombOnGroundIconIndex; + + float _respawnTime; + + + void dropBomb( Player *player ); + void respawnBomb( bool quiet = false ); + bool playerOnOffense( Player *player ); + + Player * getBomber( void ); + void makeBomber( Player *player ); + void clearBomber( void ); + bool withinGuardDistance( const Vector &origin1, const Vector &origin2 ); + + bool playerGuardedBomber( Player *attackingPlayer, Player *killedPlayer ); + bool playerGuardedBase( Player *attackingPlayer, Player *killedPlayer ); + bool playerGuardedBomb( Player *attackingPlayer, Player *killedPlayer ); + + void attachBomb( Player *player ); + +public: + ModifierDiffusion(); + ~ModifierDiffusion(); + + /* virtual */ void init( int maxPlayers ); + /* virtual */ bool shouldKeepItem( MultiplayerItem *item ); + ///* virtual */ void start( void ); + /* virtual */ void matchStarted( void ); + + /* virtual */ bool checkRule( const char *rule, bool defaultValue, Player *player = NULL ); + + /* virtual */ void update( float frameTime ); + + /* virtual */ void itemTouched( Player *player, MultiplayerItem *item ); + /* virtual */ void itemUsed( Entity *entity, MultiplayerItem *item ); + + /* virtual */ int getStat( Player *player, int statNum, int value ); + + ///* virtual */ int getPointsForKill( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath, int points ); + + /* virtual */ void addPlayer( Player *player ); + /* virtual */ void removePlayer( Player *player ); + + /* virtual */ void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); + /* virtual */ void playerCommand( Player *player, const char *command, const char *parm ); + + /* virtual */ void playerSpawned( Player *player ); + /* virtual */ void playerEventNotification( const char *eventName, const char *eventItemName, Player *eventPlayer ); + + /* virtual */ int getIcon( Player *player, int statNum, int value ); + /* virtual */ int getScoreIcon( Player *player, int index, int value ); + + /* virtual */ void playerChangedModel( Player *player ); +}; + +typedef enum +{ + SPECIALTY_NONE, + SPECIALTY_INFILTRATOR, + SPECIALTY_MEDIC, + SPECIALTY_TECHNICIAN, + SPECIALTY_DEMOLITIONIST, + SPECIALTY_HEAVY_WEAPONS, + SPECIALTY_SNIPER +} SpecialtyType; + +class SpecialtyPlayerData +{ +public: + + static const SpecialtyType _defaultSpecialty; + + SpecialtyType _specialty; + MultiplayerItem * _item; + float _lastUseTime; + bool _forced; + float _amountOfHealing; + int _lastPlayer; + float _lastPlayerTime; + float _regenHoldableItemTime; + + SpecialtyPlayerData(); + void reset( void ); +}; + +class SpecialtyItem +{ +public: + MultiplayerItem * _item; + SpecialtyType _type; + str _teamName; + + bool _needToRespawn; + float _respawnTime; + float _minimumRespawnTime; + float _pickedupTime; +}; + +class ModifierSpecialties : public MultiplayerModifier +{ +private: + static const float _infiltratorMinRespawnTime; + static const float _medicMinRespawnTime; + static const float _technicianMinRespawnTime; + static const float _demolitionistMinRespawnTime; + static const float _heavyweaponsMinRespawnTime; + static const float _sniperMinRespawnTime; + + static const float _startingInfiltratorHealth; + static const float _startingInfiltratorMassModifier; + static const float _startingMedicArmor; + static const float _startingTechnicianArmor; + static const float _startingDemolitionistArmor; + static const float _startingHeavyWeaponsHealth; + static const float _startingHeavyWeaponsArmor; + static const float _startingHeavyWeaponsMassModifier; + static const float _startingSniperArmor; + static const float _startingNormalHealth; + + static const float _medicHealOtherRate; + static const float _medicHealSelfRate; + static const float _infiltratorMoveSpeedModifier; + static const float _heavyWeaponsMoveSpeedModifier; + static const float _infiltratorJumpSpeedModifier; + static const float _infiltratorAirAccelerationModifier; + + static const float _technicianHoldableItemRegenTime; + static const float _demolitionistHoldableItemRegenTime; + + static const bool _medicCanSeeEnemyHealth; + + static const float _amountOfHealingForPoints; + static const int _pointsForHealing; + + static const bool _removeItems; + + SpecialtyPlayerData *_playerSpecialtyData; + int _maxPlayers; + + Container _specialtyItems; + + int _infiltratorIconIndex; + int _medicIconIndex; + int _technicianIconIndex; + int _demolitionistIconIndex; + int _heavyweaponsIconIndex; + int _sniperIconIndex; + + void putItemBack( Player *player ); + void removeItem( MultiplayerItem *item ); + void setupSpecialty( Player *player, SpecialtyType specialty, bool chosen ); + + SpecialtyItem * findSpecialtyItem( MultiplayerItem *item ); + void attachBackpack( Player *player ); + +public: + + ModifierSpecialties(); + ~ModifierSpecialties(); + + /* virtual */ void init( int maxPlayers ); + + /* virtual */ bool checkRule( const char *rule, bool defaultValue, Player *player = NULL ); + + /* virtual */ bool shouldKeepItem( MultiplayerItem *item ); + /* virtual */ bool shouldKeepNormalItem( Item *item ); + /* virtual */ void itemTouched( Player *player, MultiplayerItem *item ); + /* virtual */ void addPlayer( Player *player ); + /* virtual */ void removePlayer( Player *player ); + + /* virtual */ void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); + /* virtual */ void playerSpawned( Player *player ); + + /* virtual */ void applySpeedModifiers( Player *player, int *moveSpeed ); + /* virtual */ void applyJumpModifiers( Player *player, int *jumpSpeed ); + /* virtual */ void applyAirAccelerationModifiers( Player *player, int *airAcceleration ); + + /* virtual */ bool canPickup( Player *player, MultiplayerItemType itemType, const char *item_name ); + + /* virtual */ void update( float frameTime ); + + /* virtual */ int getStat( Player *player, int statNum, int value ); + /* virtual */ int getIcon( Player *player, int statNum, int value ); + /* virtual */ int getScoreIcon( Player *player, int index, int value ); + + /* virtual */ void playerUsed( Player *usedPlayer, Player *usingPlayer, Equipment *equipment ); + + /* virtual */ void playerCommand( Player *player, const char *command, const char *parm ); + + /* virtual */ str getSpawnPointType( Player *player ); + /* virtual */ float getSpawnPointPriority( Player *player ) { return 100.0f; } + + /* virtual */ void playerChangedModel( Player *player ); +}; + +class HandicapPlayerData +{ +public: + float _handicap; + + HandicapPlayerData() { _handicap = 1.0f; } + void reset( void ) { _handicap = 1.0f; } +}; + +class ModifierAutoHandicap : public MultiplayerModifier +{ +private: + static const float _attackerHandicapModifier; + static const float _victimHandicapModifier; + + HandicapPlayerData *_playerHandicapData; +public: + ModifierAutoHandicap(); + ~ModifierAutoHandicap(); + + /* virtual */ void init( int maxPlayers ); + /* virtual */ void addPlayer( Player *player ); + + /* virtual */ float playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ); + /* virtual */ void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); +}; + +class PointsPerWeaponData +{ +public: + str _name; + int _points; +}; + +class ModifierPointsPerWeapon : public MultiplayerModifier +{ +private: + Container _weapons; + Container _projectiles; + +public: + ModifierPointsPerWeapon() {}; + ~ModifierPointsPerWeapon(); + + /* virtual */ void init( int maxPlayers ); + + void readMultiplayerConfig( const char *configName ); + bool parseConfigToken( const char *key, Script *buffer ); + + /* virtual */ int getPointsForKill( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath, int points ); +}; + +typedef enum +{ + CONTROL_POINT_NONE, + CONTROL_POINT_ALPHA, + CONTROL_POINT_BETA, + CONTROL_POINT_DELTA, + CONTROL_POINT_GAMMA +} ControlPointType; + +struct ControlPointData +{ + MultiplayerItem * _controlPoint; + ControlPointType _controlPointType; + Team * _controllingTeam; + Team * _lastControllingTeam; + float _lastTime; + int _lastPlayerToTouch; + int _playerPointsAwardedForCapture; +}; + +class ModifierControlPoints : public MultiplayerModifier +{ +private: + static const float _timeNeededToControl; + static const int _maxNumberAwardsForPlayerCapture; + static const int _pointsForEachAwardForCapture; + static const int _pointsForProtectingControlPoint; + static const int _maxGuardingDist; + + Container _controlPoints; + + int _alphaControlPointRedControlledIndex; + int _alphaControlPointBlueControlledIndex; + int _alphaControlPointNeutralControlledIndex; + + int _betaControlPointRedControlledIndex; + int _betaControlPointBlueControlledIndex; + int _betaControlPointNeutralControlledIndex; + + int _deltaControlPointRedControlledIndex; + int _deltaControlPointBlueControlledIndex; + int _deltaControlPointNeutralControlledIndex; + + int _gammaControlPointRedControlledIndex; + int _gammaControlPointBlueControlledIndex; + int _gammaControlPointNeutralControlledIndex; + + float findNearestControlledControlPoint( const str &teamName, const Vector &position ); + ControlPointType getControlPointType( const str &name ); + +public: + ModifierControlPoints(); + ~ModifierControlPoints(); + + /* virtual */ void init( int maxPlayers ); + + /* virtual */ void addPlayer( Player *player ); + /* virtual */ int getStat( Player *player, int statNum, int value ); + + /* virtual */ bool shouldKeepItem( MultiplayerItem *item ); + /* virtual */ void itemTouched( Player *player, MultiplayerItem *item ); + /* virtual */ void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); + + ///* virtual */ int getPointsForKill( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath, int points ); + + /* virtual */ void update( float frameTime ); + + /* virtual */ str getSpawnPointType( Player *player ); + /* virtual */ float getSpawnPointPriority( Player *player ) { return 10.0f; } + + /* virtual */ bool checkRule( const char *rule, bool defaultValue, Player *player = NULL ); +}; + +class ModifierInstantKill : public MultiplayerModifier +{ +private: + static const float _instantKillDamage; + + static const float _regenTime; + static const int _regenAmount; + + float _lastRegenTime; + +public: + ModifierInstantKill(); + ~ModifierInstantKill() {}; + + /* virtual */ void init( int maxPlayers ); + float playerDamaged( Player *damagedPlayer, Player *attackingPlayer, float damage, int meansOfDeath ); + /* virtual */ bool canGivePlayerItem( int entnum, const str &itemName ); + /* virtual */ bool checkRule( const char *rule, bool defaultValue, Player *player = NULL ); + /* virtual */ void playerSpawned( Player *player ); + /* virtual */ bool shouldKeepNormalItem( Item *item ); + /* virtual */ void update( float frameTime ); + /* virtual */ bool skipWeaponReloads( void ) { return true; } +}; + +class ModifierActionHero : public MultiplayerModifier +{ +private: + static const float _actionHeroRegenRate; + static const int _extraPointsForKillingActionHero; + + int _actionHeroNum; + float _healthRegenRate; + + int _actionHeroIconIndex; + int _actionHeroInfoIconIndex; + +public: + ModifierActionHero(); + ~ModifierActionHero() {}; + /* virtual */ void init( int maxPlayers ); + virtual void playerKilled( Player *killedPlayer, Player *attackingPlayer, Entity *inflictor, int meansOfDeath ); + virtual void removePlayer( Player *player ); + virtual void update( float frameTime ); + + /* virutal */ void matchStarting( void ); + + /* virtual */ int getIcon( Player *player, int statNum, int value ); + /* virtual */ int getScoreIcon( Player *player, int index, int value ); + /* virtual */ int getInfoIcon( Player *player ); +}; + +#endif // __MP_MODIFIERS_HPP__ diff --git a/dlls/game/mp_shared.hpp b/dlls/game/mp_shared.hpp new file mode 100644 index 0000000..fe84bd9 --- /dev/null +++ b/dlls/game/mp_shared.hpp @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/multiplayerArena.h $ +// $Revision:: 38 $ +// $Author:: Steven $ +// $Date:: 7/23/02 3:55p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// Description: +// + +#ifndef __MP_SHARED_HPP__ +#define __MP_SHARED_HPP__ + +typedef enum +{ + SCOREICON1, + SCOREICON2, + SCOREICON3, + SCOREICON4, + SCOREICON5, + SCOREICON6, + MAX_SCORE_ICONS +} ScoreIconType; + +#endif // __MP_SHARED_HPP__ diff --git a/dlls/game/mp_team.cpp b/dlls/game/mp_team.cpp new file mode 100644 index 0000000..05ba690 --- /dev/null +++ b/dlls/game/mp_team.cpp @@ -0,0 +1,442 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/mp_team.cpp $ +// $Revision:: 8 $ +// $Author:: Steven $ +// $Date:: 5/20/03 11:39a $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// Description: +// + +#include "_pch_cpp.h" +#include "mp_manager.hpp" +#include "mp_team.hpp" + +//------------------------------------------------------------------ +// T E A M +//------------------------------------------------------------------ + +// Connect events to class methods +CLASS_DECLARATION( Class, Team, NULL ) +{ + { NULL, NULL } +}; + +//================================================================ +// Name: Team +// Class: Team +// +// Description: Constructor +// +// Parameters: const str& -- name of the team +// +// Returns: None +// +//================================================================ +Team::Team( const str& name ) : + _name(name) +{ + _startingHealth = 0; + _maxPlayers = 5; + _wins = 0; + _losses = 0; + _kills = 0; + _deaths = 0; + _points = 0; + _isFighting = false; +} + + +//================================================================ +// Name: ~Team +// Class: Team +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +// +//================================================================ +Team::~Team() +{ +} + +//================================================================ +// Name: AddDeath +// Class: Team +// +// Description: Adds a death for this team if the specified victim +// is indeed on the team. Also notifies all participants +// in the arena of the event. +// +// Parameters: Player *victim -- this team's player who died +// Player *killer -- opposing team's player who made the kill. Not used in base class +// +// Returns: None +// +//================================================================ +void Team::AddDeath( Player* victim, Player* ) +{ + assert(victim); + if (!victim) + { + warning("Team::AddDeath", "NULL Player\n"); + return ; + } + + if (!_playerList.ObjectInList(victim)) + { + warning("Team::AddDeath", va("%s is not a member of %s", victim->client->pers.netname, getName().c_str() ) ); + return ; + } + + _deaths++ ; +} + +//================================================================ +// Name: AddKill +// Class: Team +// +// Description: Adds a kill for this team if the specified killer +// is indeed on the team. +// +// Parameters: Player* killer -- this team's player who made the kill +// Player* victim -- opposing team's player who died. Not used base class +// +// Returns: None +// +//================================================================ +void Team::AddKill( Player *killer, Player * ) +{ + assert(killer); + if (!killer) + { + warning("Team::AddKill", "NULL Player\n"); + return ; + } + + if (!_playerList.ObjectInList(killer)) + { + warning("Team::AddKill", va("%s is not a member of %s", killer->client->pers.netname, getName().c_str() ) ); + return ; + } + + _kills++ ; +} + +void Team::addPoints( Player *killer, int points ) +{ + int oldPoints; + + /* if ( killer && !_playerList.ObjectInList(killer)) + { + assert( !"Player not in list" ); + warning("Team::AddKill", va("%s is not a member of %s", killer->client->pers.netname, getName().c_str() ) ); + return ; + } */ + + oldPoints = _points; + + _points += points; + + multiplayerManager.teamPointsChanged( this, oldPoints, _points ); +} + +//================================================================ +// Name: Win +// Class: Team +// +// Description: Increments number of wins for this team. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void Team::Win() +{ + _wins++ ; +} + + + +//================================================================ +// Name: Lose +// Class: Team +// +// Description: Increments number of losses for this team +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void Team::Lose() +{ + _losses++ ; +} + +//================================================================ +// Name: BeginMatch +// Class: Team +// +// Description: Activates all the players on the team +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void Team::BeginMatch() +{ + for (int idx = 1; idx <= _playerList.NumObjects(); idx++) + { + Player *player = _playerList.ObjectAt(idx); + assert(player); + if (!player) + { + warning("Team::endMatch", "NULL Player in team list\n"); + continue ; + } + _activatePlayer(player); + } +} + +//================================================================ +// Name: EndMatch +// Class: Team +// +// Description: Deactivates all the players on the team +// (turns off fighting). +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void Team::EndMatch() +{ + for (int idx = 1; idx <= _playerList.NumObjects(); idx++) + { + Player *player = _playerList.ObjectAt(idx); + assert(player); + if (!player) + { + warning("Team::endMatch", "NULL Player in team list\n"); + continue ; + } + _deactivatePlayer(player); + } +} + +//================================================================ +// Name: AddPlayer +// Class: Team +// +// Description: Adds a player to the team. +// +// Parameters: Player* -- player to add +// +// Returns: None +// +//================================================================ +void Team::AddPlayer( Player* player ) +{ + assert(player); + if (!player) + { + warning("Team::AddPlayer", va("Attempt to add NULL player to %s\n", getName().c_str() ) ); + return ; + } + + if (_playerList.NumObjects() == _maxPlayers) + { + warning("Team::AddPlayer", va("The %s team is full!\n", getName().c_str() ) ); + return ; + } + _playerList.AddObject(player); + //player->SetMultiplayerTeam(this); + + // If we are in the middle of a match, prepare them for fighting + if (isFighting()) + { + _activatePlayer( player ); + } +} + + +//================================================================ +// Name: RemovePlayer +// Class: Team +// +// Description: Removes a player from the team +// +// Parameters: Player* -- player to remove +// +// Returns: None +// +//================================================================ +void Team::RemovePlayer( Player *player ) +{ + assert(player); + if (!player) + { + warning("Team::RemovePlayer", va("Attempt to add NULL player to %s\n", getName().c_str() ) ); + return ; + } + + if ( !_playerList.ObjectInList( player ) ) + { + warning("Team::RemovePlayer", va("Player %s is not on team %s\n", player->client->pers.netname, getName().c_str() ) ); + return ; + } + _playerList.RemoveObject(player); + //player->SetMultiplayerTeam(NULL); +} + + +//================================================================ +// Name: AddStartingAmmo +// Class: Team +// +// Description: Adds the specified ammo to the list of initial ammo +// to give players on this team. +// +// Parameters: SimpleAmmoType -- ammo to add +// +// Returns: None +// +//================================================================ +void Team::AddStartingAmmo( const SimpleAmmoType &ammo ) +{ + _ammoList.AddObject(ammo); +} + + +//================================================================ +// Name: AddStartingWeapon +// Class: Team +// +// Description: Adds the specified starting weapon to the list of +// weapons. +// +// Parameters: const str& -- weapon to add +// +// Returns: None +// +//================================================================ +void Team::AddStartingWeapon( const str& weaponName ) +{ + _weaponList.AddObject(weaponName); +} + +//---------------------------------------------------------------- +// P R O T E C T E D M E T H O D S +//---------------------------------------------------------------- + + +//================================================================ +// Name: _giveInitialConditions +// Class: Team +// +// Description: Gives the specified player the starting health, ammo, +// and weapons for this team. If starting health is 0, +// no health is applied to the player meaning the player +// will get the arena default (typically 100). +// +// Parameters: Player* -- player to recieve initial conditions +// +// Returns: None +// +//================================================================ +void Team::_giveInitialConditions( Player *player ) +{ + assert(player); + if (!player) + { + warning("Team::_giveWeaponsAndAmmo", "NULL Player passed\n"); + return ; + } + + if (isStartingHealth()) + { +// Event* healthEvent = new Event(EV_Set_Health); +// healthEvent->AddInteger(getStartingHealth()); +// player->ProcessEvent(healthEvent); + } + + int idx = 0; + // Give weapons + for (idx=1; idx <= _weaponList.NumObjects(); idx++) + { + multiplayerManager.givePlayerItem( player->entnum, _weaponList.ObjectAt(idx) ); + //player->giveItem( _weaponList.ObjectAt(idx), 1 ); + } + + // Give ammo + for (idx=1; idx <= _ammoList.NumObjects(); idx++) + { + SimpleAmmoType& ammo = _ammoList.ObjectAt(idx); + player->giveItem( ammo.type, ammo.amount ); + } +} + + +//================================================================ +// Name: _activatePlayer +// Class: Team +// +// Description: Activates the specified player. This player is +// given any special weapons and ammo and the appropriate +// starting health (if any). +// +// This routine also attempts to give the player a +// team speicfic spawn point if there is one. +// +// +// Parameters: Player* -- player to activate +// +// Returns: None +// +//================================================================ +void Team::_activatePlayer( Player *player ) +{ + _giveInitialConditions(player); + multiplayerManager.allowFighting( true ); + player->takedamage = DAMAGE_YES ; +} + + + +//================================================================ +// Name: _deactivatePlayer +// Class: Team +// +// Description: Removes the player from battle. For now, this is +// simply turning off the fighting enabled flag and +// putting them into spectator mode. +// +// Parameters: Player* -- player to deactivate +// +// Returns: None +// +//================================================================ +void Team::_deactivatePlayer( Player *player ) +{ + assert(player); + if (!player) + { + warning("Team::_deactivatePlayer", va("NULL Player passed to %s team\n", getName().c_str() ) ); + return ; + } + + multiplayerManager.allowFighting( false ); + player->takedamage = DAMAGE_NO ; +} diff --git a/dlls/game/mp_team.hpp b/dlls/game/mp_team.hpp new file mode 100644 index 0000000..b667f75 --- /dev/null +++ b/dlls/game/mp_team.hpp @@ -0,0 +1,131 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/multiplayerArena.cpp $ +// $Revision:: 48 $ +// $Author:: Steven $ +// $Date:: 7/23/02 2:55p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// Description: +// + +#include "_pch_cpp.h" + +#include "mp_shared.hpp" + +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/multiplayerArena.cpp $ +// $Revision:: 48 $ +// $Author:: Steven $ +// $Date:: 7/23/02 2:55p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// Description: +// + +#ifndef __MP_TEAM_HPP__ +#define __MP_TEAM_HPP__ + +#include "g_local.h" // common game stuff +#include "player.h" // for Player +#include "item.h" +#include "PlayerStart.h" // for PlayerDeathmatchStart +#include "container.h" // for Container +#include "str.h" // for str + +//------------------------------------------------------------------- +// SimpleAmmoType -- Defines a type of ammo and a quantity. Used for +// starting players with specified ammo in multiplayer. +//------------------------------------------------------------------- +class SimpleAmmoType : public Class + { + public: + SimpleAmmoType() : type(""), amount(0) { } + SimpleAmmoType(const str& ammoType, int ammoAmount) : type(ammoType), amount(ammoAmount) { } + str type; + int amount; + }; + +//----------------------------------------------------------------------- +// Team -- A multiplayer team. The team has a name, starting ammo, weapons, +// and health, and skins. The team class tracks kills and deaths +// on a team basis. +//----------------------------------------------------------------------- +class Team : public Class + { + private: + Container _playerList ; // Players on this team + Container _weaponList ; // starting weapons for just this team + Container _ammoList ; // starting ammo for just this team + Container _spawnpointList; // spawnpoints for just this team + unsigned int _maxPlayers ; // Maximum players on this team + unsigned int _startingHealth ; // starting health for just this team + unsigned int _wins ; // matches won + unsigned int _losses ; // matches lost + unsigned int _kills ; // kills of an opposing team + int _points; + unsigned int _deaths ; // kills of this team + bool _isFighting ; // true if match has begun + str _name ; // can be used to reference the team thru events + + protected: + void _giveInitialConditions(Player *player); // gives initial health, ammo, and weapons to player + void _activatePlayer(Player* player); // prepares player for fight + void _deactivatePlayer(Player* player); // turns off fighting for player + + public: + CLASS_PROTOTYPE( Team ); + Team(const str& name="Unnamed Team"); + virtual ~Team(); + + // Queries + bool isStartingHealth() { return (_startingHealth > 0) ? true : false ; } + bool isFighting() { return _isFighting ; } + + // Gets + const str& getName() { return _name ; } + unsigned int getKills() { return _kills ; } + unsigned int getDeaths() { return _deaths ; } + int getPoints() { return _points ; } + unsigned int getWins() { return _wins ; } + unsigned int getLosses() { return _losses ; } + unsigned int getMaxPlayers() { return _maxPlayers ; } + unsigned int getActivePlayers() { return _playerList.NumObjects(); } + + // Sets + void setStartingHealth(unsigned int h) { _startingHealth = h ; } + void setMaxPlayers(unsigned int players) { _maxPlayers = players ; } + + // Match functions + void BeginMatch(); + void EndMatch(); + + // Player functions + void AddPlayer(Player* player); + void RemovePlayer(Player* player); + + // Initial conditions functions + void AddStartingWeapon(const str& weaponName); + void AddStartingAmmo( const SimpleAmmoType &ammo); + void AddSpawnpoint(PlayerDeathmatchStart* spawnpoint); + + // Stat functions + void AddKill(Player* killer, Player* victim=NULL); // try to track who we killed + void AddDeath(Player* victim, Player* killer=NULL); // try to track who killed us + void addPoints( Player *killer, int points ); + void Win(); + void Lose(); +}; + +#endif // __MP_TEAM_HPP__ diff --git a/dlls/game/mssccprj.scc b/dlls/game/mssccprj.scc new file mode 100644 index 0000000..7c136c7 --- /dev/null +++ b/dlls/game/mssccprj.scc @@ -0,0 +1,5 @@ +SCC = This is a Source Code Control file + +[game.dsp] +SCC_Aux_Path = "\\Hyperion\Data\TikiEngine SourceSafe" +SCC_Project_Name = "$/Code/DLLs/game", NEDAAAAA diff --git a/dlls/game/nature.cpp b/dlls/game/nature.cpp new file mode 100644 index 0000000..af631e6 --- /dev/null +++ b/dlls/game/nature.cpp @@ -0,0 +1,146 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/nature.cpp $ +// $Revision:: 9 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: + +#include "_pch_cpp.h" +#include "nature.h" + +/*****************************************************************************/ +/*QUAKED func_emitter (0 0.25 0.5) ? + +"emitter" - Name of emitter to use. +******************************************************************************/ +Event EV_Emitter_EmitterName +( + "emitter", + EV_DEFAULT, + "s", + "name", + "Emitter to use" +); + +CLASS_DECLARATION( Entity, Emitter, "func_emitter" ) +{ + { &EV_Emitter_EmitterName, &Emitter::EmitterName }, + + { NULL, NULL } +}; + +Emitter::Emitter() +{ + edict->s.eType = ET_EMITTER; +} + +void Emitter::setEmitter( const str &name ) +{ + emitterName = name; + edict->s.tag_num = gi.imageindex( emitterName ); +} + +void Emitter::EmitterName( Event *ev ) +{ + setEmitter( ev->GetString( 1 ) ); +} + +/*****************************************************************************/ +/*QUAKED func_rain (0 0.25 0.5) ? + +This creates a raining effect in the brush + +"emitter" - Name of emitter to use for the rain. +******************************************************************************/ + +CLASS_DECLARATION( Entity, Rain, "func_rain" ) +{ + { NULL, NULL } +}; + + +Rain::Rain() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + setSolidType( SOLID_NOT ); + edict->s.eType = ET_RAIN; + setRainName( "defaultrain" ); +} + +void Rain::setRainName( const str &name ) +{ + rainName = name; + edict->s.tag_num = gi.imageindex( rainName ); +} + + +/*****************************************************************************/ +/* Plant Puffdaddy */ +/*****************************************************************************/ + +Event EV_PuffDaddy_Idle +( + "idle", + EV_DEFAULT, + NULL, + NULL, + "Animates the puff daddy." +); + +CLASS_DECLARATION( Entity, PuffDaddy, "plant_puffdaddy" ) +{ + { &EV_Touch, &PuffDaddy::Touch }, + { &EV_PuffDaddy_Idle, &PuffDaddy::Idle }, + + { NULL, NULL } +}; + +void PuffDaddy::Idle( Event *ev ) +{ + animate->RandomAnimate( "idle" ); +} + +void PuffDaddy::Touch( Event *ev ) +{ + Entity *other; + + other = ev->GetEntity( 1 ); + + if ( !other->inheritsFrom( "Sentient" ) ) + return; + + animate->RandomAnimate( "touch", EV_PuffDaddy_Idle ); + //SetFrame( 0 ); + + SurfaceCommand( "puffdaddy", "+nodraw" ); + setSolidType( SOLID_NOT ); +} + +PuffDaddy::PuffDaddy() +{ + animate = new Animate( this ); + + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + setSolidType( SOLID_TRIGGER ); + edict->s.eType = ET_MODELANIM; + setModel( "plant_puffdaddy.tik" ); + PostEvent( EV_Show, 0.0f ); + //showModel(); +} diff --git a/dlls/game/nature.h b/dlls/game/nature.h new file mode 100644 index 0000000..0c052c1 --- /dev/null +++ b/dlls/game/nature.h @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/nature.h $ +// $Revision:: 5 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: + +#include "g_local.h" +#include "trigger.h" + +class Emitter : public Entity + { + private: + str emitterName; + void setEmitter( const str &name ); + void EmitterName( Event *ev ); + public: + CLASS_PROTOTYPE( Emitter ); + Emitter(); + virtual void Archive( Archiver &arc ); + }; + +inline void Emitter::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveString( &emitterName ); + if ( arc.Loading() ) + { + setEmitter( emitterName ); + } + } + + +class Rain : public Emitter + { + private: + str rainName; + void setRainName( const str &name ); + public: + CLASS_PROTOTYPE( Rain ); + Rain(); + virtual void Archive( Archiver &arc ); + }; + +inline void Rain::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveString( &rainName ); + if ( arc.Loading() ) + { + setRainName( rainName ); + } + } + + +class PuffDaddy : public Entity + { + private: + void Touch( Event *ev ); + void Idle( Event *ev ); + + public: + CLASS_PROTOTYPE( PuffDaddy ); + PuffDaddy(); + }; + diff --git a/dlls/game/navigate.cpp b/dlls/game/navigate.cpp new file mode 100644 index 0000000..d37a8d6 --- /dev/null +++ b/dlls/game/navigate.cpp @@ -0,0 +1,2393 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/navigate.cpp $ +// $Revision:: 44 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:43a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// C++ implementation of the A* search algorithm. +// + +#include "_pch_cpp.h" +#include "navigate.h" +#include "path.h" +#include "misc.h" +#include "doors.h" + +#define PATHFILE_VERSION 10 + +Container SpecialPathNodes; + +Event EV_AI_SavePaths +( + "ai_savepaths", + EV_CHEAT, + NULL, + NULL, + "Saves the path nodes under the default name." +); +Event EV_AI_SaveNodes +( + "ai_save", + EV_CHEAT, + "s", + "filename", + "Save path nodes." +); +Event EV_AI_LoadNodes +( + "ai_load", + EV_CHEAT, + "s", + "filename", + "Loads path nodes." +); +Event EV_AI_ClearNodes +( + "ai_clearnodes", + EV_CHEAT, + NULL, + NULL, + "Clears all of the path nodes." +); +Event EV_AI_RecalcPaths +( + "ai_recalcpaths", + EV_CHEAT, + "i", + "nodenum", + "Update the specified node." +); +Event EV_AI_CalcPath +( + "ai_calcpath", + EV_CHEAT, + "ii", + "nodenum1 nodenum2", + "Calculate path from node1 to node2." +); +Event EV_AI_SetNodeFlags +( + "ai_setflags", + EV_CHEAT, + "iSSSSSS", + "nodenum token1 token2 token3 token4 token5 token6", + "Set the flags for the specified node." ); +Event EV_AI_OptimizeNodes +( + "ai_optimize", + EV_CHEAT, + NULL, + NULL, + "Optimizes the path nodes." +); +Event EV_AI_CalcAllPaths +( + "ai_calcallpaths", + EV_CODEONLY, + NULL, + NULL, + "Calculates all of the paths." +); +Event EV_AI_ConnectNodes +( + "ai_connectNodes", + EV_CHEAT | EV_SCRIPTONLY, + "ss", + "node1 node2", + "Connects the 2 specified nodes." +); +Event EV_AI_DisconnectNodes +( + "ai_disconnectNodes", + EV_CHEAT | EV_SCRIPTONLY, + "ss", + "node1 node2", + "Disconnects the 2 specified nodes." +); + +cvar_t *ai_createnodes = NULL; +cvar_t *ai_debugpath; +cvar_t *ai_debuginfo; +cvar_t *ai_showroutes; +cvar_t *ai_showroutes_distance; +cvar_t *ai_shownodenums; +cvar_t *ai_timepaths; +cvar_t *ai_advanced_pathfinding; + +//static Entity *IgnoreObjects[ MAX_GENTITIES ]; +//static int NumIgnoreObjects; + +//static PathNode *pathnodes[ MAX_PATHNODES ]; +//static qboolean pathnodesinitialized = false; +//static qboolean loadingarchive = false; +//static qboolean _pathNodesCalculated = false; +//int ai_maxnode; + +//#define MASK_PATHSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP) + +PathManager thePathManager; + +int path_checksthisframe; + +PathNode *PathManager::FindNode( const char *name ) +{ + int i; + + if ( !name ) + { + return NULL; + } + + if ( name[ 0 ] == '!' ) + { + name++; + return thePathManager.GetNode( atoi( name ) ); + } + + if ( name[ 0 ] == '$' ) + { + name++; + } + + for( i = 0; i < _pathNodes.NumObjects(); i++ ) + { + if ( _pathNodes[ i ]->TargetName() == name ) + { + return _pathNodes[ i ]; + } + } + + return NULL; +} + +PathNode *PathManager::GetNode( int num ) +{ + return _pathNodes[ num ]; +} + +void PathManager::AddNode( PathNode *node ) +{ + _pathNodes.AddObject( node ); +} + +void PathManager::RemoveNode( PathNode *node ) +{ + assert( node ); + _pathNodes.RemoveObject( node ); +} + +void PathManager::ResetNodes( void ) +{ + PathNode *pathNode; + + for( int j = _pathNodes.NumObjects(); j > 0; j-- ) + { + pathNode = _pathNodes.ObjectAt( j ); + delete pathNode; + } + _pathNodes.FreeObjectList(); + + for( int k = _specialPathNodes.NumObjects() ; k > 0 ; k-- ) + { + pathNode = _specialPathNodes.ObjectAt( k ); + delete pathNode; + } + _specialPathNodes.FreeObjectList(); + + for( int i = 0; i < PATHMAP_GRIDSIZE; i++ ) + { + for( int j = 0; j < PATHMAP_GRIDSIZE; j++ ) + { + if ( _mapCells[i][j] ) + { + delete _mapCells[i][j]; + _mapCells[i][j] = NULL; + } + } + } +} + +/*****************************************************************************/ +/*QUAKED info_pathnode (1 0 0) (-24 -24 0) (24 24 32) FLEE DUCK COVER DOOR JUMP LADDER ACTION WORK HIBERNATE + +FLEE marks the node as a safe place to flee to. Actor will be removed when it reaches a flee node and is not visible to a player. + +DUCK marks the node as a good place to duck behind during weapon fire. + +COVER marks the node as a good place to hide behind during weapon fire. + +DOOR marks the node as a door node. If an adjacent node has DOOR marked as well, the actor will only use the path if the door in between them is unlocked. + +JUMP marks the node as one to jump from when going to the node specified by target. +"target" the pathnode to jump to. + +WORK marks the node as one that the Actor can do work (Animate) from + +HIBERNATE marks the node as one that the Actor can go into hibernation ( Basically an AI Active Sleep ) at + + +******************************************************************************/ + +Event EV_Path_FindChildren +( + "findchildren", + EV_CODEONLY, + NULL, + NULL, + "Adds this node to the path manager." +); +Event EV_Path_FindEntities +( + "findentities", + EV_CODEONLY, + NULL, + NULL, + "Finds doors." +); +Event EV_Path_SetNodeFlags +( + "spawnflags", + EV_CODEONLY, + "i", + "node_flags", + "Sets the path nodes flags." +); +Event EV_Path_SetOriginEvent +( + "origin", + EV_SCRIPTONLY, + "v", + "origin", + "Sets the path node's origin." +); +Event EV_Path_SetAngles +( + "angles", + EV_SCRIPTONLY, + "v", + "angles", + "Sets the path node's angles." +); +Event EV_Path_SetAnim +( + "anim", + EV_DEFAULT, + "s", + "animname", + "Sets the animname used for this path node." +); +Event EV_Path_SetTargetname +( + "targetname", + EV_SCRIPTONLY, + "s", + "targetname", + "Sets the target name for this path node." +); +Event EV_Path_SetJumpAngle +( + "jumpangle", + EV_SCRIPTONLY, + "f", + "jumpangle", + "Sets the jump angle for this path node." +); +Event EV_Path_SetTarget +( + "target", + EV_SCRIPTONLY, + "s", + "target", + "Sets the target for this path node." +); + +CLASS_DECLARATION( Class, PathNodeConnection, "PathNodeConnection" ) +{ + { NULL, NULL } +}; + +CLASS_DECLARATION( Listener, PathNode, "info_pathnode" ) +{ + { &EV_Path_FindChildren, &PathNode::FindChildren }, + { &EV_Path_FindEntities, &PathNode::FindEntities }, + { &EV_Path_SetNodeFlags, &PathNode::SetNodeFlags }, + { &EV_Path_SetOriginEvent, &PathNode::SetOriginEvent }, + { &EV_SetAngle, &PathNode::SetAngle }, + { &EV_Path_SetAngles, &PathNode::SetAngles }, + { &EV_Path_SetAnim, &PathNode::SetAnim }, + { &EV_Path_SetTargetname, &PathNode::SetTargetname }, + { &EV_Path_SetJumpAngle, &PathNode::SetJumpAngle }, + { &EV_Path_SetTarget, &PathNode::SetTarget }, + + { NULL, NULL } +}; + +static Vector pathNodesChecksum; +static int numLoadNodes = 0; +//static int numNodes = 0; +//static PathNode *NodeList = NULL; + +PathNode::PathNode() +{ + numLoadNodes++; + if ( !thePathManager.IsLoadingArchive() ) + { + // our archive function will take care of this stuff + thePathManager.AddNode( this ); + + thePathManager.CancelEventsOfType( EV_AI_CalcAllPaths ); + thePathManager.PostEvent( EV_AI_CalcAllPaths, 0.0f ); + + //PostEvent( EV_Path_FindChildren, 0.0f ); + } + + occupiedTime = 0; + + nodeflags = 0; + setangles = false; + drawtime = 0; + contents = 0; + + occupiedTime = 0; + entnum = 0; + + // crouch hieght + setSize( Vector(-24, -24, 0), Vector(24, 24, 40) ); + + f = 0; + h = 0; + g = 0; + + gridX = 0; + gridY = 0; + inlist = NOT_IN_LIST; + + // reject is used to indicate that a node is unfit for ending on during a search + reject = false; + + Parent = NULL; + NextNode = NULL; +} + +PathNode::~PathNode() +{ + thePathManager.RemoveNodeFromGrid( this ); + + thePathManager.RemoveNode( this ); + + for ( int i = 0; i < NumberOfConnections();i++ ) + { + delete _connections[ i ]; + } + + _connections.ClearObjectList(); + +} + +void PathNode::SetNodeFlags( Event *ev ) +{ + nodeflags = ev->GetInteger( 1 ); +} + +void PathNode::SetOriginEvent( Event *ev ) +{ + setOrigin( ev->GetVector( 1 ) ); + pathNodesChecksum += origin; +} + +void PathNode::SetAngle( Event *ev ) +{ + Vector movedir; + setangles = true; + + movedir = G_GetMovedir( ev->GetFloat( 1 ) ); + setAngles( movedir.toAngles() ); +} + +void PathNode::SetAngles( Event *ev ) +{ + setangles = true; + setAngles( ev->GetVector( 1 ) ); +} + +void PathNode::SetAnim( Event *ev ) +{ + animname = ev->GetString( 1 ); +} + +void PathNode::SetTargetname( Event *ev ) +{ + targetname = ev->GetString( 1 ); +} + +void PathNode::SetJumpAngle( Event *ev ) +{ + jumpAngle = ev->GetFloat( 1 ); +} + +void PathNode::SetTarget( Event *ev ) +{ + target = ev->GetString( 1 ); +} + +str &PathNode::TargetName( void ) +{ + return targetname; +} + +void PathNode::setAngles( const Vector &ang ) +{ + angles = ang; +} + +void PathNode::setOrigin( const Vector &org ) +{ + origin = org; + contents = gi.pointcontents( origin, 0 ); +} + +void PathNode::setSize( const Vector &min, const Vector &max ) +{ + mins = min; + maxs = max; +} + +void PathNode::Setup( const Vector &pos ) +{ + CancelEventsOfType( EV_Path_FindChildren ); + + setOrigin( pos ); + + ProcessEvent( EV_Path_FindChildren ); +} + +void PathNode::Archive( Archiver &arc ) +{ + int i; + + Listener::Archive( arc ); + + // ??? + //int contents; + + arc.ArchiveUnsigned( &nodeflags ); + arc.ArchiveFloat( &jumpAngle ); + arc.ArchiveVector( &origin ); + arc.ArchiveVector( &angles ); + + if ( arc.Loading() ) + { + setOrigin( origin ); + setAngles( angles ); + } + + // ??? + // Vector mins; + // Vector maxs; + + arc.ArchiveBoolean( &setangles ); + arc.ArchiveString( &target ); + arc.ArchiveString( &targetname ); + arc.ArchiveString( &animname ); + + // Don't archive, just for debugging + // float drawtime; + + arc.ArchiveFloat( &occupiedTime ); + arc.ArchiveInteger( &entnum ); + + // Don't archive, only used between frames + //pathlist_t inlist; + + // Don't archive, not used + //qboolean reject; + + // Don't archive, already taken care of + // EntityPtr targetEntity; + + if ( arc.Loading() && !LoadingSavegame ) + { + occupiedTime = 0; + entnum = 0; + } + + int numChildren = NumberOfConnections(); + arc.ArchiveInteger( &numChildren ); + if ( arc.Loading() ) + { + for( i = 0; i < numChildren; i++ ) + { + PathNodeConnection *newConnection = new PathNodeConnection(); + arc.ArchiveInteger( &newConnection->targetNodeIndex ); + arc.ArchiveShort( &newConnection->moveCost ); + arc.ArchiveRaw( newConnection->maxheight, sizeof( newConnection->maxheight ) ); + arc.ArchiveInteger( &newConnection->door ); + _connections.AddObject( newConnection ); + } + } + else + { + for( i = 0; i < numChildren; i++ ) + { + arc.ArchiveInteger( &_connections[i]->targetNodeIndex ); + arc.ArchiveShort( &_connections[i]->moveCost ); + arc.ArchiveRaw( _connections[i]->maxheight, sizeof( _connections[i]->maxheight ) ); + arc.ArchiveInteger( &_connections[i]->door ); + } + } + + if ( arc.Loading() ) + { + if ( !LoadingSavegame ) + { + // Fixup the doors + PostEvent( EV_Path_FindEntities, 0.0f ); + } + thePathManager.AddNode( this ); + thePathManager.AddNodeToGrid( this ); + } +} + +void PathNode::FindEntities( Event *ev ) +{ + for( int i = 0; i < NumberOfConnections(); i++ ) + { + PathNodeConnection *connection = _connections[ i ]; + if ( connection->door ) + { + PathNode *node = thePathManager.GetNode( connection->targetNodeIndex ); + + assert( node ); + + Door *door = CheckDoor( node->origin ); + if ( door ) + { + connection->door = door->entnum; + } + else + { + connection->door = 0; + } + } + } +} + +qboolean PathNode::TestMove( Entity *ent, const Vector &original_start, const Vector &original_end, const Vector &min, + const Vector &max, qboolean allowdoors, qboolean fulltest ) +{ + // NOTE: TestMove may allow wide paths to succeed when they shouldn't since it + // may place the lower node above obstacles that actors can't step over. + // Since any path that's wide enough for large boxes must also allow + // thinner boxes to go through, you must ignore the results of TestMove + // when thinner checks have already failed. + trace_t trace; + Vector end_trace; + Vector pos; + Vector dir; + float t; + float dist; + Vector start; + Vector end; + + // By requiring that paths have STEPSIZE headroom above the path, we simplify the test + // to see if an actor can move to a node down to a simple trace. By stepping up the start + // and end points, we account for the actor's ability to step up any geometry lower than + // STEPSIZE in height. + + start = original_start; + end = original_end; + + start.z += STEPSIZE; + end.z += STEPSIZE; + + // Check the move + trace = G_Trace( start, min, max, end, ent, MASK_PATHSOLID, false, "PathNode::TestMove 1" ); + if ( trace.startsolid || ( trace.fraction != 1.0f ) ) + { + // No direct path available. The actor will most likely not be able to move through here. + return false; + } + + if ( !fulltest ) + { + // Since we're not doing a full test (full tests are only done when we're connecting nodes to save time), + // we test to see if the midpoint of the move would only cause a change in height of STEPSIZE + // from the predicted height. This prevents most cases where a dropoff lies between a actor and a node. + Vector pos; + + // Since we start and end are already STEPSIZE above the ground, we have to check twice STEPSIZE below + // the midpoint to see if the midpoint is on the ground. + dir = end - start; + pos = start + ( dir * 0.5f ); + end_trace = pos; + end_trace.z -= STEPSIZE * 2.0f; + + // Check that the midpos is onground. This may fail on ok moves, but a true test would be too slow + // to do in real time. Also, we may miss cases where a dropoff exists before or after the midpoint. + // Once the actor is close enough to the drop off, it will discover the fall and hopefully try + // another route. + trace = G_Trace( pos, min, max, end_trace, ent, MASK_PATHSOLID, false, "PathNode::TestMove 2" ); + if ( trace.startsolid || ( trace.fraction == 1.0f ) ) + { + // We're not on the ground, so there's a good posibility that we can't make this move without falling. + return false; + } + } + else //if ( !( contents & MASK_WATER ) ) + { + // When we're creating the paths during load time, we do a much more exhaustive test to see if the + // path is valid. This test takes a bounding box and moves it 8 units at a time closer to the goal, + // testing the ground after each move. The test involves checking whether we will fall more than + // STEPSIZE to the ground (since we've raised the start and end points STEPSIZE above the ground, + // we must actually test 2 * STEPSIZE down to see if we're on the ground). After each test, we set + // the new height of the box to be STEPSIZE above the ground. Each move closer to the goal is only + // done horizontally to simulate how the actors normally move. This method ensures that any actor + // wider than 8 units in X and Y will be able to move from start to end. + // + // NOTE: This may allow wide paths to succeed when they shouldn't since it + // may place the lower node above obstacles that actors can't step over. + // Since any path that's wide enough for large boxes must also allow + // thinner boxes to go through, you must ignore the results of TestMove + // when thinner checks have already failed. + + dir = end - start; + dir.z = 0.0f; + dist = dir.length(); + dir *= 1.0f / dist; + + // check the entire move + pos = start; + for( t = 0.0f ; t < dist; t += 8.0f ) + { + // Move the box to our position along the path and make our downward trace vector + end_trace.x = pos.x = start.x + ( t * dir.x ); + end_trace.y = pos.y = start.y + ( t * dir.y ); + end_trace.z = pos.z - ( STEPSIZE * 2.0f ); + + // check the ground + trace = G_Trace( pos, min, max, end_trace, ent, MASK_PATHSOLID, false, "PathNode::TestMove 3" ); + if ( trace.startsolid || ( trace.fraction == 1.0f ) ) + { + // Either we're stuck in something solid, or we would fall farther than STEPSIZE to the ground, + // so the path is not acceptable. + return false; + } + + // move the box to STEPSIZE above the ground. + pos.z = trace.endpos[ 2 ] + STEPSIZE; + } + } + + return true; +} + +qboolean PathNode::CheckMove( Entity *ent, const Vector &pos, const Vector &minPos, const Vector &maxPos, qboolean allowdoors, + qboolean fulltest ) +{ + // Since we need to support actors of variable widths, we need to do some special checks when a potential + // path goes up or down stairs. Placed pathnodes are only 16x16 in width, so when they are dropped to the + // ground, they may end in a position where a larger box would not fit. Making the pathnodes larger + // would make it hard to place paths where smaller actors could go, and making paths of various sizes would + // be overkill (more work for the level designer, or a lot of redundant data). The solution is to treat + // paths with verticle movement differently than paths that are purely horizontal. For horizontal moves, + // a simple trace STEPSIZE above the ground will be sufficient to prove that we can travel from one node + // to another, in either direction. For moves that have some change in height, we can check that we have + // a clear path by tracing horizontally from the higher node to a point where larger bounding box actors + // could then move at a slope downward to the lower node. This fixes the problem where path points that + // are larger than the depth of a step would have to intersect with the step in order to get the center + // of the box on solid ground. If you're still confused, well, tough. :) Think about the problem of + // larger bounding boxes going up stairs for a bit and you should see the problem. You can also read + // section 8.4, "Translating a Convex Polygon", from Computational Geometry in C (O'Rourke) (a + // great book, BTW) for information on similar problems (which is also a good explanation of how + // Quake's collision detection works). + trace_t trace; + int height; + + height = ( int )fabs( pos.z - origin.z ); + // Check if the path is strictly horizontal + if ( !height ) + { + // We do two traces for the strictly horizontal test. One normal, and one STEPSIZE + // above. The normal trace is needed because most doors in the game aren't tall enough + // to allow actors to trace STEPSIZE above the ground. This means that failed horizontal + // tests require two traces. Annoying. + trace = G_Trace( origin, minPos, maxPos, pos, ent, MASK_PATHSOLID, false, "PathNode::CheckMove 1" ); + if ( !trace.startsolid && ( trace.fraction == 1.0f ) ) + { + return true; + } + + // Do the step test + return TestMove( ent, pos, origin, minPos, maxPos, allowdoors, fulltest ); + } + + Vector size; + float width; + + size = maxPos - minPos; + width = max( size.x, size.y ); + + // if our bounding box is smaller than that of the pathnode, we can do the standard trace. + if ( width <= 32.0f ) + { + return TestMove( ent, pos, origin, minPos, maxPos, allowdoors, fulltest ); + } + + Vector start; + Vector end; + Vector delta; + float radius; + float len; + + // We calculate the radius of the smallest cylinder that would contain the bounding box. + // In some cases, this would make the first horizontal move longer than it needs to be, but + // that shouldn't be a problem. + + // multiply the width by 1/2 the square root of 2 to get radius + radius = width * 1.415f * 0.5f; + + // Make sure that our starting position is the higher node since it doesn't matter which + // direction the move is in. + if ( pos.z < origin.z ) + { + start = origin; + end = pos; + } + else + { + start = pos; + end = origin; + } + + // If the distance between the two points is less than the radius of the bounding box, + // then we only have to do the horizontal test since larger bounding boxes would not fall. + delta = end - start; + len = delta.length(); + if ( len <= radius ) + { + end.z = start.z; + return TestMove( ent, start, end, minPos, maxPos, allowdoors, fulltest ); + } + + Vector mid; + + // normalize delta and multiply by radius (saving a few multiplies by combining into one). + delta *= radius / len; + + mid = start; + mid.x += delta.x; + mid.y += delta.y; + + // Check the horizontal move + if ( !TestMove( ent, start, mid, minPos, maxPos, allowdoors, fulltest ) ) + { + return false; + } + + // Calculate our new endpoint + end.z -= delta.z; + + // Check our new sloping move + return TestMove( ent, mid, end, minPos, maxPos, allowdoors, fulltest ); +} + +Door *PathNode::CheckDoor( const Vector &pos ) +{ + trace_t trace; + Entity *ent; + + trace = G_Trace( origin, vec_zero, vec_zero, pos, NULL, MASK_PATHSOLID, false, "PathNode::CheckDoor" ); + + if ( trace.ent ) + { + ent = trace.ent->entity; + } + else + { + ent = NULL; + } + if ( ent && ent->isSubclassOf( Door ) ) + { + return ( Door * )ent; + } + + return NULL; +} + +qboolean PathNode::CheckMove( const Vector &pos, const Vector &min, const Vector &max ) +{ + return true; +} + +qboolean PathNode::CheckPath( const PathNode *node, const Vector &min, const Vector &max, qboolean fulltest ) +{ + Vector delta; + qboolean allowdoors; + qboolean result; + + delta = node->origin - origin; + + // Make sure nodes aren't too high above or below each other + + if ( ( delta[ 2 ] < -PATHMAP_MAX_DIST ) || ( delta[ 2 ] > PATHMAP_MAX_DIST ) ) + return false; + + delta[ 2 ] = 0; + + // Make sure nodes aren't too far from each other + + if ( delta.length() >= PATHMAP_MAX_DIST ) + { + return false; + } + + allowdoors = ( nodeflags & AI_DOOR ) && ( node->nodeflags & AI_DOOR ); + + result = CheckMove( NULL, node->origin, min, max, allowdoors, fulltest ); + //RestoreEnts(); + + return result; +} + +qboolean PathNode::ClearPathTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ], qboolean fulltest ) +{ + int i; + int width; + int bottom; + int top; + Vector min; + Vector max; + Vector bmin; + Vector bmax; + qboolean path; + int touch[ MAX_GENTITIES ]; + Entity *ent; + int num; + + path = false; + for( i = 0; i < NUM_WIDTH_VALUES; i++ ) + { + maxheight[ i ] = 0; + } + + width = (int)(NUM_WIDTH_VALUES * WIDTH_STEP * 0.5f); + min = Vector( -width, -width, 0.0f ); + max = Vector( width, width, MAX_HEIGHT ); + G_CalcBoundsOfMove( origin, node->origin, min, max, &bmin, &bmax ); + + num = gi.AreaEntities( bmin, bmax, touch, MAX_GENTITIES, qfalse ); + //num = gi.BoxEdicts( bmin, bmax, touch, MAX_GENTITIES, AREA_SOLID ); + for( i = 0; i < num; i++ ) + { + ent = g_entities[ touch[ i ] ].entity; + if ( ent && ent->isSubclassOf( Door ) ) + { + ent->unlink(); + } + } + + for( i = 0; i < NUM_WIDTH_VALUES; i++ ) + { + width = (int)(( i + 1 ) * WIDTH_STEP * 0.5f); + + min.x = min.y = -width; + max.x = max.y = width; + + // Perform a binary search to find the height of the path. Neat, eh? :) + bottom = 0; + top = MAX_HEIGHT; + int height = MAX_HEIGHT & ~0x3; + while( top >= bottom ) + { + // height = ( ( bottom + top + 3 ) >> 1 ) & ~0x3; + if ( !height ) + { + break; + } + + max.z = ( float )height; + if ( !CheckPath( node, min, max, fulltest ) ) + { + top = height - 4; + } + else + { + bottom = height + 4; + maxheight[ i ] = height; + } + height = ( ( bottom + top + 3 ) >> 1 ) & ~0x3; + } + + if ( !maxheight[ i ] ) + { + // If no paths were available at this width, don't allow any wider paths. + // CheckPath uses TestMove which may allow wide paths to succeed when they + // shouldn't since it may place the lower node above obstacles that actors + // can't step over. Since any path that's wide enough for large boxes must + // also allow thinner boxes to go through, this check avoids the hole in + // TestMove's functioning. + break; + } + + path = true; + } + + // Restore the doors + for( i = 0; i < num; i++ ) + { + ent = g_entities[ touch[ i ] ].entity; + if ( ent && ent->isSubclassOf( Door ) ) + { + ent->link(); + } + } + + return path; +} + +qboolean PathNode::LadderTo( const PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ] ) +{ + int i; + int j; + int m; + int width; + Vector min; + Vector max; + qboolean path; + + trace_t trace; + + if ( !( nodeflags & AI_LADDER ) || !( node->nodeflags & AI_LADDER ) ) + { + return false; + } + + if ( ( origin.x != node->origin.x ) || ( origin.y != node->origin.y ) ) + { + return false; + } + + path = false; + + for( i = 0; i < NUM_WIDTH_VALUES; i++ ) + { + width = (int)(( i + 1 ) * WIDTH_STEP * 0.5f); + min = Vector( -width, -width, 12.0f ); + max = Vector( width, width, 40.0f ); + + trace = G_Trace( origin, min, max, node->origin, NULL, MASK_PATHSOLID, false, "PathNode::LadderTo 1" ); + if ( ( trace.fraction != 1.0f ) || trace.startsolid ) + { + maxheight[ i ] = 0; + continue; + } + + path = true; + + m = 40; + for( j = 48; j < MAX_HEIGHT; j+= 8 ) + { + max.z = j; + trace = G_Trace( origin, min, max, node->origin, NULL, MASK_PATHSOLID, false, "PathNode::LadderTo 2" ); + if ( ( trace.fraction != 1.0f ) || trace.startsolid ) + { + break; + } + + m = j; + } + + maxheight[ i ] = m; + } + + return path; +} + +qboolean PathNode::ConnectedTo( PathNode *node ) +{ + int i; + + for( i = 0; i < NumberOfConnections(); i++ ) + { + if ( thePathManager.GetNode( _connections[ i ]->targetNodeIndex ) == node ) + { + return true; + } + } + + return false; +} + +void PathNode::ConnectTo( PathNode *node, const byte maxheight[ NUM_WIDTH_VALUES ], float cost, const Door *door ) +{ + int i; + + if ( node != this ) + { + PathNodeConnection *newConnection = new PathNodeConnection(); + newConnection->targetNodeIndex = thePathManager.GetPathNodeIndex( *node ); + for( i = 0; i < NUM_WIDTH_VALUES; i++ ) + { + newConnection->maxheight[ i ] = maxheight[ i ]; + } + newConnection->moveCost = ( int )cost; + newConnection->door = door ? door->entnum : 0; + AddConnection( *newConnection ); + } +} + +void PathNode::ConnectTo( PathNode *node, const byte maxheight[ NUM_WIDTH_VALUES ] ) +{ + Vector delta; + Door *door; + + door = CheckDoor( node->origin ); + delta = node->origin - origin; + ConnectTo( node, maxheight, delta.length(), door ); +} + +void PathNode::FindChildren( Event *ev ) +{ + trace_t trace; + Vector end; + Vector start; + + thePathManager.SetPathNodesCalculated( true ); + + origin.x = (float)( ( int )( origin.x * 0.125f ) * 8 ); + origin.y = (float)( ( int )( origin.y * 0.125f ) * 8 ); + setOrigin( origin ); + + //if ( !( contents & MASK_WATER ) ) + { + start = origin + Vector( "0 0 1" ); + end = origin; + end[ 2 ] -= 256.0f; + + trace = G_Trace( start, mins, maxs, end, NULL, MASK_PATHSOLID, false, "PathNode::FindChildren" ); + if ( ( trace.fraction != 1.0f ) && !trace.allsolid ) + { + setOrigin( trace.endpos ); + } + } + + thePathManager.AddNodeToGrid( this ); + + thePathManager.CancelEventsOfType( EV_AI_OptimizeNodes ); + thePathManager.PostEvent( EV_AI_OptimizeNodes, 0.0f ); +} + +void PathNode::DrawConnections( void ) +{ + for( int i = 0; i < NumberOfConnections(); i++ ) + { + PathNode *node = thePathManager.GetNode( _connections[ i ]->targetNodeIndex ); + + G_DebugLine( origin + Vector( 0, 0, 24 ), node->origin + Vector( 0, 0, 24 ), 0.7f, 0.7f, 0.0f, 1.0f ); + } +} + +void PathManager::DrawAllConnections( void ) +{ + PathNode *n; + Vector down; + Vector up; + Vector dir; + Vector p1; + Vector p2; + Vector p3; + Vector playerorigin; + qboolean showroutes; + qboolean shownums; + qboolean draw; + int maxheight; + int pathnum; + + showroutes = ( ai_showroutes->integer != 0 ); + shownums = ( ai_shownodenums->integer != 0 ); + + if ( ( ai_showroutes->integer == 1 ) || ( ai_showroutes->integer == 0 ) ) + { + pathnum = ( 32 / WIDTH_STEP ) - 1; + } + else + { + pathnum = ( ( ( int )ai_showroutes->integer ) / WIDTH_STEP ) - 1; + } + + if ( ( pathnum < 0 ) || ( pathnum >= MAX_WIDTH ) ) + { + gi.Printf( "ai_showroutes: Value out of range\n" ); + gi.cvar_set( "ai_showroutes", "0" ); + return; + } + + // Figure out where the camera is + + if ( !g_entities[ 0 ].client ) + return; + + playerorigin.x = g_entities[ 0 ].client->ps.origin[ 0 ]; + playerorigin.y = g_entities[ 0 ].client->ps.origin[ 1 ]; + playerorigin.z = g_entities[ 0 ].client->ps.origin[ 2 ]; + + playerorigin[ 2 ] += g_entities[ 0 ].client->ps.viewheight; + + for( int nodeNum = 0; nodeNum < _pathNodes.NumObjects(); nodeNum++ ) + { + PathNode *node = _pathNodes[ nodeNum ]; + if ( Vector( node->origin - playerorigin ).length() > ai_showroutes_distance->value ) + { + continue; + } + + if ( shownums ) + { + G_DrawDebugNumber( node->origin + Vector( 0.0f, 0.0f, 14.0f ), thePathManager.GetPathNodeIndex( *node ), 1.5f, 1.0f, 1.0f, 0.0f ); + } + + draw = false; + for( int i = 0; i < node->NumberOfConnections(); i++ ) + { + n = thePathManager.GetNode( node->_connections[ i ]->targetNodeIndex ); + + maxheight = node->_connections[ i ]->maxheight[ pathnum ]; + if ( maxheight == 0 ) + { + continue; + } + + draw = true; + + if ( !showroutes ) + { + continue; + } + + // don't draw the path if it's already been drawn by the destination node + if ( ( n->drawtime < level.time ) || !n->ConnectedTo( node ) ) + { + down.z = 2; + up.z = maxheight; + + if ( ( n->nodeflags & AI_JUMP ) && ( node->nodeflags & AI_JUMP ) ) + { + // These are jump nodes draw, them in blue instead of green + G_DebugLine( node->origin + down, n->origin + down, 0.0f, 0.0f, 1.0f, 1.0f ); + G_DebugLine( n->origin + down, n->origin + up, 0.0f, 0.0f, 1.0f, 1.0f ); + G_DebugLine( node->origin + up, n->origin + up, 0.0f, 0.0f, 1.0f, 1.0f ); + G_DebugLine( node->origin + up, node->origin + down, 0.0f, 0.0f, 1.0f, 1.0f ); + } + else + { + G_DebugLine( node->origin + down, n->origin + down, 0.0f, 1.0f, 0.0f, 1.0f ); + G_DebugLine( n->origin + down, n->origin + up, 0.0f, 1.0f, 0.0f, 1.0f ); + G_DebugLine( node->origin + up, n->origin + up, 0.0f, 1.0f, 0.0f, 1.0f ); + G_DebugLine( node->origin + up, node->origin + down, 0.0f, 1.0f, 0.0f, 1.0f ); + } + } + + // draw an arrow for the direction + dir.x = n->origin.x - node->origin.x; + dir.y = n->origin.y - node->origin.y; + dir.normalize(); + + p1 = node->origin; + p1.z += maxheight * 0.5f; + p2 = dir * 8.0f; + p3 = p1 + p2 * 2.0f; + + G_DebugLine( p1, p3, 0.0f, 1.0f, 0.0f, 1.0f ); + + p2.z += 8.0f; + G_DebugLine( p3, p3 - p2, 0.0f, 1.0f, 0.0f, 1.0f ); + + p2.z -= 16.0f; + G_DebugLine( p3, p3 - p2, 0.0f, 1.0f, 0.0f, 1.0f ); + } + + if ( !draw ) + { + // Put a little X where the node is to show that it had no connections + p1 = node->origin; + p1.z += 2.0f; + + p2 = Vector( 12.0f, 12.0f, 0.0f ); + G_DebugLine( p1 - p2, p1 + p2, 1.0f, 0.0f, 0.0f, 1.0f ); + + p2.x = -12.0f; + G_DebugLine( p1 - p2, p1 + p2, 1.0f, 0.0f, 0.0f, 1.0f ); + } + + node->drawtime = level.time; + } +} + +MapCell::MapCell() +{ + Init(); +} + +MapCell::~MapCell() +{ + _pathNodes.ClearObjectList(); +} + +void MapCell::Init( void ) +{ +} + +qboolean MapCell::AddNode( PathNode *node ) +{ + _pathNodes.AddObject( node ); + return true; +} + +qboolean MapCell::RemoveNode( PathNode *node ) +{ + // Remove the node from the global list if it is still in the list + + if ( _pathNodes.ObjectInList( node ) ) + _pathNodes.RemoveObject( node ); + + return true; +} + +PathNode *MapCell::GetNode( int index ) +{ + return _pathNodes[ index ]; +} + +int MapCell::NumNodes( void ) +{ + return _pathNodes.NumObjects(); +} + +/* All + work and no play + makes Jim a dull boy. All + work and no play makes Jim a + dull boy. All work and no play + makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work and + no play makes Jim a dull boy. All work and + no play makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no play makes + Jim a dull boy. All work and no play makes Jim a + dull boy. All work and no play makes Jim a dull boy. + All work and no play makes Jim a dull boy. All work + and no play makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no play makes Jim a + dull boy. All work and no play makes Jim a dull boy. All + work and no play makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work and no play makes Jim + a dull boy. All work and no play makes Jim a dull boy. + All work and no play makes Jim a dull boy. All work and + no play makes Jim a dull boy. All work and no play makes + Jim a dull boy. All work and no play makes Jim a dull + boy. All work and no play makes Jim a dull boy. All work + and no play makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no play makes Jim a + dull boy. All work and no play makes Jim a dull boy. All + work and no play makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work and no play makes + Jim a dull boy. All work and no play makes Jim a dull + boy. All work and no play makes Jim a dull boy. All + work and no play makes Jim a dull boy. All work and + no play makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no play + makes Jim a dull boy. All work and no + play makes Jim a dull boy. All work + and no play makes Jim a dull boy. + All work and no play makes + Jim a dull boy. All work + and no play makes + Jim a +*/ + +CLASS_DECLARATION( Listener, PathManager, NULL ) +{ + { &EV_AI_SavePaths, &PathManager::SavePathsEvent }, + { &EV_AI_LoadNodes, &PathManager::LoadNodes }, + { &EV_AI_SaveNodes, &PathManager::SaveNodes }, + { &EV_AI_ClearNodes, &PathManager::ClearNodes }, + { &EV_AI_OptimizeNodes, &PathManager::OptimizeNodes }, + { &EV_AI_SetNodeFlags, &PathManager::SetNodeFlagsEvent }, + { &EV_AI_RecalcPaths, &PathManager::RecalcPathsEvent }, + { &EV_AI_CalcPath, &PathManager::CalcPathEvent }, + { &EV_AI_CalcAllPaths, &PathManager::CalcAllPaths }, + + { &EV_AI_ConnectNodes, &PathManager::connectNodes }, + { &EV_AI_DisconnectNodes, &PathManager::disconnectNodes }, + + { NULL, NULL } +}; + + +PathManager::PathManager() +{ + thePathManager.ResetNodes(); +} + +PathManager::~PathManager() +{ + thePathManager.ResetNodes(); +} + +void PathManager::AddToGrid( PathNode *node, int x, int y ) +{ + PathNode *node2; + MapCell *cell; + int numnodes; + int i; + int j; + byte maxheight[ NUM_WIDTH_VALUES ]; + + cell = GetMapCell( x, y ); + if ( !cell ) + { + return; + } + + if ( !cell->AddNode( node ) ) + { + warning( "AddToGrid", "Node overflow at ( %d, %d )\n", x, y ); + return; + } + + if ( !IsLoadingArchive() ) + { + // + // explicitly link up the targets and their destinations + // + if ( node->nodeflags & AI_JUMP ) + { + if ( node->target.length() > 1 ) + { + node2 = FindNode( node->target.c_str() ); + if ( node2 && !node->ConnectedTo( node2 ) ) + { + for( j = 0; j < NUM_WIDTH_VALUES; j++ ) + { + maxheight[ j ] = MAX_HEIGHT; + } + + if ( node != node2 ) + node->ConnectTo( node2, maxheight ); + else + gi.WDPrintf( "Can't connect pathnode to itself (%s)", node->target.c_str() ); + } + } + } + + // Connect the node to its neighbors + numnodes = cell->NumNodes(); + for( i = 0; i < numnodes; i++ ) + { + node2 = ( PathNode * )cell->GetNode( i ); + if ( node2 == node ) + { + continue; + } + + if ( !node->ConnectedTo( node2 ) ) + { + if ( node->ClearPathTo( node2, maxheight ) || node->LadderTo( node2, maxheight ) ) + { + node->ConnectTo( node2, maxheight ); + } + } + + if ( !node2->ConnectedTo( node ) ) + { + if ( node2->ClearPathTo( node, maxheight ) || node2->LadderTo( node, maxheight ) ) + { + node2->ConnectTo( node, maxheight ); + } + } + } + } +} + +qboolean PathManager::RemoveFromGrid( PathNode *node, int x, int y ) +{ + MapCell *cell = GetMapCell( x, y ); + if ( !cell->RemoveNode( node ) ) + { + return false; + } + + return true; +} + +int PathManager::NodeCoordinate( float coord ) +{ + return ( ( int )coord + MAX_WORLD_COORD - ( PATHMAP_CELLSIZE / 2 ) ) / PATHMAP_CELLSIZE; +} + +int PathManager::GridCoordinate( float coord ) +{ + return ( ( int )coord + MAX_WORLD_COORD ) / PATHMAP_CELLSIZE; +} + +void PathManager::AddNodeToGrid( PathNode *node ) +{ + int x; + int y; + + assert( node ); + + if ( !IsLoadingArchive() ) + { + gi.DPrintf( "." ); + } + + + x = NodeCoordinate( node->origin[ 0 ] ); + y = NodeCoordinate( node->origin[ 1 ] ); + + AddToGrid( node, x, y ); + AddToGrid( node, x + 1, y ); + AddToGrid( node, x, y + 1 ); + AddToGrid( node, x + 1, y + 1 ); + + node->gridX = x; + node->gridY = y; +} + +void PathManager::RemoveNodeFromGrid( PathNode *node ) +{ + int x; + int y; + + assert( node ); + + x = node->gridX; + y = node->gridY; + + RemoveFromGrid( node, x, y ); + RemoveFromGrid( node, x + 1, y ); + RemoveFromGrid( node, x, y + 1 ); + RemoveFromGrid( node, x + 1, y + 1 ); +} + +void PathManager::UpdateNode( PathNode *node ) +{ + int x; + int y; + int mx; + int my; + + assert( node ); + + x = NodeCoordinate( node->origin[ 0 ] ); + y = NodeCoordinate( node->origin[ 1 ] ); + + mx = node->gridX; + my = node->gridY; + + RemoveFromGrid( node, mx, my ); + RemoveFromGrid( node, mx + 1, my ); + RemoveFromGrid( node, mx, my + 1 ); + RemoveFromGrid( node, mx + 1, my + 1 ); + + AddToGrid( node, x, y ); + AddToGrid( node, x + 1, y ); + AddToGrid( node, x, y + 1 ); + AddToGrid( node, x + 1, y + 1 ); + + node->gridX = x; + node->gridY = y; +} + +MapCell *PathManager::GetMapCell( int x, int y ) +{ + if ( _mapCells[x][y] == NULL ) + { + _mapCells[x][y] = new MapCell(); + } + return _mapCells[x][y]; +} + +MapCell *PathManager::GetMapCell( const Vector &pos ) +{ + int x; + int y; + + x = GridCoordinate( pos[ 0 ] ); + y = GridCoordinate( pos[ 1 ] ); + + return GetMapCell( x, y ); +} + +PathNode *PathManager::NearestNode( const Vector &pos, Entity *ent, const bool usebbox, const bool checkWalk ) +{ + Vector delta; + PathNode *node; + PathNode *bestnode; + float bestdist; + float dist; + int n; + int i; + MapCell *cell; + Vector min; + Vector max; + + cell = GetMapCell( pos ); + if ( !cell ) + { + return NULL; + } + + if ( ent && usebbox ) + { + min = ent->mins; + max = ent->maxs; + } + else + { + min = Vector( -16.0f, -16.0f, 12.0f ); + max = Vector( 16.0f, 16.0f, 40.0f ); + } + + n = cell->NumNodes(); + + if ( ai_debugpath->integer ) + { + gi.DPrintf( "NearestNode: Checking %d nodes\n", n ); + } + + bestnode = NULL; + bestdist = 999999999; // greater than ( 8192 * sqr(2) ) ^ 2 -- maximum squared distance + for( i = 0; i < n; i++ ) + { + node = ( PathNode * )cell->GetNode( i ); + if ( !node ) + { + continue; + } + + if ( node->NumberOfConnections() == 0 ) + continue; + + delta = node->origin - pos; + + // get the distance squared (faster than getting real distance) + dist = delta * delta; + + if ( abs( (int)delta[ 2 ] ) > 128 ) + continue; + + if ( dist > 256 * 256 ) + continue; + + if ( ( dist < bestdist ) )// && node->CheckMove( ent, pos, min, max, false, false ) ) + { + if ( checkWalk && !node->CheckMove( ent, pos, min, max, false, false ) ) + continue; + + bestnode = node; + bestdist = dist; + + // if we're close enough, early exit + if ( dist < 16.0f ) + { + break; + } + } + } + + return bestnode; +} + +void PathManager::Teleport( const Entity *teleportee, const Vector &from, const Vector &to ) +{ + PathNode *node1; + PathNode *node2; + byte maxheight[ NUM_WIDTH_VALUES ]; + int j; + + if ( ai_createnodes->integer ) + { + node1 = new PathNode; + node1->Setup( from ); + + node2 = new PathNode; + node2->Setup( to ); + + // FIXME + // shouldn't hard-code width and height + for( j = 0; j < NUM_WIDTH_VALUES; j++ ) + { + maxheight[ j ] = 72; + } + + // connect with 0 cost + node1->ConnectTo( node2, maxheight, 0.0f ); + } +} + +void PathManager::ShowNodes( void ) +{ + if ( ai_showroutes->integer || ai_shownodenums->integer ) + { + DrawAllConnections(); + } +} + +int PathManager::NumNodes( void ) +{ + return _pathNodes.NumObjects(); +} + +int PathManager::NumLoadNodes( void ) +{ + return numLoadNodes; +} + +void PathManager::Checksum( Vector &checksum ) +{ + checksum = pathNodesChecksum; +} + +void PathManager::Archive( Archiver &arc ) +{ + int i; + + if ( arc.Saving() ) + { + int num = _pathNodes.NumObjects(); + arc.ArchiveInteger( &num ); + for( i = 0; i < _pathNodes.NumObjects(); i++ ) + { + arc.ArchiveObject( _pathNodes[ i ] ); + } + + if ( ai_debuginfo->integer ) + { + gi.DPrintf( "Wrote %d path nodes\n", num ); + } + } + else + { + int x; + int y; + + _loadingArchive = true; + + // Get rid of the nodes that were spawned by the map + thePathManager.ResetNodes(); + CancelEventsOfType( EV_AI_CalcAllPaths ); + + // Init the grid + for( x = 0; x < PATHMAP_GRIDSIZE; x++ ) + { + for( y = 0; y < PATHMAP_GRIDSIZE; y++ ) + { + GetMapCell( x, y )->Init(); + } + } + int num; + arc.ArchiveInteger( &num ); + + for( i = 0; i < num; i++ ) + { + arc.ReadObject(); + } + + if ( ai_debuginfo && ai_debuginfo->integer ) + { + gi.DPrintf( "Path nodes loaded: %d\n", NumNodes() ); + } + + _loadingArchive = false; + } +} + +void PathManager::ClearNodes( Event *ev ) +{ + for( int i = 0; i < _pathNodes.NumObjects(); i++ ) + { + _pathNodes[ i ]->PostEvent( EV_Remove, 0.0f ); + } + + if ( ai_debuginfo->integer ) + { + gi.DPrintf( "Deleted %d path nodes\n", _pathNodes.NumObjects() ); + } +} + +void PathManager::SetNodeFlagsEvent( Event *ev ) +{ + const char * token; + int i, argnum; + int mask; + int action; + int nodenum; + PathNode *node; + + nodenum = ev->GetInteger( 1 ); + node = thePathManager.GetNode( nodenum ); + + if ( !node ) + { + ev->Error( "Node not found." ); + return; + } + + argnum = 2; + for ( i = argnum; i <= ev->NumArgs() ; i++ ) + { + token = ev->GetString( i ); + action = 0; + switch( token[0] ) + { + case '+': + action = FLAG_ADD; + token++; + break; + case '-': + action = FLAG_CLEAR; + token++; + break; + default: + ev->Error( "PathManager::SetNodeFlagsEvent", "First character is not '+' or '-', assuming '+'\n" ); + action = FLAG_ADD; + break; + } + + if (!strcmpi( token, "flee")) + { + mask = AI_FLEE; + } + else if (!strcmpi (token, "duck")) + { + mask = AI_DUCK; + } + else if (!strcmpi (token, "cover")) + { + mask = AI_COVER; + } + else if (!strcmpi (token, "door")) + { + mask = AI_DOOR; + } + else if (!strcmpi (token, "jump")) + { + mask = AI_JUMP; + } + else if (!strcmpi (token, "ladder")) + { + mask = AI_LADDER; + } + else if (!strcmpi (token, "action")) + { + mask = AI_ACTION; + } + else if (!strcmpi (token, "work")) + { + mask = AI_WORK; + } + else if (!strcmpi (token, "hibernate")) + { + mask = AI_HIBERNATE; + } + else + { + mask = 0; + action = FLAG_IGNORE; + ev->Error( "Unknown token %s.", token ); + } + + switch (action) + { + case FLAG_ADD: + node->nodeflags |= mask; + break; + + case FLAG_CLEAR: + node->nodeflags &= ~mask; + break; + + case FLAG_IGNORE: + break; + } + } +} + +void PathManager::CalcPathEvent( Event *ev ) +{ + int nodenum; + PathNode *node; + PathNode *node2; + int j; + byte maxheight[ NUM_WIDTH_VALUES ]; + + nodenum = ev->GetInteger( 1 ); + node = thePathManager.GetNode( nodenum ); + + nodenum = ev->GetInteger( 2 ); + node2 = thePathManager.GetNode( nodenum ); + + if ( !node || !node2 ) + { + ev->Error( "Node not found." ); + return; + } + + if ( !node->ConnectedTo( node2 ) ) + { + if ( node->ClearPathTo( node2, maxheight, false ) || node->LadderTo( node2, maxheight ) ) + { + node->ConnectTo( node2, maxheight ); + } + else if ( ( node->nodeflags & AI_JUMP ) && ( node->target == node2->targetname ) ) + { + //FIXME + // don't hardcode size + for( j = 0; j < NUM_WIDTH_VALUES; j++ ) + { + maxheight[ j ] = MAX_HEIGHT; + } + node->ConnectTo( node2, maxheight ); + } + } + + if ( !node2->ConnectedTo( node ) ) + { + if ( node2->ClearPathTo( node, maxheight, false ) || node2->LadderTo( node, maxheight ) ) + { + node2->ConnectTo( node, maxheight ); + } + else if ( ( node2->nodeflags & AI_JUMP ) && ( node2->target == node->targetname ) ) + { + //FIXME + // don't hardcode size + for( j = 0; j < NUM_WIDTH_VALUES; j++ ) + { + maxheight[ j ] = MAX_HEIGHT; + } + node2->ConnectTo( node, maxheight ); + } + } +} + +void PathManager::RecalcPathsEvent( Event *ev ) +{ + int nodenum; + PathNode *node; + + nodenum = ev->GetInteger( 1 ); + node = thePathManager.GetNode( nodenum ); + if ( node ) + { + UpdateNode( node ); + } + else + { + ev->Error( "Node not found." ); + } +} + +qboolean PathManager::CanDropPath( const PathNode *node, const PathNode *node2, const PathNodeConnection *path ) +{ + int i, j, k; + PathNode *inbetween_node; + PathNode *temp_node; + float dist; + float test_dist; + Vector delta; + qboolean found_better_path; + + // Calculate dist + + delta = node2->origin - node->origin; + dist = delta.length(); + + // Try to find an inbetween node + + for( i = 0 ; i < node->NumberOfConnections() ; i++ ) + { + inbetween_node = thePathManager.GetNode( node->_connections[ i ]->targetNodeIndex ); + + for( j = 0 ; j < inbetween_node->NumberOfConnections() ; j++ ) + { + temp_node = thePathManager.GetNode( inbetween_node->_connections[ j ]->targetNodeIndex ); + + if ( temp_node == node2 ) + { + found_better_path = true; + + // Calculate distance through inbetween node + + delta = node2->origin - inbetween_node->origin; + test_dist = delta.length(); + + delta = inbetween_node->origin - node->origin; + test_dist += delta.length(); + + // Make sure not too much longer + + if ( test_dist > ( dist * 1.25f ) ) + found_better_path = false; + + for( k = 0 ; k < NUM_WIDTH_VALUES ; k++ ) + { + // Make sure path is at least as wide + + if ( ( path->maxheight[ k ] > node->_connections[ i ]->maxheight[ k ] ) || + ( path->maxheight[ k ] > inbetween_node->_connections[ j ]->maxheight[ k ] ) ) + { + found_better_path = false; + break; + } + } + + // If we found a better path, we can drop this one + + if ( found_better_path ) + return true; + } + } + } + + // We didn't find a better path + + return false; +} + +void PathManager::OptimizeNodes( Event *ev ) +{ + + gi.ProcessLoadingScreen( "$$OptimizingPathnodeConnections$$" ); + + for( int i = 0; i < _pathNodes.NumObjects(); i++ ) + { + if ( _pathNodes[ i ] ) + { + PathNode *node = _pathNodes[ i ]; + + // See if we can drop any paths between this node and any of its children + for( int j = node->NumberOfConnections() - 1; j >= 0 ; j-- ) + { + PathNode *node2 = thePathManager.GetNode( node->_connections[ j ]->targetNodeIndex ); + if ( CanDropPath( node, node2, node->_connections[ j ] ) ) + { + node->RemoveConnection( j ); + } + } + } + } + + // Make sure nodes are saved + + _pathNodesCalculated = true; + + Com_Printf( "\nOptimized path nodes\n" ); +} + +qboolean PathManager::ArchiveNodes( const str &name, qboolean save ) +{ + Archiver arc; + qboolean success; + + if ( save ) + { + Vector checksum; + int tempInt; + + if ( !arc.Create( name, false ) ) + { + gi.WDPrintf( "***********************************\n" ); + gi.WDPrintf( "****** Couldn't write out pathfile\n" ); + gi.WDPrintf( "***********************************\n" ); + return false; + } + + tempInt = PATHFILE_VERSION; + arc.ArchiveInteger( &tempInt ); + tempInt = NumNodes(); + arc.ArchiveInteger( &tempInt ); + Checksum( checksum ); + arc.ArchiveVector( &checksum ); + arc.ArchiveObject( this ); + success = true; + } + else + { + int version; + + success = false; + arc.Read( name, false ); + arc.ArchiveInteger( &version ); + if ( version == PATHFILE_VERSION ) + { + int numnodes, file_numnodes; + Vector checksum, file_checksum; + + // get current values + numnodes = NumLoadNodes(); + Checksum( checksum ); + + // get file values + arc.ArchiveInteger( &file_numnodes ); + arc.ArchiveVector( &file_checksum ); + if ( + ( numnodes == file_numnodes ) && + ( checksum == file_checksum ) + ) + { + arc.ArchiveObject( this ); + if ( arc.NoErrors() ) + { + success = true; + } + } + else + { + gi.Printf( "Pathnodes have changed, rebuilding.\n" ); + } + } + else + { + gi.Printf( "Expecting version %d path file. Path file is version %d. ", PATHFILE_VERSION, version ); + } + + } + arc.Close(); + return success; +} + +void PathManager::SaveNodes( Event *ev ) +{ + str name; + + if ( ev->NumArgs() != 1 ) + { + gi.WPrintf( "Usage: ai_save [filename]\n" ); + return; + } + + name = ev->GetString( 1 ); + + gi.Printf( "Archiving\n" ); + + ArchiveNodes( name, true ); + + gi.Printf( "done.\n" ); + _pathNodesCalculated = false; +} + +void PathManager::LoadNodes( Event *ev ) +{ + Archiver arc; + str name; + bool rebuild; + + if ( ev->NumArgs() != 1 ) + { + gi.WPrintf( "Usage: ai_load [filename]\n" ); + return; + } + + gi.Printf( "Loading nodes...\n" ); + + name = ev->GetString( 1 ); + + rebuild = !ArchiveNodes( name, false ); + + if ( rebuild ) + { + // Only replace the file if this event was called from our init function (as opposed to the user + // calling us from the console) + if ( ( ev->GetSource() == EV_FROM_CODE ) ) + { + gi.Printf( "Replacing file.\n\n" ); + + // At this point, the nodes are still scheduled to find their neighbors, because we posted this event + // before we the nodes were spawned. Post the event with 0 delay so that it gets processed after all + // the nodes find their neighbors. + PostEvent( EV_AI_SavePaths, 0.0f ); + } + else + { + // otherwise, just let them know that the path file needs to be replaced. + gi.Printf( "Type 'ai_savepaths' at the console to replace the current path file.\n" ); + } + + // Print out something fairly obvious + gi.DPrintf( "***********************************\n" + "***********************************\n" + "\n" + "Creating paths...\n" + "\n" + "***********************************\n" + "***********************************\n" ); + } +} + +void PathManager::SavePaths( void ) +{ + str filename; + Event *ev; + + if ( IsLoadingArchive() ) + { + // force it to zero since we probably had an error + gi.cvar_set( "ai_createnodes", "0" ); + } + + if ( + !IsLoadingArchive() && + ( + ( ai_createnodes && ai_createnodes->integer ) || + ( _pathNodesCalculated ) + ) + ) + { + filename = "maps/"; + filename += level.mapname; + filename += ".pth"; + + gi.DPrintf( "\nSaving path nodes to '%s'\n", filename.c_str() ); + + ev = new Event( EV_AI_SaveNodes ); + ev->AddString( filename ); + ProcessEvent( ev ); + } +} + +void PathManager::SavePathsEvent( Event *ev ) +{ + str temp; + + temp = ai_createnodes->string; + gi.cvar_set( "ai_createnodes", "1" ); + + SavePaths(); + + gi.cvar_set( "ai_createnodes", temp.c_str() ); +} + +void PathManager::Init( const char *mapname ) +{ + int x; + int y; + str filename; + Event *ev; + + pathNodesChecksum = vec_zero; + _pathNodesCalculated = false; + + gi.AddCommand( "ai_savepaths" ); + gi.AddCommand( "ai_save" ); + gi.AddCommand( "ai_load" ); + gi.AddCommand( "ai_clearnodes" ); + gi.AddCommand( "ai_recalcpaths" ); + + gi.AddCommand( "ai_connectNodes" ); + gi.AddCommand( "ai_disconnectNodes" ); + + ai_createnodes = gi.cvar ("ai_createnodes", "0", 0); + ai_debugpath = gi.cvar ("ai_debugpath", "0", 0); + ai_debuginfo = gi.cvar ("ai_debuginfo", "0", 0); + ai_showroutes = gi.cvar ("ai_showroutes", "0", 0); + ai_showroutes_distance = gi.cvar ("ai_showroutes_distance", "1000", 0); + ai_shownodenums = gi.cvar ("ai_shownodenums", "0", 0); + ai_timepaths = gi.cvar ("ai_timepaths", "0", 0); + ai_advanced_pathfinding = gi.cvar ("ai_advanced_pathfinding", "1", 0); + + numLoadNodes = 0; + _loadingArchive = false; + + // Get rid of the nodes that were spawned by the map + thePathManager.ResetNodes(); + + // Init the grid + for( x = 0; x < PATHMAP_GRIDSIZE; x++ ) + { + for( y = 0; y < PATHMAP_GRIDSIZE; y++ ) + { + GetMapCell( x, y )->Init(); + } + } + + if ( LoadingSavegame ) + { + // no need to go further here + return; + } + + if ( mapname ) + { + filename = "maps/"; + filename += mapname; + filename += ".pth"; + if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 ) + { + ev = new Event( EV_AI_LoadNodes ); + ev->AddString( filename ); + + // This can't happen until the world is spawned + PostEvent( ev, 0.0f ); + } + else + { + // Print out something fairly obvious + gi.DPrintf( "***********************************\n" + "***********************************\n" + "\n" + "No paths found. Creating paths...\n" + "\n" + "***********************************\n" + "***********************************\n" ); + } + } +} + +void PathManager::FindAllTargets( void ) +{ + for( int i = 0; i < _pathNodes.NumObjects(); i++ ) + { + PathNode *node = _pathNodes[ i ]; + + for ( int j = 0; j < MAX_GENTITIES; j++ ) + { + gentity_t *ed = &g_entities[ j ]; + + if ( !ed->inuse || !ed->entity ) + continue; + + Entity *entity = g_entities[ j ].entity; + + if ( entity->targetname == node->target ) + node->targetEntity = entity; + } + } +} + +void PathManager::CalcAllPaths( Event *ev ) +{ + for( int i = 0; i < _pathNodes.NumObjects(); i++ ) + { + PathNode *node = _pathNodes[ i ]; + + if ( node ) + { + str loadingStatus; + loadingStatus = "$$GeneratingPathnodeConnections$$"; + loadingStatus += i; + loadingStatus += " of "; + loadingStatus += _pathNodes.NumObjects(); + gi.ProcessLoadingScreen( loadingStatus.c_str() ); + + node->ProcessEvent( EV_Path_FindChildren ); + } + } +} + +int PathManager::NumberOfSpecialNodes( void ) const +{ + return _specialPathNodes.NumObjects(); +} + +PathNode * PathManager::GetSpecialNode( const int index ) const +{ + return (_specialPathNodes.ObjectAt( index ) ); +} + +void PathManager::AddSpecialNode( PathNode &pathNode ) +{ + _specialPathNodes.AddObject( &pathNode ); +} + +void PathManager::connectNodes( Event *ev ) +{ + connectNodes( ev->GetString( 1 ), ev->GetString( 2 ) ); +} + +void PathManager::connectNodes( const str &nodeName1, const str &nodeName2 ) +{ + PathNode *node1; + PathNode *node2; + + node1 = FindNode( nodeName1 ); + node2 = FindNode( nodeName2 ); + + connectConnection( node1, node2 ); + connectConnection( node2, node1 ); +} + +void PathManager::connectConnection( PathNode *node1, PathNode *node2 ) +{ + byte maxheight[ NUM_WIDTH_VALUES ]; + + if ( !node1 || !node2 ) + return; + + if ( node1 == node2 ) + return; + + if ( !node1->ConnectedTo( node2 ) ) + { + if ( node1->ClearPathTo( node2, maxheight ) || node1->LadderTo( node2, maxheight ) ) + { + node1->ConnectTo( node2, maxheight ); + } + } +} + +void PathManager::disconnectNodes( Event *ev ) +{ + disconnectNodes( ev->GetString( 1 ), ev->GetString( 2 ) ); +} + +void PathManager::disconnectNodes( const str &nodeName1, const str &nodeName2 ) +{ + PathNode *node1; + PathNode *node2; + + node1 = FindNode( nodeName1 ); + node2 = FindNode( nodeName2 ); + + disconnectConnection( node1, node2 ); + disconnectConnection( node2, node1 ); +} + +void PathManager::disconnectConnection( PathNode *node1, PathNode *node2 ) +{ + int i; + PathNodeConnection *connection; + PathNode *nodeToCheck; + + if ( !node1 || !node2 ) + return; + + for( i = 0 ; i < node1->NumberOfConnections() ; i++ ) + { + connection = node1->_connections[ i ]; + + if ( _pathNodes[ connection->targetNodeIndex ] ) + { + nodeToCheck = _pathNodes[ connection->targetNodeIndex ]; + + if ( nodeToCheck == node2 ) + { + node1->RemoveConnection( i ); + return; + } + } + } +} diff --git a/dlls/game/navigate.h b/dlls/game/navigate.h new file mode 100644 index 0000000..4602dd1 --- /dev/null +++ b/dlls/game/navigate.h @@ -0,0 +1,870 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/navigate.h $ +// $Revision:: 40 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:43a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Potentially could be an C++ implementation of the A* search algorithm, but +// is currently unfinished. +// + +#ifndef __NAVIGATE_H__ +#define __NAVIGATE_H__ + +#include "g_local.h" +#include "class.h" +#include "entity.h" +#include "stack.h" +#include "container.h" +#include "doors.h" +#include + + +extern Event EV_AI_SavePaths; +extern Event EV_AI_SaveNodes; +extern Event EV_AI_LoadNodes; +extern Event EV_AI_ClearNodes; +extern Event EV_AI_RecalcPaths; +extern Event EV_AI_CalcPath; + +extern cvar_t *ai_createnodes; +extern cvar_t *ai_shownodes; +extern cvar_t *ai_debugpath; +extern cvar_t *ai_debuginfo; +extern cvar_t *ai_showroutes; +extern cvar_t *ai_timepaths; +extern cvar_t *ai_advanced_pathfinding; + +extern int ai_maxnode; + +#define MAX_PATHCHECKSPERFRAME 4 + +extern int path_checksthisframe; + +#define MAX_PATH_LENGTH 512 // should be more than plenty +#define NUM_PATHSPERNODE 32 + +class Path; +class PathNode; + +#define NUM_WIDTH_VALUES 16 +#define WIDTH_STEP 8 +#define MAX_WIDTH ( WIDTH_STEP * NUM_WIDTH_VALUES ) +#define MAX_HEIGHT 128 + +#define CHECK_PATH( path, width, height ) \ + ( ( ( ( width ) >= MAX_WIDTH ) || ( ( width ) < 0 ) ) ? false : \ + ( ( int )( path )->maxheight[ ( ( width ) / WIDTH_STEP ) - 1 ] < ( int )( height ) ) ) + +class PathNodeConnection : public Class +{ +public: + CLASS_PROTOTYPE( PathNodeConnection ); + int targetNodeIndex; + short moveCost; + byte maxheight[ NUM_WIDTH_VALUES ]; + int door; + + virtual void Archive( Archiver &arc ) + { + arc.ArchiveInteger( &targetNodeIndex ); + arc.ArchiveShort( &moveCost ); + arc.ArchiveRaw( maxheight, sizeof( maxheight ) ); + arc.ArchiveInteger( &door ); + } +}; + + +typedef enum { NOT_IN_LIST, IN_OPEN, IN_CLOSED } pathlist_t; + +#define AI_FLEE (1<<0) +#define AI_DUCK (1<<1) +#define AI_COVER (1<<2) +#define AI_DOOR (1<<3) +#define AI_JUMP (1<<4) +#define AI_LADDER (1<<5) +#define AI_ACTION (1<<6) +#define AI_WORK (1<<7) +#define AI_HIBERNATE (1<<8) + + +class PathNode : public Listener + { + public: + Container + _connections; // these are the real connections between nodex + + // These variables are all used during the search + int f; + int h; + int g; + + int gridX; + int gridY; + + float drawtime; + float occupiedTime; + int entnum; + + pathlist_t inlist; + + // reject is used to indicate that a node is unfit for ending on during a search + qboolean reject; + + PathNode *Parent; + + // For the open and closed lists + PathNode *NextNode; + + unsigned int nodeflags; + float jumpAngle; + + EntityPtr targetEntity; + + friend class PathSearch; + friend void DrawAllConnections( void ); + qboolean ConnectedTo( PathNode *node ); + void ConnectTo( PathNode *node, const byte maxheight[ NUM_WIDTH_VALUES ], float cost, const Door *door = NULL ); + void ConnectTo( PathNode *node, const byte maxheight[ NUM_WIDTH_VALUES ] ); + + private : + qboolean TestMove( Entity *ent, const Vector &start, const Vector &end, const Vector &min, const Vector &max, qboolean allowdoors = false, qboolean fulltest = false ); + + + void FindChildren( Event *ev ); + void FindEntities( Event *ev ); + void SetNodeFlags( Event *ev ); + void SetOriginEvent( Event *ev ); + void SetAngle( Event *ev ); + void SetAngles( Event *ev ); + void SetAnim( Event *ev ); + void SetTargetname( Event *ev ); + void SetJumpAngle( Event *ev ); + void SetTarget( Event *ev ); + + public: + CLASS_PROTOTYPE( PathNode ); + + int contents; + Vector origin; + Vector angles; + Vector mins; + Vector maxs; + str targetname; + str target; + qboolean setangles; + str animname; + + PathNode(); + ~PathNode(); + + void Setup( const Vector &pos ); + void setAngles( const Vector &ang ); + void setOrigin( const Vector &org ); + void setSize( const Vector &min, const Vector &max ); + str &TargetName( void ); + virtual void Archive( Archiver &arc ); + + qboolean CheckPath( const PathNode *node, const Vector &min, const Vector &max, qboolean fulltest = true ); + Door *CheckDoor( const Vector &pos ); + + qboolean CheckMove( Entity *ent, const Vector &pos, const Vector &min, const Vector &max, qboolean allowdoors = false, qboolean fulltest = false ); + qboolean CheckMove( const Vector &pos, const Vector &min, const Vector &max ); + qboolean ClearPathTo( PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ], qboolean fulltest = true ); + qboolean LadderTo( const PathNode *node, byte maxheight[ NUM_WIDTH_VALUES ] ); + void DrawConnections( void ); + friend bool operator==( const PathNode &node1, const PathNode &node2 ) { return (node1.origin == node2.origin) != 0; } + friend bool operator!=( const PathNode &node1, const PathNode &node2 ) { return !( node1 == node2 ); } + int NumberOfConnections( void ) const { return _connections.NumObjects(); } + PathNodeConnection & GetConnection( const int index ) const { return *_connections[index]; } + void AddConnection( PathNodeConnection &connection ) { _connections.AddObject( &connection ); } + void RemoveConnection( const int index ) { delete _connections[index]; _connections.RemoveObjectAt( index + 1 ); } + }; + +typedef SafePtr PathNodePtr; + +//#define PATHMAP_CELLSIZE 2048 // Was 256 +#define PATHMAP_CELLSIZE 1024 // Was 256 +#define PATHMAP_MAX_DIST 256.0f +#define PATHMAP_GRIDSIZE ( WORLD_SIZE / PATHMAP_CELLSIZE ) // 65536 / 2048 = 32 was 8192 * 2 / 256 = 64 + +//#define PATHMAP_NODES 126 // 128 - sizeof( int ) / sizeof( short ) +#define PATHMAP_NODES ( 512 - sizeof( int ) / sizeof( short ) ) + +class MapCell : public Class + { + private : + Container _pathNodes; + //int numnodes; + //short nodes[ PATHMAP_NODES ]; + + public : + MapCell(); + ~MapCell(); + void Init( void ); + qboolean AddNode( PathNode *node ); + qboolean RemoveNode( PathNode *node ); + PathNode *GetNode( int index ); + int NumNodes( void ); + }; + +class PathManager : public Listener +{ +public: + CLASS_PROTOTYPE( PathManager ); + + PathManager(); + ~PathManager(); + void Archive( Archiver &arc ); + void AddToGrid( PathNode *node, int x, int y ); + void AddNode( PathNode *node ); + void AddNodeToGrid( PathNode *node ); + qboolean RemoveFromGrid( PathNode *node, int x, int y ); + void RemoveNodeFromGrid( PathNode *node ); + void RemoveNode( PathNode *node ); + void UpdateNode( PathNode *node ); + MapCell * LookupMapCell( const int x, const int y ) const; + MapCell * GetMapCell( const int x, const int y ) ; + MapCell * GetMapCell( const Vector &pos ); + PathNode * NearestNode( const Vector &pos, Entity *ent = NULL, const bool usebbox = true, const bool checkWalk = true ); + void Teleport( const Entity *teleportee, const Vector &from, const Vector &to ); + void ShowNodes( void ); + void Checksum( Vector &checksum ); + void SavePaths( void ); + void Init( const char *mapname ); + void InsertNodeIntoGrid( PathNode &node ); + void RemoveNodeFromGrid( PathNode &node ); + void InsertNodesIntoGrid( void ); + void ConnectToNeighbors( PathNode &node ); +// void ConnectPathNodes( void ); + void FindAllTargets( void ); + PathNode * FindNode( const char *name ); + PathNode * GetNode( int num ); + void RemoveNode( const PathNode *node ); + void ResetNodes( void ); + int NumNodes( void ); + int NumLoadNodes( void ); + bool IsLoadingArchive( void ) const { return _loadingArchive; } + void SetPathNodesCalculated( const bool pathNodesCalculated ) { _pathNodesCalculated = pathNodesCalculated; } + bool GetPathNodesCalculated( void ) const { return _pathNodesCalculated; } + int GetPathNodeIndex( PathNode &pathNode ) const { const int index = _pathNodes.IndexOfObject( &pathNode ) - 1 ; assert( index >= 0 ); return index;} + int NumberOfSpecialNodes( void ) const; + PathNode * GetSpecialNode( const int index ) const; + void AddSpecialNode( PathNode &pathNode ); + void OptimizeNodes( Event *ev ); + void DrawAllConnections( void ); + + void connectNodes( Event *ev ); + void connectNodes( const str &nodeName1, const str &nodeName2 ); + void connectConnection( PathNode *node1, PathNode *node2 ); + + void disconnectNodes( Event *ev ); + void disconnectNodes( const str &nodeName1, const str &nodeName2 ); + void disconnectConnection( PathNode *node1, PathNode *node2 ); + +private: + void ConnectToNeighbor( PathNode &node, const int x, const int y ); + bool RemoveFromGrid( PathNode &node, const int x, const int y ); + int NodeCoordinate( float coord ); + int GridCoordinate( float coord ); + void ClearNodes( Event *ev ); + void LoadNodes( Event *ev ); + void SaveNodes( Event *ev ); + qboolean CanDropPath( const PathNode *node, const PathNode *node2, const PathNodeConnection *path ); + qboolean ArchiveNodes( const str &name, qboolean save ); + void SavePathsEvent( Event *ev ); + void SetNodeFlagsEvent( Event *ev ); + void RecalcPathsEvent( Event *ev ); + void CalcPathEvent( Event *ev ); + void CalcAllPaths( Event *ev ); + + Container _pathNodes; + Container _specialPathNodes; + bool _loadingArchive; + bool _pathNodesCalculated; + + MapCell * _mapCells[PATHMAP_GRIDSIZE][PATHMAP_GRIDSIZE]; +}; + +extern PathManager thePathManager; + +//#define MAX_PATHNODES 2048 + +#include "path.h" + +template +class PathFinder : public Class + { + private: + Stack stack; + PathNode *OPEN; + PathNode *CLOSED; + PathNode *endnode; + int _maxNodesToCheck; + int _checkedNodes; + + void ClearPath( void ); + void ClearOPEN( void ); + void ClearCLOSED( void ); + PathNode *ReturnBestNode( void ); + void GenerateSuccessors( PathNode *BestNode ); + void Insert( PathNode *Successor ); + void PropagateDown( PathNode *Old ); + Path *CreatePath( PathNode *startnode ); + + public: + Heuristic heuristic; + + PathFinder(); + ~PathFinder(); + Path *FindPath( PathNode *from, PathNode *to, int maxNodesToCheck = 0 ); + Path *FindPath( const Vector &start, const Vector &end, int maxNodesToCheck = 0 ); + }; + +template +PathFinder::PathFinder() + { + OPEN = NULL; + CLOSED = NULL; + } + +template +PathFinder::~PathFinder() + { + ClearPath(); + } + +template +void PathFinder::ClearOPEN + ( + void + ) + + { + PathNode *node; + + while( OPEN ) + { + node = OPEN; + OPEN = node->NextNode; + + node->inlist = NOT_IN_LIST; + node->NextNode = NULL; + node->Parent = NULL; + + // reject is used to indicate that a node is unfit for ending on during a search + node->reject = false; + } + } + +template +void PathFinder::ClearCLOSED + ( + void + ) + + { + PathNode *node; + + while( CLOSED ) + { + node = CLOSED; + CLOSED = node->NextNode; + + node->inlist = NOT_IN_LIST; + node->NextNode = NULL; + node->Parent = NULL; + + // reject is used to indicate that a node is unfit for ending on during a search + node->reject = false; + } + } + +template +void PathFinder::ClearPath + ( + void + ) + + { + stack.Clear(); + ClearOPEN(); + ClearCLOSED(); + } + +template +Path *PathFinder::FindPath + ( + PathNode *from, + PathNode *to, + int maxNodesToCheck + ) + + { + Path *path; + PathNode *node; + int start = 0; + int end; + int count; + qboolean checktime; + + checktime = false; + if ( ai_timepaths->integer ) + { + start = gi.Milliseconds(); + checktime = true; + } + + OPEN = NULL; + CLOSED = NULL; + + endnode = to; + + // Should always be NULL at this point + assert( !from->NextNode ); + + // make Open List point to first node + OPEN = from; + from->g = 0; + from->h = heuristic.dist( from, endnode ); + from->NextNode = NULL; + + _maxNodesToCheck = maxNodesToCheck; + _checkedNodes = 0; + + node = ReturnBestNode(); + + count = 0; + while( node && !heuristic.done( node, endnode ) ) + { + count++; + GenerateSuccessors( node ); + node = ReturnBestNode(); + } + + //assert ( count < MAX_PATH_LENGTH ); + + if ( !node ) + { + path = NULL; + if ( ai_debugpath->integer ) + { + gi.WDPrintf( "Search failed--no path found.\n" ); + } + } + else + { + path = CreatePath( node ); + } + + ClearPath(); + + if ( checktime ) + { + end = gi.Milliseconds(); + if ( ai_timepaths->integer <= ( end - start ) ) + { + gi.DebugPrintf( "%d: ent #%d : %d\n", level.framenum, heuristic.entnum, end - start ); + } + } + + return path; + } + +template +Path *PathFinder::FindPath + ( + const Vector &start, + const Vector &end, + int maxNodesToCheck + ) + + { + PathNode *from; + PathNode *to; + Entity *ent; + + ent = G_GetEntity( heuristic.entnum ); + from = thePathManager.NearestNode( start, ent ); + to = thePathManager.NearestNode( end, ent ); + + if ( !from ) + { + if ( ai_debugpath->integer ) + { + gi.WDPrintf( "Search failed--couldn't find closest source.\n" ); + } + return NULL; + } + + if ( !from || !to ) + { + if ( ai_debugpath->integer ) + { + gi.WDPrintf( "Search failed--couldn't find closest destination.\n" ); + } + return NULL; + } + + return FindPath( from, to, maxNodesToCheck ); + } + +template +Path *PathFinder::CreatePath + ( + PathNode *startnode + ) + + { + PathNode *node; + Path *p; + int i; + int n; + PathNode *reverse[ MAX_PATH_LENGTH ]; + + // unfortunately, the list goes goes from end to start, so we have to reverse it + for( node = startnode, n = 0; ( node != NULL ) && ( n < MAX_PATH_LENGTH ); node = node->Parent, n++ ) + { + assert( n < MAX_PATH_LENGTH ); + reverse[ n ] = node; + } + + p = new Path( n ); + for( i = n - 1; i >= 0; i-- ) + { + p->AddNode( reverse[ i ] ); + } + + if ( ai_debugpath->integer ) + { + gi.DPrintf( "%d nodes in path\n", n ); + gi.DPrintf( "%d nodes allocated\n", thePathManager.NumNodes() ); + } + + return p; + } + +template +PathNode *PathFinder::ReturnBestNode + ( + void + ) + + { + PathNode *bestnode; + + if ( !OPEN || ( _maxNodesToCheck && _checkedNodes > _maxNodesToCheck ) ) + { + // No more nodes on OPEN + return NULL; + } + + // Pick node with lowest f, in this case it's the first node in list + // because we sort the OPEN list wrt lowest f. Call it BESTNODE. + + bestnode = OPEN; // point to first node on OPEN + OPEN = bestnode->NextNode; // Make OPEN point to nextnode or NULL. + + // Next take BESTNODE (or temp in this case) and put it on CLOSED + bestnode->NextNode = CLOSED; + CLOSED = bestnode; + + bestnode->inlist = IN_CLOSED; + + return( bestnode ); + } + +template +void PathFinder::GenerateSuccessors + ( + PathNode *BestNode + ) + + { + int i; + int g; // total path cost - as stored in the linked lists. + PathNode *node; + PathNodeConnection *path; + + for( i = 0; i < BestNode->NumberOfConnections(); i++ ) + { + path = &BestNode->GetConnection( i ); + node = thePathManager.GetNode( path->targetNodeIndex ); + + // g(Successor)=g(BestNode)+cost of getting from BestNode to Successor + g = BestNode->g + heuristic.cost( BestNode, i ); + + switch( node->inlist ) + { + case NOT_IN_LIST : + // Only allow this if it's valid + if ( heuristic.validpath( BestNode, i ) ) + { + node->Parent = BestNode; + node->g = g; + node->h = heuristic.dist( node, endnode ); + node->f = g + node->h; + + // Insert Successor on OPEN list wrt f + Insert( node ); + + _checkedNodes++; + } + break; + + case IN_OPEN : + // if our new g value is < node's then reset node's parent to point to BestNode + if ( g < node->g ) + { + node->Parent = BestNode; + node->g = g; + node->f = g + node->h; + } + break; + + case IN_CLOSED : + // if our new g value is < Old's then reset Old's parent to point to BestNode + if ( g < node->g ) + { + node->Parent = BestNode; + node->g = g; + node->f = g + node->h; + + // Since we changed the g value of Old, we need + // to propagate this new value downwards, i.e. + // do a Depth-First traversal of the tree! + PropagateDown( node ); + } + break; + + default : + // shouldn't happen, but try to catch it during debugging phase + assert( NULL ); + gi.Error( ERR_DROP, "Corrupted path node" ); + break; + } + } + } + +template +void PathFinder::Insert + ( + PathNode *node + ) + + { + PathNode *prev; + PathNode *next; + int f; + + node->inlist = IN_OPEN; + f = node->f; + + // special case for if the list is empty, or it should go at head of list (lowest f) + if ( ( OPEN == NULL ) || ( f < OPEN->f ) ) + { + node->NextNode = OPEN; + OPEN = node; + return; + } + + // do sorted insertion into OPEN list in order of ascending f + prev = OPEN; + next = OPEN->NextNode; + while( ( next != NULL ) && ( next->f < f ) ) + { + prev = next; + next = next->NextNode; + } + + // insert it between the two nodes + node->NextNode = next; + prev->NextNode = node; + } + +template +void PathFinder::PropagateDown + ( + PathNode *node + ) + + { + int c; + unsigned g; + unsigned movecost; + PathNode *child; + PathNode *parent; + PathNodeConnection *path; + int n; + + g = node->g; + n = node->NumberOfConnections(); + for( c = 0; c < n; c++ ) + { + path = &node->GetConnection( c ); + child = thePathManager.GetNode( path->targetNodeIndex ); + + movecost = g + heuristic.cost( node, c ); + if ( movecost < child->g ) + { + child->g = movecost; + child->f = child->g + child->h; + child->Parent = node; + + // reset parent to new path. + // Now the _connections's branch need to be + // checked out. Remember the new cost must be propagated down. + stack.Push( child ); + } + } + + while( !stack.Empty() ) + { + parent = stack.Pop(); + n = parent->NumberOfConnections(); + for( c = 0; c < n; c++ ) + { + path = &parent->GetConnection( c ); + child = thePathManager.GetNode( path->targetNodeIndex ); + + // we stop the propagation when the g value of the child is equal or better than + // the cost we're propagating + movecost = parent->g + path->moveCost; + if ( movecost < child->g ) + { + child->g = movecost; + child->f = child->g + child->h; + child->Parent = parent; + stack.Push( child ); + } + } + } + } + +class StandardMovement : public Class + { + public: + int minwidth; + int minheight; + int entnum; + qboolean can_jump; + + inline void setSize + ( + const Vector &size + ) + + { + minwidth = (int)max( size.x, size.y ); + minheight = (int)size.z; + } + + inline int dist + ( + const PathNode *node, + const PathNode *end + ) + + { + Vector delta; + int d1; + int d2; + int d3; + int h; + + delta = node->origin - end->origin; + d1 = abs( ( int )delta[ 0 ] ); + d2 = abs( ( int )delta[ 1 ] ); + d3 = abs( ( int )delta[ 2 ] ); + h = max( d1, d2 ); + h = max( d3, h ); + + return h; + } + + inline qboolean validpath + ( + PathNode *node, + int i + ) + + { + PathNodeConnection *path; + PathNode *n; + + path = &node->GetConnection( i ); + + if ( minwidth < WIDTH_STEP ) + { + minwidth = WIDTH_STEP ; +// return false; + } + + if ( CHECK_PATH( path, minwidth, minheight ) ) + { + return false; + } + + if ( path->door ) + { + Entity *entity; + Door *door; + + entity = G_GetEntity( path->door ); + + if ( entity && entity->isSubclassOf( Door ) ) + { + door = ( Door * )entity; + if ( !door->CanBeOpenedBy( G_GetEntity( entnum ) ) ) + { + return false; + } + } + } + + n = thePathManager.GetNode( path->targetNodeIndex ); + + if ( ( node->nodeflags & AI_JUMP ) && ( n->nodeflags & AI_JUMP ) && ( !can_jump ) ) + return false; + + if ( n && ( n->occupiedTime > level.time ) && ( n->entnum != entnum ) ) + { + return false; + } + + return true; + } + + inline int cost + ( + const PathNode *node, + int i + ) + + { + return node->GetConnection( i ).moveCost; + } + + inline qboolean done + ( + const PathNode *node, + const PathNode *end + ) + + { + return node == end; + } + }; + +typedef PathFinder StandardMovePath; + +#endif /* navigate.h */ diff --git a/dlls/game/object.cpp b/dlls/game/object.cpp new file mode 100644 index 0000000..f1d168f --- /dev/null +++ b/dlls/game/object.cpp @@ -0,0 +1,483 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/object.cpp $ +// $Revision:: 9 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + + +#include "_pch_cpp.h" +#include "object.h" +#include "sentient.h" +#include "misc.h" +#include "explosion.h" +#include "gibs.h" +#include "specialfx.h" + +Event EV_Object_Setup +( + "_setup", + EV_CODEONLY, + NULL, + NULL, + "Sets up an object." +); +Event EV_Object_SetAnim +( + "anim", + EV_DEFAULT, + "s", + "animname", + "Sets up the object with the specified animname." +); +Event EV_Object_Shootable +( + "shootable", + EV_DEFAULT, + NULL, + NULL, + "Make the object shootable but not necessarily solid to the player." +); + +CLASS_DECLARATION( Entity, Object, "object" ) +{ + { &EV_Killed, &Object::Killed }, + { &EV_Object_Setup, &Object::Setup }, + { &EV_Object_SetAnim, &Object::SetAnim }, + { &EV_Object_Shootable, &Object::MakeShootable }, + + { NULL, NULL } +}; + +Object::Object() +{ + animate = new Animate( this ); + + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + // + // all objects default to not solid + // + setSolidType( SOLID_NOT ); + + health = 0; + + takedamage = ( spawnflags & 2 ) ? DAMAGE_NO : DAMAGE_YES; + + // + // we want the bounds of this model auto-rotated + // + flags |= FL_ROTATEDBOUNDS; + + if ( !com_blood->integer ) + { + flags &= ~FL_BLOOD; + flags &= ~FL_DIE_GIBS; + } + + PostEvent( EV_Object_Setup, EV_POSTSPAWN ); +} + +void Object::SetAnim( Event *ev ) +{ + int animnum; + + if ( ( ev->NumArgs() >= 1 ) && gi.IsModel( edict->s.modelindex ) ) + { + animnum = gi.Anim_Random( edict->s.modelindex, ev->GetString( 1 ) ); + if ( animnum >= 0 ) + { + animate->NewAnim( animnum ); + } + } +} + +void Object::Setup( Event *ev ) +{ + if ( !health ) + { + health = ( maxs - mins ).length(); + max_health = health; + } +} + +void Object::MakeShootable( Event *ev ) +{ + setContents( CONTENTS_SHOOTABLE_ONLY ); + link(); +} + +void Object::Killed(Event *ev) +{ + Entity * ent; + Entity * attacker; + Vector dir; + Event * event; + const char * name; + + takedamage = DAMAGE_NO; + setSolidType( SOLID_NOT ); + hideModel(); + + attacker = ev->GetEntity( 1 ); + + if (flags & FL_DIE_EXPLODE) + { + CreateExplosion( origin, 50.0f, this, this, this ); + } + + if (flags & FL_DIE_GIBS) + { + setSolidType( SOLID_NOT ); + hideModel(); + + CreateGibs( this, -150.0f, edict->s.scale, 3 ); + } + + // + // kill the killtargets + // + name = KillTarget(); + if ( name && strcmp( name, "" ) ) + { + ent = NULL; + do + { + ent = G_FindTarget( ent, name ); + if ( !ent ) + { + break; + } + ent->PostEvent( EV_Remove, 0.0f ); + } + while ( 1 ); + } + + // + // fire targets + // + name = Target(); + if ( name && strcmp( name, "" ) ) + { + ent = NULL; + do + { + ent = G_FindTarget( ent, name ); + if ( !ent ) + { + break; + } + event = new Event( EV_Activate ); + event->AddEntity( attacker ); + ent->ProcessEvent( event ); + } + while ( 1 ); + } + + PostEvent( EV_Remove, 0.0f ); +} + +/*****************************************************************************/ +/*QUAKED func_throwobject (0 0.25 0.5) (-16 -16 0) (16 16 32) + +This is an object you can pickup and throw at people +******************************************************************************/ + +Event EV_ThrowObject_Pickup +( + "pickup", + EV_DEFAULT, + "es", + "entity tag_name", + "Picks up this throw object and attaches it to the entity." +); +Event EV_ThrowObject_Throw +( + "throw", + EV_DEFAULT, + "efeF", + "owner speed targetent grav", + "Throw this throw object." +); +Event EV_ThrowObject_PickupOffset +( + "pickupoffset", + EV_DEFAULT, + "v", + "pickup_offset", + "Sets the pickup_offset." +); +Event EV_ThrowObject_ThrowSound +( + "throwsound", + EV_DEFAULT, + "s", + "throw_sound", + "Sets the sound to play when object is thrown." +); + +CLASS_DECLARATION( Object, ThrowObject, "func_throwobject" ) +{ + { &EV_Touch, &ThrowObject::Touch }, + { &EV_ThrowObject_Pickup, &ThrowObject::Pickup }, + { &EV_ThrowObject_Throw, &ThrowObject::Throw }, + { &EV_ThrowObject_PickupOffset, &ThrowObject::PickupOffset }, + { &EV_ThrowObject_ThrowSound, &ThrowObject::ThrowSound }, + + { NULL, NULL } +}; + +ThrowObject::ThrowObject() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + pickup_offset = vec_zero; + damage = 0; + hurt_target = false; + owner = NULL; +} + +void ThrowObject::PickupOffset( Event *ev ) +{ + pickup_offset = edict->s.scale * ev->GetVector( 1 ); +} + +void ThrowObject::ThrowSound( Event *ev ) +{ + throw_sound = ev->GetString( 1 ); +} + +void ThrowObject::Touch( Event *ev ) +{ + Entity *other; + + if ( movetype != MOVETYPE_BOUNCE ) + { + return; + } + + other = ev->GetEntity( 1 ); + assert( other ); + + if ( other->isSubclassOf( Teleporter ) ) + { + return; + } + + if ( other->entnum == owner ) + { + return; + } + + if ( throw_sound.length() ) + { + StopLoopSound(); + } + + if ( other->takedamage && !hurt_target ) + { + // other->Damage( this, G_GetEntity( owner ), size.length() * velocity.length() / 400, origin, velocity, + // level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT ); + + other->Damage( this, G_GetEntity( owner ), damage, origin, velocity, + level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT ); + hurt_target = true; + } + + //Damage( this, this, max_health, origin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT ); + Damage( this, this, 0.0f, origin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT ); +} + +void ThrowObject::Throw( const Entity *owner, float speed, const Sentient *targetent, float gravity, float throw_damage ) +{ + float traveltime; + float vertical_speed; + Vector target; + Vector dir; + Vector xydir; + Event *e; + + + e = new Event( EV_Detach ); + ProcessEvent( e ); + + this->owner = owner->entnum; + edict->ownerNum = owner->entnum; + + damage = throw_damage; + target = targetent->origin; + target.z += targetent->viewheight; + + setMoveType( MOVETYPE_BOUNCE ); + setSolidType( SOLID_BBOX ); + edict->clipmask = MASK_PROJECTILE; + + dir = target - origin; + xydir = dir; + xydir.z = 0; + traveltime = xydir.length() / speed; + vertical_speed = ( dir.z / traveltime ) + ( 0.5f * gravity * sv_currentGravity->value * traveltime ); + xydir.normalize(); + + // setup ambient flying sound + if ( throw_sound.length() ) + { + LoopSound( throw_sound.c_str() ); + } + + velocity = speed * xydir; + velocity.z = vertical_speed; + + angles = velocity.toAngles(); + setAngles( angles ); + + avelocity.x = crandom() * 200.0f; + avelocity.y = crandom() * 200.0f; + takedamage = DAMAGE_YES; +} + +void ThrowObject::Throw( Event *ev ) +{ + Entity *owner; + Sentient *targetent; + float speed; + float grav; + float throw_damage; + + owner = ev->GetEntity( 1 ); + assert( owner ); + + if ( !owner ) + { + return; + } + + speed = ev->GetFloat( 2 ); + if ( !speed ) + { + speed = 1; + } + + targetent = ( Sentient * )ev->GetEntity( 3 ); + assert( targetent ); + if (!targetent) + { + return; + } + + if ( ev->NumArgs() > 3 ) + { + grav = ev->GetFloat( 4 ); + } + else + { + grav = 1; + } + + if ( ev->NumArgs() > 4 ) + throw_damage = ev->GetFloat( 5 ); + else + throw_damage = 0; + + Throw( owner, speed, targetent, grav, throw_damage ); + + /* + e = new Event( EV_Detach ); + ProcessEvent( e ); + + this->owner = owner->entnum; + edict->ownerNum = owner->entnum; + + gravity = grav; + + + target = targetent->origin; + target.z += targetent->viewheight; + + setMoveType( MOVETYPE_BOUNCE ); + setSolidType( SOLID_BBOX ); + edict->clipmask = MASK_PROJECTILE; + + dir = target - origin; + xydir = dir; + xydir.z = 0; + traveltime = xydir.length() / speed; + vertical_speed = ( dir.z / traveltime ) + ( 0.5f * gravity * sv_currentGravity->value * traveltime ); + xydir.normalize(); + + // setup ambient flying sound + if ( throw_sound.length() ) + { + LoopSound( throw_sound.c_str() ); + } + + velocity = speed * xydir; + velocity.z = vertical_speed; + + angles = velocity.toAngles(); + setAngles( angles ); + + avelocity.x = crandom() * 200; + avelocity.y = crandom() * 200; + takedamage = DAMAGE_YES; + */ +} + +void ThrowObject::Pickup( Entity *ent, const str &bone ) +{ + Event *e; + + setOrigin( pickup_offset ); + + e = new Event( EV_Attach ); + e->AddEntity( ent ); + e->AddString( bone ); + ProcessEvent( e ); + hurt_target = false; + edict->s.renderfx &= ~RF_FRAMELERP; +} + +void ThrowObject::Pickup( Event *ev ) +{ + Entity * ent; + //Event * e; + str bone; + + ent = ev->GetEntity( 1 ); + + assert( ent ); + if ( !ent ) + { + return; + } + bone = ev->GetString( 2 ); + + /* + setOrigin( pickup_offset ); + + e = new Event( EV_Attach ); + e->AddEntity( ent ); + e->AddString( bone ); + ProcessEvent( e ); + + edict->s.renderfx &= ~RF_FRAMELERP; + */ + + Pickup( ent , bone ); +} diff --git a/dlls/game/object.h b/dlls/game/object.h new file mode 100644 index 0000000..f9fb21f --- /dev/null +++ b/dlls/game/object.h @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/object.h $ +// $Revision:: 5 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Object class +// + +#ifndef __OBJECT_H__ +#define __OBJECT_H__ + +#include "g_local.h" +#include "sentient.h" +#include "animate.h" +#include "specialfx.h" + +class Object : public Entity + { + public: + CLASS_PROTOTYPE( Object ); + + Object(); + void Killed( Event *ev ); + void SetAngle( Event *ev ); + void Setup( Event *ev ); + void SetAnim( Event *ev ); + void MakeShootable( Event *ev ); + }; + +extern Event EV_ThrowObject_Pickup; +extern Event EV_ThrowObject_Throw; + +class ThrowObject : public Object + { + private: + int owner; + Vector pickup_offset; + str throw_sound; + float damage; + qboolean hurt_target; + + public: + CLASS_PROTOTYPE( ThrowObject ); + ThrowObject(); + void Touch(Event *ev); + void Throw( Event * ev ); + void Throw( const Entity *owner, float speed, const Sentient *targetent, float gravity, float throw_damage ); + void Pickup( Event * ev ); + void Pickup( Entity* ent, const str &bone ); + void PickupOffset( Event * ev ); + void ThrowSound( Event * ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void ThrowObject::Archive + ( + Archiver &arc + ) + { + Object::Archive( arc ); + + arc.ArchiveInteger( &owner ); + arc.ArchiveVector( &pickup_offset ); + arc.ArchiveString( &throw_sound ); + arc.ArchiveFloat( &damage ); + arc.ArchiveBoolean( &hurt_target ); + } + +#endif /* object.h */ diff --git a/dlls/game/path.cpp b/dlls/game/path.cpp new file mode 100644 index 0000000..32a1bcf --- /dev/null +++ b/dlls/game/path.cpp @@ -0,0 +1,478 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/path.cpp $ +// $Revision:: 7 $ +// $Author:: Steven $ +// $Date:: 10/06/02 7:08p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "path.h" +#include "container.h" +#include "navigate.h" +#include "misc.h" + +CLASS_DECLARATION( Class, Path, NULL ) +{ + { NULL, NULL } +}; + +Path::Path() +{ + pathlength = 0; + from = NULL; + to = NULL; + nextnode = 1; +} + +Path::Path( int numnodes ) +{ + pathlength = 0; + from = NULL; + to = NULL; + nextnode = 1; + pathlist.Resize( numnodes ); + dirToNextNode.Resize( numnodes ); + distanceToNextNode.Resize( numnodes ); +} + +void Path::Clear( void ) +{ + nextnode = 1; + pathlength = 0; + from = NULL; + to = NULL; + pathlist.FreeObjectList(); + dirToNextNode.FreeObjectList(); + distanceToNextNode.FreeObjectList(); +} + +void Path::Reset( void ) +{ + nextnode = 1; +} + +PathNode *Path::Start( void ) +{ + return from; +} + +PathNode *Path::End( void ) +{ + return to; +} + +void Path::AddNode( PathNode *node ) +{ + Vector dir; + float len; + int num; + + if ( !from ) + { + from = node; + } + + to = node; + pathlist.AddObject( PathNodePtr( node ) ); + + len = 0; + distanceToNextNode.AddObject( len ); + dirToNextNode.AddObject( vec_zero ); + + num = NumNodes(); + if ( num > 1 ) + { + dir = node->origin - GetNode( num - 1 )->origin; + len = dir.length(); + dir *= 1.0f / len; + + distanceToNextNode.SetObjectAt( num - 1, len ); + dirToNextNode.SetObjectAt( num - 1, dir ); + + pathlength += len; + } +} + +void Path::_UpdateNodeValues(int const nodeNumber) +{ + if (nodeNumber < 1) + { + return; + } + + Vector dir(vec_zero); + float len=0.0f; + + if (nodeNumber < NumNodes()) + { + dir = GetNode( nodeNumber + 1 )->origin - GetNode( nodeNumber )->origin; + len = dir.length(); + dir *= 1.0f / len; + } + distanceToNextNode.SetObjectAt( nodeNumber, len ); + dirToNextNode.SetObjectAt( nodeNumber, dir ); +} + +void Path::_UpdatePathLength(void) +{ + int numNodes=NumNodes(); + pathlength=0.0f; + for (int i=1; i<=numNodes;i++) + { + pathlength+=distanceToNextNode.ObjectAt(i); + } +} + +void Path::InsertNode( PathNode *node, int const insertionPoint ) +{ + if (insertionPoint == 1) + { + from = node; + } + + if (insertionPoint > NumNodes()) + { + to = node; + } + pathlist.InsertObjectAt(insertionPoint, PathNodePtr( node ) ); + distanceToNextNode.InsertObjectAt( insertionPoint, 0.0f ); + dirToNextNode.InsertObjectAt( insertionPoint, vec_zero ); + + _UpdateNodeValues(insertionPoint-1); + _UpdateNodeValues(insertionPoint); + _UpdatePathLength(); +} + +void Path::RemoveNode( PathNode *node ) +{ + + int nodeIndex=GetNodeIndex(node); + int numNodes=NumNodes(); + if (nodeIndex < 1) + { + return; + } + + pathlist.RemoveObjectAt(nodeIndex); + if (NumNodes() == 0) + { + to=NULL; + from=NULL; + } + else + { + if (nodeIndex == numNodes) + { + to=GetNode(NumNodes()); + } + if (nodeIndex == 1) + { + from=GetNode(1); + } + else + { + _UpdateNodeValues(nodeIndex-1); + } + } + _UpdatePathLength(); +} + +PathNode *Path::GetNode( int num ) +{ + PathNode *node; + + node = pathlist.ObjectAt( num ); + assert( node != NULL ); + if ( node == NULL ) + { + error( "GetNode", "Null pointer in node list\n" ); + } + + return node; +} + +int Path::GetNodeIndex( PathNode *node ) +{ + int num = NumNodes(); + for( int i = 1; i <= num; i++ ) + { + if ( pathlist.ObjectAt( i ) == node ) + { + return i; + } + } + return -1; +} + +PathNode *Path::NextNode( void ) +{ + if ( nextnode <= NumNodes() ) + { + return pathlist.ObjectAt( nextnode++ ); + } + return NULL; +} + +PathNode *Path::NextNode( const PathNode *node ) +{ + int i; + int num; + PathNode *n; + + num = NumNodes(); + + // NOTE: We specifically DON'T check the last object (hence the i < num instead + // of the usual i <= num, so don't go doing something stupid like trying to fix + // this without keeping this in mind!! :) + for( i = 1; i < num; i++ ) + { + n = pathlist.ObjectAt( i ); + if ( n == node ) + { + // Since we only check up to num - 1, it's ok to do this. + // We do this since the last node in the list has no next node (duh!). + return pathlist.ObjectAt( i + 1 ); + } + } + + return NULL; +} + +Vector Path::ClosestPointOnPath( const Vector &pos ) +{ + PathNode *s; + PathNode *e; + int num; + int i; + float bestdist; + Vector bestpoint; + float dist; + float segmentlength; + Vector delta; + Vector p1; + Vector p2; + Vector p3; + float t; + + num = NumNodes(); + s = GetNode( 1 ); + + bestpoint = s->origin; + delta = bestpoint - pos; + bestdist = delta * delta; + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + // check if we're closest to the endpoint + delta = e->origin - pos; + dist = delta * delta; + + if ( dist < bestdist ) + { + bestdist = dist; + bestpoint = e->origin; + } + + // check if we're closest to the segment + segmentlength = distanceToNextNode.ObjectAt( i - 1 ); + p1 = dirToNextNode.ObjectAt( i - 1 ); + p2 = pos - s->origin; + + t = p1 * p2; + if ( ( t > 0.0f ) && ( t < segmentlength ) ) + { + p3 = ( p1 * t ) + s->origin; + + delta = p3 - pos; + dist = delta * delta; + if ( dist < bestdist ) + { + bestdist = dist; + bestpoint = p3; + } + } + + s = e; + } + + return bestpoint; +} + +float Path::DistanceAlongPath( const Vector &pos ) +{ + PathNode *s; + PathNode *e; + int num; + int i; + float bestdist; + float dist; + float segmentlength; + Vector delta; + Vector p1; + Vector p2; + Vector p3; + float t; + float pathdist; + float bestdistalongpath; + + pathdist = 0; + + num = NumNodes(); + s = GetNode( 1 ); + delta = s->origin - pos; + bestdist = delta * delta; + bestdistalongpath = 0; + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + segmentlength = distanceToNextNode.ObjectAt( i - 1 ); + + // check if we're closest to the endpoint + delta = e->origin - pos; + dist = delta * delta; + + if ( dist < bestdist ) + { + bestdist = dist; + bestdistalongpath = pathdist + segmentlength; + } + + // check if we're closest to the segment + p1 = dirToNextNode.ObjectAt( i - 1 ); + p2 = pos - s->origin; + + t = p1 * p2; + if ( ( t > 0.0f ) && ( t < segmentlength ) ) + { + p3 = ( p1 * t ) + s->origin; + + delta = p3 - pos; + dist = delta * delta; + if ( dist < bestdist ) + { + bestdist = dist; + bestdistalongpath = pathdist + t; + } + } + + s = e; + + pathdist += segmentlength; + } + + return bestdistalongpath; +} + +Vector Path::PointAtDistance( float dist ) +{ + PathNode *s; + PathNode *e; + int num; + int i; + float t; + float pathdist; + float segmentlength; + + num = NumNodes(); + s = GetNode( 1 ); + pathdist = 0; + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + segmentlength = distanceToNextNode.ObjectAt( i - 1 ); + if ( ( pathdist + segmentlength ) > dist ) + { + t = dist - pathdist; + return s->origin + ( dirToNextNode.ObjectAt( i - 1 ) * t ); + } + + s = e; + pathdist += segmentlength; + } + + // cap it off at start or end of path + return s->origin; +} + +PathNode *Path::NextNode( float dist ) +{ + PathNode *s; + PathNode *e; + int num; + int i; + float pathdist; + float segmentlength; + + num = NumNodes(); + s = GetNode( 1 ); + pathdist = 0; + + for( i = 2; i <= num; i++ ) + { + e = GetNode( i ); + + segmentlength = distanceToNextNode.ObjectAt( i - 1 ); + if ( ( pathdist + segmentlength ) > dist ) + { + return e; + } + + s = e; + pathdist += segmentlength; + } + + // cap it off at start or end of path + return s; +} + +void Path::DrawPath( float r, float g, float b, float time ) +{ + Vector s; + Vector e; + Vector offset; + PathNode *node; + int num; + int i; + + num = NumNodes(); + + node = GetNode( 1 ); + s = node->origin; + + offset = Vector( r, g, b ) * 4.0f + Vector( 0.0f, 0.0f, 20.0f ); + for( i = 2; i <= num; i++ ) + { + node = GetNode( i ); + e = node->origin; + + G_DebugLine( s + offset, e + offset, r, g, b, 1.0f ); + s = e; + } +} + +int Path::NumNodes( void ) +{ + return pathlist.NumObjects(); +} + +float Path::Length( void ) +{ + return pathlength; +} diff --git a/dlls/game/path.h b/dlls/game/path.h new file mode 100644 index 0000000..6c5664f --- /dev/null +++ b/dlls/game/path.h @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/path.h $ +// $Revision:: 5 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __PATH_H__ +#define __PATH_H__ + +#include "g_local.h" +#include "class.h" +#include "container.h" +#include "navigate.h" + +class Path : public Class + { + private: + Container pathlist; + Container distanceToNextNode; + Container dirToNextNode; + float pathlength; + PathNodePtr from; + PathNodePtr to; + int nextnode; + void _UpdateNodeValues(int const nodeNumber); + void _UpdatePathLength(void); + + public: + CLASS_PROTOTYPE( Path ); + + Path(); + Path( int numnodes ); + void Clear( void ); + void Reset( void ); + void AddNode( PathNode *node ); + void InsertNode( PathNode *node, int const insertionPoint ); + void RemoveNode( PathNode *node ); + PathNode *GetNode( int num ); + int GetNodeIndex( PathNode *node ); + PathNode *PreviousNode( void ); + PathNode *NextNode( void ); + PathNode *NextNode( const PathNode *node ); + Vector ClosestPointOnPath( const Vector &pos ); + float DistanceAlongPath( const Vector &pos ); + Vector PointAtDistance( float dist ); + PathNode *NextNode( float dist ); + void DrawPath( float r, float g, float b, float time ); + int NumNodes( void ); + float Length( void ); + PathNode *Start( void ); + PathNode *End( void ); + virtual void Archive( Archiver &arc ); + }; + +inline void Path::Archive + ( + Archiver &arc + ) + { + PathNodePtr node; + int i, num; + + Class::Archive( arc ); + + if ( arc.Saving() ) + { + num = pathlist.NumObjects(); + } + arc.ArchiveInteger( &num ); + if ( arc.Loading() ) + { + pathlist.FreeObjectList(); + if ( num ) + pathlist.Resize( num ); + distanceToNextNode.FreeObjectList(); + dirToNextNode.FreeObjectList(); + } + for ( i = 1; i <= num; i++ ) + { + if ( arc.Loading() ) + pathlist.AddObject( node ); + + arc.ArchiveSafePointer( pathlist.AddressOfObjectAt( i ) ); + } + + distanceToNextNode.Archive( arc ); + dirToNextNode.Archive( arc ); + + arc.ArchiveFloat( &pathlength ); + arc.ArchiveSafePointer( &from ); + arc.ArchiveSafePointer( &to ); + arc.ArchiveInteger( &nextnode ); + } + +typedef SafePtr PathPtr; + +#endif /* path.h */ diff --git a/dlls/game/patrol.cpp b/dlls/game/patrol.cpp new file mode 100644 index 0000000..7628859 --- /dev/null +++ b/dlls/game/patrol.cpp @@ -0,0 +1,797 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/patrol.cpp $ +// $Revision:: 13 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CloseInOnEnemy Implementation +// +// PARAMETERS: +// +// +// ANIMATIONS: +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "patrol.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, Patrol, NULL ) + { + { &EV_Behavior_Args, &Patrol::SetArgs }, + { &EV_Behavior_AnimDone, &Patrol::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: Patrol() +// Class: Patrol +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +Patrol::Patrol() +{ + _anim = "walk"; +} + +//-------------------------------------------------------------- +// Name: ~Patrol() +// Class: Patrol +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +Patrol::~Patrol() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: Patrol +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::SetArgs ( Event *ev) +{ + _anim = ev->GetString( 1 ); +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: Patrol +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::AnimDone( Event *ev ) +{ + if ( _state == PATROL_WAITING_AT_NODE_FOR_ANIM ) + { + _state = PATROL_FIND_NEXT_PATROL_NODE; + } +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: Patrol +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::Begin( Actor &self ) +{ + _init( self ); +} + + + +//-------------------------------------------------------------- +// Name: _init() +// Class: Patrol +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_init( Actor &self ) +{ + _nextMoveAttempt = 0.0f; + _waitTime = 0.0f; + _moveFailures = 0; + + //Setup Torso Anim if appropriate + _torsoAnim = self.combatSubsystem->GetAnimForMyWeapon( "Idle" ); + if ( _torsoAnim.length() ) + { + self.SetAnim( _torsoAnim, NULL , torso ); + } + + _state = PATROL_FIND_CLOSEST_PATROL_NODE; + if ( !_setupFindClosestPatrolNode( self ) ) + _setupFindClosestPatrolNodeFailed( self ); +} + + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: Patrol +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t Patrol::Evaluate ( Actor &self ) +{ + switch ( _state ) + { + case PATROL_FIND_CLOSEST_PATROL_NODE: + _findClosestPatrolNode( self ); + break; + + case PATROL_MOVING_TO_PATROL_NODE: + _moveToPatrolNode( self ); + break; + + case PATROL_AT_PATROL_NODE: + _atPatrolNode( self ); + break; + + case PATROL_WAITING_AT_NODE: + _waitAtPatrolNode( self ); + break; + + case PATROL_WAITING_AT_NODE_FOR_ANIM: + _waitAtPatrolNodeForAnim( self ); + break; + + case PATROL_FIND_NEXT_PATROL_NODE: + _findNextPatrolNode( self ); + break; + + case PATROL_HOLD: + _hold( self ); + break; + + case PATROL_FAILED: + return BEHAVIOR_FAILED; + break; + + case PATROL_SUCCESSFUL: + return BEHAVIOR_SUCCESS; + break; + } + + return BEHAVIOR_EVALUATING; +} + + + + +//-------------------------------------------------------------- +// Name: End() +// Class: Patrol +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::End ( Actor &self ) +{ +} + + + +//-------------------------------------------------------------- +// Name: _setupFindClosestPatrolNode() +// Class: Patrol +// +// Description: Sets up the behavior for the Find Node State +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool Patrol::_setupFindClosestPatrolNode( Actor &self ) +{ + _node = NULL; + return true; +} + + + +//-------------------------------------------------------------- +// Name: _setupFindClosestPatrolNodeFailed() +// Class: Patrol +// +// Description: Failure Handler for _setupFindClosestPatrolNode() +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_setupFindClosestPatrolNodeFailed( Actor &self ) +{ + _state = PATROL_FAILED; +} + + + +//-------------------------------------------------------------- +// Name: _findClosestPatrolNode +// Class: Patrol +// +// Description: Finds the closest the Patrol Node, and changes +// the behavior's state +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_findClosestPatrolNode( Actor &self ) +{ + if ( _node ) + { + _state = PATROL_MOVING_TO_PATROL_NODE; + if (!_setupMovingToPatrolNode( self ) ) + _setupMovingToPatrolNodeFailed( self ); + return; + } + + _node = HelperNode::FindClosestHelperNode( self , NODETYPE_PATROL , 512.0f, 0 ); + + if ( !_node ) + { + _findClosestPatrolNodeFailed(self); + return; + } + + //Now Let's check if we're already "on the path" to help prevent backtracking + str nextNodeTargetName; + nextNodeTargetName = _node->target; + + // See if we hit the end of our path + if ( nextNodeTargetName.length() ) + { + HelperNode* testNode; + testNode = HelperNode::GetTargetedHelperNode( nextNodeTargetName ); + if ( testNode ) + { + Vector selfToTestNode = testNode->origin - self.origin; + float selfToTestNodeDist = selfToTestNode.length(); + + Vector currentNodeToTestNode = testNode->origin - _node->origin; + float currentNodeToTestNodeDist = currentNodeToTestNode.length(); + + //If we're closer to the next node than the distance between the "closest" node and the next node then + //we'll use the next node instead to prevent backtracking + if ( currentNodeToTestNodeDist > selfToTestNodeDist ) + _node = testNode; + } + + } + + + + _state = PATROL_MOVING_TO_PATROL_NODE; + if (!_setupMovingToPatrolNode( self ) ) + _setupMovingToPatrolNodeFailed( self ); + +} + + +//-------------------------------------------------------------- +// Name: _findClosestPatrolNodeFailed() +// Class: Patrol +// +// Description: Failure Handler for _findClosestPatrolNode +// +// Parameters: +// +// Returns: +//-------------------------------------------------------------- +void Patrol::_findClosestPatrolNodeFailed( Actor &self ) +{ + SetFailureReason( "Unable to find a patrol node within 512.0 units" ); + _state = PATROL_FAILED; +} + + + +//-------------------------------------------------------------- +// Name: _setupMovingToPatrolNode() +// Class: Patrol +// +// Description: Sets up behavior for the Moving To Node State +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool Patrol::_setupMovingToPatrolNode( Actor &self ) +{ + //If we have a _lastNode let's run our exit thread + if ( _lastNode ) + _lastNode->RunExitThread(); + + //Setup Torso Anim if appropriate + _torsoAnim = self.combatSubsystem->GetAnimForMyWeapon( "Idle" ); + if ( _torsoAnim.length() ) + { + self.SetAnim( _torsoAnim, NULL , torso ); + } + + _gotoHelperNode.SetDistance( 34.0f ); + _gotoHelperNode.SetAnim( _anim ); + _gotoHelperNode.SetPoint( _node->origin ); + _gotoHelperNode.Begin( self ); + + return true; +} + + + +//-------------------------------------------------------------- +// Name: _setupMovingToPatrolNodeFailed() +// Class: Patrol +// +// Description: Failure Handler for _setupMovingToPatrolNode() +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_setupMovingToPatrolNodeFailed( Actor &self ) +{ + _state = PATROL_FAILED; +} + + + +//-------------------------------------------------------------- +// Name: _moveToPatrolNode() +// Class: Patrol +// +// Description: Evaluates the GotoEntity Component Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_moveToPatrolNode( Actor &self ) +{ + BehaviorReturnCode_t moveResult; + + moveResult = _gotoHelperNode.Evaluate( self ); + + if ( moveResult == BEHAVIOR_SUCCESS ) + { + _nextMoveAttempt = 0.0f; + _moveFailures = 0; + _state = PATROL_AT_PATROL_NODE; + if ( !_setupAtPatrolNode( self ) ) + _setupAtPatrolNodeFailed( self ); + } + else if ( moveResult == BEHAVIOR_FAILED ) + { + _moveToPatrolNodeFailed( self ); + } + /* + else if ( moveResult == BEHAVIOR_EVALUATING ) + { + //BULLET PROOFING-------------------------------------- + //Stupid Check to see if anim got stomped somewhere + //BULLET PROOFING-------------------------------------- + str currentAnim = self.animate->GetName(); + if ( currentAnim != _anim ) + self.SetAnim( _anim, NULL , legs ); + }*/ + +} + + + +//-------------------------------------------------------------- +// Name: _moveToPatrolNodeFailed() +// Class: Patrol +// +// Description: Failure Handler for _moveToPatrolNode +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_moveToPatrolNodeFailed( Actor &self ) +{ + _moveFailures++; + + //if ( _moveFailures > 10 ) + // { + _state = PATROL_FAILED; + // } + //else + // { + // _state = PATROL_HOLD; + // if ( !_setupHold( self ) ) + // _setupHoldFailed( self ); + // } + +} + + + +//-------------------------------------------------------------- +// Name: _setupAtPatrolNode() +// Class: Patrol +// +// Description: Sets behavior up to enter the At Node state +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool Patrol::_setupAtPatrolNode( Actor &self ) +{ + //self.SetAnim( "walk" ); + return true; +} + + +//-------------------------------------------------------------- +// Name: _setupAtPatrolNodeFailed() +// Class: Patrol +// +// Description: Failure Handler for _setupAtPatrolNodeFailed +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_setupAtPatrolNodeFailed( Actor &self ) +{ + _state = PATROL_FAILED; +} + + + +//-------------------------------------------------------------- +// Name: _AtPatrolNode() +// Class: Patrol +// +// Description: Main Function for the At Node State +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_atPatrolNode( Actor &self ) +{ + str nodeAnim; + nodeAnim = _node->GetAnim(); + + + // Run this thread, if we have one + _node->RunEntryThread(); + + // First, See if we have a wait time or wait random time + _waitTime = _node->GetWaitTime() + level.time; + + if ( _node->isWaitRandom() ) + _waitTime = level.time + G_Random( _waitTime ) + 1; + + if ( _waitTime > level.time ) + { + if ( nodeAnim.length() && self.animate->HasAnim( nodeAnim.c_str() ) ) + { + self.ClearLegAnim(); + self.ClearTorsoAnim(); + self.SetAnim( nodeAnim ); + } + else + self.SetAnim( "idle" ); + + _state = PATROL_WAITING_AT_NODE; + if ( !_setupWaitingAtPatrolNode( self ) ) + _setupWaitingAtPatrolNodeFailed( self ); + + return; + } + + // We didn't have a wait time, let's see if we're supposed to wait for an animation + if ( _node->isWaitForAnim() && nodeAnim.length() ) + { + self.SetAnim( nodeAnim , EV_Actor_NotifyBehavior); + _state = PATROL_WAITING_AT_NODE_FOR_ANIM; + if ( !_setupWaitingAtPatrolNodeForAnim( self ) ) + _setupWaitingAtPatrolNodeForAnimFailed( self ); + + return; + } + + // We're not waiting here, so let's just get the next node + _state = PATROL_FIND_NEXT_PATROL_NODE; + if ( !_setupFindNextPatrolNode( self ) ) + _setupFindNextPatrolNodeFailed( self ); +} + + + + +//-------------------------------------------------------------- +// Name: _setupWaitingAtPatrolNode() +// Class: Patrol +// +// Description: Sets Behavior up for Wait State +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool Patrol::_setupWaitingAtPatrolNode( Actor &self ) +{ + return true; +} + + + +//-------------------------------------------------------------- +// Name: _setupWaitingAtPatrolNodeFailed() +// Class: Patrol +// +// Description: Failure Handler for _setupWaitingAtPatrol +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_setupWaitingAtPatrolNodeFailed(Actor &self ) +{ + _state = PATROL_FAILED; +} + + + +//-------------------------------------------------------------- +// Name: _waitAtPatrolNode +// Class: Patrol +// +// Description: Waits at the Patrol Node +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_waitAtPatrolNode(Actor &self ) +{ + if ( level.time > _waitTime ) + { + _state = PATROL_FIND_NEXT_PATROL_NODE; + if ( !_setupFindNextPatrolNode( self ) ) + _setupFindNextPatrolNodeFailed( self ); + } +} + + + +//-------------------------------------------------------------- +// Name: _setupWaitingAtPatrolNodeForAnim() +// Class: Patrol +// +// Description: Sets up behavior to wait for anim +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool Patrol::_setupWaitingAtPatrolNodeForAnim( Actor &self ) +{ + self.setAngles(_node->angles); + return true; +} + + + +//-------------------------------------------------------------- +// Name: _setupWaitingAtPatrolNodeForAnimFailed() +// Class: Patrol +// +// Description: Failure Handler for _setupWaitingAtPatrolNode() +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_setupWaitingAtPatrolNodeForAnimFailed( Actor &self ) +{ + _state = PATROL_FAILED; +} + + + +//-------------------------------------------------------------- +// Name: _waitAtPatrolNodeForAnim() +// Class: Patrol +// +// Description: Nothing now, here in case we need it +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_waitAtPatrolNodeForAnim( Actor &self ) +{ + // Transition is handled by the AnimDone Event +} + + + +//-------------------------------------------------------------- +// Name: _setupFindNextPatrolNode() +// Class: Patrol +// +// Description: Sets behavior up to find the next patrol node +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool Patrol::_setupFindNextPatrolNode( Actor &self ) +{ + return true; +} + + + +//-------------------------------------------------------------- +// Name: _setupFindNextPatrolNodeFailed() +// Class: Patrol +// +// Description: Failure Handler for _setupFindNextPatrolNode() +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_setupFindNextPatrolNodeFailed( Actor &self ) +{ + SetFailureReason( "Unable to find the next Node in the patrol path" ); + _state = PATROL_FAILED; +} + + + +//-------------------------------------------------------------- +// Name: _findNextPatrolNode() +// Class: Patrol +// +// Description: Finds next patrol node +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_findNextPatrolNode( Actor &self ) +{ + str nextNodeTargetName; + nextNodeTargetName = _node->target; + + // See if we hit the end of our path + if ( !nextNodeTargetName.length() ) + { + _state = PATROL_SUCCESSFUL; + return; + } + + _lastNode = _node; + _node = HelperNode::GetTargetedHelperNode( nextNodeTargetName ); + + if ( !_node ) + { + _state = PATROL_FAILED; + return; + } + + _state = PATROL_MOVING_TO_PATROL_NODE; + if ( !_setupMovingToPatrolNode( self ) ) + _setupMovingToPatrolNodeFailed( self ); +} + + + +//-------------------------------------------------------------- +// Name: _setupHold() +// Class: Patrol +// +// Description: Sets Behavior up to hold +// +// Parameters: Actor &self +// +// Returns: true or false +//-------------------------------------------------------------- +bool Patrol::_setupHold( Actor &self ) +{ + _nextMoveAttempt = level.time + 0.5f + G_Random(); + return true; +} + + + +//-------------------------------------------------------------- +// Name: _setupHoldFailed() +// Class: Patrol +// +// Description: Failure Handler for _setupHold() +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_setupHoldFailed( Actor &self ) +{ + _state = PATROL_FAILED; +} + + + +//-------------------------------------------------------------- +// Name: _hold() +// Class: Patrol +// +// Description: Holds the Actor in place for a brief time +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Patrol::_hold( Actor &self ) +{ + if ( level.time > _nextMoveAttempt ) + { + _state = PATROL_MOVING_TO_PATROL_NODE; + if ( !_setupMovingToPatrolNode( self ) ) + _setupMovingToPatrolNodeFailed( self ); + } +} diff --git a/dlls/game/patrol.hpp b/dlls/game/patrol.hpp new file mode 100644 index 0000000..932d0f1 --- /dev/null +++ b/dlls/game/patrol.hpp @@ -0,0 +1,167 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/closeInOnEnemy.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CloseInOnEnemy Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class Patrol; + +#ifndef __PATROL_HPP__ +#define __PATROL_HPP__ + +//------------------------- CLASS ------------------------------ +// +// Name: Patrol +// Base Class: Behavior +// +// Description: Makes the actor use Patrol Helper Nodes +// +// Method of Use: Statemachine or another behavior +//-------------------------------------------------------------- +class Patrol : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + PATROL_FIND_CLOSEST_PATROL_NODE, + PATROL_MOVING_TO_PATROL_NODE, + PATROL_AT_PATROL_NODE, + PATROL_WAITING_AT_NODE, + PATROL_WAITING_AT_NODE_FOR_ANIM, + PATROL_FIND_NEXT_PATROL_NODE, + PATROL_HOLD, + PATROL_FAILED, + PATROL_SUCCESSFUL, + } patrolStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void _init( Actor &self ); + + bool _setupFindClosestPatrolNode ( Actor &self ); + void _setupFindClosestPatrolNodeFailed ( Actor &self ); + void _findClosestPatrolNode ( Actor &self ); + void _findClosestPatrolNodeFailed ( Actor &self ); + + + bool _setupMovingToPatrolNode ( Actor &self ); + void _setupMovingToPatrolNodeFailed ( Actor &self ); + void _moveToPatrolNode ( Actor &self ); + void _moveToPatrolNodeFailed ( Actor &self ); + + bool _setupAtPatrolNode ( Actor &self ); + void _setupAtPatrolNodeFailed ( Actor &self ); + void _atPatrolNode ( Actor &self ); + + bool _setupWaitingAtPatrolNode ( Actor &self ); + void _setupWaitingAtPatrolNodeFailed ( Actor &self ); + void _waitAtPatrolNode ( Actor &self ); + + bool _setupWaitingAtPatrolNodeForAnim ( Actor &self ); + void _setupWaitingAtPatrolNodeForAnimFailed ( Actor &self ); + void _waitAtPatrolNodeForAnim ( Actor &self ); + + bool _setupFindNextPatrolNode ( Actor &self ); + void _setupFindNextPatrolNodeFailed ( Actor &self ); + void _findNextPatrolNode ( Actor &self ); + + bool _setupHold ( Actor &self ); + void _setupHoldFailed ( Actor &self ); + void _hold ( Actor &self ); + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( Patrol ); + + Patrol(); + ~Patrol(); + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + virtual void Archive ( Archiver &arc ); + void SetAnim ( const str& anim ); + void SetInitialNode( HelperNode* node ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoPoint _gotoHelperNode; + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + HelperNodePtr _node; + HelperNodePtr _lastNode; + unsigned int _state; + int _moveFailures; + float _nextMoveAttempt; + float _waitTime; + str _torsoAnim; + str _anim; + }; + +inline void Patrol::SetInitialNode( HelperNode *node ) +{ + _node = node; +} + +inline void Patrol::SetAnim( const str &anim ) +{ + _anim = anim; +} + +inline void Patrol::Archive( Archiver &arc ) + { + Behavior::Archive ( arc ); + + // Archive Parmeters + + // Archive Components + arc.ArchiveObject ( &_gotoHelperNode ); + + // Archive Member Vars + arc.ArchiveSafePointer ( &_node ); + arc.ArchiveSafePointer ( &_lastNode ); + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveInteger ( &_moveFailures ); + arc.ArchiveFloat ( &_nextMoveAttempt ); + arc.ArchiveFloat ( &_waitTime ); + arc.ArchiveString ( &_torsoAnim ); + arc.ArchiveString ( &_anim ); + } + +#endif /* PATROL_HPP */ diff --git a/dlls/game/player.cpp b/dlls/game/player.cpp new file mode 100644 index 0000000..5f00666 --- /dev/null +++ b/dlls/game/player.cpp @@ -0,0 +1,14157 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/player.cpp $ +// $Revision:: 590 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Class definition of the player. +// +#include "_pch_cpp.h" +#include "entity.h" +#include "player.h" +#include "worldspawn.h" +#include "weapon.h" +#include "trigger.h" +#include "scriptmaster.h" +#include "path.h" +#include "navigate.h" +#include "misc.h" +#include "gravpath.h" +#include "armor.h" +#include "inventoryitem.h" +#include "gibs.h" +#include "actor.h" +#include "object.h" +#include "characterstate.h" +#include "weaputils.h" +#include "mp_manager.hpp" +#include "body.h" +#include "viewthing.h" +#include "equipment.h" +#include "powerups.h" +#include "gamecmds.h" +#include "teammateroster.hpp" +#include + +//Forward +//Back +//TurnRight +//TurnLeft +//Moveleft (strafe) +//Moveright (strafe) +//Moveup (Jump) +//Movedown (Duck) +//Action (Use) +//Sneak (Toggle or Momentary) +//Speed/Walk (Toggle or Momentary) +//Fire Left hand +//Fire Right hand + +#define SLOPE_45_MIN 0.7071f +#define SLOPE_45_MAX 0.831f +#define SLOPE_22_MIN SLOPE_45_MAX +#define SLOPE_22_MAX 0.95f + +#define PUSH_OBJECT_DISTANCE 16.0f + +#define R_ARM_NAME "Bip01 R UpperArm" +#define L_ARM_NAME "Bip01 L UpperArm" + +#define MELEE_ATTACK_LEFT (1<<0) +#define MELEE_ATTACK_RIGHT (1<<1) + +const float standardTorsoMult = 0.75f; + +const Vector power_color( 0.0f, 1.0f, 0.0f ); +const Vector acolor( 1.0f, 1.0f, 1.0f ); +const Vector bcolor( 0.5f, 0.0f, 0.0f ); + +const Vector damageNormalColor( 0.5f, 0.0f, 0.0f ); +const Vector damageFireColor( 0.5f, 0.25f, 0.0f ); + +qboolean TryPush( int entnum, vec3_t move_origin, vec3_t move_end ); + +Event EV_Player_DumpState +( + "state", + EV_CHEAT, + NULL, + NULL, + "Dumps the player's state to the console." +); +Event EV_Player_ForceTorsoState +( + "forcetorsostate", + EV_DEFAULT, + "s", + "torsostate", + "Force the player's torso to a certain state" +); +Event EV_Player_GiveAllCheat +( + "wuss", + EV_CHEAT, + NULL, + NULL, + "Gives player all weapons." +); +Event EV_Player_EndLevel +( + "endlevel", + EV_DEFAULT, + NULL, + NULL, + "Called when the player gets to the end of the level." +); +Event EV_Player_DevGodCheat +( + "god", + EV_CHEAT, + "I", + "god_mode", + "Sets the god mode cheat or toggles it." +); +Event EV_Player_DevNoTargetCheat +( + "notarget", + EV_CHEAT, + NULL, + NULL, + "Toggles the notarget cheat." +); +Event EV_Player_DevNoClipCheat +( + "noclip", + EV_CHEAT, + NULL, + NULL, + "Toggles the noclip cheat." +); +Event EV_Player_PrevItem +( + "invprev", + EV_CONSOLE, + NULL, + NULL, + "Cycle to player's previous item." +); +Event EV_Player_NextItem +( + "invnext", + EV_CONSOLE, + NULL, + NULL, + "Cycle to player's next item." +); +Event EV_Player_GiveCheat +( + "give", + EV_CHEAT | EV_TIKIONLY | EV_SCRIPTONLY, + "sI", + "name amount", + "Gives the player the specified thing (weapon, ammo, item, etc.) and optionally the amount." +); +Event EV_Player_GiveWeaponCheat +( + "giveweapon", + EV_CHEAT, + "s", + "weapon_name", + "Gives the player the specified weapon." +); +Event EV_Player_UseItem +( + "use", + EV_CONSOLE, + "sI", + "name weapon_hand", + "Use the specified weapon in the hand choosen (optional)." +); +Event EV_Player_GameVersion +( + "gameversion", + EV_CONSOLE, + NULL, + NULL, + "Prints the game version." +); +Event EV_Player_Fov +( + "fov", + EV_CONSOLE, + "F", + "fov", + "Sets the fov." +); +Event EV_Player_Dead +( + "dead", + EV_CODEONLY, + NULL, + NULL, + "Called when the player is dead." +); +Event EV_Player_SpawnEntity +( + "spawn", + EV_CHEAT | EV_SCRIPTONLY, + "sSSSSSSSS", + "entityname keyname1 value1 keyname2 value2 keyname3 value3 keyname4 value4", + "Spawns an entity." +); +Event EV_Player_SpawnActor +( + "actor", + EV_CHEAT, + "sSSSSSSSS", + "modelname keyname1 value1 keyname2 value2 keyname3 value3 keyname4 value4", + "Spawns an actor." +); +Event EV_Player_Respawn +( + "respawn", + EV_CODEONLY, + NULL, + NULL, + "Respawns the player." +); +Event EV_Player_TestThread +( + "testthread", + EV_CHEAT, + "sS", + "scriptfile label", + "Starts the named thread at label if provided." +); +Event EV_Player_ResetState +( + "resetstate", + EV_CHEAT, + NULL, + NULL, + "Reset the player's state table." +); +Event EV_Player_WhatIs +( + "whatis", + EV_CHEAT, + "i", + "entity_number", + "Prints info on the specified entity." +); +Event EV_Player_ActorInfo +( + "actorinfo", + EV_CHEAT, + "i", + "actor_number", + "Prints info on the specified actor." +); +Event EV_Player_ShowHeuristics +( + "showheuristics", + EV_CHEAT, + NULL, + NULL, + "Shows the current heuristic numbers" +); +Event EV_Player_KillEnt +( + "killent", + EV_CHEAT | EV_SCRIPTONLY, + "i", + "entity_number", + "Kills the specified entity." +); +Event EV_Player_KillClass +( + "killclass", + EV_CHEAT | EV_SCRIPTONLY, + "sI", + "classname except_entity_number", + "Kills all of the entities in the specified class." +); +Event EV_Player_RemoveEnt +( + "removeent", + EV_CHEAT | EV_SCRIPTONLY, + "i", + "entity_number", + "Removes the specified entity." +); +Event EV_Player_RemoveClass +( + "removeclass", + EV_CHEAT | EV_SCRIPTONLY, + "sI", + "classname except_entity_number", + "Removes all of the entities in the specified class." +); +Event EV_Player_ActivateNewWeapon +( + "activatenewweapon", + EV_DEFAULT, + NULL, + NULL, + "Active the new weapon specified by useWeapon." +); +Event EV_Player_DeactivateWeapon +( + "deactivateweapon", + EV_DEFAULT, + "s", + "side", + "Deactivate the weapon in the specified hand." +); +Event EV_Player_Jump +( + "jump", + EV_TIKIONLY, + "f", + "height", + "Makes the player jump." +); +Event EV_Player_AnimLoop_Torso +( + "animloop_torso", + EV_CODEONLY, + NULL, + NULL, + "Called when the torso animation has finished." +); +Event EV_Player_AnimLoop_Legs +( + "animloop_legs", + EV_CODEONLY, + NULL, + NULL, + "Called when the legs animation has finished." +); +Event EV_Player_DoUse +( + "usestuff", + EV_DEFAULT, + "E", + "usingEntity", + "Makes the player try to use whatever is in front of her." +); +Event EV_Player_SetHeadTarget +( + "headtarget", + EV_CONSOLE, + NULL, + NULL, + "Sets the Makes the player try to use whatever is in front of her." +); +Event EV_Player_ListInventory +( + "listinventory", + EV_CONSOLE, + NULL, + NULL, + "List of the player's inventory." +); +Event EV_Player_ClearTarget +( + "cleartarget", + EV_DEFAULT, + NULL, + NULL, + "Clears the target of the player" +); +Event EV_Player_ActivateShield +( + "activateshield", + EV_DEFAULT, + NULL, + NULL, + "Activates the player's shield" +); +Event EV_Player_DeactivateShield +( + "deactivateshield", + EV_DEFAULT, + NULL, + NULL, + "Deactivates the player's shield" +); +Event EV_Player_AdjustTorso +( + "adjust_torso", + EV_DEFAULT, + "b", + "boolean", + "Turn or off the torso adjustment" +); +Event EV_Player_DualWield +( + "dualwield", + EV_CONSOLE, + "ss", + "weaponleft weaponright", + "Dual wield the specified weapons" +); +Event EV_Player_UseDualWield +( + "usedualwield", + EV_CONSOLE, + NULL, + NULL, + "Use the weapons that are on the dual wield list" +); +Event EV_Player_EvaluateTorsoAnim +( + "evaluatetorsoanim", + EV_CONSOLE, + NULL, + NULL, + "Evaluate the torso anim for possible changes" +); +Event EV_Player_Turn +( + "turn", + EV_SCRIPTONLY, + "fF", + "yawangle time", + "Causes player to turn the specified amount." +); +Event EV_Player_TurnTowardsEntity +( + "turnTowardsEntity", + EV_SCRIPTONLY, + "e", + "entity", + "Causes the player to turn towards the specified entity." +); +Event EV_Player_TurnUpdate +( + "turnupdate", + EV_SCRIPTONLY, + "ff", + "yaw timeleft", + "Causes player to turn the specified amount." +); +Event EV_Player_TurnLegs +( + "turnlegs", + EV_SCRIPTONLY, + "f", + "yawangle", + "Turns the players legs instantly by the specified amount." +); +Event EV_Player_DontTurnLegs +( + "dontturnlegs", + EV_CODEONLY, + "b", + "flag", + "Specifies whether or not to turn the legs while strafing." +); +Event EV_Player_NextPainTime +( + "nextpaintime", + EV_CODEONLY, + "f", + "seconds", + "Set the next time the player experiences pain (Current time + seconds specified)." +); +Event EV_Player_FinishUseAnim +( + "finishuseanim", + EV_DEFAULT, + NULL, + NULL, + "Fires off all targets associated with a particular useanim." +); +Event EV_Player_Nightvision +( + "nightvision", + EV_CODEONLY | EV_CONSOLE, + NULL, + NULL, + "Toggles player nightvision mode" +); +Event EV_Player_Holster +( + "holster", + EV_SCRIPTONLY, + NULL, + NULL, + "Holsters all wielded weapons, or unholsters previously put away weapons" +); +Event EV_Player_SafeHolster +( + "safeholster", + EV_SCRIPTONLY, + "b", + "putaway", + "Holsters all wielded weapons, or unholsters previously put away weapons\n" + "preserves state, so it will not holster or unholster unless necessary" +); +Event EV_Player_StartUseObject +( + "startuseobject", + EV_DEFAULT, + NULL, + NULL, + "starts up the useobject's animations." +); +Event EV_Player_FinishUseObject +( + "finishuseobject", + EV_DEFAULT, + NULL, + NULL, + "Fires off all targets associated with a particular useobject." +); +Event EV_Player_WatchActor +( + "watchactor", + EV_SCRIPTONLY, + "eFFB", + "entity_to_watch time angle watchEntireDuration", + "Makes the player's camera watch the specified entity." +); +Event EV_Player_WatchEntity +( + "watchentity", + EV_SCRIPTONLY, + "eFFB", + "entityToWatch time angle watchEntireDuration", + "Makes the player's camera watch the specified entity." +); +Event EV_Player_StopWatchingEntity +( + "stopwatchingactor", + EV_SCRIPTONLY, + "e", + "entity_to_stop_watching", + "Makes the player's camera stop watching the specified entity." +); +Event EV_Player_PutawayWeapon +( + "putawayweapon", + EV_DEFAULT, + "s", + "whichHand", + "Put away or deactivate the current weapon, whichHand can be left, right or dual." +); +Event EV_Player_Weapon +( + "weaponcommand", + EV_CODEONLY, + "sSSSSSSS", + "hand arg1 arg2 arg3 arg4 arg5 arg6 arg7", + "Pass the args to the active weapon in the specified hand" +); +Event EV_Player_Done +( + "playerdone", + EV_CODEONLY, + NULL, + NULL, + "Clears the waitForPlayer script command, allowing threads to run" +); +Event EV_Player_ActivateDualWeapons +( + "activatedualweapons", + EV_DEFAULT, + NULL, + NULL, + "Activates 2 weapons at once" +); +Event EV_Player_StartCoolItem +( + "startcoolitem", + EV_CODEONLY, + NULL, + NULL, + "Player is starting to show off the cool item" +); +Event EV_Player_StopCoolItem +( + "stopcoolitem", + EV_CODEONLY, + NULL, + NULL, + "Player is starting to show off the cool item" +); +Event EV_Player_ShowCoolItem +( + "showcoolitem", + EV_CODEONLY, + NULL, + NULL, + "Player is showing the cool item, actually display it" +); +Event EV_Player_HideCoolItem +( + "hidecoolitem", + EV_CODEONLY, + NULL, + NULL, + "Player is finished showing the cool item, now hide it" +); +Event EV_Player_SetDamageMultiplier +( + "damage_multiplier", + EV_SCRIPTONLY, + "f", + "damage_multiplier", + "Sets the current damage multiplier" +); +Event EV_Player_WaitForState +( + "waitForState", + EV_SCRIPTONLY, + "s", + "stateToWaitFor", + "When set, the player will clear waitforplayer when this state is hit\n" + "in the legs or torso." +); +Event EV_Player_LogStats +( + "logstats", + EV_CHEAT, + "b", + "state", + "Turn on/off the debugging playlog" +); +Event EV_Player_TakePain +( + "takepain", + EV_DEFAULT, + "b", + "bool", + "Set whether or not to take pain" +); +Event EV_Player_SkipCinematic +( + "skipcinematic", + EV_CONSOLE, + NULL, + NULL, + "Skip the current cinematic" +); +Event EV_Player_ResetHaveItem +( + "resethaveitem", + EV_CONSOLE, + "s", + "weapon_name", + "Resets the game var that keeps track that we have gotten this weapon" +); +Event EV_Player_Score +( + "score", + EV_CONSOLE, + NULL, + NULL, + "Show the score for the current deathmatch game" +); +Event EV_Player_CallVote +( + "callvote", + EV_CONSOLE, + "sS", + "arg1 arg2", + "Player calls a vote" +); +Event EV_Player_Vote +( + "vote", + EV_CONSOLE, + "s", + "arg1", + "Player votes either yes or no" +); +Event EV_Player_JoinTeam +( + "joinmpteam", + EV_CONSOLE, + "s", + "teamName", + "Makes the player join the specified team." +); +Event EV_Player_MultiplayerCommand +( + "mpCmd", + EV_CONSOLE, + "ss", + "command parm", + "Sends a command from the player to the multiplayer system." +); +Event EV_Player_DeadBody +( + "deadbody", + EV_CODEONLY, + NULL, + NULL, + "Spawn a dead body" +); +Event EV_Player_SetAimType +( + "setaim", + EV_DEFAULT, + "s", + "aimtype", + "Sets the accuracy modifiers for the player" +); +Event EV_Player_ReloadWeapon +( + "forcereload", + EV_TIKIONLY, + NULL, + NULL, + "Tells the weapon to reload" +); +Event EV_Player_AnimateWeapon +( + "animateweapon", + EV_DEFAULT, + "sSB", + "anim hand animatingFlag", + "Animates the weapon, optionally, in the specific hand (defaults to DUAL)" + "If the animatingFlag is set to false, it will not set the WEAPON_ANIMATING" + "as the status for this animation." +); +Event EV_Player_SwitchWeaponMode +( + "switchmode", + EV_TIKIONLY, + NULL, + NULL, + "Tells the weapon to switch modes of fire" +); +Event EV_Player_ReloadTiki +( + "reloadviewtiki", + EV_CHEAT, + "s", + "tikiname", + "Reloads the specified TIKI file from disk" +); +Event EV_Player_SetViewAngles +( + "playerviewangles", + EV_CHEAT, + "v[0,360][0,360][0,360]", + "newAngles", + "set the angles of the player to newAngles." + ); +Event EV_Player_ProjDetonate +( + "projdetonate", + EV_CODEONLY, + "b", + "detonate", + "Event that sets the whether or not to detonate a trigger projectile (grenade launcher)" +); +Event EV_Player_UseEntity +( + "useentity", + EV_DEFAULT, + "e", + "entity", + "makes player use the passed in entity" +); +Event EV_Player_SetupDialog +( + "setupdialog", + EV_CODEONLY, + "is", + "entnum soundname", + "Sets up the dialog for the player" +); +Event EV_Player_ClearDialog +( + "cleardialog", + EV_CODEONLY, + NULL, + NULL, + "Clears the dialog for the player" +); + +Event EV_Player_ClearTextDialog +( + "cleartextdialog", + EV_CODEONLY, + NULL, + NULL, + "Clears the text dialog of the player" +); + +// +// Objective Events +// +Event EV_Player_LoadObjectives +( + "loadobjectives", + EV_SCRIPTONLY, + "s", + "name", + "Loads the a set of objectives" +); +Event EV_Player_SetObjectiveShow +( + "setobjectiveshow", + EV_SCRIPTONLY, + "sb", + "name show", + "Sets whether or not a specified objective is shown" +); +Event EV_Player_SetObjectiveComplete +( + "setobjectivecomplete", + EV_SCRIPTONLY, + "sb", + "name complete", + "Sets whether or not a specified objective is complete" +); +Event EV_Player_SetObjectiveFailed +( + "setobjectivefailed", + EV_SCRIPTONLY, + "sb", + "name failed", + "Sets whether or not a specified objective is failed" +); + +// +// Information Events +// +Event EV_Player_SetInformationShow +( + "setinformationshow", + EV_SCRIPTONLY, + "sb", + "name show", + "Sets whether or not a specified information is shown" +); +Event EV_Player_MissionFailed +( + "missionfailed", + EV_SCRIPTONLY, + "S", + "reason", + "Displays the mission failed screen on the client side" +); +Event EV_Player_SetStat +( + "setstat", + EV_SCRIPTONLY, + "si", + "stat_name stat_value", + "Sets a stat value" +); +Event EV_Player_SpecialMoveChargeStart +( + "specialmovestart", + EV_CODEONLY, + NULL, + NULL, + "Starts charging up for a special move" +); +Event EV_Player_SpecialMoveChargeEnd +( + "specialmoveend", + EV_CODEONLY, + NULL, + NULL, + "Ends charging up for a special move" +); +Event EV_Player_SpecialMoveChargeTime +( + "specialmovetime", + EV_CODEONLY, + "f", + "time", + "Sets how long to wait before triggering the special move." +); +Event EV_Player_ChangeChar +( + "changechar", + EV_SCRIPTONLY, + "ssss", + "statefile model replacemodelName spawnNPC", + "Changes the character state and model." +); +Event EV_Player_SetPlayerCharacter +( + "setplayerchar", + EV_SCRIPTONLY, + "ss", + "statefile model", + "Sets the player character." +); +Event EV_Player_PlayerKnockback +( + "playerknockback", + EV_CODEONLY, + "f", + "knockback", + "Sets knockback for the player" +); +Event EV_Player_KnockbackMultiplier +( + "knockbackmultiplier", + EV_CODEONLY, + "i", + "knockbackmult", + "Sets knockback multiplierfor the player" +); +Event EV_Player_Melee +( + "melee", + EV_DEFAULT, + "FSF", + "damage means_of_death knockback", + "Makes the player do a weapon-less melee attack. " +); +Event EV_Player_ChangeStance +( + "stance", + EV_CONSOLE, + "i", + "stanceNumber", + "Makes the player change to the specified stance" +); +Event EV_Player_ClearStanceTorso +( + "clearstancetorso", + EV_CODEONLY, + NULL, + NULL, + "Clears internal torso stance data." +); +Event EV_Player_ClearStanceLegs +( + "clearstancelegs", + EV_CODEONLY, + NULL, + NULL, + "Clears internal legs stance data." +); +Event EV_Player_GivePoints +( + "givepoints", + EV_SCRIPTONLY, + "i", + "points", + "Gives the player points (added to current points)" +); +Event EV_Player_SetPoints +( + "setpoints", + EV_SCRIPTONLY, + "i", + "points", + "Sets the players number of points." +); +Event EV_Player_ChangeCharFadeIn +( + "changecharfadein", + EV_CODEONLY, + NULL, + NULL, + "Begins the fade in and swaps character." +); +Event EV_Player_ClearIncomingMelee +( + "clearincomingmelee", + EV_CODEONLY, + NULL, + NULL, + "Clears the incoming melee flag." +); +Event EV_Player_BendTorso +( + "bendtorso", + EV_DEFAULT, + "F", + "multiplier", + "Sets multiplier for the torso bend angles, defaults to 0.75" +); +Event EV_Player_RemovePowerup +( + "removepowerup", + EV_CODEONLY, + NULL, + NULL, + "Removes the current powerup." +); +Event EV_Player_DropRune +( + "droprune", + EV_CODEONLY, + NULL, + NULL, + "Drops the player's current rune." +); +Event EV_Player_SetCanTransferEnergy +( + "canTransferEnergy", + EV_DEFAULT, + NULL, + NULL, + "Makes the player able to transfer energy from ammo to armor." +); +Event EV_Player_SetDoDamageScreenFlash +( + "doDamageScreenFlash", + EV_DEFAULT, + "B", + "bool", + "Makes the screen flash when the player is damaged (or turns it off)." +); +Event EV_Player_MeleeDamageStart +( + "meleedamagestart", + EV_DEFAULT, + "S", + "hand", + "Start doing melee damage with the weapon in the specified hand." +); +Event EV_Player_MeleeDamageEnd +( + "meleedamageend", + EV_DEFAULT, + "S", + "hand", + "Stop doing melee damage with the weapon in the specified hand." +); +Event EV_Player_PointofView +( + "pointofview", + EV_DEFAULT, + NULL, + NULL, + "Changes the viewpoint of the player, alternates between 1st and 3rd person" +); +Event EV_Player_FinishingMove +( + "finishingmove", + EV_TIKIONLY, + "ssFFFF", + "state direction coneangle distance enemyyaw chance", + "Changes the viewpoint of the player, alternates between 1st and 3rd person" +); +Event EV_Player_ClearFinishingMove +( + "clearfinishingmovelist", + EV_DEFAULT, + NULL, + NULL, + "Clears the finishing move list." +); +Event EV_Player_DoFinishingMove +( + "dofinishingmove", + EV_DEFAULT, + NULL, + NULL, + "Fires off the finishing move." +); +Event EV_Player_ForceTimeScale +( + "forcetimescale", + EV_DEFAULT, + "F", + "timescale", + "Sets the timescale for the game" +); +Event EV_Player_Freeze +( + "freeze", + EV_DEFAULT, + "B", + "freeze", + "Freezes the player until the freeze 0 is called" +); +Event EV_Player_Immobilize +( + "immobilize", + EV_DEFAULT, + "B", + "immobilize", + "Immobilizes the player until the immobilize 0 is called (can only look around)" +); +Event EV_Player_DoUseEntity +( + "douseentity", + EV_DEFAULT, + NULL, + NULL, + "Called from the tiki on a frame to use an entity." +); +Event EV_Player_DoneUseEntity +( + "doneuseentity", + EV_DEFAULT, + NULL, + NULL, + "When the useentity animation is complete" +); +Event EV_Player_SetAttackType +( + "attacktype", + EV_DEFAULT, + "s", + "attacktype", + "Sets the attack type of the attack" +); +Event EV_Player_NextGameplayAnim +( + "nextgameplayanim", + EV_CODEONLY, + "s", + "objname", + "Increments the gameplay animation index." +); +Event EV_Player_SetGameplayAnim +( + "setgameplayanim", + EV_CODEONLY, + "I", + "index", + "Sets the gameplay animation index directly." +); +Event EV_Player_DisableUseWeapon +( + "disableuseweapon", + EV_DEFAULT, + "B", + "disable", + "Disables the weapon use" +); +Event EV_Player_DisableInventory +( + "disableinventory", + EV_DEFAULT, + "f", + "disable", + "Disables the player's inventory" +); + +Event EV_Player_EquipItems +( + "equipitems", + EV_DEFAULT, + NULL, + NULL, + "Equips active items from the database." +); +Event EV_Player_AddRosterTeammate1 +( + "addrosterteammate1", + EV_DEFAULT, + "e", + "entity", + "Sets the teammate to roster position 1" +); +Event EV_Player_AddRosterTeammate2 +( + "addrosterteammate2", + EV_DEFAULT, + "e", + "entity", + "Sets the teammate to roster position 2" +); +Event EV_Player_AddRosterTeammate3 +( + "addrosterteammate3", + EV_DEFAULT, + "e", + "entity", + "Sets the teammate to roster position 3" +); +Event EV_Player_AddRosterTeammate4 +( + "addrosterteammate4", + EV_DEFAULT, + "e", + "entity", + "Sets the actor to roster position 4" +); +Event EV_Player_RemoveRosterTeammate1 +( + "removerosterteammate1", + EV_DEFAULT, + NULL, + NULL, + "Removed the teammate from roster position 1" +); +Event EV_Player_RemoveRosterTeammate2 +( + "removerosterteammate2", + EV_DEFAULT, + NULL, + NULL, + "Removed the teammate from roster position 2" +); +Event EV_Player_RemoveRosterTeammate3 +( + "removerosterteammate3", + EV_DEFAULT, + NULL, + NULL, + "Removed the teammate from roster position 3" +); +Event EV_Player_RemoveRosterTeammate4 +( + "removerosterteammate4", + EV_DEFAULT, + NULL, + NULL, + "Removed the teammate from roster position 4" +); + +Event EV_Player_HudPrint +( + "hudPrint", + EV_DEFAULT, + "s", + "string", + "Prints to the hud." +); +Event EV_Player_ClearItemText +( + "clearItemText", + EV_CODEONLY, + NULL, + NULL, + "Clears the item text." +); +Event EV_Player_ValidPlayerModel +( + "validPlayerModel", + EV_TIKIONLY, + NULL, + NULL, + "Specifies that this model is valid for the player." +); +Event EV_Player_AddHud +( + "addHud", + EV_SCRIPTONLY, + "s", + "hudName", + "Tells the player to add a hud to his display." +); +Event EV_Player_RemoveHud +( + "removeHud", + EV_SCRIPTONLY, + "s", + "hudName", + "Tells the player to remove a hud from his display." +); + +Event EV_Player_KillAllDialog +( + "killalldialog", + EV_DEFAULT, + NULL, + NULL, + "Calls stopdialog on all the actors in the level" +); +Event EV_Player_ForceMoveType +( + "forceMoveType", + EV_DEFAULT, + "s", + "moveTypeName", + "Forces the player to use the named move type" +); +Event EV_Player_IsOnGround +( + "isplayeronground", + EV_DEFAULT, + "@f", + "onground", + "Checks if the player is on the ground -- returns 1 if true 0 if false" +); +Event EV_Player_BackpackAttachOffset +( + "backpackAttachOffset", + EV_DEFAULT, + "v", + "offset", + "Sets the attachment offset for backpacks" +); +Event EV_Player_BackpackAttachAngles +( + "backpackAttachAngles", + EV_DEFAULT, + "v", + "anglesOffset", + "Sets the attachment angles offset for backpacks" +); +Event EV_Player_FlagAttachOffset +( + "flagAttachOffset", + EV_DEFAULT, + "v", + "offset", + "Sets the attachment offset for flags" +); +Event EV_Player_FlagAttachAngles +( + "flagAttachAngles", + EV_DEFAULT, + "v", + "anglesOffset", + "Sets the attachment angles offset for flags" +); +Event EV_Player_BackupModel +( + "backupModel", + EV_DEFAULT, + "s", + "backupModelName", + "Sets the name of the model to use as backup if the client doesn't have this one" +); + +/* +============================================================================== + +PLAYER + +============================================================================== +*/ + +CLASS_DECLARATION( Sentient, Player, "player" ) +{ + { &EV_ClientMove, &Player::ClientThink }, + { &EV_ClientEndFrame, &Player::EndFrame }, + { &EV_Vehicle_Enter, &Player::EnterVehicle }, + { &EV_Vehicle_Exit, &Player::ExitVehicle }, + { &EV_Player_EndLevel, &Player::EndLevel }, + { &EV_Player_UseItem, &Player::EventUseItem }, + { &EV_Player_GiveCheat, &Player::GiveCheat }, + { &EV_Player_GiveWeaponCheat, &Player::GiveWeaponCheat }, + { &EV_Player_GiveAllCheat, &Player::GiveAllCheat }, + { &EV_Player_DevGodCheat, &Player::GodCheat }, + { &EV_Player_DevNoTargetCheat, &Player::NoTargetCheat }, + { &EV_Player_DevNoClipCheat, &Player::NoclipCheat }, + { &EV_Player_GameVersion, &Player::GameVersion }, + { &EV_Player_DumpState, &Player::DumpState }, + { &EV_Player_ForceTorsoState, &Player::ForceTorsoState }, + { &EV_Player_Fov, &Player::Fov }, + { &EV_Kill, &Player::Kill }, + { &EV_Player_Dead, &Player::Dead }, + { &EV_Player_SpawnEntity, &Player::SpawnEntity }, + { &EV_Player_SpawnActor, &Player::SpawnActor }, + { &EV_Player_Respawn, &Player::Respawn }, + { &EV_Player_DoUse, &Player::DoUse }, + { &EV_Pain, &Player::Pain }, + { &EV_Killed, &Player::Killed }, + { &EV_Gib, &Player::GibEvent }, + { &EV_GotKill, &Player::GotKill }, + { &EV_Player_TestThread, &Player::TestThread }, + { &EV_Player_ResetState, &Player::ResetState }, + { &EV_Player_WhatIs, &Player::WhatIs }, + { &EV_Player_ActorInfo, &Player::ActorInfo }, + { &EV_Player_ShowHeuristics, &Player::ShowHeuristics }, + { &EV_Player_KillEnt, &Player::KillEnt }, + { &EV_Player_RemoveEnt, &Player::RemoveEnt }, + { &EV_Player_KillClass, &Player::KillClass }, + { &EV_Player_RemoveClass, &Player::RemoveClass }, + { &EV_Player_AnimLoop_Legs, &Player::EndAnim_Legs }, + { &EV_Player_AnimLoop_Torso, &Player::EndAnim_Torso }, + { &EV_Player_Jump, &Player::Jump }, + { &EV_Sentient_JumpXY, &Player::JumpXY }, + { &EV_Player_ActivateNewWeapon, &Player::ActivateNewWeapon }, + { &EV_Player_DeactivateWeapon, &Player::DeactivateWeapon }, + { &EV_Player_SetHeadTarget, &Player::SetHeadTarget }, + { &EV_Player_ListInventory, &Player::ListInventoryEvent }, + { &EV_Player_ClearTarget, &Player::ClearTarget }, + { &EV_Player_ActivateShield, &Player::ActivateShield }, + { &EV_Player_DeactivateShield, &Player::DeactivateShield }, + { &EV_Player_AdjustTorso, &Player::AdjustTorso }, + { &EV_Player_DualWield, &Player::DualWield }, + { &EV_Player_UseDualWield, &Player::UseDualWield }, + { &EV_Player_EvaluateTorsoAnim, &Player::EvaluateTorsoAnim }, + { &EV_Player_NextPainTime, &Player::NextPainTime }, + { &EV_Player_Turn, &Player::Turn }, + { &EV_Player_TurnTowardsEntity, &Player::turnTowardsEntity }, + { &EV_Player_TurnUpdate, &Player::TurnUpdate }, + { &EV_Player_TurnLegs, &Player::TurnLegs }, + { &EV_Player_DontTurnLegs, &Player::DontTurnLegs }, + { &EV_Player_FinishUseAnim, &Player::FinishUseAnim }, + { &EV_Sentient_SetMouthAngle, &Player::SetMouthAngle }, + { &EV_Player_Holster, &Player::HolsterToggle }, + { &EV_Player_Nightvision, &Player::NightvisionToggle }, + { &EV_Player_SafeHolster, &Player::Holster }, + { &EV_Player_StartUseObject, &Player::StartUseObject }, + { &EV_Player_FinishUseObject, &Player::FinishUseObject }, + { &EV_Player_WatchActor, &Player::WatchEntity }, + { &EV_Player_WatchEntity, &Player::WatchEntity }, + { &EV_Player_StopWatchingEntity, &Player::StopWatchingEntity }, + { &EV_Player_PutawayWeapon, &Player::PutawayWeapon }, + { &EV_Player_Weapon, &Player::WeaponCommand }, + { &EV_Player_Done, &Player::PlayerDone }, + { &EV_Player_ActivateDualWeapons, &Player::ActivateDualWeapons }, + { &EV_Player_StartCoolItem, &Player::StartCoolItem }, + { &EV_Player_StopCoolItem, &Player::StopCoolItem }, + { &EV_Player_ShowCoolItem, &Player::ShowCoolItem }, + { &EV_Player_HideCoolItem, &Player::HideCoolItem }, + { &EV_Player_SetDamageMultiplier, &Player::SetDamageMultiplier }, + { &EV_Player_WaitForState, &Player::WaitForState }, + { &EV_Player_LogStats, &Player::LogStats }, + { &EV_Player_TakePain, &Player::SetTakePain }, + { &EV_Player_SkipCinematic, &Player::SkipCinematic }, + { &EV_Player_ResetHaveItem, &Player::ResetHaveItem }, + { &EV_Show, &Player::PlayerShowModel }, + { &EV_Player_Score, &Player::Score }, + { &EV_Player_CallVote, &Player::CallVote }, + { &EV_Player_Vote, &Player::Vote }, + { &EV_Player_JoinTeam, &Player::joinTeam }, + { &EV_Player_MultiplayerCommand, &Player::multiplayerCommand }, + { &EV_Player_DeadBody, &Player::DeadBody }, + { &EV_Player_SetAimType, &Player::SetAimType }, + { &EV_Player_ReloadWeapon, &Player::ReloadWeapon }, + { &EV_Player_AnimateWeapon, &Player::AnimateWeapon }, + { &EV_Player_SwitchWeaponMode, &Player::SwitchWeaponMode }, + { &EV_Player_ReloadTiki, &Player::ReloadTiki }, + { &EV_Player_SetViewAngles, &Player::SetViewAnglesEvent }, + { &EV_Player_ProjDetonate, &Player::ProjDetonate }, + { &EV_Player_UseEntity, &Player::UseSpecifiedEntity }, + { &EV_Player_SetupDialog, &Player::SetupDialog }, + { &EV_Player_ClearDialog, &Player::ClearDialog }, + { &EV_Player_ClearTextDialog, &Player::ClearTextDialog }, + { &EV_Player_LoadObjectives, &Player::LoadObjectives }, + { &EV_Player_SetObjectiveShow, &Player::SetObjectiveShow }, + { &EV_Player_SetObjectiveComplete, &Player::SetObjectiveComplete }, + { &EV_Player_SetObjectiveFailed, &Player::SetObjectiveFailed }, + { &EV_Player_SetInformationShow, &Player::SetInformationShow }, + { &EV_Player_MissionFailed, &Player::MissionFailed }, + + { &EV_Player_SetStat, &Player::SetStat }, + { &EV_Player_SpecialMoveChargeStart, &Player::SpecialMoveChargeStart }, + { &EV_Player_SpecialMoveChargeEnd, &Player::SpecialMoveChargeEnd }, + { &EV_Player_SpecialMoveChargeTime, &Player::SpecialMoveChargeTime }, + { &EV_Player_ChangeChar, &Player::ChangeChar }, + { &EV_Player_SetPlayerCharacter, &Player::SetPlayerChar }, + { &EV_Player_PlayerKnockback, &Player::PlayerKnockback }, + { &EV_Player_KnockbackMultiplier, &Player::KnockbackMultiplier }, + { &EV_Player_Melee, &Player::MeleeEvent }, + { &EV_Player_ChangeStance, &Player::ChangeStance }, + { &EV_Player_ClearStanceTorso, &Player::ClearStanceTorso }, + { &EV_Player_ClearStanceLegs, &Player::ClearStanceLegs }, + + { &EV_Player_MeleeDamageStart, &Player::MeleeDamageStart }, + { &EV_Player_MeleeDamageEnd, &Player::MeleeDamageEnd }, + { &EV_Player_GivePoints, &Player::GivePointsEvent }, + { &EV_Player_SetPoints, &Player::SetPointsEvent }, + { &EV_Player_ChangeCharFadeIn, &Player::ChangeCharFadeIn }, + { &EV_Player_ClearIncomingMelee, &Player::ClearIncomingMelee }, + { &EV_Player_BendTorso, &Player::SetBendTorso }, + { &EV_Sentient_HeadWatchAllowed, &Player::HeadWatchAllowed }, + + { &EV_Player_RemovePowerup, &Player::removePowerupEvent }, + { &EV_Player_DropRune, &Player::dropRune }, + { &EV_Sentient_AddMeleeAttacker, &Player::AddMeleeAttacker }, + + { &EV_Player_SetCanTransferEnergy, &Player::setCanTransferEnergy }, + { &EV_Player_SetDoDamageScreenFlash, &Player::setDoDamageScreenFlash }, + { &EV_Player_PointofView, &Player::pointOfView }, + { &EV_Player_FinishingMove, &Player::addFinishingMove }, + { &EV_Player_ClearFinishingMove, &Player::clearFinishingMove }, + { &EV_Player_DoFinishingMove, &Player::doFinishingMove }, + { &EV_Player_ForceTimeScale, &Player::forceTimeScale }, + { &EV_Player_Freeze, &Player::freezePlayer }, + { &EV_Player_Immobilize, &Player::immobilizePlayer }, + { &EV_Player_DoUseEntity, &Player::doUseEntity }, + { &EV_Player_DoneUseEntity, &Player::doneUseEntity }, + { &EV_Player_SetAttackType, &Player::setAttackType }, + { &EV_Player_NextGameplayAnim, &Player::nextGameplayAnim }, + { &EV_Player_SetGameplayAnim, &Player::setGameplayAnim }, + { &EV_Player_DisableUseWeapon, &Player::setDisableUseWeapon }, + { &EV_Player_DisableInventory, &Player::setDisableInventory }, + { &EV_Player_EquipItems, &Player::equipItems }, + + { &EV_Use, &Player::usePlayer }, + + { &EV_Player_AddRosterTeammate1, &Player::addRosterTeammate1 }, + { &EV_Player_AddRosterTeammate2, &Player::addRosterTeammate2 }, + { &EV_Player_AddRosterTeammate3, &Player::addRosterTeammate3 }, + { &EV_Player_AddRosterTeammate4, &Player::addRosterTeammate4 }, + + { &EV_Player_RemoveRosterTeammate1, &Player::removeRosterTeammate1 }, + { &EV_Player_RemoveRosterTeammate2, &Player::removeRosterTeammate2 }, + { &EV_Player_RemoveRosterTeammate3, &Player::removeRosterTeammate3 }, + { &EV_Player_RemoveRosterTeammate4, &Player::removeRosterTeammate4 }, + + { &EV_Player_HudPrint, &Player::hudPrint }, + + { &EV_Player_ClearItemText, &Player::clearItemText }, + + { &EV_Player_ValidPlayerModel, &Player::setValidPlayerModel }, + + { &EV_Player_AddHud, &Player::addHud }, + { &EV_Player_RemoveHud, &Player::removeHud }, + + //Vehicle Event Handoff + { &EV_Driver_AnimDone, &Player::PassEventToVehicle }, + + { &EV_Warp, &Player::warp }, + { &EV_Player_KillAllDialog, &Player::killAllDialog }, + + { &EV_Player_ForceMoveType, &Player::forceMoveType }, + { &EV_Player_IsOnGround, &Player::isPlayerOnGround }, + + { &EV_Player_BackpackAttachOffset, &Player::setBackpackAttachOffset }, + { &EV_Player_BackpackAttachAngles, &Player::setBackpackAttachAngles }, + + { &EV_Player_FlagAttachOffset, &Player::setFlagAttachOffset }, + { &EV_Player_FlagAttachAngles, &Player::setFlagAttachAngles }, + + { &EV_Player_BackupModel, &Player::setBackupModel }, + { NULL, NULL } +}; + +qboolean Player::checkturnleft( Conditional &condition ) +{ + float yaw; + + yaw = SHORT2ANGLE( last_ucmd.angles[ YAW ] + client->ps.delta_angles[ YAW ] ); + + return ( angledist( old_v_angle[ YAW ] - yaw ) < -8.0f ); +} + +qboolean Player::checkturnright( Conditional &condition ) +{ + float yaw; + + yaw = SHORT2ANGLE( last_ucmd.angles[ YAW ] + client->ps.delta_angles[ YAW ] ); + + return ( angledist( old_v_angle[ YAW ] - yaw ) > 8.0f ); +} + +qboolean Player::checkforward( Conditional &condition ) +{ + return last_ucmd.forwardmove > 0; +} + +qboolean Player::checkbackward( Conditional &condition ) +{ + return last_ucmd.forwardmove < 0; +} + +qboolean Player::checkstrafeleft( Conditional &condition ) +{ + return last_ucmd.rightmove < 0; +} + +qboolean Player::checkstraferight( Conditional &condition ) +{ + return last_ucmd.rightmove > 0; +} + +qboolean Player::checkleanleft(Conditional &condition) +{ + if( client->ps.leanDelta > 0 ) + { + return qtrue; + } + + return qfalse; +} + +qboolean Player::checkleanright( Conditional &condition ) +{ + + if(client->ps.leanDelta < 0) + { + return qtrue; + } + + return qfalse; +} + +qboolean Player::checkduck( Conditional &condition ) +{ + if ( client->ps.pm_flags & PMF_DUCKED ) + return qtrue; + else + return qfalse; +} + +qboolean Player::checkrise( Conditional &condition ) +{ + if ( ( do_rise ) && ( velocity.z > 32.0f ) ) + { + return qtrue; + } + do_rise = qfalse; + + return qfalse; +} + +qboolean Player::checkjump( Conditional &condition ) +{ + if ( sv_instantjump->integer ) + return client->ps.jumped; + else + return last_ucmd.upmove > 0; +} + +qboolean Player::checkcrouch( Conditional &condition ) +{ + if ( last_ucmd.upmove < 0 ) // check for downward movement + return qtrue; + + return qfalse; +} + +qboolean Player::checkjumpflip( Conditional &condition ) +{ + return velocity.z < ( sv_currentGravity->value * 0.5f ); +} + +qboolean Player::checkanimdone_legs( Conditional &condition ) +{ + if ( ( edict->s.anim & ANIM_BLEND ) == 0 ) + return true; + + return animdone_Legs; +} + +qboolean Player::checkanimdone_torso( Conditional &condition ) +{ + if ( ( edict->s.torso_anim & ANIM_BLEND ) == 0 ) + return true; + + return animdone_Torso; +} + +qboolean Player::checkattackleft( Conditional &condition ) +{ + if ( level.playerfrozen || ( flags & FL_IMMOBILE ) ) + { + return qfalse; + } + + if ( multiplayerManager.inMultiplayer() && ( !multiplayerManager.isFightingAllowed() || multiplayerManager.isPlayerSpectator( this ) ) ) + { + return qfalse; + } + + // Make sure we aren't leaning + + if ( client->ps.leanDelta != 0 ) + return false; + + if ( last_ucmd.buttons & BUTTON_ATTACKLEFT ) + { + Weapon *weapon; + + last_attack_button = BUTTON_ATTACKLEFT; + + weapon = GetActiveWeapon( WEAPON_LEFT ); + if ( weapon ) + { + weapon->CheckReload( FIRE_MODE1 ); + return qtrue; + } + + weapon = GetActiveWeapon( WEAPON_RIGHT ); + if ( weapon ) + { + weapon->CheckReload( FIRE_MODE1 ); + return qtrue; + } + + weapon = GetActiveWeapon( WEAPON_DUAL ); + if ( weapon ) + { + weapon->CheckReload( FIRE_MODE1 ); + return qtrue; + } + + // No ammo + return qfalse; + } + else + { + return qfalse; + } +} + +qboolean Player::checkattackbuttonleft( Conditional &condition ) +{ + if ( level.playerfrozen || ( flags & FL_IMMOBILE ) ) + { + return qfalse; + } + + if ( multiplayerManager.inMultiplayer() && ( !multiplayerManager.isFightingAllowed() || multiplayerManager.isPlayerSpectator( this ) ) ) + { + return qfalse; + } + else + { + return ( last_ucmd.buttons & BUTTON_ATTACKLEFT ); + } +} + +qboolean Player::checkattackright( Conditional &condition ) +{ + Weapon *weapon; + + if ( level.playerfrozen || ( flags & FL_IMMOBILE ) ) + { + return qfalse; + } + + if ( multiplayerManager.inMultiplayer() && ( !multiplayerManager.isFightingAllowed() || multiplayerManager.isPlayerSpectator( this ) ) ) + { + return qfalse; + } + + // Make sure we aren't leaning + + if ( client->ps.leanDelta != 0 ) + return false; + + // If we're in the middle of a reload, we're not done attacking + /* weapon = GetActiveWeapon( WEAPON_RIGHT ); + if (weapon ) + { + if ( weapon->weaponstate == WEAPON_RELOADING ) + return true; + }*/ + + if ( last_ucmd.buttons & BUTTON_ATTACKRIGHT ) + { + last_attack_button = BUTTON_ATTACKRIGHT; + + weapon = GetActiveWeapon( WEAPON_RIGHT ); + if ( weapon ) + { + weapon->CheckReload( FIRE_MODE2 ); + return qtrue; + } + + weapon = GetActiveWeapon( WEAPON_LEFT ); + if ( weapon ) + { + weapon->CheckReload( FIRE_MODE2 ); + return qtrue; + } + + weapon = GetActiveWeapon( WEAPON_DUAL ); + if ( weapon ) + { + weapon->CheckReload( FIRE_MODE2 ); + return qtrue; + } + + // No ammo + return qfalse; + } + else + { + return qfalse; + } +} + +qboolean Player::checkattackbuttonright( Conditional &condition ) +{ + if ( level.playerfrozen || ( flags & FL_IMMOBILE ) ) + { + return qfalse; + } + + if ( multiplayerManager.inMultiplayer() && ( !multiplayerManager.isFightingAllowed() || multiplayerManager.isPlayerSpectator( this ) ) ) + { + return qfalse; + } + else + { + return ( last_ucmd.buttons & BUTTON_ATTACKRIGHT ); + } +} + +qboolean Player::checksneak( Conditional &condition ) +{ + return qfalse; //( last_ucmd.buttons & BUTTON_SNEAK ) != 0; +} + +qboolean Player::checkrun( Conditional &condition ) +{ + return ( last_ucmd.buttons & BUTTON_RUN ) != 0; +} + +qboolean Player::checkwasrunning( Conditional &condition ) +{ + return ( pm_lastruntime > MINIMUM_RUNNING_TIME ); +} + +qboolean Player::checkholsterweapon( Conditional &condition ) +{ + if(client->ps.pm_flags & PMF_DISABLE_INVENTORY || _disableUseWeapon) + { + return qfalse; + } + + return ( last_ucmd.buttons & BUTTON_HOLSTERWEAPON ) != 0; +} + +qboolean Player::checkuse( Conditional &condition ) +{ + return ( last_ucmd.buttons & BUTTON_USE ) != 0; +} + +qboolean Player::checkcanturn( Conditional &condition ) +{ + float yaw; + Vector oldang( v_angle ); + qboolean result; + + yaw = (float)atof( condition.getParm( 1 ) ); + + v_angle[ YAW ] = (float)( ( int )( anglemod( v_angle[ YAW ] + yaw ) / 22.5f ) * 22.5f ); + SetViewAngles( v_angle ); + + result = qtrue; //CheckMove( vec_zero ); + + SetViewAngles( oldang ); + + return result; +} + +qboolean Player::checkblocked( Conditional &condition ) +{ + int test_moveresult; + + test_moveresult = moveresult; + + if ( flags & FL_IMMOBILE ) + test_moveresult = MOVERESULT_BLOCKED; + + if ( condition.numParms() ) + { + return test_moveresult >= atoi( condition.getParm( 1 ) ); + } + + return test_moveresult >= MOVERESULT_BLOCKED; +} + +qboolean Player::checkonground( Conditional &condition ) +{ + return client->ps.walking; +} + +qboolean Player::check22degreeslope( Conditional &condition ) +{ + if ( client->ps.walking && client->ps.groundPlane && ( client->ps.groundTrace.plane.normal[ 2 ] < SLOPE_22_MAX ) && + ( client->ps.groundTrace.plane.normal[ 2 ] >= SLOPE_22_MIN ) ) + { + return qtrue; + } + + return qfalse; +} + +qboolean Player::check45degreeslope( Conditional &condition ) +{ + if ( client->ps.walking && client->ps.groundPlane && ( client->ps.groundTrace.plane.normal[ 2 ] < SLOPE_45_MAX ) && + ( client->ps.groundTrace.plane.normal[ 2 ] >= SLOPE_45_MIN ) ) + { + return qtrue; + } + + return qfalse; +} + +qboolean Player::checkrightleghigh( Conditional &condition ) +{ + float groundyaw; + float yawdelta; + int which; + + groundyaw = ( int )vectoyaw( client->ps.groundTrace.plane.normal ); + yawdelta = anglemod( v_angle.y - groundyaw ); + which = ( ( int )yawdelta + 45 ) / 90; + + return ( which == 3 ); +} + +qboolean Player::checkleftleghigh( Conditional &condition ) +{ + float groundyaw; + float yawdelta; + int which; + + groundyaw = ( int )vectoyaw( client->ps.groundTrace.plane.normal ); + yawdelta = anglemod( v_angle.y - groundyaw ); + which = ( ( int )yawdelta + 45 ) / 90; + + return ( which == 1 ); +} + +qboolean Player::checkfacingupslope( Conditional &condition ) +{ + float groundyaw; + float yawdelta; + int which; + + groundyaw = ( int )vectoyaw( client->ps.groundTrace.plane.normal ); + yawdelta = anglemod( v_angle.y - groundyaw ); + which = ( ( int )yawdelta + 45 ) / 90; + + return ( which == 2 ); +} + +qboolean Player::checkfacingdownslope( Conditional &condition ) +{ + float groundyaw; + float yawdelta; + int which; + + groundyaw = ( int )vectoyaw( client->ps.groundTrace.plane.normal ); + yawdelta = anglemod( v_angle.y - groundyaw ); + which = ( ( int )yawdelta + 45 ) / 90; + + return ( ( which == 0 ) || ( which == 4 ) ); +} + +qboolean Player::checkfalling( Conditional &condition ) +{ + return falling; +} + +qboolean Player::checkgroundentity( Conditional &condition ) +{ + return ( groundentity != NULL ); +} + +qboolean Player::checkhardimpact( Conditional &condition ) +{ + return hardimpact; +} + +qboolean Player::checkcanfall( Conditional &condition ) +{ + return canfall; +} + +qboolean Player::checkatdoor( Conditional &condition ) +{ + // Check if the player is at a door + return ( atobject && atobject->isSubclassOf( Door ) ); +} + +qboolean Player::checkatuseanim( Conditional &condition ) +{ + // Check if the player is at a useanim + if ( atobject && atobject->isSubclassOf( UseAnim ) ) + { + return ( ( UseAnim * )( Entity * )atobject )->canBeUsed( this ); + } + + return qfalse; +} + +qboolean Player::checktouchuseanim( Conditional &condition ) +{ + if ( toucheduseanim ) + { + return ( ( UseAnim * )( Entity * )toucheduseanim )->canBeUsed( this ); + } + + return qfalse; +} + +qboolean Player::checkuseanimfinished( Conditional &condition ) +{ + return ( useanim_numloops <= 0 ); +} + +qboolean Player::checkatuseobject( Conditional &condition ) +{ + // Check if the player is at a useanim + if ( atobject && atobject->isSubclassOf( UseObject ) ) + { + return ( ( UseObject * )( Entity * )atobject )->canBeUsed( origin, yaw_forward ); + } + + return qfalse; +} + +qboolean Player::checkloopuseobject( Conditional &condition ) +{ + // Check if the player is at a useanim + if ( useitem_in_use && useitem_in_use->isSubclassOf( UseObject ) ) + { + return ( ( UseObject * )( Entity * )useitem_in_use )->Loop(); + } + + return qfalse; +} + +qboolean Player::checkdead( Conditional &condition ) +{ + return ( deadflag ); +} + +qboolean Player::checkhealth( Conditional &condition ) +{ + return health < atoi( condition.getParm( 1 ) ); +} + +qboolean Player::checkpain( Conditional &condition ) +{ + return ( ( pain != 0 ) || ( knockdown != 0 ) ); +} + +qboolean Player::checkknockdown( Conditional &condition ) +{ + if ( knockdown ) + { + knockdown = false; + return qtrue; + } + else + { + return qfalse; + } +} + +qboolean Player::checkpaintype( Conditional &condition ) +{ + if ( pain_type == MOD_NameToNum( condition.getParm( 1 ) ) ) + { + return qtrue; + } + else + { + return qfalse; + } +} + +qboolean Player::checkpaindirection( Conditional &condition ) +{ + if ( pain_dir == Pain_string_to_int( condition.getParm( 1 ) ) ) + { + return qtrue; + } + else + { + return qfalse; + } +} + +qboolean Player::checkaccumulatedpain( Conditional &condition ) +{ + float threshold = (float)atof( condition.getParm( 1 ) ); + + if ( accumulated_pain >= threshold ) + { + accumulated_pain = 0; // zero out accumulation + return qtrue; + } + else + { + return qfalse; + } +} + +qboolean Player::checkpainthreshold( Conditional &condition ) +{ + float threshold = (float)atof( condition.getParm( 1 ) ); + + if ( ( pain >= threshold ) && ( level.time > nextpaintime ) ) + { + accumulated_pain = 0; // zero out accumulation since we are going into a pain anim right now + return qtrue; + } + else + { + return qfalse; + } +} + +qboolean Player::checklegsstate( Conditional &condition ) +{ + if ( currentState_Legs ) + { + str current = currentState_Legs->getName(); + str compare = condition.getParm( 1 ); + + if ( current == compare ) + { + return qtrue; + } + } + + return qfalse; +} + +qboolean Player::checktorsostate( Conditional &condition ) +{ + if ( currentState_Torso ) + { + str current = currentState_Torso->getName(); + str compare = condition.getParm( 1 ); + + if ( current == compare ) + { + return qtrue; + } + } + + return qfalse; +} + +qboolean Player::checkhasweapon( Conditional &condition ) +{ + if ( condition.numParms() > 0 ) + { + weaponhand_t hand = WeaponHandNameToNum( condition.getParm( 1 ) ); + if ( GetActiveWeapon( hand ) ) + return true; + else + return false; + } + + return WeaponsOut(); +} + +qboolean Player::checkhasdualweapon( Conditional & condition ) +{ + return IsDualWeaponActive(); +} + +qboolean Player::checknewweapon( Conditional &condition ) +{ + Weapon * weapon; + + weapon = GetNewActiveWeapon(); + + if ( weapon && _disableUseWeapon == false) + return qtrue; + else + return qfalse; +} + +// Check to see if a weapon has been raised +qboolean Player::checkuseweapon( Conditional &condition ) +{ + const char *weaponName; + const char *parm; + + weaponhand_t hand; + Weapon *weap; + + weap = GetNewActiveWeapon(); + parm = condition.getParm( 1 ); + + if ( !str::icmp( parm, "ERROR" ) ) + { + if ( weap ) + warning( "Player::checkuseweapon", "%s does not have a valid RAISE_WEAPON state\n", weap->item_name.c_str() ); + else + warning( "Player::checkuseweapon", "New Active weapon does not exist\n" ); + + ClearNewActiveWeapon(); + return qtrue; + } + + hand = WeaponHandNameToNum( parm ); + + if ( hand == WEAPON_ERROR ) + return qfalse; + + weaponName = condition.getParm( 2 ); + + if ( + ( weap != NULL ) && + ( GetNewActiveWeaponHand() == hand ) && + ( !stricmp( weap->item_name, weaponName ) ) + ) + { + return qtrue; + } + else + { + return qfalse; + } +} + +// Checks to see if any weapon is active in the specified hand +qboolean Player::checkanyweaponactive( Conditional &condition ) +{ + weaponhand_t hand; + Weapon *weap; + + hand = WeaponHandNameToNum( condition.getParm( 1 ) ); + + if ( hand == WEAPON_ERROR ) + return qfalse; + + weap = GetActiveWeapon( hand ); + return ( weap != NULL ); +} + +// Checks to see if any weapon is active in the specified hand +qboolean Player::checkweaponhasammo( Conditional &condition ) +{ + weaponhand_t hand; + Weapon *weap; + firemode_t mode = FIRE_MODE1; + int numShots = 1; + + hand = WeaponHandNameToNum( condition.getParm( 1 ) ); + + if ( condition.numParms() > 1 ) + mode = WeaponModeNameToNum( condition.getParm( 2 ) ); + + if ( condition.numParms() > 2 ) + numShots = atoi( condition.getParm( 3 ) ); + + if ( hand == WEAPON_ERROR ) + return qfalse; + + weap = GetActiveWeapon( hand ); + + if ( !weap ) + return qfalse; + else + return ( weap->HasAmmo( mode, numShots ) ); +} + +qboolean Player::checkweaponhasfullammo( Conditional &condition ) +{ + str ammoName; + + ammoName = condition.getParm( 1 ); + + if ( AmmoCount( ammoName ) == MaxAmmoCount( ammoName ) ) + return true; + else + return false; +} + +// Checks to see if any weapon is active in the specified hand +qboolean Player::checkweaponhasinvammo( Conditional &condition ) +{ + weaponhand_t hand; + Weapon *weap; + firemode_t mode = FIRE_MODE1; + + hand = WeaponHandNameToNum( condition.getParm( 1 ) ); + + if ( condition.numParms() > 1 ) + mode = WeaponModeNameToNum( condition.getParm( 2 ) ); + + if ( hand == WEAPON_ERROR ) + return qfalse; + + weap = GetActiveWeapon( hand ); + + if ( !weap ) + return qfalse; + else + return ( weap->HasInvAmmo( mode ) ); +} + +// Checks to see if weapon is active +qboolean Player::checkweaponactive( Conditional &condition ) +{ + const char *weaponName; + weaponhand_t hand; + + weaponName = condition.getParm( 2 ); + hand = WeaponHandNameToNum( condition.getParm( 1 ) ); + + if ( hand == WEAPON_ERROR ) + return qfalse; + + Weapon *weapon = GetActiveWeapon( hand ); + + return ( weapon && !strcmp( weaponName, weapon->item_name ) ); +} + +qboolean Player::checkweaponreload( Conditional &condition ) +{ + Weapon *weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( weapon ) + if ( weapon->weaponstate == WEAPON_RELOADING ) + return qtrue; + + return qfalse; +} + +qboolean Player::checkweaponswitchmode( Conditional &condition ) +{ + Weapon *weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( weapon ) + if ( weapon->weaponstate == WEAPON_SWITCHINGMODE ) + return qtrue; + + return qfalse; +} + +qboolean Player::checkweaponinmode( Conditional &condition ) +{ + Weapon *weapon = GetActiveWeapon( WEAPON_DUAL ); + str mode; + + if ( weapon ) + { + if ( condition.numParms() > 0 ) + mode = condition.getParm( 1 ); + if ( weapon->GetCurMode() == WeaponModeNameToNum( mode ) ) + return qtrue; + else + return qfalse; + } + + return qfalse; +} + +qboolean Player::checkweapondonefiring( Conditional &condition ) +{ + Weapon *weapon = GetActiveWeapon( WEAPON_DUAL ); + + // Weapon there check + if ( !weapon ) + return qtrue; + + return weapon->IsDoneFiring(); +} + +// Checks to see if weapon is active and ready to fire +qboolean Player::checkweaponreadytofire( Conditional &condition ) +{ + firemode_t mode = FIRE_MODE1; + str weaponName="None"; + weaponhand_t hand; + qboolean ready; + + if ( level.playerfrozen || ( flags & FL_IMMOBILE ) ) + { + return qfalse; + } + + hand = WeaponHandNameToNum( condition.getParm( 1 ) ); + + if ( hand == WEAPON_DUAL ) + { + gi.DPrintf( "This check should only be used for single handed weapons\n" ); + return qfalse; + } + + if ( condition.numParms() > 1 ) + weaponName = condition.getParm( 2 ); + + if ( hand == WEAPON_ERROR ) + return qfalse; + + Weapon *weapon = GetActiveWeapon( hand ); + + // Weapon there check + if ( !weapon ) + return qfalse; + + // Name check + if ( condition.numParms() > 1 ) + { + if ( strcmp( weaponName, weapon->item_name ) ) + { + return qfalse; + } + } + + // Ammo check + ready = weapon->ReadyToFire( mode ); + return( ready ); +} + +qboolean Player::checkdualweaponreadytofire( Conditional &condition ) +{ + firemode_t mode = FIRE_MODE1; + str weaponName="None"; + Weapon *weapon = GetActiveWeapon( WEAPON_DUAL ); + qboolean ready; + + + mode = WeaponModeNameToNum( condition.getParm( 1 ) ); + + if ( condition.numParms() > 1 ) + weaponName = condition.getParm( 2 ); + + // Make sure we aren't leaning + + if ( client->ps.leanDelta != 0 ) + return false; + + // Weapon there check + if ( !weapon ) + return qfalse; + + // Name check + if ( condition.numParms() > 1 ) + { + if ( strcmp( weaponName, weapon->item_name ) ) + { + return qfalse; + } + } + + // Ammo check + ready = weapon->ReadyToFire( mode ); + + // Fire timer checks + + if ( !weapon->isModeNoDelay( mode ) ) + { + if ( weapon->next_fire_time[mode] > level.time ) + return qfalse; + + if ( weapon->next_fire_time[FIRE_MODE1] > level.time ) + return qfalse; + + if ( weapon->next_fire_time[FIRE_MODE2] > level.time ) + return qfalse; + } + + return( ready ); +} + +qboolean Player::checkweaponlowered( Conditional &condition ) +{ + Weapon *weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( !weapon ) + return true; + else + return ( weapon->weaponstate == WEAPON_LOWERING ); +} + +qboolean Player::checkweaponfiretimer( Conditional &condition ) +{ + firemode_t mode = FIRE_MODE1; + Weapon *weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( weapon->GetSwitchMode() ) + mode = weapon->GetCurMode(); + else + mode = WeaponModeNameToNum( condition.getParm( 1 ) ); + + if ( weapon->GetFireTime(mode) > level.time ) + return qfalse; + + return qtrue; +} + +qboolean Player::checkweaponfullclip( Conditional &condition ) +{ + Weapon *weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( !weapon ) + return qtrue; + + return weapon->HasFullClip(); +} + +qboolean Player::checkweaponforcereload( Conditional &condition ) +{ + return ( last_ucmd.buttons & BUTTON_RELOAD ) != 0; +} + +qboolean Player::checkweaponcanreload( Conditional &condition ) +{ + Weapon *weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( !weapon ) + return qfalse; + + return weapon->canReload(); +} + +// Check to see if any of the active weapons need to be put away +qboolean Player::checkputawayleft( Conditional &condition ) +{ + Weapon *weapon = GetActiveWeapon( WEAPON_LEFT ); + + return weapon && weapon->GetPutaway(); +} + +qboolean Player::checkputawayright( Conditional &condition ) +{ + Weapon *weapon = GetActiveWeapon( WEAPON_RIGHT ); + + return weapon && weapon->GetPutaway(); +} + +qboolean Player::checkputawayboth( Conditional &condition ) +{ + Weapon *weapon = GetActiveWeapon( WEAPON_DUAL ); + + return weapon && weapon->GetPutaway(); +} + +qboolean Player::checktargetacquired( Conditional &condition ) +{ + return (targetEnemy != NULL); +} + +qboolean Player::returntrue( Conditional &condition ) +{ + return qtrue; +} + +qboolean Player::checkstatename( Conditional &condition ) +{ + str part = condition.getParm( 1 ); + str statename = condition.getParm( 2 ); + + if ( currentState_Legs && !part.icmp( "legs" ) ) + { + return ( !statename.icmpn( currentState_Legs->getName(), statename.length() ) ); + } + else if ( !part.icmp( "torso" ) ) + { + return ( !statename.icmpn( currentState_Torso->getName(), statename.length() ) ); + } + + return qfalse; +} + +qboolean Player::checkattackblocked( Conditional &condition ) +{ + if ( attack_blocked ) + { + attack_blocked = false; + return qtrue; + } + else + { + return qfalse; + } +} + +qboolean Player::checkblockdelay( Conditional &condition ) +{ + float t = (float)atof ( condition.getParm( 1 ) ); + return ( level.time > ( attack_blocked_time + t ) ); +} + +qboolean Player::checkpush( Conditional &condition ) +{ + // Check if the player is at a pushobject + if ( atobject && atobject->isSubclassOf( PushObject ) && ( atobject_dist < ( PUSH_OBJECT_DISTANCE + 15.0f ) ) ) + { + Vector dir; + + dir = atobject_dir * 8.0f; + return ( ( PushObject * )( Entity * )atobject )->canPush( dir ); + } + + return qfalse; +} + +qboolean Player::checkpull( Conditional &condition ) +{ + // Check if the player is at a pushobject + if ( atobject && atobject->isSubclassOf( PushObject ) && ( atobject_dist < ( PUSH_OBJECT_DISTANCE + 15.0f ) ) ) + { + Vector dir; + + dir = atobject_dir * -64.0f; + return ( ( PushObject * )( Entity * )atobject )->canPush( dir ); + } + + return qfalse; +} + +qboolean Player::checkcharavailable( Conditional &condition ) +{ + str character = condition.getParm( 1 ); + Actor *act = Actor::FindActorByName(character.c_str()); + if ( act ) + return true; + + return false; +} + +qboolean Player::checkOnLadder( Conditional &condition ) +{ + return _onLadder; +} + +qboolean Player::checkcanstand( Conditional &condition ) +{ + Vector newmins( mins ); + Vector newmaxs( maxs ); + Vector tmporg( origin ); + trace_t trace; + + // Start the trace alittle higher than the origin, for some + // reason crouching causes the trace to start allsolid if you're + // on a slope + tmporg[ 2 ] += 2.0f; + newmins[ 2 ] = MINS_Z; + newmaxs[ 2 ] = MAXS_Z; + + trace = G_Trace( tmporg, newmins, newmaxs, tmporg, this, edict->clipmask, true, "checkcanstand" ); + if ( trace.startsolid ) + { + return qfalse; + } + + return qtrue; +} + +qboolean Player::checkdualwield( Conditional &condition ) +{ + // Only start the dual wield state if dual wield is set and the hands have no weapons + + return ( dual_wield_active ); +} + +qboolean Player::checkdualweapons( Conditional &condition ) +{ + int i,j; + + for ( i=1; i<=condition.numParms(); i++ ) + { + str weaponName; + weaponName = condition.getParm( i ); + + for ( j=1; j<=dual_wield_weaponlist.NumObjects(); j++ ) + { + WeaponSetItem *dw; + dw = dual_wield_weaponlist.ObjectAt( j ); + + if ( dw->name == weaponName ) + { + goto out; + } + } + return qfalse; +out: + ; + } + + return qtrue; +} + +qboolean Player::checkchance( Conditional &condition ) +{ + float percent_chance; + + percent_chance = (float)atof( condition.getParm( 1 ) ); + + return ( G_Random() < percent_chance ); +} + +qboolean Player::checkturnedleft( Conditional &condition ) +{ + return yawing_left; +} + +qboolean Player::checkturnedright( Conditional &condition ) +{ + return yawing_right; +} + +//----------------------------------------------------- +// +// Name: checkinvehicle +// Class: Player +// +// Description: Checks if the vehicle is in specific type of vehicle +// +// Parameters: condition - the condition that contains the vehicle type +// +// Returns: +//----------------------------------------------------- +qboolean Player::checkinvehicle( Conditional &condition ) +{ + + if(vehicle == NULL || vehicle->getArchetype() != condition.getParm(1)) + return qfalse; + + return qtrue; + +} + +qboolean Player::checksolidforward( Conditional &condition ) +{ + // Trace out forward to see if there is a solid ahead + float dist = (float)atof( condition.getParm( 1 ) ); + Vector end( centroid + ( yaw_forward * dist ) ); + + trace_t trace = G_Trace( centroid, Vector( mins.x, mins.y, -8.0f ), Vector( maxs.x, maxs.y, 8.0f ), + end, this, MASK_SOLID, true, "Player::checksolidforward" ); + + return ( trace.fraction < 0.7f ); +} + +qboolean Player::checkweaponsholstered( Conditional &condition ) +{ + + if ( + ( holsteredWeapons[WEAPON_DUAL] ) || + ( holsteredWeapons[WEAPON_LEFT] ) || + ( holsteredWeapons[WEAPON_RIGHT] ) + ) + { + return qtrue; + } + else + { + return qfalse; + } +} + +qboolean Player::checkfakeplayeractive( Conditional &condition ) +{ + return fakePlayer_active; +} + +qboolean Player::checkspecialmovecharge( Conditional &condition ) +{ + if ( specialMoveCharge >= specialMoveEndTime ) + return true; + return false; +} + +qboolean Player::checkstancechangedtorso( Conditional &condition ) +{ + return changedStanceTorso; +} + +qboolean Player::checkstancechangedlegs( Conditional &condition ) +{ + return changedStanceLegs; +} + +qboolean Player::checkstance( Conditional &condition ) +{ + int stancenum; + stancenum = (int)atoi( condition.getParm( 1 ) ); + + if ( stancenum == stanceNumber ) + return true; + + return false; +} + +qboolean Player::checkpoints( Conditional &condition ) +{ + int pointnum; + pointnum = (int)atoi( condition.getParm( 1 ) ); + + if ( points >= pointnum ) + return true; + + return false; +} + +qboolean Player::checkincomingmeleeattack( Conditional &condition ) +{ + return incomingMeleeAttack; +} + + +qboolean Player::checkfinishingmove( Conditional &condition ) +{ + if ( _finishActor ) + return true; + return false; +} + +//-------------------------------------------------------------- +// +// Name: checkatuseentity +// Class: Player +// +// Description: Conditional to see if the player is standing at a usable entity +// +// Parameters: Conditional &condition +// +// Returns: qboolean +// +//-------------------------------------------------------------- +qboolean Player::checkatuseentity(Conditional &condition) +{ + // Check if the player is at a useentity + if ( atobject ) + { + Entity *ent = (Entity*)atobject; + if ( ent->hasUseData() ) + return qtrue; + } + + return qfalse; +} + +//-------------------------------------------------------------- +// +// Name: checkusingentity +// Class: Player +// +// Description: Conditional to see if the player is in the middle +// of using an entity. +// +// Parameters: Conditional &condition +// +// Returns: qboolean +// +//-------------------------------------------------------------- +qboolean Player::checkusingentity(Conditional &condition) +{ + return _usingEntity; +} + +//-------------------------------------------------------------- +// +// Name: checkthirdperson +// Class: Player +// +// Description: Conditional to see if the player is in third person +// +// Parameters: Conditional &condition +// +// Returns: qboolean +// +//-------------------------------------------------------------- +qboolean Player::checkthirdperson(Conditional &condition) +{ + return _isThirdPerson; +} + + +//-------------------------------------------------------------- +// +// Name: checkPropChance +// Class: Player +// +// Description: Check the chance based on the property passed. +// This property should be located in the Actor's +// StateData object in the gameplay database. +// +// Parameters: Conditional &conditional +// +// Returns: qboolean +// +//-------------------------------------------------------------- +qboolean Player::checkPropChance( Conditional &condition ) +{ + str propname; + str objname = condition.getParm( 1 ); + if ( condition.numParms() > 1 ) + propname = condition.getParm( 2 ); + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str scopestr = getArchetype() + "." + objname; + if ( !gpm->hasObject(scopestr) ) + return false; + + float chance; + if ( propname.length() ) + chance = gpm->getFloatValue(scopestr, propname); + else + chance = gpm->getFloatValue(scopestr, "value"); + + return ( G_Random() <= chance ); +} + +//-------------------------------------------------------------- +// +// Name: checkPropExists +// Class: Player +// +// Description: Check to see if the property exists +// The Actor's StateData object will be asked +// if it has the property. +// +// Parameters: Conditional &conditional +// +// Returns: qboolean +// +//-------------------------------------------------------------- +qboolean Player::checkPropExists( Conditional &condition ) +{ + str objname = condition.getParm( 1 ); + str propname = condition.getParm( 2 ); + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str scopestr = getArchetype() + "." + objname; + if ( !gpm->hasProperty(scopestr, propname) ) + return false; + + return true; +} + +//-------------------------------------------------------------- +// +// Name: checkEndAnimChain +// Class: Player +// +// Description: Check to see if the current animation chain is on +// the last anim. +// +// Parameters: Conditional &conditional +// +// Returns: qboolean +// +//-------------------------------------------------------------- +qboolean Player::checkEndAnimChain( Conditional &condition ) +{ + if ( condition.numParms() < 1 ) + return false; + + str objname = condition.getParm( 1 ); + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str scopestr = "CurrentPlayer.ChainedMoves"; + if ( !gpm->hasObject(scopestr) ) + return false; + + int max = (int)gpm->getFloatValue(scopestr, "value"); + max++; // Increment to compensate for 0 based skill system, we always have to play the first anim + if ( _gameplayAnimIdx > max ) + return false; // Loop anim + + if ( _gameplayAnimIdx == max ) + return true; + + return false; +} + +qboolean Player::checkWeaponType( Conditional &condition ) +{ + if ( condition.numParms() < 1 ) + return false; + + str type = condition.getParm( 1 ); + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + Weapon* weap = GetActiveWeapon(WEAPON_RIGHT); + if ( !weap ) + weap = GetActiveWeapon(WEAPON_LEFT); + if ( !weap ) + return false; + + if ( !gpm->hasObject(weap->getArchetype()) ) + return false; + + str weaptype = gpm->getStringValue(weap->getArchetype(), "class"); + if ( weaptype == type ) + return true; + + return false; +} + +qboolean Player::checkHasAnim( Conditional &condition ) +{ + str animName; + + if ( condition.numParms() < 1 ) + return false; + + animName = condition.getParm( 1 ); + + if ( gi.Anim_NumForName( edict->s.modelindex, animName.c_str() ) < 0 ) + return false; + else + return true; +} + +qboolean Player::checkIsWeaponControllingProjectile( Conditional &condition ) +{ + Weapon *weapon; + + // Only support dual handed weapon for now + + weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( weapon ) + { + if ( weapon->getControllingProjectile() ) + return true; + else + return false; + } + + return false; +} + + +Condition Player::Conditions[] = +{ + { "default", &Player::returntrue }, + { "SNEAK", &Player::checksneak }, + { "RUN", &Player::checkrun }, + { "WAS_RUNNING", &Player::checkwasrunning }, + { "HOLSTERWEAPON", &Player::checkholsterweapon }, + { "USE", &Player::checkuse }, + { "LEFT", &Player::checkturnleft }, + { "RIGHT", &Player::checkturnright }, + { "FORWARD", &Player::checkforward }, + { "BACKWARD", &Player::checkbackward }, + { "STRAFE_LEFT", &Player::checkstrafeleft }, + { "STRAFE_RIGHT", &Player::checkstraferight }, + { "LEAN_LEFT", &Player::checkleanleft }, + { "LEAN_RIGHT", &Player::checkleanright}, + { "DUCK", &Player::checkduck }, + { "JUMP", &Player::checkjump }, + { "RISE", &Player::checkrise }, + { "CROUCH", &Player::checkcrouch }, + { "ANIMDONE_LEGS", &Player::checkanimdone_legs }, + { "ANIMDONE_TORSO", &Player::checkanimdone_torso }, + { "CAN_TURN", &Player::checkcanturn }, + { "BLOCKED", &Player::checkblocked }, + { "ONGROUND", &Player::checkonground }, + { "CAN_FALL", &Player::checkcanfall }, + { "AT_DOOR", &Player::checkatdoor }, + { "FALLING", &Player::checkfalling }, + { "HARD_IMPACT", &Player::checkhardimpact }, + { "KILLED", &Player::checkdead }, + { "HEALTH", &Player::checkhealth }, + { "PAIN", &Player::checkpain }, + { "PAIN_TYPE", &Player::checkpaintype }, + { "PAIN_DIRECTION", &Player::checkpaindirection }, + { "PAIN_THRESHOLD", &Player::checkpainthreshold }, + { "PAIN_ACCUMULATED", &Player::checkaccumulatedpain }, + { "KNOCKDOWN", &Player::checkknockdown }, + { "LEGS", &Player::checklegsstate }, + { "TORSO", &Player::checktorsostate }, + { "AT_USEANIM", &Player::checkatuseanim }, + { "TOUCHEDUSEANIM", &Player::checktouchuseanim }, + { "FINISHEDUSEANIM", &Player::checkuseanimfinished }, + { "AT_USEOBJECT", &Player::checkatuseobject }, + { "AT_USEENTITY", &Player::checkatuseentity }, + { "LOOP_USEOBJECT", &Player::checkloopuseobject }, + { "CAN_PUSH", &Player::checkpush }, + { "CAN_PULL", &Player::checkpull }, + { "ON_LADDER", &Player::checkOnLadder }, + { "CAN_STAND", &Player::checkcanstand }, + { "CHANCE", &Player::checkchance }, + { "TURNED_LEFT", &Player::checkturnedleft }, + { "TURNED_RIGHT", &Player::checkturnedright }, + { "IN_VEHICLE", &Player::checkinvehicle }, + // { "WATER_LEVEL", checkwaterlevel }, + { "SOLID_FORWARD", &Player::checksolidforward }, + { "GROUNDENTITY", &Player::checkgroundentity }, + { "FAKEPLAYERACTIVE", &Player::checkfakeplayeractive }, + { "SLOPE_22", &Player::check22degreeslope }, + { "SLOPE_45", &Player::check45degreeslope }, + { "RIGHT_LEG_HIGH", &Player::checkrightleghigh }, + { "LEFT_LEG_HIGH", &Player::checkleftleghigh }, + { "FACING_UP_SLOPE", &Player::checkfacingupslope }, + { "FACING_DOWN_SLOPE", &Player::checkfacingdownslope }, + + // Weapon conditions + { "ATTACKLEFT", &Player::checkattackleft }, // Checks to see if there is an active weapon as well as the button being pressed + { "ATTACKRIGHT", &Player::checkattackright }, // Checks to see if there is an active weapon as well as the button being pressed + { "ATTACKLEFTBUTTON", &Player::checkattackbuttonleft }, // Checks to see if the left attack button is pressed + { "ATTACKRIGHTBUTTON", &Player::checkattackbuttonright },// Checks to see if the right attack button is pressed + { "HAS_DUAL_WEAPON", &Player::checkhasdualweapon}, + { "HAS_WEAPON", &Player::checkhasweapon }, + { "NEW_WEAPON", &Player::checknewweapon }, + { "IS_NEW_WEAPON", &Player::checkuseweapon }, + { "IS_WEAPON_ACTIVE", &Player::checkweaponactive }, + { "IS_WEAPON_RELOADING", &Player::checkweaponreload }, + { "IS_IN_MODE", &Player::checkweaponinmode }, + { "IS_WEAPON_SWITCHINGMODE", &Player::checkweaponswitchmode }, + { "IS_WEAPON_READY_TO_FIRE", &Player::checkweaponreadytofire }, + { "IS_DUALWEAPON_READY_TO_FIRE", &Player::checkdualweaponreadytofire }, + { "IS_WEAPON_FINISHED_FIRING", &Player::checkweapondonefiring }, + { "PUTAWAYBOTH", &Player::checkputawayboth }, + { "PUTAWAYLEFT", &Player::checkputawayleft }, + { "PUTAWAYRIGHT", &Player::checkputawayright }, + { "TARGET_ACQUIRED", &Player::checktargetacquired }, + { "ANY_WEAPON_ACTIVE", &Player::checkanyweaponactive }, + { "ATTACK_BLOCKED", &Player::checkattackblocked }, + { "STATE_ACTIVE", &Player::checkstatename }, + { "BLOCK_DELAY", &Player::checkblockdelay }, + { "DUALWIELD", &Player::checkdualwield }, + { "DUALWIELDWEAPONS", &Player::checkdualweapons }, + { "HAS_AMMO", &Player::checkweaponhasammo }, + { "HAS_FULLAMMO", &Player::checkweaponhasfullammo }, + { "HAS_INVAMMO", &Player::checkweaponhasinvammo }, + { "WEAPONS_HOLSTERED", &Player::checkweaponsholstered }, + { "WEAPON_LOWERED", &Player::checkweaponlowered }, // See if we're done with our putaway anim + { "WEAPON_FIRETIMER", &Player::checkweaponfiretimer }, + { "WEAPON_FORCERELOAD", &Player::checkweaponforcereload }, + { "WEAPON_CAN_RELOAD", &Player::checkweaponcanreload }, + { "WEAPON_FULLCLIP", &Player::checkweaponfullclip }, + { "IS_SPECIALMOVE_CHARGED", &Player::checkspecialmovecharge }, + { "IS_CHAR_AVAILABLE", &Player::checkcharavailable }, + + { "STANCE_CHANGEDTORSO", &Player::checkstancechangedtorso }, + { "STANCE_CHANGEDLEGS", &Player::checkstancechangedlegs }, + { "STANCE", &Player::checkstance }, + { "POINTS", &Player::checkpoints }, + { "INCOMING_MELEE", &Player::checkincomingmeleeattack }, + { "FINISHINGMOVE", &Player::checkfinishingmove }, + { "USINGENTITY", &Player::checkusingentity }, + { "IS_THIRDPERSON", &Player::checkthirdperson }, + { "PROP_CHANCE", &Player::checkPropChance }, + { "PROP_EXISTS", &Player::checkPropExists }, + { "ANIMCHAIN_END", &Player::checkEndAnimChain }, + { "WEAPON_TYPE", &Player::checkWeaponType }, + { "HAS_ANIM", &Player::checkHasAnim }, + { "IS_WEAPON_CONTROLLING_PROJECTILE", &Player::checkIsWeaponControllingProjectile }, + + { NULL, NULL }, +}; + +movecontrolfunc_t Player::MoveStartFuncs[] = +{ + NULL, // MOVECONTROL_USER, // Quake style + NULL, // MOVECONTROL_LEGS, // Quake style, legs state system active + NULL, // MOVECONTROL_ANIM, // move based on animation, with full collision testing + NULL, // MOVECONTROL_ABSOLUTE, // move based on animation, with full collision testing but no turning + NULL, // MOVECONTROL_HANGING + NULL, // MOVECONTROL_WALLHUG + NULL, // MOVECONTROL_MONKEYBARS + NULL, // MOVECONTROL_PIPECRAWL + NULL, // MOVECONTROL_PIPEHANG + NULL, // MOVECONTROL_STEPUP + NULL, // MOVECONTROL_ROPE_GRAB + NULL, // MOVECONTROL_ROPE_RELEASE + NULL, // MOVECONTROL_ROPE_MOVE + NULL, // MOVECONTORL_PICKUPENEMY + &Player::StartPush, // MOVECONTROL_PUSH + NULL, // MOVECONTORL_CLIMBWALL + &Player::StartUseAnim, // MOVECONTROL_USEANIM + NULL, // MOVECONTROL_CROUCH + &Player::StartLoopUseAnim, // MOVECONTROL_LOOPUSEANIM + &Player::SetupUseObject, // MOVECONTROL_USEOBJECT + &Player::StartCoolItemAnim, // MOVECONTROL_COOLOBJECT + &Player::StartFakePlayer // MOVECONTROL_FAKEPLAYER +}; + +Player::Player() +{ + // + // set the entity type + // + edict->s.eType = ET_PLAYER; + respawn_time = -1; + statemap_Legs = NULL; + statemap_Torso = NULL; + camera = NULL; + atobject = NULL; + atobject_dist = 0; + toucheduseanim = NULL; + useitem_in_use = NULL; + damage_blood = 0; + damage_count = 0; + damage_from = vec_zero; + damage_alpha = 0; + last_attack_button = 0; + attack_blocked = false; + shield_active = false; + canfall = false; + moveresult = MOVERESULT_NONE; + animspeed = 0; + airspeed = 350; + vehicle = NULL; + action_level = 0; + adjust_torso = false; + dual_wield_active = false; + cool_item = NULL; + weapons_holstered_by_code = false; + actor_camera = NULL; + cool_camera = NULL; + + _started = false; + + StopWatchingEntity(); + playerCameraMode = PLAYER_CAMERA_MODE_NORMAL; + + damage_multiplier = 1; + take_pain = true; + look_at_time = 0; + fakePlayer_active = false; + projdetonate = false; + + _flashMaxTime = 0.0f; + _flashMinTime = 0.0f; + + dont_turn_legs = false; + + specialMoveChargeTime = 3.0f; + specialMoveCharge = 0.0f; + specialMoveEndTime = 0.0f; + playerKnockback = 0.0f; + knockbackMultiplier = 1.0f; + changedStanceTorso = false; + changedStanceLegs = false; + incomingMeleeAttack = false; + stanceNumber = 1; + points = 0; + bendTorsoMult = standardTorsoMult; + meleeAttackFlags = 0; + changingChar = false; + _isThirdPerson = true; // This will get set later + _finishActor = 0; + _finishState = ""; + _doingFinishingMove = false; + _usingEntity = false; + _attackType = ""; + _gameplayAnimIdx = 1; + _disableUseWeapon = false; + _infoHudOn = false; + _nextRegenTime = 0; + _useEntityStartTimer = 0.0f; + +#ifdef DEDICATED + p_heuristics = 0; +#else + p_heuristics = new PlayerHeuristics; +#endif + + currentCallVolume = ""; + + //Add the player to the teammates list + //Probably should be a better way to do this. + TeamMateList.AddObject( this ); + + // make sure that we are not overflowing the stats for players + assert( STAT_LAST_STAT <= MAX_STATS ); + + /* fov = (float)atof( Info_ValueForKey( client->pers.userinfo, "fov" ) ); + if ( fov < 1.0f ) + { + fov = sv_defaultFov->value; + } + else if ( fov > 160.0f ) + { + fov = 160.0f; + } */ + + fov = sv_defaultFov->value; + //_userFovChoice = fov; + + if ( atoi( Info_ValueForKey( client->pers.userinfo, "mp_autoSwitchWeapons" ) ) ) + _autoSwitchWeapons = true; + else + _autoSwitchWeapons = false; + + // + // set targetnameplayer + // + if ( !LoadingSavegame ) + SetTargetName( "player" ); + + _powerup = NULL; + _rune = NULL; + _holdableItem = NULL; + + if ( !LoadingSavegame ) + { + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + + const str selectedHighlight( "target_selected_highlight" ); + if ( gpm->isDefined( selectedHighlight ) ) + { + const str targetSelectedHighlightModelName( gpm->getDefine( selectedHighlight ) ); + _targetSelectedHighlight = new Entity; + _targetSelectedHighlight->setModel( targetSelectedHighlightModelName ); + _targetSelectedHighlight->setSolidType( SOLID_NOT ); + _targetSelectedHighlight->setMoveType( MOVETYPE_NONE ); + _targetSelectedHighlight->takedamage = DAMAGE_NO; + _targetSelectedHighlight->hideModel(); + } + + const str lockedHighlight( "target_locked_highlight" ); + if ( gpm->isDefined( lockedHighlight ) ) + { + const str targetLockedHighlightModelName( gpm->getDefine( lockedHighlight ) ); + _targetLockedHighlight = new Entity; + _targetLockedHighlight->setModel( targetLockedHighlightModelName ); + _targetLockedHighlight->setSolidType( SOLID_NOT ); + _targetLockedHighlight->setMoveType( MOVETYPE_NONE ); + _targetLockedHighlight->takedamage = DAMAGE_NO; + _targetLockedHighlight->hideModel(); + } + } + + Init(); + + _viewMode = 0; + + addAffectingViewModes( gi.GetViewModeClassMask( "player" ) ); + + _canTransferEnergy = false; + + _doDamageScreenFlash = false; + + Vector maxs(MAXS_X,MAXS_Y,MAXS_Z),mins(MINS_X,MINS_Y,MINS_Z); + + setSize( mins, maxs ); + + // Setup ladder stuff + + _onLadder = false; + _nextLadderTime = 0.0f; + _ladderTop = 0.0f; + + _objectiveStates = 0; + _informationStates = 0; + _objectiveNameIndex = 0; + + _lastDamagedTimeFront = 0.0f; + _lastDamagedTimeBack = 0.0f; + _lastDamagedTimeLeft = 0.0f; + _lastDamagedTimeRight = 0.0f; + + _totalGameFrames = 0; + _updateGameFrames = true; + + _nextPainShaderTime = 0.0f; + _lastPainShaderMod = MOD_NONE; + + _validPlayerModel = false; + + _secretsFound = 0; + + _allowActionMusic = true; + + setSkill( skill->integer ); + + _needToSendBranchDialog = false; + _branchDialogActor = NULL; +} + +Player::~Player() +{ + if ( p_heuristics ) + { + p_heuristics->SaveHeuristics( this ); + delete p_heuristics; + } + + Sentient *player; + player = this; + + if ( TeamMateList.ObjectInList( player ) ) + TeamMateList.RemoveObject( player ); + + if ( edict->svflags & SVF_BOT ) + { + BotAIShutdownClient( entnum, qfalse ); + } + + edict->s.modelindex = 0; + + freeConditionals( legs_conditionals ); + freeConditionals( torso_conditionals ); + + removePowerup(); + removeRune(); + removeHoldableItem(); + + clearFinishingMove(NULL); + + if ( multiplayerManager.inMultiplayer() ) + { + multiplayerManager.removePlayer( this ); + } +} + +static qboolean logfile_started = false; + +void Player::Init( void ) +{ + + InitClient(); + InitPhysics(); + InitWorldEffects(); + InitSound(); + InitView(); + InitState(); + InitEdict(); + InitModel(); + InitWeapons(); + InitInventory(); + InitHealth(); + //InitArmorValue(); + InitStats(); + + _dialogEntnum = ENTITYNUM_NONE; + _dialogSoundIndex = -1; + _dialogTextSoundIndex = -1; + + client->ps.objectiveNameIndex = -1; + + edict->s.clientNum = client->ps.clientNum ; + + LoadStateTable(); + + logfile_started = false; + + removePowerup(); + removeRune(); + removeHoldableItem(); + + _nextEnergyTransferTime = 0.0f; + _nextPainShaderTime = 0.0f; + + edict->s.renderfx &= ~RF_FORCE_ALPHA; + + setAlpha( 1.0f ); + + if ( multiplayerManager.inMultiplayer() ) + { + multiplayerManager.addPlayer( this ); + } + else + { + if ( !LoadingSavegame ) + { + ChooseSpawnPoint(); + } + } + + clearAllHuds(); + + + _backpackAttachOffset = Vector( -12, -9, 0 ); + _backpackAttachAngles = Vector( 0, 90, 90 ); + + _flagAttachOffset = Vector( -96, -16, 0 ); + _flagAttachAngles = Vector( 0, 90, 90 ); + + _cameraCutThisFrame = false; + + //Clear out deaththread + + + // Make sure we put the player back into the world + + link(); +} + +void Player::InitEdict( void ) +{ + // entity state stuff + setSolidType( SOLID_BBOX ); + setMoveType( MOVETYPE_WALK ); + setSize( Vector( MINS_X, MINS_Y, 0.0f ), Vector( MAXS_X, MAXS_Y, MAXS_Z ) ); + edict->clipmask = MASK_PLAYERSOLID; + edict->svflags &= ~SVF_DEADMONSTER; + edict->svflags &= ~SVF_HIDEOWNER; + edict->ownerNum = ENTITYNUM_NONE; + + // clear entity state values + edict->s.eFlags = 0; + edict->s.frame = 0; + + // players have precise shadows + edict->s.renderfx |= RF_SHADOW_PRECISE; +} + +void Player::InitSound( void ) +{ + // + // reset the music + // + client->ps.current_music_mood = mood_normal; + client->ps.fallback_music_mood = mood_normal; + ChangeMusic( "normal", "normal", false ); + + client->ps.music_volume = 1.0f; + client->ps.music_volume_fade_time = 0.0f; + + _allowMusicDucking = true; + client->ps.allowMusicDucking = true; + + ChangeMusicVolume( 1.0f, 0.0f ); + + music_forced = false; + + // Reset the reverb stuff + + client->ps.reverb_type = eax_generic; + client->ps.reverb_level = 0; + SetReverb( client->ps.reverb_type, client->ps.reverb_level ); +} + +void Player::InitClient( void ) +{ + // deathmatch wipes most client data every spawn + if ( multiplayerManager.inMultiplayer() ) + { + float savedTime; + char userinfo[ MAX_INFO_STRING ]; + //char savedTeamName[ 16 ]; + + savedTime = client->pers.enterTime; + //strcpy( savedTeamName, client->pers.lastTeam ); + + memcpy( userinfo, client->pers.userinfo, sizeof( userinfo ) ); + G_InitClientPersistant( client ); + G_ClientUserinfoChanged( edict, userinfo ); + + //strcpy( client->pers.lastTeam, savedTeamName ); + client->pers.enterTime = savedTime; + } + + // save things that should be saved before memset-ing the client + // during loadgame, ps.stats is loaded before this point, so don't trample that data + int savedstats[ sizeof( client->ps.stats ) ]; // only a few hundred bytes + assert( sizeof( savedstats ) < 2048 ); // should be allocated if it gets too big + + memcpy( savedstats, client->ps.stats, sizeof( client->ps.stats ) ); + client_persistant_t savedpers = client->pers; + + memset( client, 0, sizeof( *client ) ); + + client->pers = savedpers; + memcpy( client->ps.stats, savedstats, sizeof( client->ps.stats ) ); + + client->ps.clientNum = client - game.clients; + client->ps.vehicleoffset[0] = 0; + client->ps.vehicleoffset[1] = 0; + client->ps.vehicleoffset[2] = 0; + client->ps.in_vehicle = false; + + client->ps.viewheight = (int) sv_defaultviewheight->value; + client->ps.pm_defaultviewheight = (int) sv_defaultviewheight->value; +} + +void Player::InitState( void ) +{ + gibbed = false; + pain = 0; + accumulated_pain = 0; + nextpaintime = 0.0f; + knockdown = false; + pain_dir = PAIN_NONE; + pain_type = MOD_NONE; + takedamage = DAMAGE_AIM; + deadflag = DEAD_NO; + flags &= ~FL_NO_KNOCKBACK; + flags |= ( FL_BLOOD | FL_DIE_GIBS ); + + if ( !com_blood->integer ) + { + flags &= ~FL_BLOOD; + flags &= ~FL_DIE_GIBS; + } +} + +/* +void Player::InitArmorValue + ( + void + ) + + { + //ArmorValue = 0; + } +*/ + +void Player::InitHealth( void ) +{ + // Don't do anything if we're loading a server game. + // This is either a loadgame or a restart + if ( LoadingSavegame ) + { + return; + } + + // reset the health values + health = 100; + max_health = 100; +} + +void Player::InitModel( const char *modelName ) +{ + orientation_t orient; + int anim; + int tagnum; + int i; + + + // Reset all surface to visible + + for ( i = 0 ; i < MAX_MODEL_SURFACES ; i++ ) + { + edict->s.surfaces[ i ] &= ~MDL_SURFACE_NODRAW; + } + + if ( modelName ) + setModel( modelName ); + else + setModel( str( g_playermodel->string ) + ".tik" ); + + SetControllerTag( HEAD_TAG, gi.Tag_NumForName( edict->s.modelindex, "Bip01 Head" ) ); + SetControllerTag( TORSO_TAG, gi.Tag_NumForName( edict->s.modelindex, "Bip01 Spine1" ) ); + SetControllerTag( R_ARM_TAG, gi.Tag_NumForName( edict->s.modelindex, R_ARM_NAME ) ); + SetControllerTag( L_ARM_TAG, gi.Tag_NumForName( edict->s.modelindex, L_ARM_NAME ) ); + SetControllerTag( MOUTH_TAG, gi.Tag_NumForName( edict->s.modelindex, "tag_mouth" ) ); + + anim = gi.Anim_NumForName( edict->s.modelindex, "stand_idle" ); + + tagnum = gi.Tag_NumForName( edict->s.modelindex, "Bip01 R Foot" ) & TAG_MASK; + orient = gi.Tag_Orientation( edict->s.modelindex, anim, 0, tagnum, 1.0f, NULL, NULL ); + base_rightfoot_pos = orient.origin; + base_rightfoot_pos.z = 0; + + tagnum = gi.Tag_NumForName( edict->s.modelindex, "Bip01 L Foot" ) & TAG_MASK; + orient = gi.Tag_Orientation( edict->s.modelindex, anim, 0, tagnum, 1.0f, NULL, NULL ); + base_leftfoot_pos = orient.origin; + base_leftfoot_pos.z = 0; + + showModel(); + + yawing_left = false; + yawing_right = false; +} + +void Player::InitPhysics( void ) +{ + // Physics stuff + oldvelocity = vec_zero; + velocity = vec_zero; + old_v_angle = v_angle; + gravity = 1.0; + falling = false; + hardimpact = false; + mass = 200; + setContents( CONTENTS_BODY ); + memset( &last_ucmd, 0, sizeof( last_ucmd ) ); + + _forcedMoveType = PM_NONE; +} + +void Player::InitWorldEffects( void ) +{ + // world effects + next_drown_time = 0; + next_painsound_time = 0; + air_finished = level.time + 20.0f; + old_waterlevel = 0; + drown_damage = 0.0f; +} + +void Player::InitWeapons( void ) +{ + // Don't do anything if we're loading a server game. + // This is either a loadgame or a restart + if ( LoadingSavegame ) + { + return; + } + + ClearNewActiveWeapon(); +} + +void Player::InitInventory( void ) +{ +} + +void Player::InitView( void ) +{ + // view stuff + camera = NULL; + v_angle = vec_zero; + SetViewAngles( v_angle ); + viewheight = STAND_EYE_HEIGHT; + + head_target = NULL; + targetEnemy = NULL; + targetEnemyLocked = false; + _targetedEntity = NULL; + + // blend stuff + damage_blend = vec_zero; +} + +void Player::InitStats( void ) +{ + if ( p_heuristics ) + { + p_heuristics->LoadHeuristics(); + p_heuristics->ClearLevelStatistics(); + } +} + +void Player::ChooseSpawnPoint( void ) +{ + str thread; + // set up the player's spawn location + SelectSpawnPoint( origin, angles, thread ); + setOrigin( origin + Vector( "0 0 1" ) ); + origin.copyTo( edict->s.origin2 ); + edict->s.renderfx |= RF_FRAMELERP; + + KillBox( this ); + + setAngles( angles ); + SetViewAngles( angles ); + + if ( thread.length() ) + { + ExecuteThread( thread ); + } +} + +void Player::EndLevel( Event *ev ) +{ + // this happens here to avoid the last frame (displayed while loading) from being fullbright or whatever + setViewMode( "none" ); + + if ( health > max_health ) + { + health = max_health; + } + + if ( health < 1.0f ) + { + health = 1.0f; + } + + _updateGameFrames = false; +} + +void Player::LevelCleanup( void ) +{ + // Do any re-initialization things that affect visuals that aren't supposed to be visible to player + // reset stats that aren't supposed to carry across missions, i.e. only between sublevels + // should only be called in single player + + if( !gi.areSublevels( level.mapname, level.nextmap ) && client ) + { + client->ps.stats[ STAT_ENEMIES_KILLED ] = 0; + client->ps.stats[ STAT_TEAMMATES_KILLED ] = 0; + client->ps.stats[ STAT_SHOTS_FIRED ] = 0; + client->ps.stats[ STAT_SHOTS_HIT ] = 0; + client->ps.stats[ STAT_MISSION_DURATION ] =0; + } + + client->ps.pm_flags &= ~PMF_NIGHTVISION; +} + +void Player::Respawn( Event *ev ) +{ + if ( multiplayerManager.inMultiplayer() ) + { + assert ( deadflag == DEAD_DEAD ); + + respawn_time = level.time; + + Init(); + + // hold in place briefly + client->ps.pm_time = 50; + client->ps.pm_flags |= PMF_TIME_TELEPORT; + + return; + } + else + { +#ifdef PRE_RELEASE_DEMO + gi.SendConsoleCommand( "forcemenu demomain; forcemenu loadsave\n" ); +#else +// gi.SendConsoleCommand( "forcemenu main; forcemenu loadsave\n" ); +#endif + logfile_started = false; + } +} + +void Player::SetDeltaAngles( void ) +{ + int i; + + // Use v_angle since we may be in a camera + for( i = 0; i < 3; i++ ) + { + client->ps.delta_angles[ i ] = ANGLE2SHORT( v_angle[ i ] ); + } +} + +void Player::Dead( Event *ev ) +{ + + CancelEventsOfType( EV_Player_Dead ); + + animate->StopAnimatingAtEnd(); + + if ( edict->s.torso_anim & ANIM_BLEND ) + animate->StopAnimatingAtEnd(torso); + + //if ( ( pain_type == MOD_VAPORIZE || pain_type == MOD_VAPORIZE_COMP || pain_type == MOD_VAPORIZE_DISRUPTOR ) && ( edict->s.eFlags | EF_EFFECT_PHASER ) && ( edict->s.alpha > 0.0f ) ) + if ( ( ( pain_type == MOD_VAPORIZE ) || ( pain_type == MOD_VAPORIZE_COMP ) || + ( pain_type == MOD_VAPORIZE_DISRUPTOR ) || ( pain_type == MOD_VAPORIZE_PHOTON ) || ( pain_type == MOD_SNIPER ) ) && + ( edict->s.alpha > 0.0f ) && ( level.time < respawn_time ) ) + { + PostEvent( EV_Player_Dead, FRAMETIME ); + return; + } + + deadflag = DEAD_DEAD; + + // stop animating + //animate->StopAnimating( legs ); + + // increase player's death count + if ( p_heuristics ) + p_heuristics->IncrementNumberOfDeaths(); + ++client->ps.stats[ STAT_DEATHS ]; + + // Heuristics Stay Persistant Regardless of deaths. + if ( p_heuristics ) + p_heuristics->SaveHeuristics( this ); + + if ( multiplayerManager.inMultiplayer() ) + { + multiplayerManager.playerDead( this ); + } + else + { + //pick random player killed string. + int i = (rand() % 10) + 1; + str playerKilled = "PlayerKilled"; + playerKilled += i; + G_MissionFailed(playerKilled); + } + + _updateGameFrames = false; +} + +void Player::Killed( Event *ev ) +{ + Entity *attacker; + Entity *inflictor; + int meansofdeath; + Vector direction; + + attacker = ev->GetEntity( 1 ); + inflictor = ev->GetEntity( 3 ); + meansofdeath = ev->GetInteger( 4 ); + direction = ev->GetVector( 7 ); + + pain_type = (meansOfDeath_t)meansofdeath; + + damage_from = direction; + + if ( multiplayerManager.inMultiplayer() ) + { + if ( attacker && attacker->isSubclassOf( Player ) ) + multiplayerManager.playerKilled( this, (Player *)attacker, inflictor, meansofdeath ); + else + multiplayerManager.playerKilled( this, NULL, inflictor, meansofdeath ); + } + + SpawnDamageEffect( (meansOfDeath_t)meansofdeath ); + + animate->ClearTorsoAnim(); + animate->ClearLegsAnim(); + + deadflag = DEAD_DYING; + + respawn_time = level.time + 1.0f; + + edict->clipmask = MASK_DEADSOLID; + edict->svflags |= SVF_DEADMONSTER; + + setContents( CONTENTS_CORPSE ); + + setMoveType( MOVETYPE_NONE ); + + angles.x = 0; + angles.z = 0; + setAngles( angles ); + + // + // change music + // + ChangeMusic( "failure", "normal", true ); + + health = 0; + + // Stop targeting monsters + if( _targetedEntity != NULL) + { + _targetedEntity->edict->s.eFlags &= ~EF_DISPLAY_INFO; + _targetedEntity = NULL; + } + + // Post a dead event just in case + PostEvent( EV_Player_Dead, 5.0f ); + + dropRune(); + dropPowerup(); + removeHoldableItem(); + + // In Deathmatch drop your weapon + + if ( multiplayerManager.inMultiplayer() && !multiplayerManager.checkFlag( MP_FLAG_NO_DROP_WEAPONS ) && + multiplayerManager.checkRule( "dropWeapons", true ) ) + { + int i; + for ( i=0; iDrop(); + activeWeaponList[i] = NULL; + } + } + + } + + ClearNewActiveWeapon(); + + // Now take away the rest of the weapons + FreeInventory(); + + + _updateGameFrames = false; +} + +void Player::Pain( Event *ev ) +{ + float damage,yawdiff; + Entity *attacker; + int meansofdeath; + Vector dir,pos,attack_angle; + + damage = ev->GetFloat( 1 ); + attacker = ev->GetEntity( 2 ); + meansofdeath = ev->GetInteger( 3 ); + pos = ev->GetVector( 4 ); + dir = ev->GetVector( 5 ); + + if ( !damage && !knockdown ) + return; + + client->ps.stats[ STAT_LAST_PAIN ] = (int) damage; + + // Determine direction + attack_angle = dir.toAngles(); + yawdiff = torsoAngles[YAW] - attack_angle[YAW] + 180.0f; + yawdiff = AngleNormalize180( yawdiff ); + + if ( ( yawdiff > -45.0f ) && ( yawdiff < 45.0f ) ) + pain_dir = PAIN_FRONT; + else if ( ( yawdiff < -45.0f ) && ( yawdiff > -135.0f ) ) + pain_dir = PAIN_LEFT; + else if ( ( yawdiff > 45.0f ) && ( yawdiff < 135.0f ) ) + pain_dir = PAIN_RIGHT; + else + pain_dir = PAIN_REAR; + + // accumulate pain for animation purposes + if ( take_pain ) + { + accumulated_pain += damage; + } + + // Spawn off any damage effect if we get hit with a certain type of damage + SpawnDamageEffect( (meansOfDeath_t)meansofdeath ); + + pain_type = (meansOfDeath_t)meansofdeath; + + // Only set the regular pain level if enough time since last pain has passed + if ( ( level.time > nextpaintime ) && take_pain ) + { + pain = damage; + } + + if ( ( level.time > next_painsound_time ) && ( health > 0.0f ) ) + { + if ( G_GetDatabaseFloat( "MOD", MOD_NumToName( meansofdeath ), "DoPainSound" ) ) + { + next_painsound_time = level.time + 0.25f + G_Random( 0.25f ); + Sound( "snd_pain", CHAN_VOICE, DEFAULT_VOL, 300.0f ); + } + } + + // add to the damage inflicted on a player this frame + // the total will be turned into screen blends and view angle kicks + // at the end of the frame + + damage_blood += damage; + + if ( deadflag == DEAD_NO ) + { + if ( meansofdeath == MOD_DEATH_QUAD ) + damage_from = vec_zero; + else + damage_from = ev->GetVector( 5 ) * damage; + } + + + if ( meansofdeath == MOD_FIRE || meansofdeath == MOD_ON_FIRE ) + damage_blend = damageFireColor; + else + damage_blend = damageNormalColor; + + // Determine which side was hit + + if ( damage_from == vec_zero ) + { + _lastDamagedTimeFront = level.time; + _lastDamagedTimeLeft = level.time; + _lastDamagedTimeRight = level.time; + _lastDamagedTimeBack = level.time; + } + else + { + Vector damageAngles; + float yaw; + + damageAngles = damage_from * -1.0f; + damageAngles = damageAngles.toAngles(); + + yaw = AngleNormalize180( AngleNormalize180( damageAngles[ YAW ] ) - AngleNormalize180( v_angle[ YAW ] ) ); + + if ( ( yaw >= -45.0f ) && ( yaw <= 45.0f ) ) + _lastDamagedTimeFront = level.time; + else if ( ( yaw > 45.0f ) && ( yaw < 135.0f ) ) + _lastDamagedTimeLeft = level.time; + else if ( ( yaw < -45.0f ) && ( yaw > -135.0f ) ) + _lastDamagedTimeRight = level.time; + else + _lastDamagedTimeBack = level.time; + } +} + +void Player::DoUse( Event *ev ) +{ + int i; + int num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + Event *event; + Vector min; + Vector max; + Vector offset; + trace_t trace; + Vector start; + Vector end; + float t; + Entity *usingEntity; + bool entityActuallyUsed; + Entity *bestActor; + + + if ( multiplayerManager.inMultiplayer() && multiplayerManager.isPlayerSpectator( this ) ) + return; + + if ( ev->NumArgs() > 0 ) + usingEntity = ev->GetEntity( 1 ); + else + usingEntity = this; + + // if we are in a vehicle, we want to use the vehicle always + if ( vehicle ) + { + event = new Event( EV_Use ); + event->AddEntity( this ); + vehicle->ProcessEvent( event ); + return; + } + + start = origin; + start.z += client->ps.viewheight; + end = start + ( yaw_forward * 64.0f ); + + trace = G_Trace( start, vec_zero, vec_zero, end, this, MASK_USABLE, true, "Player::DoUse" ); + + t = 64.0f * trace.fraction - maxs[ 0 ]; + if ( t < 0.0f ) + { + t = 0.0f; + } + + offset = yaw_forward * t; + + min = start + offset + Vector( "-31 -31 -31" ); + max = start + offset + Vector( "31 31 31" ); + + num = gi.AreaEntities( min, max, touch, MAX_GENTITIES, qfalse ); + + entityActuallyUsed = false; + + bestActor = getBestActorToUse( touch, num ); + + // be careful, it is possible to have an entity in this + // list removed before we get to it (killtriggered) + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + if ( !hit->inuse ) + { + continue; + } + + if ( usingEntity == hit->entity ) + continue; + + if ( hit->entity->isSubclassOf( Actor ) && ( hit->entity != bestActor ) ) + continue; + + assert( hit->entity ); + + event = new Event( EV_Use ); + event->AddEntity( usingEntity ); + hit->entity->ProcessEvent( event ); + + if ( hit->entity->isSubclassOf( Weapon ) ) + continue; + + if ( hit->entity->isSubclassOf( Projectile ) ) + continue; + + if ( hit->entity->getSolidType() == SOLID_NOT ) + continue; + + entityActuallyUsed = true; + } + + // If we didn't use any object in the world, use our holdable item (if any) + + if ( !entityActuallyUsed ) + useHoldableItem(); +} + +Actor *Player::getBestActorToUse( int *entityList, int count ) +{ + int i; + float bestYawDiff = 1000.0f; + float yawDiff; + Actor *bestActor = NULL; + Actor *currentActor; + gentity_t *edict; + Vector dir; + Vector angles; + + for ( i = 0 ; i < count ; i++ ) + { + edict = &g_entities[ entityList[ i ] ]; + + if ( !edict->inuse || !edict->entity || !edict->entity->isSubclassOf( Actor ) ) + continue; + + currentActor = (Actor *)edict->entity; + + dir = currentActor->centroid - centroid; + dir.normalize(); + angles = dir.toAngles(); + + yawDiff = AngleNormalize180( AngleNormalize180( v_angle[ YAW ] ) - AngleNormalize180( angles[ YAW ] ) ); + yawDiff = abs( (int)yawDiff ); + + if ( yawDiff < bestYawDiff ) + { + bestYawDiff = yawDiff; + bestActor = currentActor; + } + } + + return bestActor; +} + +void Player::TouchStuff( const pmove_t *pm ) +{ + gentity_t *other; + Event *event; + int i; + int j; + + // + // clear out any conditionals that are controlled by touching + // + toucheduseanim = NULL; + + if ( GetMovePlayerMoveType() != PM_NOCLIP ) + { + // See if we should touch all triggers or just teleporters + + if ( multiplayerManager.inMultiplayer() && multiplayerManager.isPlayerSpectator( this ) ) + G_TouchTeleporters( this ); + else + G_TouchTriggers( this ); + } + + // touch other objects + for( i = 0; i < pm->numtouch; i++ ) + { + other = &g_entities[ pm->touchents[ i ] ]; + + for( j = 0; j < i ; j++ ) + { + gentity_t *ge = &g_entities[ j ]; + + if ( ge == other ) + break; + } + + if ( j != i ) + { + // duplicated + continue; + } + + // Don't bother touching the world + if ( ( !other->entity ) || ( other->entity == world ) ) + { + continue; + } + + event = new Event( EV_Touch ); + event->AddEntity( this ); + other->entity->ProcessEvent( event ); + + event = new Event( EV_Touch ); + event->AddEntity( other->entity ); + ProcessEvent( event ); + } +} + +//----------------------------------------------------- +// +// Name: disableInventory +// Class: Player +// +// Description: Disables the player's inventory +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void Player::disableInventory( void ) +{ + client->ps.pm_flags |= PMF_DISABLE_INVENTORY; +} + + +//----------------------------------------------------- +// +// Name: enableInventory +// Class: Player +// +// Description: Enables the players inventory +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void Player::enableInventory( void ) +{ + client->ps.pm_flags &= ~PMF_DISABLE_INVENTORY; +} + +usercmd_t Player::GetLastUcmd(void) +{ + return last_ucmd; +} + +void Player::GetMoveInfo( pmove_t *pm ) +{ + moveresult = pm->moveresult; + + if ( !deadflag || ( multiplayerManager.inMultiplayer() && multiplayerManager.isPlayerSpectator( this ) ) ) + { + v_angle[ 0 ] = pm->ps->viewangles[ 0 ]; + v_angle[ 1 ] = pm->ps->viewangles[ 1 ]; + v_angle[ 2 ] = pm->ps->viewangles[ 2 ]; + + if ( moveresult == MOVERESULT_TURNED ) + { + angles.y = v_angle[ 1 ]; + setAngles( angles ); + SetViewAngles( angles ); + } + } + + setOrigin( Vector( pm->ps->origin[ 0 ], pm->ps->origin[ 1 ], pm->ps->origin[ 2 ] ) ); + velocity = Vector( pm->ps->velocity[ 0 ], pm->ps->velocity[ 1 ], pm->ps->velocity[ 2 ] ); + + if ( ( client->ps.pm_flags & PMF_FROZEN ) || ( client->ps.pm_flags & PMF_NO_MOVE ) ) + { + velocity = vec_zero; + } + else + { + if ( !vehicle ) + setSize( pm->mins, pm->maxs ); + else + setSize( vehicle->_DriverBBoxMins , vehicle->_DriverBBoxMaxs ); + } + + // water type and level is set in the predicted code + waterlevel = pm->waterlevel; + watertype = pm->watertype; + + // Set the ground entity + groundentity = NULL; + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + groundentity = &g_entities[ pm->ps->groundEntityNum ]; + airspeed = 350; + } +} + +void Player::SetMoveInfo( pmove_t *pm, const usercmd_t *ucmd ) +{ + Vector move; + + //float test; + //test = velocity[0]; + //test = velocity[1]; + //test = velocity[2]; + // set up for pmove + memset( pm, 0, sizeof( pmove_t ) ); + + velocity.copyTo( client->ps.velocity ); + + pm->ps = &client->ps; + + if ( ucmd ) + { + pm->cmd = *ucmd; + } + + //pm->tracemask = MASK_PLAYERSOLID; + pm->tracemask = edict->clipmask; + pm->trace = gi.trace; + pm->pointcontents = gi.pointcontents; + pm->trypush = TryPush; + + pm->ps->origin[ 0 ] = origin.x; + pm->ps->origin[ 1 ] = origin.y; + pm->ps->origin[ 2 ] = origin.z; + + pm->ps->velocity[ 0 ] = velocity.x; + pm->ps->velocity[ 1 ] = velocity.y; + pm->ps->velocity[ 2 ] = velocity.z; + + // save off pm_runtime + if ( pm->ps->pm_runtime ) + pm_lastruntime = pm->ps->pm_runtime; +} + +pmtype_t Player::GetMovePlayerMoveType( void ) +{ + if ( multiplayerManager.isPlayerSpectator( this, SPECTATOR_TYPE_FOLLOW ) ) + { + return PM_SPECTATOR_FOLLOW; + } + else if ( multiplayerManager.isPlayerSpectator( this, SPECTATOR_TYPE_FREEFORM ) ) + { + return PM_NOCLIP; + } + else if ( multiplayerManager.isPlayerSpectator( this ) ) + { + return PM_SPECTATOR; + } + else if ( getMoveType() == MOVETYPE_NOCLIP ) + { + return PM_NOCLIP; + } + else if ( deadflag ) + { + return PM_DEAD; + } + else if ( _forcedMoveType != PM_NONE ) + { + return _forcedMoveType; + } + else + { + return PM_NORMAL; + } +} + +void Player::CheckGround( void ) +{ + pmove_t pm; + + SetMoveInfo( &pm, current_ucmd ); + Pmove_GroundTrace( &pm ); + GetMoveInfo( &pm ); +} + +qboolean Player::AnimMove( const Vector &move, Vector *endpos ) +{ + trace_t trace; + int mask; + Vector start( origin ); + Vector end( origin + move ); + + mask = MASK_PLAYERSOLID; + + // test the player position if they were a stepheight higher + trace = G_Trace( start, mins, maxs, end, this, mask, true, "AnimMove" ); + if ( trace.fraction < 1 ) + { + return TestMove( move, endpos ); + } + else + { + if ( endpos ) + { + *endpos = trace.endpos; + } + + return true; + } +} + +qboolean Player::TestMove( const Vector &move, Vector *endpos ) +{ + trace_t trace; + Vector pos( origin + move ); + + trace = G_Trace( origin, mins, maxs, pos, this, edict->clipmask, true, "TestMove" ); + if ( trace.allsolid ) + { + // player is completely trapped in another solid + if ( endpos ) + { + *endpos = origin; + } + return false; + } + + if ( trace.fraction < 1.0f ) + { + Vector up( origin ); + up.z += STEPSIZE; + + trace = G_Trace( origin, mins, maxs, up, this, edict->clipmask, true, "TestMove" ); + if ( trace.fraction == 0.0f ) + { + if ( endpos ) + { + *endpos = origin; + } + return false; + } + + Vector temp( trace.endpos ); + Vector end( temp + move ); + + trace = G_Trace( temp, mins, maxs, end, this, edict->clipmask, true, "TestMove" ); + if ( trace.fraction == 0.0f ) + { + if ( endpos ) + { + *endpos = origin; + } + return false; + } + + temp = trace.endpos; + + Vector down( trace.endpos ); + down.z = origin.z; + + trace = G_Trace( temp, mins, maxs, down, this, edict->clipmask, true, "TestMove" ); + } + + if ( endpos ) + { + *endpos = trace.endpos; + } + + return true; +} + +//-------------------------------------------------------------- +// +// Name: showObjectInfo +// Class: Player +// +// Description: This build the string to be displayed when +// the sv_showinfo cvar is set. +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::showObjectInfo() +{ + str desc; + char addstr[512]; + + Entity *ent = (Entity*)atobject; + desc = "tiki : " + ent->model + "\n"; + sprintf(addstr,"entnum: %d\n",ent->entnum); + desc += addstr; + sprintf(addstr,"origin: (%.2f, %.2f, %.2f)\n",ent->origin.x, ent->origin.y, ent->origin.z); + desc += addstr; + sprintf(addstr,"angles: (%.2f, %.2f, %.2f)\n",ent->angles.x, ent->angles.y, ent->angles.z); + desc += addstr; + sprintf(addstr,"bounds Mins: ( %.2f, %.2f, %.2f )\n", ent->mins.x, ent->mins.y, ent->mins.z); + desc += addstr; + sprintf(addstr,"bounds Maxs: ( %.2f, %.2f, %.2f )\n", ent->maxs.x, ent->maxs.y, ent->maxs.z ); + desc += addstr; + sprintf(addstr,"size: ( %.2f, %.2f, %.2f )\n", ent->size.x, ent->size.y, ent->size.z ); + desc += addstr; + sprintf(addstr,"velocity: ( %.2f, %.2f, %.2f )\n\n", ent->velocity.x, ent->velocity.y, ent->velocity.z ); + desc += addstr; + + if ( ent->isSubclassOf(Actor) ) + { + Actor *act = (Actor *)ent; + desc += "--------- SUBCLASS OF ACTOR ------------\n"; + sprintf(addstr, "Class ID: %s\n", act->getClassID() ); + desc += addstr; + sprintf(addstr, "Classname: %s\n", act->getClassname() ); + desc += addstr; + sprintf(addstr, "Name: %s\n", act->name.c_str() ); + desc += addstr; + if ( act->part_name.length() > 0 ) + { + sprintf(addstr, "Part name: %s\n", act->part_name.c_str() ); + desc += addstr; + } + sprintf(addstr, "Targetname: %s\n", act->TargetName() ); + desc += addstr; + + if ( act->behavior ) + sprintf(addstr, "Behavior: %s\n", act->behavior->getClassname() ); + else + sprintf(addstr, "Behavior: NULL -- was '%s'\n", act->currentBehavior.c_str() ); + desc += addstr; + + if ( act->headBehavior ) + sprintf(addstr, "Head Behavior: %s\n", act->headBehavior->getClassname() ); + else + sprintf(addstr, "Head Behavior: NULL -- was %s\n", act->currentHeadBehavior.c_str() ); + desc += addstr; + + if ( act->eyeBehavior ) + sprintf(addstr, "Eye Behavior: %s\n", act->eyeBehavior->getClassname() ); + else + sprintf(addstr, "Eye Behavior: NULL -- was %s\n", act->currentEyeBehavior.c_str() ); + desc += addstr; + + if ( act->torsoBehavior ) + sprintf(addstr, "Torso Behavior: %s\n", act->torsoBehavior->getClassname() ); + else + sprintf(addstr, "Torso Behavior: NULL -- was %s\n", act->currentTorsoBehavior.c_str() ); + desc += addstr; + + if ( act->currentState ) + sprintf(addstr, "State: %s\n", act->currentState->getName() ); + else + sprintf(addstr, "State: NONE\n" ); + desc += addstr; + + if ( act->GetActorFlag( ACTOR_FLAG_AI_ON ) ) + sprintf(addstr, "AI is ON\n" ); + else + sprintf(addstr, "AI is OFF\n" ); + desc += addstr; + + if ( act->isThinkOn() ) + sprintf(addstr, "Think is ON\n" ); + else + sprintf(addstr, "Think is OFF\n" ); + desc += addstr; + + if ( act->mode == ACTOR_MODE_IDLE ) + sprintf(addstr, "Mode: IDLE\n" ); + else if ( act->mode == ACTOR_MODE_AI ) + sprintf(addstr, "Mode: AI\n" ); + else if ( act->mode == ACTOR_MODE_SCRIPT ) + sprintf(addstr, "Mode: SCRIPT\n" ); + else if ( act->mode == ACTOR_MODE_TALK ) + sprintf(addstr, "Mode: TALK\n" ); + desc += addstr; + + sprintf(addstr, "Actortype: %d\n", act->actortype ); + desc += addstr; + sprintf(addstr, "Anim: %s\n", act->animname.c_str() ); + desc += addstr; + sprintf(addstr, "Health: %f\n", act->health ); + desc += addstr; + + sprintf(addstr, "CurrentEnemy: " ); + desc += addstr; + + // Get our current enemy + Entity *currentEnemy; + currentEnemy = act->enemyManager->GetCurrentEnemy(); + if ( currentEnemy ) + sprintf(addstr, "%d : '%s'\n", currentEnemy->entnum, currentEnemy->targetname.c_str() ); + else + sprintf(addstr, "None\n" ); + desc += addstr; + + switch( act->deadflag ) + { + case DEAD_NO : + sprintf(addstr, "deadflag: NO\n" ); + break; + case DEAD_DYING : + sprintf(addstr, "deadflag: DYING\n" ); + break; + case DEAD_DEAD : + sprintf(addstr, "deadflag: DEAD\n" ); + break; + case DEAD_RESPAWNABLE : + sprintf(addstr, "deadflag: RESPAWNABLE\n" ); + break; + } + desc += addstr; + + } + + G_SendCommandToPlayer( edict, "ui_addhud showinfo"); + G_EnableWidgetOfPlayer( edict, "InfoHUD", true ); + G_SetWidgetTextOfPlayer( edict, "InfoHUD", desc); +} + +//-------------------------------------------------------------- +// +// Name: clearActionType +// Class: Player +// +// Description: Clears the action icon on the hud and NULL's +// out use entity. +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::clearActionType() +{ + if ( lastActionType.length() ) + { + G_EnableWidgetOfPlayer( edict, lastActionType.c_str(), false ); + //G_EnableWidgetOfPlayer( edict, "ActionTextArea", false ); + lastActionType = ""; + } + + atobject = NULL; +} + +//-------------------------------------------------------------- +// +// Name: handleUseEntity +// Class: Player +// +// Description: Called from CheckMoveFlags to handle the +// case that our trace hit an entity. +// We test to see if this is a usable entity +// +// Parameters: Entity *ent -- Entity to test +// float real_dist -- The real distance away from this entity, +// ignoring pitch. +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::handleUseEntity(Entity *ent, float real_dist) +{ + if ( ent->hasUseData() ) + { + // See if this entity is within distance to use. + if ( real_dist > ent->useData->getUseMaxDist() ) + { + clearActionType(); + return; + } + + // Check to see if this entity is usable (due to count) + if ( ent->useData->getUseCount() == 0) + { + // Our count is 0, we can't use this entity anymore, + // so delete the useData for him. + delete ent->useData; + ent->useData = NULL; + clearActionType(); + return; + } + + // If he has a usetype show the HUD icon + if ( ent->useData->getUseType().length() > 0 && !lastActionType.length() ) + { + G_EnableWidgetOfPlayer( edict, ent->useData->getUseType().c_str(), true ); + //G_EnableWidgetOfPlayer( edict, "ActionTextArea", true ); + //G_SetWidgetTextOfPlayer( edict, "ActionTextArea", ent->getArchetype().c_str()); + lastActionType = ent->useData->getUseType(); + } + } + else + clearActionType(); +} + +//-------------------------------------------------------------- +// +// Name: handleUseObject +// Class: Player +// +// Description: Called from CheckMoveFlags to handle the +// case that our trace hit a useobject +// +// Parameters: UseObject *uo +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::handleUseObject(UseObject *uo) +{ + if ( uo->canBeUsed( origin, yaw_forward ) ) + { + if ( uo->action_type.length() > 0 && !lastActionType.length() ) + { + G_EnableWidgetOfPlayer( edict, uo->action_type.c_str(), true ); + lastActionType = uo->action_type; + } + } + else // Not usable, clear HUD icon and data + clearActionType(); +} + +void Player::CheckMoveFlags() +{ + trace_t trace; + Vector start, end, fwd, yfwd; + float oldsp, tracelen, real_dist; + int content; + Vector olddir( oldvelocity.x, oldvelocity.y, 0.0f ); + + //MatrixTransformVector( base_righthand_pos, orientation, righthand_pos ); + //MatrixTransformVector( base_lefthand_pos, orientation, lefthand_pos ); + MatrixTransformVector( base_rightfoot_pos, orientation, rightfoot_pos ); + MatrixTransformVector( base_leftfoot_pos, orientation, leftfoot_pos ); + //righthand_pos += origin; + //lefthand_pos += origin; + rightfoot_pos += origin; + leftfoot_pos += origin; + + // + // Check if moving forward will cause the player to fall + // + start = origin + ( yaw_forward * 52.0f ); + end = start; + end.z -= STEPSIZE * 2.0f; + + trace = G_Trace( start, mins, maxs, end, this, edict->clipmask, true, "CheckMoveFlags" ); + canfall = !( trace.fraction < 1.0f ); + + CheckGround(); + if ( !groundentity && ( velocity.z < -250.0f ) ) + { + falling = true; + hardimpact = false; + } + else + { + hardimpact = ( oldvelocity.z < -1000.0f ); + falling = false; + } + + // check for running into walls + oldsp = VectorNormalize( olddir ); + if ( ( oldsp > 250.0f ) && ( velocity * olddir < 5.0f ) ) + moveresult = MOVERESULT_HITWALL; + + // + // Check if the useobject or useentity + // + start = origin; + start[ 2 ] += viewheight; + GetVAngles().AngleVectors(&fwd); + yfwd = yaw_forward; + yfwd.normalize(); + + // Normal test trace + tracelen = 96.0f; + content = MASK_USABLE; + + // If we have sv_showinfo on, use a better, longer trace. + if ( sv_showinfo->integer ) + { + _infoHudOn = true; + tracelen = sv_showinfodist->value; + + if ( sv_showinfo->integer == 1 ) + content = MASK_ALL; + else + content = CONTENTS_BODY | CONTENTS_CORPSE; + } + + // See if the cvar has been turned off, if so, clear the hud. + if ( !sv_showinfo->integer && _infoHudOn ) + { + G_EnableWidgetOfPlayer( edict, "InfoHUD", false ); + _infoHudOn = false; + } + + float tracedist = tracelen / (Vector::Dot(fwd, yfwd)); // lengthen the trace to compensate for pitch + end = start + ( fwd * tracedist ); + trace = G_Trace( start, Vector(-5,-5,5), Vector(-5,-5,5), end, this, content, true, "CheckMoveFlags -- Checking for UseEntities" ); + if ( trace.ent && trace.ent->entity && ( trace.ent->entity != world ) ) + { + atobject = trace.ent->entity; + if ( trace.startsolid ) + trace.fraction = 0.0; + atobject_dist = trace.fraction * tracedist; + real_dist = (atobject_dist / tracedist) * tracelen; // real distance ignoring pitch + atobject_dir.setXYZ( -trace.plane.normal[ 0 ], -trace.plane.normal[ 1 ], -trace.plane.normal[ 2 ] ); + + if ( sv_showinfo->integer ) + showObjectInfo(); + + if ( atobject->isSubclassOf( UseObject ) ) + { + UseObject *uo = (UseObject *)(Entity *)atobject; + handleUseObject(uo); + } + else + { + Entity *ent = (Entity *)atobject; + handleUseEntity(ent, real_dist); // see if this entity is usable + } + } + else // Didn't hit anything + { + if ( sv_showinfo->integer ) + { + G_EnableWidgetOfPlayer( edict, "InfoHUD", false ); + } + + clearActionType(); + } + + /* + // + // get the distances the player can move left, right, forward, and back + // + if ( ( movecontrol == MOVECONTROL_USER ) || ( movecontrol == MOVECONTROL_LEGS ) ) + { + move_left_dist = TestMoveDist( yaw_left * 128.0f ); + move_right_dist = TestMoveDist( yaw_left * -128.0f ); + move_backward_dist = TestMoveDist( yaw_forward * -128.0f ); + move_forward_dist = TestMoveDist( yaw_forward * 128.0f ); + } + else + { + move_left_dist = CheckMoveDist( yaw_left * 2.0f ); + move_right_dist = CheckMoveDist( yaw_left * -2.0f ); + move_backward_dist = CheckMoveDist( yaw_forward * -2.0f ); + move_forward_dist = CheckMoveDist( yaw_forward * 2.0f ); + } + */ +} + +qboolean Player::CheckMove( const Vector &move, Vector *endpos ) +{ + return AnimMove( move, endpos ); +} + +void Player::ApplyPowerupEffects(int &moveSpeed) +{ + if ( _powerup ) + moveSpeed *= (int)_powerup->getMoveMultiplier(); + + if ( _rune ) + moveSpeed *= (int)_rune->getMoveMultiplier(); + + /* if ( poweruptype == POWERUP_SPEED ) + moveSpeed *= 2; + + if ( poweruptype == POWERUP_STEALTH ) + { + if ( !(flags & FL_NOTARGET) ) + flags ^= FL_NOTARGET; + } + + if ( poweruptype == POWERUP_PROTECTION ) + { + if ( !(flags & FL_GODMODE) ) + flags ^= FL_GODMODE; + } + + if ( poweruptype == POWERUP_FLIGHT ) + { + client->ps.pm_flags |= PMF_FLIGHT; + } + + if ( poweruptype == POWERUP_ACCURACY ) + { + // Tell the weapon we have the accuracy powerup + Weapon *weap; + weap = GetActiveWeapon( WEAPON_DUAL ); + if ( weap ) + weap->SetAccuracyPowerup( true ); + } */ +} + +//---------------------------------------------------------------- +// Name: applyWeaponSpeedModifiers +// Class: Player +// +// Description: Applies any weapon speed modifiers +// +// Parameters: int *moveSpeed - the move speed to modify +// +// Returns: none +//---------------------------------------------------------------- + +void Player::applyWeaponSpeedModifiers( int *moveSpeed ) +{ + Weapon *weap; + + weap = GetActiveWeapon( WEAPON_DUAL ); + + if ( weap ) + { + *moveSpeed *= (int) weap->getMoveSpeedModifier(); + } + +} + +void Player::ClientMoveLadder( usercmd_t *ucmd ) +{ + if ( _onLadder ) + { + // Get off the ladder if the player jumps, the player touches the ground while moving down, or the player + // is no longer on the ladder physically + + if ( ucmd->upmove > 0 ) + { + velocity = _ladderNormal * sv_jumpvelocity->value; + _nextLadderTime = level.time + 0.20; + } + else + { + Vector climbDir; + Vector climbAngles; + Vector playerForward; + Vector playerLeft; + Vector forward; + Vector left; + Vector climbForward; + Vector climbLeft; + Vector climbUp; + bool goingUp; + float upAmount; + float rightAmount; + float forwardAmount; + trace_t trace; + bool pushForward; + + // Change the forward velocity to directly up or down depending on how the player is facing and whether + // he is trying to go forwards or backwards + + v_angle.AngleVectors( &playerForward, &playerLeft ); + + if ( playerForward.z > 0.0f ) + goingUp = true; + else + goingUp = false; + + if ( ucmd->forwardmove < 0 ) + goingUp = !goingUp; + + // Get the ladder climbing angles + + climbDir = -_ladderNormal; + + climbAngles = climbDir.toAngles(); + climbAngles.AngleVectors( &climbForward, &climbLeft, &climbUp ); + + // Determine if we want to push against the ladder + + trace = G_Trace( origin, mins, maxs, origin + climbForward * 4, this, edict->clipmask, true, "ClientMoveLadder" ); + + if ( trace.fraction < 1.0f ) + pushForward = false; + else + pushForward = true; + + // Get the forward/backwards movement of the player + + if ( ucmd->forwardmove < 0 ) + playerForward *= -1; + + // Get the up/down amount from the forward movement + + upAmount = playerForward * climbUp; + + if ( ( upAmount >= -0.1f ) && ( upAmount <= 0.1f ) ) + upAmount = 0.1f; + + forwardAmount = abs( (int)(playerForward * climbForward) ); + + if ( upAmount >= 0.0f ) + upAmount += forwardAmount; + else + upAmount -= forwardAmount; + + // Get the left/right amount from the forward movement + + rightAmount = playerForward * climbLeft; + + if ( ( rightAmount >= -0.1f ) && ( rightAmount <= 0.1f ) ) + rightAmount = 0.0f; + + // Calculate the forward movement + + forward = ( climbUp * upAmount + climbLeft * rightAmount ) * 0.5f; + forward.normalize(); + + if ( pushForward ) + { + forward = ( climbForward + forward ) * 0.5f; + } + + // Get the left/right movement of the player + + if ( ucmd->rightmove > 0 ) + playerLeft *= -1; + + // Get the up/down amount from the strafe movement + + upAmount = playerLeft * climbUp; + + forwardAmount = playerLeft * climbForward; + + upAmount += forwardAmount; + + // Get the left/right amount from the strafe movement + + rightAmount = playerLeft * climbLeft; + + if ( ( rightAmount >= -0.1f ) && ( rightAmount <= 0.1f ) ) + rightAmount = 0.0f; + + // Calculate the strafe movement + + left = ( climbUp * upAmount + climbLeft * rightAmount ) * 0.5f; + left.normalize(); + + if ( pushForward ) + { + left = ( climbForward + left ) * 0.5f; + } + + // Change our velocity based on the forward/strafe movements calculated + + forward.normalize(); + + forward *= abs( ucmd->forwardmove ) / 127.0f * sv_maxspeed->value * 0.75f; + + left *= abs( ucmd->rightmove ) / 127.0f * sv_maxspeed->value * 0.75f; + + velocity = forward + left; + + ucmd->forwardmove = 0; + + client->ps.pm_flags |= PMF_NO_GRAVITY; + + // If we have a groundentity and we are going down get off the ladder + + if ( groundentity && !goingUp ) + { + _nextLadderTime = level.time + 0.20; + } + } + + _onLadder = false; + } +} + +void Player::ClientMoveDuck( usercmd_t *ucmd ) +{ + if ( client->ps.pm_flags & PMF_DUCKED ) + { + // See if we can stand where we are, otherwise we have to stay ducked. + Vector newmins( mins ); + Vector newmaxs( maxs ); + Vector tmporg( origin ); + trace_t trace; + tmporg[ 2 ] += 2.0f; + newmins[ 2 ] = MINS_Z; + newmaxs[ 2 ] = MAXS_Z; + trace = G_Trace( tmporg, newmins, newmaxs, tmporg, this, edict->clipmask, true, "checkcanstand" ); + if ( trace.startsolid ) + { + client->ps.pm_flags |= PMF_DUCKED; + } + else + { + client->ps.pm_flags &= ~PMF_DUCKED; + } + } + + if ( ucmd->upmove < 0 && sv_cancrouch->integer ) + client->ps.pm_flags |= PMF_DUCKED; +} + +//----------------------------------------------- +// Name: ClientMoveLean +// +// Class: Player +// +// Description: Leans the player +// +// Parameters: ucmd - the structure of user commands from the client +// +// Returns: None +//----------------------------------------------- +void Player::ClientMoveLean( usercmd_t *ucmd ) +{ +/* + client->ps.pm_flags &= ~(PMF_LEAN_RIGHT | PMF_LEAN_LEFT); + + if(ucmd->lean > 0) + { + client->ps.pm_flags |= PMF_LEAN_RIGHT; + } + + if(ucmd->lean < 0) + { + client->ps.pm_flags |= PMF_LEAN_LEFT; + } + */ +} + +void Player::ClientMoveFlagsAndSpeeds( int moveSpeed, int noclipSpeed, int crouchSpeed, int airSpeed ) +{ + if ( level.playerfrozen || ( flags & FL_STUNNED ) ) + { + client->ps.pm_flags |= PMF_FROZEN; + } + + if ( ( flags & FL_IMMOBILE ) || ( flags & FL_PARTIAL_IMMOBILE ) ) + { + client->ps.pm_flags |= PMF_NO_MOVE; + //client->ps.pm_flags |= PMF_NO_PREDICTION; + } + + if ( movecontrol == MOVECONTROL_ANIM ) + client->ps.pm_flags |= PMF_NO_PREDICTION; + + if ( !groundentity ) + { + client->ps.speed = airSpeed; + } + else if ( getMoveType() == MOVETYPE_NOCLIP || ( waterlevel > 1 ) ) + { + // No clip underwater speeds + if ( last_ucmd.buttons & BUTTON_RUN ) + client->ps.speed = noclipSpeed; + else + client->ps.speed = noclipSpeed/2; + } + else + { + // Duck speed + if ( client->ps.pm_flags & PMF_DUCKED ) + { + client->ps.speed = crouchSpeed; + } + else + { + // Normal run/walk speed + if ( sv_useanimmovespeed->integer || movecontrol == MOVECONTROL_ANIM ) + { + client->ps.speed = (int) animspeed; + } + else + { + if ( last_ucmd.buttons & BUTTON_RUN ) + client->ps.speed = moveSpeed; + else + client->ps.speed = moveSpeed/2; + } + } + } + + if ( getMoveType() == MOVETYPE_NOCLIP ) + { + // Normal noclip speed (run/walk) + if ( last_ucmd.buttons & BUTTON_RUN ) + client->ps.speed = noclipSpeed; + else + client->ps.speed = noclipSpeed/2; + } + + if ( sv_instantjump->integer ) + client->ps.instantJump = true; + else + client->ps.instantJump = false; + + if ( sv_strafeJumpingAllowed->integer ) + client->ps.strafeJumpingAllowed = true; + else + client->ps.strafeJumpingAllowed = false; +} + +void Player::ClientMoveMisc( usercmd_t *ucmd ) +{ + // We're falling, can't move around in the air + if ( client->ps.feetfalling && ( waterlevel < 2 ) && !( client->ps.pm_time ) ) + { + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + } + + // Fake player, cancel any movement input + if ( fakePlayer_active || movecontrol == MOVECONTROL_ANIM ) + { + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + ucmd->upmove = 0; + } +} + +void Player::ClientMove( usercmd_t *ucmd ) +{ + pmove_t pm; + Vector move; + + int moveSpeed; + int noclipSpeed = (int) sv_noclipspeed->value; + int crouchSpeed; + int airSpeed; + + if ( world->getPhysicsVar( WORLD_PHYSICS_MAXSPEED ) != -1.0f ) + moveSpeed = (int) world->getPhysicsVar( WORLD_PHYSICS_MAXSPEED ); + else + moveSpeed = (int) sv_maxspeed->value; + + if ( sv_crouchspeed->value > 0.0f ) + crouchSpeed = (int) sv_crouchspeed->value; + else + crouchSpeed = (int)((float)moveSpeed * 0.5); + + if ( sv_airmaxspeed->value > 0.0f ) + airSpeed = (int) sv_airmaxspeed->value; + else + airSpeed = moveSpeed; + + // Save off current origin + oldorigin = origin; + + client->ps.pm_type = GetMovePlayerMoveType(); + + // Clear movement flags + client->ps.pm_flags &= ~( PMF_FLIGHT | PMF_FROZEN | PMF_NO_PREDICTION | PMF_NO_MOVE | PMF_HAVETARGET | PMF_NO_GRAVITY ); + + // Modify speeds + + ApplyPowerupEffects( moveSpeed ); + applyWeaponSpeedModifiers( &moveSpeed ); + + if ( multiplayerManager.inMultiplayer() ) + multiplayerManager.applySpeedModifiers( this, &moveSpeed ); + + ApplyPowerupEffects( crouchSpeed ); + applyWeaponSpeedModifiers( &crouchSpeed ); + + if ( multiplayerManager.inMultiplayer() ) + multiplayerManager.applySpeedModifiers( this, &crouchSpeed ); + + ApplyPowerupEffects( airSpeed ); + applyWeaponSpeedModifiers( &airSpeed ); + + if ( multiplayerManager.inMultiplayer() ) + multiplayerManager.applySpeedModifiers( this, &airSpeed ); + + // Handle ducking + ClientMoveDuck(ucmd); + + //Handle leaning + ClientMoveLean(ucmd); + + // Movement speed and mobility flags + ClientMoveFlagsAndSpeeds(moveSpeed, noclipSpeed, crouchSpeed, airSpeed); + + // Misc movement stuff, like falling, and fake player control + ClientMoveMisc(ucmd); + + // Handle ladder situations + ClientMoveLadder(ucmd); + + // Gravity modifier + client->ps.gravity = (int)(sv_currentGravity->value * gravity); + + // Perform the move + CheckGround(); + SetMoveInfo( &pm, ucmd ); + Pmove( &pm ); + GetMoveInfo( &pm ); + ProcessPmoveEvents( pm.pmoveEvent ); + + // Animation driven stuff happens here + if ( ( getMoveType() == MOVETYPE_NOCLIP ) || !( client->ps.pm_flags & PMF_NO_PREDICTION ) ) + { + total_delta = vec_zero; + } + else + { + if ( movecontrol == MOVECONTROL_ABSOLUTE ) + { + velocity = vec_zero; + } + + if ( total_delta != vec_zero ) + { + // Animation driven move + MatrixTransformVector( total_delta, orientation, move ); + CheckMove( move, &origin ); + setOrigin( origin ); + CheckGround(); + } + } + + total_delta = vec_zero; + + TouchStuff( &pm ); + + // Debug output + if ( ( whereami->integer ) && ( origin != oldorigin ) ) + gi.DPrintf( "x %8.2f y%8.2f z %8.2f area %2d\n", origin[ 0 ], origin[ 1 ], origin[ 2 ], edict->areanum ); +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame. +============== +*/ +void Player::ClientThink( Event *ev ) +{ + // sanity check the command time to prevent speedup cheating + if ( current_ucmd->serverTime > level.inttime ) + { + // + // we don't want any future commands, these could be from the previous game + // + return; + } + + if ( current_ucmd->serverTime < ( level.inttime - 1000 ) ) + { + current_ucmd->serverTime = level.inttime - 1000; + } + + if ( ( current_ucmd->serverTime - client->ps.commandTime ) < 1 ) + { + return; + } + + _started = true; + + last_ucmd = *current_ucmd; + new_buttons = current_ucmd->buttons & ~buttons; + buttons = current_ucmd->buttons; + + // Handle Heath Regen if it's in the gameplay database + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str scopestr = "CurrentPlayer.HPRegen"; + if ( gpm->hasObject(scopestr) ) + { + int value = (int)gpm->getFloatValue(scopestr, "value"); + if ( level.inttime > _nextRegenTime && value > 0 ) + { + _nextRegenTime = level.inttime + ((6-value) * 1000); + AddHealth(1.0f); + } + } + + if ( _useEntityStartTimer != 0.0f ) + { + if ( level.time > _useEntityStartTimer ) + { + _usingEntity = false; + _useEntityStartTimer = 0.0f; + } + } + + if ( current_ucmd->thirdperson ) + _isThirdPerson = true; + else + _isThirdPerson = false; + + // Set cvar values + if ( world->getPhysicsVar( WORLD_PHYSICS_AIRACCELERATE ) != -1.0f ) + client->ps.pm_airaccelerate = (int) world->getPhysicsVar( WORLD_PHYSICS_AIRACCELERATE ); + else + client->ps.pm_airaccelerate = (int) sv_airaccelerate->value; + + if ( multiplayerManager.inMultiplayer() ) + multiplayerManager.applyAirAccelerationModifiers( this, &client->ps.pm_airaccelerate ); + + client->ps.pm_wateraccelerate = (int) sv_wateraccelerate->value; + client->ps.pm_stopspeed = (int) sv_stopspeed->value; + client->ps.pm_friction = (int) sv_friction->value; + client->ps.pm_waterfriction = (int) sv_waterfriction->value; + client->ps.jumpvelocity = (int) sv_jumpvelocity->value; + + if ( multiplayerManager.inMultiplayer() ) + multiplayerManager.applyJumpModifiers( this, &client->ps.jumpvelocity ); + + client->ps.crouchjumpvelocity = (int) sv_crouchjumpvelocity->value; + + client->ps.pm_accelerate = (int) sv_accelerate->value; + client->ps.pm_defaultviewheight = (int) sv_defaultviewheight->value; + + viewheight = client->ps.pm_defaultviewheight; + + + + if ( level.intermissiontime ) + { + client->ps.pm_flags |= PMF_FROZEN; + + if (g_endintermission->integer > 0 ) + { + g_endintermission->integer = 0; + level.exitintermission = true; + } + // can exit intermission after 10 seconds (default) + if ( (( level.time - level.intermissiontime ) > level.intermission_advancetime) + && (level.intermission_advancetime != 0)) + { + if ( multiplayerManager.inMultiplayer() ) + { + //if ( new_buttons & BUTTON_ANY ) + if ( ( new_buttons & BUTTON_ATTACKRIGHT ) || ( new_buttons & BUTTON_ATTACKLEFT ) ) + { + level.exitintermission = true; + } + } + else + { + level.exitintermission = true; + + } + } + + // Save cmd angles so that we can get delta angle movements next frame + client->cmd_angles[ 0 ] = SHORT2ANGLE( current_ucmd->angles[ 0 ] ); + client->cmd_angles[ 1 ] = SHORT2ANGLE( current_ucmd->angles[ 1 ] ); + client->cmd_angles[ 2 ] = SHORT2ANGLE( current_ucmd->angles[ 2 ] ); + + return; + } + + moveresult = MOVERESULT_NONE; + + if ( !vehicle || !vehicle->Drive( current_ucmd ) ) + { + ClientMove( current_ucmd ); + } + + if ( vehicle ) + { + pmove_t pm; + + SetMoveInfo( &pm, current_ucmd ); + Pmove( &pm ); + } + + + if(current_ucmd) + { + _crossHairXOffset = current_ucmd->choffset[0]; + _crossHairYOffset = current_ucmd->choffset[1]; + } + + + client->ps.pm_flags &= ~(PMF_RADAR_MODE | PMF_SCANNER ); + + CheckForTargetedEntity(); + ProcessTargetedEntity(); + + Weapon *weap; + weap = GetActiveWeapon( WEAPON_DUAL ); + if ( weap ) + { + if(weap->GetPutaway()) + { + // weap->SetTargetedEntity(0); + } + else + { + // weap->SetRealViewOrigin(); + // weap->SetThirdPerson(); + // weap->SetCHOffset( current_ucmd->choffset[0], current_ucmd->choffset[1]); + + weap->ProcessTargetedEntity(GetTargetedEntity()); + } + + if ( ( ( weap->GetFireType( FIRE_MODE1 ) == FT_CONTROL_PROJECTILE ) && ( new_buttons & BUTTON_ATTACKLEFT ) ) || + ( ( weap->GetFireType( FIRE_MODE2 ) == FT_CONTROL_PROJECTILE ) && ( new_buttons & BUTTON_ATTACKRIGHT ) ) ) + { + weap->toggleProjectileControl(); + } + + if ( ( ( weap->GetFireType( FIRE_MODE1 ) == FT_CONTROL_ZOOM ) && ( new_buttons & BUTTON_ATTACKLEFT ) ) || + ( ( weap->GetFireType( FIRE_MODE2 ) == FT_CONTROL_ZOOM ) && ( new_buttons & BUTTON_ATTACKRIGHT ) ) ) + { + weap->changeZoomStage( 40.0f, 10.0f ); + } + } + + // If we're charging up for a special move, set the current time + if ( specialMoveCharge > 0.0f ) + { + specialMoveCharge = level.time; + //gi.Printf("Charging Up... Time: %.2f\n",specialMoveEndTime - level.time); + } + + + // only evaluate the state when not noclipping + if ( getMoveType() == MOVETYPE_NOCLIP ) + { + // force the stand animation if were in noclip + SetAnim( "idle", all ); + } + else + { + // set flags for state machine + CheckMoveFlags(); + EvaluateState(); + + if ( groundentity && groundentity->entity && groundentity->entity->isSubclassOf( Actor ) ) + { + Event *event = new Event( EV_Actor_Push ); + event->AddVector( Vector( 0.0f, 0.0f, -10.0f ) ); + groundentity->entity->PostEvent( event, 0.0f ); + } + } + + oldvelocity = velocity; + old_v_angle = v_angle; + + // If we're dying, check for respawn + if ( ( deadflag == DEAD_DEAD && ( level.time > respawn_time ) ) ) + { + // wait for any button just going down + //if ( new_buttons || ( multiplayerManager.inMultiplayer() && multiplayerManager.checkFlag( MP_FLAG_FORCE_RESPAWN ) ) ) + if ( ( ( new_buttons & BUTTON_ATTACKRIGHT ) || ( new_buttons & BUTTON_ATTACKLEFT ) ) || + ( multiplayerManager.inMultiplayer() && multiplayerManager.checkFlag( MP_FLAG_FORCE_RESPAWN ) ) ) + { + if ( multiplayerManager.inMultiplayer() ) + { + //respawn_time = level.time ; + multiplayerManager.respawnPlayer( this, false ); + // ProcessEvent( EV_Player_Respawn ); + } + + else + { + G_RestartLevelWithDelay( 1.0f ); + } + } + } + + // Save cmd angles so that we can get delta angle movements next frame + client->cmd_angles[ 0 ] = SHORT2ANGLE( current_ucmd->angles[ 0 ] ); + client->cmd_angles[ 1 ] = SHORT2ANGLE( current_ucmd->angles[ 1 ] ); + client->cmd_angles[ 2 ] = SHORT2ANGLE( current_ucmd->angles[ 2 ] ); + + if ( client->ps.pm_flags & PMF_CAMERA_VIEW ) + { + VectorCopy( current_ucmd->realvieworigin, client->ps.camera_origin ); + VectorCopy( current_ucmd->realviewangles, client->ps.camera_angles ); + } + + if ( g_logstats->integer ) + { + if ( !logfile_started ) + { + ProcessEvent( EV_Player_LogStats ); + logfile_started = true; + } + } + + if ( ( last_ucmd.buttons & BUTTON_USE ) != 0 ) + ProcessEvent( EV_Player_DoUse ); + + if ( ( last_ucmd.buttons & BUTTON_DROP_RUNE ) != 0 ) + dropRune(); + + if ( ( last_ucmd.buttons & BUTTON_TRANSFER_ENERGY ) != 0 ) + transferEnergy(); + + if ( multiplayerManager.inMultiplayer() ) + multiplayerManager.playerInput( this, new_buttons ); + + // Check for incoming melee attacks and set the incomingMeleeAttack flag + incomingMeleeAttack = false; + if ( meleeAttackerList.NumObjects() ) + { + Entity *attacker; + int i; + for( i = meleeAttackerList.NumObjects() ; i >= 1 ; i-- ) + { + attacker = (Entity*)meleeAttackerList.ObjectAt(i); + if ( !attacker ) + continue; + + if ( attacker->isSubclassOf(Actor) ) + { + Actor *act = (Actor *)attacker; + if ( act->in_melee_attack ) + incomingMeleeAttack = true; + else + meleeAttackerList.RemoveObjectAt( i ); + } + } + + // No one is attacking us, make sure the list is clear + if ( !incomingMeleeAttack ) + meleeAttackerList.ClearObjectList(); + } +} + +// This function returns the endpoint of where the crosshair is located on the +// screen. Regardless of whether we're in camera, first or 3rd person view. +void Player::GetViewTrace( trace_t& trace, int contents, float maxDistance ) +{ +// trace_t trace; + Vector f,r,u,p,s; + Vector pos, forward, endpoint, vorg, dir; + vec3_t viewAngles; + + + if (client->ps.pm_flags & PMF_CAMERA_VIEW ) // 3rd person automatic camera + { + vorg = client->ps.camera_origin; + VectorCopy(client->ps.camera_angles, viewAngles); + } + else if ( !_isThirdPerson ) // First person + { + vorg = origin; + vorg.z += client->ps.viewheight; + VectorCopy(GetVAngles(), viewAngles); + } + else // Third person + { + //vorg = current_ucmd->realviewangles; + VectorCopy(GetVAngles(), viewAngles); + + } + + vec3_t left; + //Adjust the view end point if we are leaning. + if( client->ps.leanDelta != 0) + { + viewAngles[2] -= client->ps.leanDelta / 2.0f; + + AngleVectors( viewAngles, NULL, left, NULL ); + VectorMA( vorg, client->ps.leanDelta, left, vorg ); + } + + // Add the damage angles + VectorSubtract( viewAngles, client->ps.damage_angles, viewAngles ); + + AngleVectors(viewAngles, f,r,u); + + float xmax,ymax, fov_x, fov_y, x; + + fov_x = client->ps.fov; + x = 640.0f / (float)tan( fov_x / 360.0 * M_PI ); + fov_y = (float)atan2( 480.0, x ); + fov_y *= 360.0f / M_PI; + + ymax = 4.0f * (float)tan( fov_y * M_PI / 360.0 ); + xmax = 4.0f * (float)tan( fov_x * M_PI / 360.0 ); + + p.x = (float) _crossHairXOffset * (-xmax / 320.0f); + p.y = (float) _crossHairYOffset * (-ymax / 240.0f); + s = ( 4.0f * f ) + ( p.x * r ) + ( p.y * u ); + s.normalize(); + endpoint = vorg + (s * maxDistance ); + + if ( !multiplayerManager.inMultiplayer() || multiplayerManager.fullCollision() ) + trace = G_FullTrace( vorg, vec_zero, vec_zero, endpoint, this, contents, true, "Player::GetViewEndPoint" ); + else + trace = G_Trace( vorg, vec_zero, vec_zero, endpoint, this, contents, true, "Player::GetViewEndPoint" ); + + //endpoint = trace.endpos; + + //G_DebugLine( vorg, endpoint, 1,0,1,1 ); + + //return endpoint; +} +//----------------------------------------------------- +// +// Name: CheckForTargetedEntity +// Class: Player +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::CheckForTargetedEntity( void ) +{ + trace_t viewTrace; + + memset(&viewTrace,0,sizeof(trace_t)); + GetViewTrace(viewTrace, MASK_SHOT | CONTENTS_TARGETABLE); + + if ( viewTrace.ent && ( viewTrace.entityNum != ENTITYNUM_WORLD )) + { + if(viewTrace.ent->entity->isSubclassOf(Actor) && viewTrace.ent->entity->getHealth() <= 0) + { + SetTargetedEntity(0); + } + else + { + SetTargetedEntity(viewTrace.ent->entity); + } + + return; + } + + SetTargetedEntity(0); +} + + +//----------------------------------------------------- +// +// Name: SetTargetedEntity +// Class: Player +// +// Description: Sets the targeted entity. +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::SetTargetedEntity(EntityPtr entity) +{ + //remove the EF_DISPLAY_INFO from the previous entity. + if(_targetedEntity != 0) + _targetedEntity ->edict->s.eFlags &= ~(EF_DISPLAY_INFO|EF_DISPLAY_DESC1 | EF_DISPLAY_DESC2 | EF_DISPLAY_DESC3); + + + _targetedEntity = entity; + if(_targetedEntity != 0) + _targetedEntity->edict->s.eFlags |= EF_DISPLAY_INFO; +} + + + +//----------------------------------------------------- +// +// Name: ProcessTargetedEntity +// Class: Player +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::ProcessTargetedEntity( void ) +{ + if(_targetedEntity == 0) + return; + + _targetedEntity->edict->s.eFlags |= EF_DISPLAY_DESC1; +} + + +//-------------------------------------------------------------- +// +// Name: SetState +// Class: Player +// +// Description: Sets the state machine to the specific legs and torso state specified +// +// Parameters: const str& legsstate -- Legs state name +// const str& torsostate -- Torso state name +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::SetState(const str& legsstate, const str& torsostate) +{ + animdone_Legs = false; + animdone_Torso = false; + + movecontrol = MOVECONTROL_USER; + + currentState_Legs = statemap_Legs->FindState( legsstate.c_str() ); + currentState_Torso = statemap_Torso->FindState( torsostate.c_str() ); + + str legsAnim( currentState_Legs->getLegAnim( *this, &legs_conditionals ) ); + if ( !animate->HasAnim(legsAnim) ) + legsAnim = getGameplayAnim(legsAnim); + if ( legsAnim == "" ) + { + partAnim[ legs ] = ""; + animate->ClearLegsAnim(); + } + else if ( legsAnim != "none" ) + { + SetAnim( legsAnim, legs ); + } + + str torsoAnim( currentState_Torso->getTorsoAnim( *this, &torso_conditionals ) ); + if ( !animate->HasAnim(torsoAnim) ) + torsoAnim = getGameplayAnim(torsoAnim); + if ( torsoAnim == "" ) + { + partAnim[ torso ] = ""; + animate->ClearTorsoAnim(); + } + else if ( torsoAnim != "none" ) + { + SetAnim( torsoAnim.c_str(), torso ); + } + + movecontrol = currentState_Legs->getMoveType(); + if ( ( movecontrol < ( sizeof( MoveStartFuncs ) / sizeof( MoveStartFuncs[ 0 ] ) ) ) && ( MoveStartFuncs[ movecontrol ] ) ) + { + ( this->*MoveStartFuncs[ movecontrol ] )(); + } + + // This seems breaks the secret move mode because it's called at a bad time and causes the client/server angles + // to get out of sync because delta_angles is not updated correctly. + // I don't think it's necessary to call it anyway, so I'm removing it. + //SetViewAngles( v_angle ); +} + + +//-------------------------------------------------------------- +// +// Name: LoadStateTable +// Class: Player +// +// Description: Loads the state table for the player. +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::LoadStateTable() +{ + statemap_Legs = NULL; + statemap_Torso = NULL; + + freeConditionals( legs_conditionals ); + freeConditionals( torso_conditionals ); + + statemap_Legs = GetStatemap( str( g_statefile->string ) + "_legs.st", ( Condition * )Conditions, &legs_conditionals, false ); + statemap_Torso = GetStatemap( str( g_statefile->string ) + "_torso.st", ( Condition * )Conditions, &torso_conditionals, false ); + + SetState("STAND", "START"); +} + +void Player::ResetState( Event *ev ) +{ + movecontrol = MOVECONTROL_USER; + LoadStateTable(); +} + +void Player::StartPush( void ) +{ + trace_t trace; + Vector end( origin + ( yaw_forward * 64.0f ) ); + + trace = G_Trace( origin, mins, maxs, end, this, MASK_SOLID, true, "StartPush" ); + if ( trace.fraction == 1.0f ) + { + return; + } + v_angle.y = vectoyaw( trace.plane.normal ) - 180.0f; + SetViewAngles( v_angle ); + + setOrigin( trace.endpos - ( yaw_forward * 0.4f ) ); +} + +void Player::StartUseAnim( void ) +{ + UseAnim *ua; + Vector neworg; + Vector newangles; + str newanim; + str state; + str camera; + trace_t trace; + + if ( toucheduseanim ) + { + ua = ( UseAnim * )( Entity * )toucheduseanim; + } + else if ( atobject ) + { + ua = ( UseAnim * )( Entity * )atobject; + } + else + { + return; + } + + useitem_in_use = ua; + toucheduseanim = NULL; + atobject = NULL; + + if ( ua->GetInformation( this, &neworg, &newangles, &newanim, &useanim_numloops, &state, &camera ) ) + { + trace = G_Trace( origin, mins, maxs, neworg, this, edict->clipmask, true, "StartUseAnim" ); + if ( trace.startsolid || ( trace.fraction < 1.0f ) ) + { + gi.WDPrintf( "Move to UseAnim was blocked.\n" ); + } + + if ( !trace.startsolid ) + { + setOrigin( trace.endpos ); + } + + setAngles( newangles ); + v_angle.y = newangles.y; + SetViewAngles( v_angle ); + + movecontrol = MOVECONTROL_ABSOLUTE; + + if ( state.length() ) + { + State * newState; + + newState = statemap_Torso->FindState( state ); + if ( newState ) + { + EvaluateState( newState ); + } + else + { + gi.WDPrintf( "Could not find state %s on UseAnim\n", state.c_str() ); + } + } + else + { + if ( currentState_Torso ) + { + if ( camera.length() ) + { + currentState_Torso->setCameraType( camera ); + } + else + { + currentState_Torso->setCameraType( "behind" ); + } + } + SetAnim( newanim, legs ); + } + } +} + +void Player::StartLoopUseAnim( void ) +{ + useanim_numloops--; +} + +void Player::FinishUseAnim( Event *ev ) +{ + UseAnim *ua; + + if ( !useitem_in_use ) + return; + + ua = ( UseAnim * )( Entity * )useitem_in_use; + ua->TriggerTargets( this ); + useitem_in_use = NULL; +} + +void Player::SetupUseObject() +{ + UseObject *uo; + Vector neworg; + Vector newangles; + str state; + trace_t trace; + + if ( atobject ) + uo = ( UseObject * )( Entity * )atobject; + else + return; + + useitem_in_use = uo; + + uo->Setup( this, &neworg, &newangles, &state ); + if ( uo->movetheplayer ) + { + trace = G_Trace( neworg, mins, maxs, neworg, this, edict->clipmask, true, "SetupUseObject - 1" ); + if ( trace.startsolid || trace.allsolid ) + { + trace = G_Trace( origin, mins, maxs, neworg, this, edict->clipmask, true, "SetupUseObject - 2" ); + if ( trace.startsolid || ( trace.fraction < 1.0f ) ) + { + gi.WDPrintf( "Move to UseObject was blocked.\n" ); + } + } + + if ( !trace.startsolid ) + { + setOrigin( trace.endpos ); + } + + setAngles( newangles ); + v_angle.y = newangles.y; + SetViewAngles( v_angle ); + } + + movecontrol = MOVECONTROL_ABSOLUTE; + if ( state.length() ) + { + State * newState; + + newState = statemap_Torso->FindState( state ); + if ( newState ) + EvaluateState( newState ); + else + gi.WDPrintf( "Could not find state %s on UseObject\n", state.c_str() ); + } + else + { + uo->Start(); + } +} + +void Player::StartUseObject( Event *ev ) +{ + UseObject *uo; + + if ( !useitem_in_use ) + return; + + uo = ( UseObject * )( Entity * )useitem_in_use; + uo->Start(); +} + +void Player::FinishUseObject( Event *ev ) +{ + UseObject *uo; + + if ( !useitem_in_use ) + return; + + uo = ( UseObject * )( Entity * )useitem_in_use; + uo->Stop( this ); + useitem_in_use = NULL; +} + +void Player::turnTowardsEntity( Event *ev ) +{ + Entity *entity; + Vector dir; + Vector angles; + Event *turnEvent; + float currentYaw; + float yawDiff; + + entity = ev->GetEntity( 1 ); + + if ( !entity ) + return; + + // Get the angles to the entity + + dir = entity->origin - origin; + dir.normalize(); + + angles = dir.toAngles(); + + // Get the yaw difference + + currentYaw = anglemod( v_angle[ YAW ] ); + + yawDiff = angles[ YAW ] - currentYaw; + + yawDiff = AngleNormalize180( yawDiff ); + + // Actually turn the appropriate amount + + turnEvent = new Event( EV_Player_Turn ); + turnEvent->AddFloat( yawDiff ); + ProcessEvent( turnEvent ); +} + +void Player::Turn( Event *ev ) +{ + float yaw; + float time; + int numFrames; + + yaw = ev->GetFloat( 1 ); + + if ( ev->NumArgs() > 1 ) + time = ev->GetFloat( 2 ); + else + time = 0.5; + + numFrames = (int)(time * ( 1 / level.frametime )); + time = numFrames * level.frametime; + + CancelEventsOfType( EV_Player_TurnUpdate ); + + if ( time > 0 ) + { + ev = new Event( EV_Player_TurnUpdate ); + ev->AddFloat( yaw / numFrames ); + ev->AddFloat( time ); + ProcessEvent( ev ); + } + else + { + v_angle[ YAW ] += yaw; + SetViewAngles( v_angle ); + } +} + +void Player::TurnUpdate( Event *ev ) +{ + float yaw; + float timeleft; + + yaw = ev->GetFloat( 1 ); + timeleft = ev->GetFloat( 2 ); + + // Remove some time + + timeleft -= 0.05f; + + // Turn the specified amount + + v_angle[ YAW ] += yaw; + SetViewAngles( v_angle ); + + // Repost the turn event if we have time remaining + + if ( timeleft > 0.0f ) + { + ev = new Event( EV_Player_TurnUpdate ); + ev->AddFloat( yaw ); + ev->AddFloat( timeleft ); + PostEvent( ev, 0.05f ); + } +} + +void Player::TurnLegs( Event *ev ) +{ + float yaw; + + yaw = ev->GetFloat( 1 ); + + angles[ YAW ] += yaw; + setAngles( angles ); +} + +void Player::DontTurnLegs( Event *ev ) +{ + dont_turn_legs = ev->GetBoolean( 1 ); +} + +void Player::EvaluateState( State *forceTorso, State *forceLegs ) +{ + int count; + State *laststate_Legs; + State *laststate_Torso; + State *startstate_Legs; + State *startstate_Torso; + movecontrol_t move; + + if ( getMoveType() == MOVETYPE_NOCLIP ) + { + return; + } + + // Evaluate the current state. + // When the state changes, we reevaluate the state so that if the + // conditions aren't met in the new state, we don't play one frame of + // the animation for that state before going to the next state. + startstate_Torso = laststate_Torso = currentState_Torso; + count = 0; + do + { + // since we could get into an infinite loop here, do a check + // to make sure we don't. + count++; + if ( count > 10 ) + { + gi.WDPrintf( "Possible infinite loop in state '%s'\n", currentState_Torso->getName() ); + assert( 0 ); + if ( count > 20 ) + { + gi.Error( ERR_DROP, "Stopping due to possible infinite state loop\n" ); + break; + } + } + + laststate_Torso = currentState_Torso; + + if ( forceTorso ) + currentState_Torso = forceTorso; + else + currentState_Torso = currentState_Torso->Evaluate( *this, &torso_conditionals ); + + animdone_Torso = false; + if ( movecontrol != MOVECONTROL_LEGS ) + { + animdone_Legs = false; + } + if ( laststate_Torso != currentState_Torso ) + { + // Process exit commands of the last state + laststate_Torso->ProcessExitCommands( this ); + + // Process entry commands of the new state + currentState_Torso->ProcessEntryCommands( this ); + + if ( waitForState.length() && ( !waitForState.icmpn( currentState_Torso->getName(), waitForState.length() ) ) ) + { + waitForState = ""; + PlayerDone( NULL ); + } + + move = currentState_Torso->getMoveType(); + + str legsAnim( currentState_Torso->getLegAnim( *this, &torso_conditionals ) ); + if ( !animate->HasAnim(legsAnim) ) + legsAnim = getGameplayAnim(legsAnim); + str torsoAnim( currentState_Torso->getTorsoAnim( *this, &torso_conditionals ) ); + if ( !animate->HasAnim(torsoAnim) ) + torsoAnim = getGameplayAnim(torsoAnim); + + if ( legsAnim != "" ) + { + animdone_Legs = false; + if ( !vehicle ) + SetAnim( legsAnim, legs ); + } + else if ( move == MOVECONTROL_LEGS ) + { + if ( !currentState_Legs ) + { + animdone_Legs = false; + currentState_Legs = statemap_Legs->FindState( "STAND" ); + legsAnim = currentState_Legs->getLegAnim( *this, &legs_conditionals ); + if ( !animate->HasAnim(legsAnim) ) + legsAnim = getGameplayAnim(legsAnim); + if ( legsAnim == "" ) + { + partAnim[ legs ] = ""; + animate->ClearLegsAnim(); + } + else if ( legsAnim != "none" ) + { + if ( !vehicle ) + SetAnim( legsAnim, legs ); + } + } + } + else + { + partAnim[ legs ] = ""; + animate->ClearLegsAnim(); + } + + if ( torsoAnim == "" ) + { + partAnim[ torso ] = ""; + animate->ClearTorsoAnim(); + } + else if ( torsoAnim != "none" ) + { + SetAnim( torsoAnim.c_str(), torso ); + } + + if ( movecontrol != move ) + { + movecontrol = move; + if ( ( move < ( sizeof( MoveStartFuncs ) / sizeof( MoveStartFuncs[ 0 ] ) ) ) && ( MoveStartFuncs[ move ] ) ) + { + ( this->*MoveStartFuncs[ move ] )(); + } + } + + // This seems breaks the secret move mode because it's called at a bad time and causes the client/server angles + // to get out of sync because delta_angles is not updated correctly. + // I don't think it's necessary to call it anyway, so I'm removing it. + //SetViewAngles( v_angle ); + } + } + while( laststate_Torso != currentState_Torso ); + + // Evaluate the current state. + // When the state changes, we reevaluate the state so that if the + // conditions aren't met in the new state, we don't play one frame of + // the animation for that state before going to the next state. + startstate_Legs = laststate_Legs = currentState_Legs; + if ( movecontrol == MOVECONTROL_LEGS ) + { + count = 0; + do + { + // since we could get into an infinite loop here, do a check + // to make sure we don't. + count++; + if ( count > 10 ) + { + gi.WDPrintf( "Possible infinite loop in state '%s'\n", currentState_Legs->getName() ); + assert( 0 ); + if ( count > 20 ) + { + gi.Error( ERR_DROP, "Stopping due to possible infinite state loop\n" ); + break; + } + } + + if ( !currentState_Legs ) + { + currentState_Legs = statemap_Legs->FindState( "STAND" ); + } + + laststate_Legs = currentState_Legs; + + if ( forceLegs ) + currentState_Legs = forceLegs; + else + currentState_Legs = currentState_Legs->Evaluate( *this, &legs_conditionals ); + + animdone_Legs = false; + if ( laststate_Legs != currentState_Legs ) + { + // Process exit commands of the last state + laststate_Legs->ProcessExitCommands( this ); + + // Process entry commands of the new state + currentState_Legs->ProcessEntryCommands( this ); + + if ( waitForState.length() && ( !waitForState.icmpn( currentState_Legs->getName(), waitForState.length() ) ) ) + { + waitForState = ""; + PlayerDone( NULL ); + } + + str legsAnim( currentState_Legs->getLegAnim( *this, &legs_conditionals ) ); + if ( !animate->HasAnim(legsAnim) ) + legsAnim = getGameplayAnim(legsAnim); + if ( legsAnim == "" ) + { + partAnim[ legs ] = ""; + animate->ClearLegsAnim(); + } + else if ( legsAnim != "none" ) + { + if ( !vehicle ) + SetAnim( legsAnim, legs ); + } + } + } + while( laststate_Legs != currentState_Legs ); + } + else + { + currentState_Legs = NULL; + } + + if ( g_showplayeranim->integer ) + { + if ( last_leg_anim_name != animate->AnimName( legs ) ) + { + gi.DPrintf( "Legs change from %s to %s\n", last_leg_anim_name.c_str(), animate->AnimName( legs ) ); + last_leg_anim_name = animate->AnimName( legs ); + } + + if ( last_torso_anim_name != animate->AnimName( torso ) ) + { + gi.DPrintf( "Torso change from %s to %s\n", last_torso_anim_name.c_str(), animate->AnimName( torso ) ); + last_torso_anim_name = animate->AnimName( torso ); + } + } + + if ( g_showplayerstate->integer ) + { + if ( startstate_Legs != currentState_Legs ) + { + gi.DPrintf( "Legs change from %s to %s\n", + startstate_Legs ? startstate_Legs->getName() : "NULL", + currentState_Legs ? currentState_Legs->getName() : "NULL" ); + } + + if ( startstate_Torso != currentState_Torso ) + { + gi.DPrintf( "Torso change from %s to %s\n", + startstate_Torso ? startstate_Torso->getName() : "NULL", + currentState_Torso ? currentState_Torso->getName() : "NULL" ); + } + } + + if ( g_showplayerweapon->integer ) + { + Weapon *weapon; + + weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( !weapon ) + weapon = GetActiveWeapon( WEAPON_RIGHT ); + + if ( !weapon ) + weapon = GetActiveWeapon( WEAPON_LEFT ); + + if ( weapon ) + { + gi.DPrintf( "Weapon - anim %s, frame %d\n", weapon->animate->GetName().c_str(), weapon->animate->CurrentFrame() ); + } + } + + // This is so we don't remember pain when we change to a state that has a PAIN condition + pain = 0; + // Every second drop accumulated pain by 1 + if ( ( float )( int )( level.time ) == level.time ) + { + accumulated_pain -= 1.0f; + if ( accumulated_pain < 0.0f ) + accumulated_pain = 0.0f; + } +} + +void Player::EventUseItem( Event *ev ) +{ + const char *name; + weaponhand_t hand = WEAPON_DUAL; + + if ( deadflag || _disableUseWeapon ) + { + return; + } + + if ( multiplayerManager.inMultiplayer() && ( !multiplayerManager.isFightingAllowed() || multiplayerManager.isPlayerSpectator( this ) ) ) + { + return; + } + + name = ev->GetString( 1 ); + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + + str objectName("CurrentPlayer."); + objectName += name ; + if ( gpm->hasProperty( objectName, "name" ) ) + name = gpm->getStringValue( objectName, "name" ); + else if ( gpm->hasProperty(name, "consumable" ) ) + { + str slotname = gpm->getStringValue(name, "invslot"); + if ( !gpm->hasProperty(slotname, "quantity") ) + return; + + float quantity = gpm->getFloatValue(slotname, "quantity"); + if ( quantity < 1.0f ) + return; + + // Give the model, decrement the quantity + str model = gpm->getStringValue(name, "model"); + giveItem(model); + gpm->setFloatValue(slotname, "quantity", quantity-1.0f); + } + + Item *item = ( Item * )FindItem( name ); + + if ( item && item->isSubclassOf( InventoryItem ) ) + { + InventoryItem *ii = ( InventoryItem * )item; + Event *ev1; + + ev1 = new Event( EV_InventoryItem_Use ); + ev1->AddEntity( this ); + ii->ProcessEvent( ev1 ); + return; + } + else if ( ev->NumArgs() > 1 ) + { + hand = WeaponHandNameToNum( ev->GetString( 2 ) ); + } + + useWeapon( name, hand ); +} + +void Player::GiveWeaponCheat( Event *ev ) +{ + giveItem( ev->GetString( 1 ) ); +} + +void Player::GiveCheat( Event *ev ) +{ + str name; + + if ( deadflag ) + { + return; + } + + name = ev->GetString( 1 ); + + if ( !name.icmp( "all" ) ) + { + GiveAllCheat( ev ); + return; + } + EventGiveItem( ev ); +} + +void Player::GiveAllCheat( Event *ev ) +{ + char *buffer; + char *buf; + char com_token[MAX_STRING_CHARS]; + int numEvents; + float postTime; + + + if ( deadflag ) + { + return; + } + + if ( !gi.isClientActive( this->edict ) ) + { + PostEvent( *ev, FRAMETIME ); + return; + } + + numEvents = 0; + postTime = 0.0f; + + if ( gi.FS_ReadFile( "global/giveall.scr", ( void ** )&buf, true ) != -1 ) + { + buffer = buf; + + while ( 1 ) + { + strcpy( com_token, COM_ParseExt( &buffer, true ) ); + + if (!com_token[0]) + break; + + // Create the event + ev = new Event( com_token ); + + // get the rest of the line + while( 1 ) + { + strcpy( com_token, COM_ParseExt( &buffer, false ) ); + if (!com_token[0]) + break; + + ev->AddToken( com_token ); + } + + if ( numEvents >= 5 ) + { + numEvents = 0; + //postTime += level.frametime * 4; + postTime += 1.0f; + } + + PostEvent( ev, postTime ); + + numEvents++; + } + + gi.FS_FreeFile( buf ); + } +} + +void Player::GodCheat( Event *ev ) +{ + const char *msg; + + if ( ev->NumArgs() > 0 ) + { + if ( ev->GetInteger( 1 ) ) + { + flags |= FL_GODMODE; + } + else + { + flags &= ~FL_GODMODE; + } + } + else + { + flags ^= FL_GODMODE; + } + + if ( ev->GetSource() == EV_FROM_CONSOLE ) + { + if ( !( flags & FL_GODMODE ) ) + { + msg = "godmode OFF\n"; + } + else + { + msg = "godmode ON\n"; + } + + gi.SendServerCommand( edict-g_entities, "print \"%s\"", msg ); + } +} + +void Player::Kill( Event *ev ) +{ + if ( ( level.time - respawn_time ) < 5.0f ) + { + return; + } + + flags &= ~FL_GODMODE; + health = 1.0f; + Damage( this, this, 10.0f, origin, vec_zero, vec_zero, 0, DAMAGE_NO_PROTECTION, MOD_SUICIDE ); +} + +void Player::NoTargetCheat( Event *ev ) +{ + const char *msg; + + flags ^= FL_NOTARGET; + if ( !( flags & FL_NOTARGET ) ) + { + msg = "notarget OFF\n"; + } + else + { + msg = "notarget ON\n"; + } + + gi.SendServerCommand( edict-g_entities, "print \"%s\"", msg ); +} + +//-------------------------------------------------------------- +// +// Name: NoclipCheat +// Class: Player +// +// Description: This is the event that puts the player in noclip mode +// +// Parameters: Event *ev - No params +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::NoclipCheat( Event *ev ) +{ + const char *msg; + + if ( vehicle ) + msg = "Must exit vehicle first\n"; + else if ( getMoveType() == MOVETYPE_NOCLIP ) + { + setMoveType( MOVETYPE_WALK ); + msg = "noclip OFF\n"; + + // reset the state machine so that her animations are correct + SetState("STAND", "STAND"); + } + else + { + client->ps.feetfalling = false; + movecontrol = MOVECONTROL_USER; + + setMoveType( MOVETYPE_NOCLIP ); + msg = "noclip ON\n"; + } + + gi.SendServerCommand( edict-g_entities, "print \"%s\"", msg ); +} + +void Player::GameVersion( Event *ev ) +{ + gi.SendServerCommand( edict-g_entities, "print \"%s : %s\n\"", GAME_NAME, __DATE__ ); +} + +void Player::SetFov( float newFov, bool forced ) +{ + fov = newFov; + + if ( !forced && ( fov != sv_defaultFov->value ) && multiplayerManager.checkFlag( MP_FLAG_FIXED_FOV ) ) + { + fov = sv_defaultFov->value; + return; + } + + //if ( !forced ) + //{ + // _userFovChoice = newFov; + //} + + if ( fov < 1.0f ) + { + fov = sv_defaultFov->value; + } + else if ( fov > 160.0f ) + { + fov = 160.0f; + } + + str tempString( fov ); + gi.cvar_set( "r_fov", tempString.c_str() ); +} + +float Player::getDefaultFov( void ) +{ + if ( multiplayerManager.inMultiplayer() && multiplayerManager.checkFlag( MP_FLAG_FIXED_FOV ) ) + return sv_defaultFov->value; + else + //return _userFovChoice; + return userFov; +} + +void Player::Fov( Event *ev ) +{ + CancelEventsOfType(EV_Player_Fov); + if ( ev->NumArgs() > 1 ) + { + Event *event = new Event( EV_Player_Fov ); + event->AddFloat( ev->GetFloat( 1 ) ); + PostEvent ( event, ev->GetFloat( 2 ) ); + return; + } + + if ( ev->NumArgs() < 1 ) + { + gi.SendServerCommand( edict-g_entities, "print \"Fov = %d\n\"", fov ); + return; + } + + SetFov( ev->GetFloat( 1 ) ); +} + +/* +=============== +CalcRoll + +=============== +*/ +float Player::CalcRoll( void ) +{ + float sign; + float side; + float value; + Vector l; + + angles.AngleVectors( NULL, &l, NULL ); + side = velocity * l; + sign = side < 0.0f ? 4.0f : -4.0f; + side = fabs( side ); + + value = sv_rollangle->value; + + if ( side < sv_rollspeed->value ) + { + side = side * value / sv_rollspeed->value; + } + else + { + side = value; + } + + return side * sign; +} + +void Player::GravityNodes( void ) +{ + Vector grav; + Vector gravnorm; + Vector velnorm; + float dot; + qboolean force; + float max_speed; + Vector new_velocity; + + // + // Check for gravity pulling points + // + + // no pull during waterjumps + if ( client->ps.pm_flags & PMF_TIME_WATERJUMP ) + { + return; + } + + grav = gravPathManager.CalculateGravityPull( *this, origin, &force, &max_speed ); + + // Check for unfightable gravity. + if ( force && ( grav != vec_zero ) ) + { + velnorm = velocity; + velnorm.normalize(); + + gravnorm = grav; + gravnorm.normalize(); + + dot = ( gravnorm.x * velnorm.x ) + ( gravnorm.y * velnorm.y ) + ( gravnorm.z * velnorm.z ); + + // This prevents the player from trying to swim upstream + if ( dot < 0.0f ) + { + float tempdot; + Vector tempvec; + + tempdot = 0.2f - dot; + tempvec = velocity * tempdot; + velocity = velocity - tempvec; + } + } + + if ( grav != vec_zero ) + { + new_velocity = velocity + grav; + + if ( new_velocity.length() < velocity.length() ) + { + // Is slowing down, defintely need to apply grav + velocity = new_velocity; + } + else if ( velocity.length() < max_speed ) + { + // Applay grav + + velocity = new_velocity; + + // Make sure we aren't making the player go too fast + + if ( velocity.length() > max_speed ) + { + velocity.normalize(); + velocity *= max_speed; + } + } + else + { + // Going too fast but still want to pull the player up if any z velocity in grav + + grav.x = 0; + grav.y = 0; + + velocity = velocity + grav; + } + } +} + +// +// PMove Events +// +void Player::ProcessPmoveEvents( int event ) +{ + float damage; + + switch( event ) + { + case EV_NONE: + break; + case EV_FALL_VERY_SHORT: + case EV_FALL_SHORT: + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + case EV_FALL_VERY_FAR: + case EV_FALL_FATAL: + damage = 0.0f; + if ( event == EV_FALL_VERY_SHORT ) + damage = 5.0f; + else if ( event == EV_FALL_SHORT ) + damage = 10.0f; + else if ( event == EV_FALL_MEDIUM ) + damage = 25.0f; + else if ( event == EV_FALL_FAR ) + damage = 50.0f; + else if ( event == EV_FALL_VERY_FAR ) + damage = 100.0f; + else if ( event == EV_FALL_FATAL ) + damage = 1000.0f; + + if ( !multiplayerManager.checkFlag( MP_FLAG_NO_FALLING ) ) + { + Damage( world, world, damage, origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_FALLING ); + } + break; + case EV_TERMINAL_VELOCITY: + Sound( "snd_fall", CHAN_VOICE ); + break; + + case EV_WATER_TOUCH: // foot touches + if ( watertype & CONTENTS_LAVA ) + { + Sound( "snd_burn", CHAN_LOCAL ); + } + else + { + Entity *water; + trace_t trace; + Vector start; + float scale; + + Sound( "impact_playersplash", CHAN_AUTO ); + + // Find the correct place to put the splash + + start = origin + Vector( 0.0f, 0.0f, 90.0f ); + trace = G_Trace( start, vec_zero, vec_zero, origin, NULL, MASK_WATER, false, "ProcessPmoveEvents" ); + + // Figure out a good scale for the splash + + scale = 1.0f + ( velocity[2] + 400.0f ) / -1500.0f; + + if ( scale < 1.0f ) + scale = 1.0f; + else if ( scale > 1.5f ) + scale = 1.5f; + + // Spawn in a water splash + + water = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + water->setOrigin( trace.endpos ); + water->setModel( "fx_splashsmall.tik" ); + water->setScale( scale ); + water->animate->RandomAnimate( "idle" ); + water->PostEvent( EV_Remove, 5.0f ); + + } + break; + case EV_WATER_LEAVE: // foot leaves + Sound( "impact_playerleavewater", CHAN_AUTO ); + break; + case EV_WATER_UNDER: // head touches + Sound( "impact_playersubmerge", CHAN_AUTO ); + break; + case EV_WATER_CLEAR: // head leaves + Sound( "snd_gasp", CHAN_LOCAL ); + break; + } +} + +/* +============= +WorldEffects +============= +*/ +void Player::WorldEffects( void ) +{ + if ( deadflag == DEAD_DEAD ) + { + // if we are dead, no world effects + return; + } + + if ( movetype == MOVETYPE_NOCLIP ) + { + // don't need air + air_finished = level.time + 20.0f; + return; + } + + // + // check for on fire + // + if ( on_fire ) + { + if ( next_painsound_time < level.time ) + { + next_painsound_time = level.time + 4.0f; + Sound( "snd_onfire", CHAN_LOCAL ); + } + } + + // + // check for lava + // + if ( watertype & CONTENTS_LAVA ) + { + if ( next_drown_time < level.time ) + { + next_drown_time = level.time + 0.2f; + Damage( world, world, 10.0f * waterlevel, origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_LAVA ); + } + if ( next_painsound_time < level.time ) + { + next_painsound_time = level.time + 3.0f; + Sound( "snd_burned", CHAN_LOCAL ); + } + } + + // + // check for slime + // + if ( watertype & CONTENTS_SLIME ) + { + if ( next_drown_time < level.time ) + { + next_drown_time = level.time + 0.4f; + Damage( world, world, 7.0f * waterlevel, origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_SLIME ); + } + if ( next_painsound_time < level.time ) + { + next_painsound_time = level.time + 5.0f; + Sound( "snd_burned", CHAN_LOCAL ); + } + } + + // + // check for drowning + // + if ( waterlevel == 3 ) + { + // if out of air, start drowning + if ( ( air_finished < level.time ) && !( flags & FL_GODMODE ) ) + { + // drown! + if ( ( next_drown_time < level.time ) && ( health > 0 ) ) + { + next_drown_time = level.time + 1.0f; + + // take more damage the longer underwater + drown_damage += 2.0f; + if ( drown_damage > 15.0f ) + { + drown_damage = 15.0f; + } + + // play a gurp sound instead of a normal pain sound + if ( health <= drown_damage ) + { + Sound( "snd_drown", CHAN_LOCAL ); + BroadcastSound(); + } + else if ( rand() & 1 ) + { + Sound( "snd_choke", CHAN_LOCAL ); + BroadcastSound(); + } + else + { + Sound( "snd_choke", CHAN_LOCAL ); + BroadcastSound(); + } + + Damage( world, world, drown_damage, origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_DROWN ); + } + } + } + else + { + air_finished = level.time + 20.0f; + drown_damage = 2.0f; + } + + GravityNodes(); + + old_waterlevel = waterlevel; +} + +/* +============= +AddBlend +============= +*/ +void Player::AddBlend( float r, float g, float b, float a ) +{ + float a2; + float a3; + + if ( a <= 0.0f ) + { + return; + } + + // new total alpha + a2 = blend[ 3 ] + ( 1.0f - blend[ 3 ] ) * a; + + // fraction of color from old + a3 = blend[ 3 ] / a2; + + blend[ 0 ] = ( blend[ 0 ] * a3 ) + ( r * ( 1.0f - a3 ) ); + blend[ 1 ] = ( blend[ 1 ] * a3 ) + ( g * ( 1.0f - a3 ) ); + blend[ 2 ] = ( blend[ 2 ] * a3 ) + ( b * ( 1.0f - a3 ) ); + blend[ 3 ] = a2; +} + +/* +============= +SetBlend +============= +*/ +void Player::SetBlend( float r, float g, float b, float a, int additive ) +{ + client->ps.blend[ 0 ] = r; + client->ps.blend[ 1 ] = g; + client->ps.blend[ 2 ] = b; + client->ps.blend[ 3 ] = a; + + client->ps.stats[ STAT_ADDFADE ] = additive; +} + +void Player::SetBlend( int additive ) +{ + SetBlend( blend[ 0 ], blend[ 1 ], blend[ 2 ], blend[ 3], additive ); +} + +/* +============= +CalcBlend +============= +*/ +void Player::CalcBlend( void ) +{ + int contents; + Vector vieworg; + Vector viewModeColor; + float viewModeAlpha; + qboolean viewModeAdditive; + + client->ps.stats[STAT_ADDFADE] = 0; + blend[ 0 ] = blend[ 1 ] = blend[ 2 ] = blend[ 3 ] = 0; + SetBlend( false ); + + // Set to view mode blend if necessary + + if ( gi.GetViewModeScreenBlend( getViewMode(), viewModeColor, &viewModeAlpha, &viewModeAdditive ) ) + { + AddBlend( viewModeColor[ 0 ], viewModeColor[ 1 ], viewModeColor[ 2 ], viewModeAlpha ); + SetBlend( viewModeAdditive ); + + return; + } + + // add for contents + vieworg = origin; + vieworg[ 2 ] += viewheight; + + contents = gi.pointcontents( vieworg, 0 ); + + if ( flags & FL_STUNNED ) + { + AddBlend( 0.0f, 0.0f, 0.5f, 0.1f ); + SetBlend( true ); + } + else if ( contents & CONTENTS_SOLID ) + { + // Outside of world + //AddBlend( 0.8, 0.5, 0.0, 0.2 ); + } + else if ( contents & CONTENTS_LAVA ) + { + AddBlend( level.lava_color[0], level.lava_color[1], level.lava_color[2], level.lava_alpha ); + SetBlend( true ); + } + else if ( contents & CONTENTS_WATER ) + { + AddBlend( level.water_color[0], level.water_color[1], level.water_color[2], level.water_alpha ); + SetBlend( true ); + } + else if ( contents & CONTENTS_SLIME ) + { + AddBlend( level.slime_color[0], level.slime_color[1], level.slime_color[2], level.slime_alpha ); + SetBlend( true ); + } + + // add for damage + if ( _doDamageScreenFlash && ( damage_alpha > 0 ) ) + { + AddBlend( damage_blend[ 0 ], damage_blend[ 1 ], damage_blend[ 2 ], damage_alpha ); + SetBlend( false ); + + // drop the damage value + /* damage_alpha -= 0.06; + + if ( damage_alpha < 0 ) + { + damage_alpha = 0; + } */ + } + + // Add screen flash + + if ( level.time < _flashMaxTime ) + { + float currentFlashAlpha; + + currentFlashAlpha = _flashAlpha; + + if ( level.time > _flashMinTime ) + { + currentFlashAlpha = _flashAlpha * ( _flashMaxTime - level.time ) / ( _flashMaxTime - _flashMinTime ); + } + + AddBlend( _flashBlend[ 0 ], _flashBlend[ 1 ], _flashBlend[ 2 ], currentFlashAlpha ); + SetBlend( false ); + } + + + // Do the cinematic fading + float alpha=1; + + level.m_fade_time -= level.frametime; + + // Return if we are completely faded in + if ( ( level.m_fade_time <= 0.0f ) && ( level.m_fade_type == fadein ) ) + { + //client->ps.blend[3] = 0 + damage_alpha; + return; + } + + // If we are faded out, and another fade out is coming in, then don't bother + /* if ( ( level.m_fade_time_start > 0 ) && ( level.m_fade_type == fadeout ) ) + { + if ( client->ps.blend[3] >= 1 ) + return; +} */ + + if ( level.m_fade_time_start > 0.0f ) + alpha = level.m_fade_time / level.m_fade_time_start; + + if ( level.m_fade_type == fadeout ) + alpha = 1.0f - alpha; + + if ( alpha < 0.0f ) + alpha = 0.0f; + + if ( alpha > 1.0f ) + alpha = 1.0f; + + if ( level.m_fade_style == additive ) + { + if ( client->ps.stats[STAT_ADDFADE] == 1 ) + { + AddBlend( level.m_fade_color[0] * level.m_fade_alpha * alpha, level.m_fade_color[1] * level.m_fade_alpha * alpha, + level.m_fade_color[2] * level.m_fade_alpha * alpha, level.m_fade_alpha * alpha ); + SetBlend( true ); + } + else + { + SetBlend( level.m_fade_color[0] * level.m_fade_alpha * alpha, level.m_fade_color[1] * level.m_fade_alpha * alpha, + level.m_fade_color[2] * level.m_fade_alpha * alpha, level.m_fade_alpha * alpha, true ); + } + } + else + { + SetBlend( level.m_fade_color[0], level.m_fade_color[1], level.m_fade_color[2], level.m_fade_alpha * alpha, false ); + } +} + +void Player::setFlash( const Vector &color, float alpha, float minTime, float maxTime ) +{ + _flashBlend = color; + _flashAlpha = alpha; + _flashMinTime = level.time + minTime; + _flashMaxTime = level.time + maxTime; +} + +/* +=============== +P_DamageFeedback + +Handles color blends and view kicks +=============== +*/ + +#define DAMAGE_MAX_PITCH_SCALE 0.3f +#define DAMAGE_MAX_YAW_SCALE 0.3f + +void Player::DamageFeedback( void ) +{ + if ( _lastDamagedTimeFront + 0.25f >= level.time ) + client->ps.pm_flags |= PMF_DAMAGE_FRONT; + else + client->ps.pm_flags &= ~PMF_DAMAGE_FRONT; + + if ( _lastDamagedTimeBack + 0.25f >= level.time ) + client->ps.pm_flags |= PMF_DAMAGE_BACK; + else + client->ps.pm_flags &= ~PMF_DAMAGE_BACK; + + if ( _lastDamagedTimeLeft + 0.25f >= level.time ) + client->ps.pm_flags |= PMF_DAMAGE_LEFT; + else + client->ps.pm_flags &= ~PMF_DAMAGE_LEFT; + + if ( _lastDamagedTimeRight + 0.25f >= level.time ) + client->ps.pm_flags |= PMF_DAMAGE_RIGHT; + else + client->ps.pm_flags &= ~PMF_DAMAGE_RIGHT; + + // if we are dead, don't setup any feedback + + if ( health <= 0.0f ) + { + damage_count = 0; + damage_blood = 0; + damage_alpha = 0; + VectorClear( damage_angles ); + return; + } + + if ( damage_blood > damage_count ) + { + Vector delta; + Vector damageDir; + + //damageDir = damage_from * -1.0f; + damageDir = damage_from; + + delta = damageDir.toAngles(); + + delta[ PITCH ] = AngleDelta( delta[ PITCH ], v_angle[ PITCH ] ); + delta[ YAW ] = AngleDelta( delta[ YAW ], v_angle[ YAW ] ); + delta[ ROLL ] = AngleDelta( delta[ ROLL ], v_angle[ ROLL ] ); + + // Dim it down by a factor of a hundred so we can multiple it by our built up damage later + + delta *= 0.01f; + + damage_angles = delta; + } + + if ( damage_count > 0 ) + { + // decay damage_count over time + + if ( damage_count * 0.1f < 0.25f ) + { + damage_count -= 0.25f; + } + else + { + damage_count *= 0.9f; + } + + if ( damage_count < 0.1f ) + { + damage_count = 0; + } + } + + damage_count += damage_blood; + + if ( damage_count <= 0.0f ) + { + damage_alpha = 0.0f; + return; + } + + // the total alpha of the blend is always proportional to count + + damage_alpha = damage_count / 25.0f; + + // Make sure the alpha is within a reasonable range + + if ( damage_alpha < 0.1f ) + { + damage_alpha = 0.1f; + } + else if ( damage_alpha > 0.6f ) + { + damage_alpha = 0.6f; + } + + //damage_blend = bcolor; + + // clear totals + + damage_blood = 0; +} + +void Player::GetPlayerView( Vector *pos, Vector *angle ) +{ + if ( pos ) + { + *pos = origin; + pos->z += viewheight; + } + + if ( angle ) + { + *angle = Vector( client->ps.viewangles ); + } +} + +#define EARTHQUAKE_SCREENSHAKE_PITCH 2 +#define EARTHQUAKE_SCREENSHAKE_YAW 2 +#define EARTHQUAKE_SCREENSHAKE_ROLL 3 + +void Player::SetClientViewAngles( const Vector &position, const float cameraoffset, const Vector &ang, const Vector &vel, const float camerafov ) const +{ + if ( client->ps.useCameraFocalPoint ) + { + client->ps.cameraFocalPoint[ 0 ] = position[ 0 ]; + client->ps.cameraFocalPoint[ 1 ] = position[ 1 ]; + client->ps.cameraFocalPoint[ 2 ] = position[ 2 ] + cameraoffset + 18.0f; + } + + client->ps.origin[ 0 ] = position[ 0 ]; + client->ps.origin[ 1 ] = position[ 1 ]; + client->ps.origin[ 2 ] = position[ 2 ]; + + client->ps.viewheight = (int) cameraoffset; + + client->ps.viewangles[ 0 ] = ang[ 0 ]; + client->ps.viewangles[ 1 ] = ang[ 1 ]; + client->ps.viewangles[ 2 ] = ang[ 2 ]; + + client->ps.velocity[ 0 ] = vel[ 0 ]; + client->ps.velocity[ 1 ] = vel[ 1 ]; + client->ps.velocity[ 2 ] = vel[ 2 ]; + + client->ps.fov = camerafov; +} + +void Player::ShakeCamera( void ) +{ + // Shake the player's view + + VectorClear( client->ps.damage_angles ); + + // Don't shake camera if in cinematic and not allowed to + + if ( ( playerCameraMode == PLAYER_CAMERA_MODE_CINEMATIC ) && !world->canShakeCamera() ) + return; + + // Add earthquake shaking + + float earthquakeMagnitude = level.getEarthquakeMagnitudeAtPosition( origin ); + + if ( earthquakeMagnitude > 0.0f ) + { + client->ps.damage_angles[ PITCH ] += G_CRandom() * earthquakeMagnitude * EARTHQUAKE_SCREENSHAKE_PITCH; + client->ps.damage_angles[ YAW ] += G_CRandom() * earthquakeMagnitude * EARTHQUAKE_SCREENSHAKE_YAW; + client->ps.damage_angles[ ROLL ] += G_CRandom() * earthquakeMagnitude * EARTHQUAKE_SCREENSHAKE_ROLL; + } + + // Don't do anything else to the camera if in a cinematic + + if ( playerCameraMode == PLAYER_CAMERA_MODE_CINEMATIC ) + return; + + VectorAdd( client->ps.damage_angles, getWeaponViewShake(), client->ps.damage_angles ); + + // Add damage shaking + + if ( damage_count && sv_showdamageshake->integer ) + { + if ( health <= 0.0f ) + { + damage_angles = vec_zero; + } + else if ( multiplayerManager.inMultiplayer() ) + { + damage_angles[ YAW ] = G_CRandom( 0.05f ); + damage_angles[ PITCH ] = G_CRandom( 0.05f ); + damage_angles[ ROLL ] = G_CRandom( 0.05f ); + } + else + { + damage_angles[ YAW ] = G_CRandom( 0.2f ); + damage_angles[ PITCH ] = G_CRandom( 0.2f ); + damage_angles[ ROLL ] = G_CRandom( 0.2f ); + } + + client->ps.damage_angles[ YAW ] += damage_angles[ YAW ] * damage_count; + client->ps.damage_angles[ PITCH ] += damage_angles[ PITCH ] * damage_count; + client->ps.damage_angles[ ROLL ] += damage_angles[ ROLL ] * damage_count; + } + + if ( client->pers.mp_lowBandwidth ) + { + client->ps.damage_angles[ YAW ] = 0.0f; + client->ps.damage_angles[ PITCH ] = 0.0f; + client->ps.damage_angles[ ROLL ] = 0.0f; + } +} + + +void Player::SetPlayerViewUsingNoClipController( void ) +{ + client->ps.pm_flags &= ~PMF_CAMERA_VIEW; + + // Force camera to behind in NOCLIP + client->ps.camera_flags = client->ps.camera_flags & CF_CAMERA_CUT_BIT; +} + +void Player::SetPlayerViewUsingCinematicController( Camera *camera ) +{ + client->ps.camera_angles[ 0 ] = camera->angles[ 0 ]; + client->ps.camera_angles[ 1 ] = camera->angles[ 1 ]; + client->ps.camera_angles[ 2 ] = camera->angles[ 2 ]; + + client->ps.camera_origin[ 0 ] = camera->origin[ 0 ]; + client->ps.camera_origin[ 1 ] = camera->origin[ 1 ]; + client->ps.camera_origin[ 2 ] = camera->origin[ 2 ]; + client->ps.pm_flags |= PMF_CAMERA_VIEW; + + // + // clear out the flags, but preserve the CF_CAMERA_CUT_BIT + // + client->ps.camera_flags = client->ps.camera_flags & CF_CAMERA_CUT_BIT; + + ShakeCamera(); +} + + +void Player::SetPlayerViewUsingActorController( Camera *camera ) +{ + if ( actor_camera ) + { + // Find the focal point ( either the actor's watch offset or top of the bounding box) + + // Go a little above the view height + + actor_camera->origin = origin; + actor_camera->origin[2] += client->ps.pm_defaultviewheight + 10.f; + + // Shift the camera back just a little + + Vector forward; + Vector left; + angles.AngleVectors( &forward, &left ); + actor_camera->origin -= forward * 15.0f; + + // Shift the camera a little to the left or right + actor_camera->origin -= left * 15.0f; + + // Set the camera's position + + actor_camera->setOrigin( actor_camera->origin ); + + // Set the camera's angles + + actor_camera->setAngles( angles ); + + // Set this as our camera + + SetCamera( actor_camera, 0.5f ); + + } + else if ( ( level.automatic_cameras.NumObjects() > 0 ) && ( !camera || camera->IsAutomatic() ) ) + { + // if we currently are not in a camera or the camera we are looking through is automatic, evaluate our camera choices + AutomaticallySelectedNewCamera(); + } + + client->ps.camera_angles[ 0 ] = camera->angles[ 0 ]; + client->ps.camera_angles[ 1 ] = camera->angles[ 1 ]; + client->ps.camera_angles[ 2 ] = camera->angles[ 2 ]; + + client->ps.camera_origin[ 0 ] = camera->origin[ 0 ]; + client->ps.camera_origin[ 1 ] = camera->origin[ 1 ]; + client->ps.camera_origin[ 2 ] = camera->origin[ 2 ]; + client->ps.pm_flags |= PMF_CAMERA_VIEW; + + // + // clear out the flags, but preserve the CF_CAMERA_CUT_BIT + // + client->ps.camera_flags = client->ps.camera_flags & CF_CAMERA_CUT_BIT; +} + +void Player::SetPlayerViewUsingEntityWatchingController( void ) +{ + + SetPlayerViewNormal(); + + // Find the focal point ( either the actor's watch offset or top of the bounding box) + + Vector focal_point; + if ( entity_to_watch->watch_offset != vec_zero ) + { + MatrixTransformVector( entity_to_watch->watch_offset, entity_to_watch->orientation, focal_point ); + focal_point += entity_to_watch->origin; + focal_point[2] += client->ps.pm_defaultviewheight + 18.0f; + } + else + { + focal_point = entity_to_watch->origin; + focal_point[2] += client->ps.pm_defaultviewheight + 18.0f; + } + + if ( client->ps.useCameraFocalPoint ) + { + client->ps.cameraFocalPoint[ 0 ] = focal_point[ 0 ]; + client->ps.cameraFocalPoint[ 1 ] = focal_point[ 1 ]; + client->ps.cameraFocalPoint[ 2 ] = focal_point[ 2 ]; + } + +} + +void Player::SetPlayerViewNormal( void ) +{ + client->ps.pm_flags &= ~PMF_CAMERA_VIEW; + + // + // make sure the third person camera is setup correctly. + // + + int camera_type = currentState_Torso->getCameraType(); + if ( last_camera_type != camera_type ) + { + // + // clear out the flags, but preserve the CF_CAMERA_CUT_BIT + // + client->ps.camera_flags = client->ps.camera_flags & CF_CAMERA_CUT_BIT; + bool do_cut = false; + switch( camera_type ) + { + case CAMERA_TOPDOWN: + client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH; + client->ps.camera_offset[ PITCH ] = -75; + client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET; + break; + case CAMERA_FRONT: + client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH; + client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET; + client->ps.camera_offset[ YAW ] = 180; + client->ps.camera_offset[ PITCH ] = 0; + do_cut = true; + break; + case CAMERA_SIDE: + client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH; + client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET; + // randomly invert the YAW + if ( G_Random( 1.0f ) > 0.5f ) + { + client->ps.camera_offset[ YAW ] = -90; + } + else + { + client->ps.camera_offset[ YAW ] = 90; + } + client->ps.camera_offset[ PITCH ] = 0; + break; + case CAMERA_SIDE_LEFT: + client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH; + client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET; + client->ps.camera_offset[ YAW ] = 90; + client->ps.camera_offset[ PITCH ] = 0; + break; + case CAMERA_SIDE_RIGHT: + client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH; + client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET; + client->ps.camera_offset[ YAW ] = -90; + client->ps.camera_offset[ PITCH ] = 0; + break; + case CAMERA_BEHIND_FIXED: + client->ps.camera_offset[ YAW ] = 0; + client->ps.camera_offset[ PITCH ] = 0; + client->ps.camera_flags |= CF_CAMERA_ANGLES_ALLOWOFFSET; + break; + case CAMERA_BEHIND_NOPITCH: + client->ps.camera_flags |= CF_CAMERA_ANGLES_IGNORE_PITCH; + client->ps.camera_offset[ YAW ] = 0; + client->ps.camera_offset[ PITCH ] = 0; + break; + case CAMERA_BEHIND: + client->ps.camera_offset[ YAW ] = 0; + client->ps.camera_offset[ PITCH ] = 0; + break; + default: + client->ps.camera_offset[ YAW ] = 0; + client->ps.camera_offset[ PITCH ] = 0; + break; + } + last_camera_type = camera_type; + if ( do_cut ) + CameraCut(); + } + + // Add weapon shaking + ShakeCamera(); + +} + + + +void Player::SetupView( void ) +{ + // Check for change of state + // These switch statements are indicative that we need a polymorphic set of classes based off a base class called PlayerCameraController + switch( playerCameraMode ) + { + case PLAYER_CAMERA_MODE_NORMAL: + case PLAYER_CAMERA_MODE_CINEMATIC: + break; + + case PLAYER_CAMERA_MODE_ACTOR: + { + if ( !entity_to_watch || entity_to_watch->deadflag ) + { + DestroyActorCamera(); + } + else + { + trace_t trace = G_Trace( actor_camera->origin, Vector(-5, -5, -5), Vector(5, 5, 5), actor_camera->origin, actor_camera, MASK_DEADSOLID, false, "SetupView" ); + if ( trace.startsolid ) + { + DestroyActorCamera(); + } + } + } + break; + + case PLAYER_CAMERA_MODE_ENTITY_WATCHING: + // See if we still want to watch this actor + { + if ( entity_to_watch ) + { + Vector cameraPosition; + Vector cameraAngles; + GetPlayerView( &cameraPosition, &cameraAngles ); + Vector directionToWatchedEntity( entity_to_watch->origin - cameraPosition ); + Vector cameraForward; + cameraAngles.AngleVectors( &cameraForward ); + const float angleToWatchedEntity = RAD2DEG( Vector::AngleBetween( directionToWatchedEntity, cameraForward ) ); + if ( angleToWatchedEntity > maximumAngleToWatchedEntity ) + { + if ( watchEntityForEntireDuration ) + { + client->ps.useCameraFocalPoint = false; + } + else + { + StopWatchingEntity(); + } + } + else + { + client->ps.useCameraFocalPoint = true; + } + + } + } + break; + + case PLAYER_CAMERA_MODE_NO_CLIP: + break; + + default: + assert( false ); // default case is not valid + } + + + + // Select the camera mode for the player (btw I hate that the player knows anything about cameras) -BillS + if ( actor_camera) + { + playerCameraMode = PLAYER_CAMERA_MODE_ACTOR; + if ( !camera ) + { + AutomaticallySelectedNewCamera(); + } + } + else if ( entity_to_watch ) + { + playerCameraMode = PLAYER_CAMERA_MODE_ENTITY_WATCHING; + } + else if ( camera ) + { + playerCameraMode = PLAYER_CAMERA_MODE_CINEMATIC; + } + else if ( getMoveType() == MOVETYPE_NOCLIP ) + { + playerCameraMode = PLAYER_CAMERA_MODE_NO_CLIP; + } + else + { + playerCameraMode = PLAYER_CAMERA_MODE_NORMAL; + } + + + // Update the camera position using the appropriate controller + switch( playerCameraMode ) + { + case PLAYER_CAMERA_MODE_NORMAL: + { + SetClientViewAngles( origin, client->ps.viewheight, v_angle, velocity, fov ); + SetPlayerViewNormal(); + } + break; + + case PLAYER_CAMERA_MODE_ACTOR: + { + // Create the camera if we don't have one yet + if ( !actor_camera ) + { + actor_camera = new Camera(); + } + if ( !camera ) + { + SetCamera( actor_camera, 0.5f ); + } + SetClientViewAngles( origin, viewheight, v_angle, velocity, camera->Fov() ); + SetPlayerViewUsingActorController( camera ); + } + break; + + case PLAYER_CAMERA_MODE_ENTITY_WATCHING: + { + SetClientViewAngles( origin, client->ps.viewheight, v_angle, velocity, fov); + SetPlayerViewUsingEntityWatchingController(); + } + break; + + case PLAYER_CAMERA_MODE_NO_CLIP: + { + SetClientViewAngles( origin, client->ps.viewheight, v_angle, velocity, fov ); + SetPlayerViewUsingNoClipController(); + } + break; + + case PLAYER_CAMERA_MODE_CINEMATIC: + { + SetClientViewAngles( origin, viewheight, v_angle, velocity, camera->Fov() ); + SetPlayerViewUsingCinematicController( camera ); + } + break; + + default: + assert( false ); // default case is not valid + } +} + + +void Player::AutomaticallySelectedNewCamera( void ) +{ + float bestScore = 999; + Camera *bestCamera = NULL; + + for( int i = 1; i <= level.automatic_cameras.NumObjects(); i++ ) + { + Camera *cam = level.automatic_cameras.ObjectAt( i ); + float score = cam->CalculateScore( this, currentState_Torso->getName() ); + + // if this is our current camera, scale down the score a bit to favor it. + if ( cam == camera ) + { + score *= 0.9f; + } + + if ( score < bestScore ) + { + bestScore = score; + bestCamera = cam; + } + } + if ( bestScore <= 1.0f ) + { + // we have a camera to switch to + if ( bestCamera != camera ) + { + if ( camera ) + { + camera->AutomaticStop( this ); + } + SetCamera( bestCamera, bestCamera->AutomaticStart( this ) ); + } + } + else + { + // we don't have a camera to switch to + if ( camera ) + { + SetCamera( NULL, camera->AutomaticStop( this ) ); + } + } +} + +//---------------------------------------------------------------- +// Name: getWeaponViewShake +// Class: Player +// +// Description: Gets the view shake from the current weapon(s) +// +// Parameters: none +// +// Returns: Vector - angles to shake the view by +//---------------------------------------------------------------- + +Vector Player::getWeaponViewShake( void ) +{ + Vector viewShake; + Weapon *weapon; + + // Try dual hand + + weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( weapon ) + { + viewShake = weapon->getViewShake(); + } + else + { + // Try left hand + + weapon = GetActiveWeapon( WEAPON_LEFT ); + + if ( weapon ) + { + viewShake = weapon->getViewShake(); + } + + // Try right hand + + weapon = GetActiveWeapon( WEAPON_RIGHT ); + + if ( weapon ) + { + viewShake += weapon->getViewShake(); + } + } + + return viewShake; +} + +void Player::StopWatchingEntity( void ) +{ + entity_to_watch = NULL; + maximumAngleToWatchedEntity = 0.0f; + client->ps.useCameraFocalPoint = false; + client->ps.pm_flags &= ~PMF_CAMERA_VIEW; +} + +void Player::DestroyActorCamera( void ) +{ + if ( actor_camera ) + { + delete actor_camera; + actor_camera = NULL; + SetCamera( NULL, .5f ); + } +} +/* +================== +SwingAngles +================== +*/ +void Player::SwingAngles( float destination, float swingTolerance, float clampTolerance, float speed, float *angle, + qboolean *swinging ) +{ + float swing; + float move; + float scale; + + if ( !*swinging ) + { + // see if a swing should be started + swing = AngleSubtract( *angle, destination ); + if ( ( swing > swingTolerance ) || ( swing < -swingTolerance ) ) + { + *swinging = true; + // we intentionally return so that we can start the animation before turning + return; + } + } + + if ( !*swinging ) + { + return; + } + + // modify the speed depending on the delta + // so it doesn't seem so linear + swing = AngleSubtract( destination, *angle ); + scale = fabs( swing ); + +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + if ( scale < swingTolerance * 0.5 ) + { + scale = 0.5; + } + else if ( scale < swingTolerance ) + { + scale = 1.0; + } + else + { + scale = 2.0; + } +#else + +****************************************************************************/ + + scale = 1.0f; +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + + #endif + +****************************************************************************/ + + + // swing towards the destination angle + if ( swing >= 0.0f ) + { + move = level.frametime * scale * speed; + if ( move >= swing ) + { + move = swing; + *swinging = false; + } + + *angle = AngleMod( *angle + move ); + } + else if ( swing < 0.0f ) + { + move = level.frametime * scale * -speed; + if ( move <= swing ) + { + move = swing; + *swinging = false; + } + *angle = AngleMod( *angle + move ); + } + + // clamp to no more than tolerance + swing = AngleSubtract( destination, *angle ); + if ( swing > clampTolerance ) + { + *angle = AngleMod( destination - ( clampTolerance - 1.0f ) ); + } + else if ( swing < -clampTolerance ) + { + *angle = AngleMod( destination + ( clampTolerance - 1.0f ) ); + } +} + +void Player::SetHeadTarget + ( + Event *ev + ) + + { + str ht = ev->GetString( 1 ); + + head_target = G_FindTarget( 0, ht ); + } + +qboolean Player::GetTagPositionAndOrientation( int tagnum, orientation_t *new_or ) +{ + int i; + orientation_t tag_or; + vec3_t axis[3]; + + tag_or = gi.Tag_OrientationEx( edict->s.modelindex, + CurrentAnim( legs ), + CurrentFrame( legs ), + tagnum & TAG_MASK, + edict->s.scale, + edict->s.bone_tag, + edict->s.bone_quat, + 0, + 0, + 1.0f, + ( edict->s.anim & ANIM_BLEND ) != 0, + ( edict->s.torso_anim & ANIM_BLEND ) != 0, + CurrentAnim( torso ), + CurrentFrame( torso ), + 0, + 0, + 1.0f + ); + + AnglesToAxis( angles, axis ); + VectorCopy( origin, new_or->origin ); + + for ( i=0; i<3; i++ ) + VectorMA( new_or->origin, tag_or.origin[i], axis[i], new_or->origin ); + + MatrixMultiply( tag_or.axis, axis, new_or->axis ); + return true; +} + +qboolean Player::GetTagPositionAndOrientation( const str &tagname, orientation_t *new_or ) +{ + int tagnum; + + tagnum = gi.Tag_NumForName( edict->s.modelindex, tagname ); + + if ( tagnum < 0 ) + { + warning( "Player::GetTagPositionAndOrientation", "Could not find tag \"%s\"", tagname.c_str() ); + return false; + } + + return GetTagPositionAndOrientation( tagnum, new_or ); +} + +Vector Player::GetAngleToTarget( const Entity *ent, const str &tag, float yawclamp, float pitchclamp, + const Vector &baseangles ) +{ + assert( ent ); + + if ( ent ) + { + Vector delta,angs; + orientation_t tag_or; + + int tagnum = gi.Tag_NumForName( edict->s.modelindex, tag.c_str() ); + + if ( tagnum < 0 ) + return Vector( 0.0f, 0.0f, 0.0f ); + + GetTagPositionAndOrientation( tagnum, &tag_or ); + + delta = ent->centroid - tag_or.origin; + delta.normalize(); + + angs = delta.toAngles(); + + //AnglesSubtract( angs, baseangles, angs ); + angs -= baseangles; + + angs[PITCH] = AngleNormalize180( angs[PITCH] ); + angs[YAW] = AngleNormalize180( angs[YAW] ); + + if ( angs[PITCH] > pitchclamp ) + angs[PITCH] = pitchclamp; + else if ( angs[PITCH] < -pitchclamp ) + angs[PITCH] = -pitchclamp; + + if ( angs[YAW] > yawclamp ) + angs[YAW] = yawclamp; + else if ( angs[YAW] < -yawclamp ) + angs[YAW] = -yawclamp; + + return angs; + } + else + { + return Vector( 0.0f, 0.0f, 0.0f ); + } +} + +void Player::DebugWeaponTags( int controller_tag, Weapon *weapon, const str &weapon_tagname ) +{ + int i; + orientation_t bone_or, tag_weapon_or, barrel_or, final_barrel_or; + + GetTagPositionAndOrientation( edict->s.bone_tag[controller_tag], &bone_or ); + //G_DrawCoordSystem( Vector( bone_or.origin ), Vector( bone_or.axis[0] ), Vector( bone_or.axis[1] ), Vector( bone_or.axis[2] ), 20 ); + + GetTagPositionAndOrientation( gi.Tag_NumForName( edict->s.modelindex, weapon_tagname ), &tag_weapon_or ); + //G_DrawCoordSystem( Vector( tag_weapon_or.origin ), Vector( tag_weapon_or.axis[0] ), Vector( tag_weapon_or.axis[1] ), Vector( tag_weapon_or.axis[2] ), 40 ); + + weapon->GetRawTag( "tag_barrel", &barrel_or ); + VectorCopy( tag_weapon_or.origin, final_barrel_or.origin ); + + for ( i = 0 ; i < 3 ; i++ ) + VectorMA( final_barrel_or.origin, barrel_or.origin[i], tag_weapon_or.axis[i], final_barrel_or.origin ); + + MatrixMultiply( barrel_or.axis, tag_weapon_or.axis, final_barrel_or.axis ); + //G_DrawCoordSystem( Vector( final_barrel_or.origin ), Vector( final_barrel_or.axis[0] ), Vector( final_barrel_or.axis[1] ), Vector( final_barrel_or.axis[2] ), 80 ); +} + +Entity const * Player::GetTarget( void ) const +{ + return targetEnemy; +} + +bool Player::GetProjectileLaunchAngles( Vector &launchAngles, const Vector &launchPoint, const float initialSpeed, const float gravity ) const +{ + Entity const *target = GetTarget(); + if ( target && targetEnemyLocked) + { + const Vector targetPoint( target->centroid ); + + Trajectory projectileTrajectory( launchPoint, targetPoint, initialSpeed, gravity * -sv_currentGravity->value ); + if ( projectileTrajectory.GetTravelTime() > 0.0f ) + { + launchAngles.setPitch( projectileTrajectory.GetLaunchAngle() ); + + Vector direction( targetPoint - launchPoint ); + direction.z = 0.0f; + direction.normalize(); + + launchAngles.setYaw( direction.toYaw() ); + launchAngles.setRoll( 0.0f ); + return true; + } + + } + return false; +} + +void Player::AcquireTarget( void ) +{ + targetEnemyLocked = false; + + if ( GetTarget() ) + { + GetTarget()->edict->s.eFlags &= ~PMF_ENEMY_TARGETED; + } + + // Find a right hand target ( might be the same one ) + + Weapon *weapon = GetActiveWeapon( WEAPON_RIGHT ); + if ( ! (weapon && weapon->autoAimTargetSelectionAngle) ) + { + weapon = GetActiveWeapon( WEAPON_LEFT ); + } + if ( weapon && weapon->autoAimTargetSelectionAngle ) + { + Entity *newTarget = FindClosestEntityInRadius( weapon->autoAimTargetSelectionAngle, 170.0f, weapon->GetMaxRange() ); + targetEnemy = newTarget; + if ( newTarget ) + { + + if ( FindClosestEntityInRadius( weapon->autoAimLockonAngle, 170.0f, weapon->GetMaxRange() ) ) + { + targetEnemyLocked = true; + targetEnemy->edict->s.eFlags |= PMF_ENEMY_TARGETED; + } + return; + } + } + +} + +void Player::AutoAim( void ) +{ + if ( deadflag ) + return; + + // Check for targets in the FOV + AcquireTarget(); + + // Update Crosshair + if ( _targetSelectedHighlight ) + { + _targetSelectedHighlight->hideModel(); + } + + if ( _targetLockedHighlight ) + { + _targetLockedHighlight->hideModel(); + } + + EntityPtr currentHighlight = _targetSelectedHighlight; + if ( targetEnemyLocked ) + { + currentHighlight = _targetLockedHighlight; + } + + if ( currentHighlight && !level.cinematic) + { + if ( !GetTarget() ) + { + return; + } + Weapon *weapon = GetActiveWeapon( WEAPON_RIGHT ); + if ( !(weapon && weapon->crosshair) ) + { + weapon = GetActiveWeapon( WEAPON_LEFT ); + } + if ( weapon && weapon->crosshair ) + { + Vector newCrosshairLocation( GetTarget()->origin ); + currentHighlight->setOrigin( newCrosshairLocation ); + currentHighlight->showModel(); + } + + } +} + + +/* +=============== +PlayerAngles +=============== +*/ +void Player::PlayerAngles( void ) +{ + float deltayaw; + Vector moveangles; + float speed; + float yawAngle; + float speedscale; + vec3_t temp; + Vector dir; + Vector newAimAngles; + + if ( gi.Anim_Flags( edict->s.modelindex, CurrentAnim( legs ) ) & MDL_ANIM_DEFAULT_ANGLES ) + { + //SetControllerAngles( HEAD_TAG, vec_zero ); + SetControllerAngles( TORSO_TAG, vec_zero ); + setAngles( Vector( 0.0f, v_angle.y, 0.0f ) ); + return; + } + + // set the head and torso directly + headAngles.setXYZ( v_angle.x, AngleMod( v_angle.y ), v_angle.z ); + torsoAngles.setXYZ( v_angle.x, AngleMod( v_angle.y ), v_angle.z ); + + dir = Vector( velocity.x, velocity.y, 0.0f ); + speed = VectorNormalize( dir ); + + // If moving, angle the legs toward the direction we are moving + if ( ( speed > 32.0f ) && groundentity && ( last_ucmd.forwardmove || last_ucmd.rightmove ) ) + { + speedscale = 3; + yawing = true; // always center + deltayaw = AngleSubtract( dir.toYaw(), headAngles[ YAW ] ); + } + else + { + speedscale = 1; + deltayaw = 0; + } + + // --------- yaw ------------- + // Clamp to g_legclampangle + if ( fabs( deltayaw ) > 90.0f ) + deltayaw = AngleSubtract( deltayaw, 180.0f ); + + if ( deltayaw > g_legclampangle->value ) + deltayaw = g_legclampangle->value; + else if ( deltayaw < -g_legclampangle->value ) + deltayaw = -g_legclampangle->value; + + yawAngle = headAngles[ YAW ] + deltayaw; + + yawing_left = false; + yawing_right = false; + + if ( client->ps.walking && client->ps.groundPlane && ( movecontrol == MOVECONTROL_LEGS ) && + !last_ucmd.forwardmove && !last_ucmd.rightmove && ( client->ps.groundTrace.plane.normal[ 2 ] < SLOPE_22_MAX ) ) + { + float groundyaw; + float yawdelta; + + groundyaw = ( int )vectoyaw( client->ps.groundTrace.plane.normal ); + yawdelta = anglemod( v_angle.y - groundyaw ); + yawdelta = (float)( ( ( ( int )yawdelta + 45 ) / 90 ) * 90.0f ); + angles.y = groundyaw + yawdelta; + } + else + { + // if purely strafing, don't swing the legs + if ( dont_turn_legs ) + { + setAngles( Vector( 0, v_angle.y, 0 ) ); + } + else + { + SwingAngles( yawAngle, g_legtolerance->value, g_legclamptolerance->value, g_legswingspeed->value * speedscale, &angles[ YAW ], &yawing ); + /*if ( yawing ) + { + float swing; + swing = AngleSubtract( yawAngle, angles[ YAW ] ); + if ( swing > 0.0f ) + { + yawing_left = true; + } + else + { + yawing_right = true; + } + }*/ + + if(last_ucmd.deltaAngles[ YAW ] > 0.0f) + yawing_left = true; + if(last_ucmd.deltaAngles[ YAW] < 0.0f) + yawing_right = true; + } + } + + if ( ( deadflag == DEAD_DYING ) || ( deadflag == DEAD_DEAD ) ) + headAngles[PITCH] = 0.0f; + + // --------- pitch ------------- + headAngles[PITCH] *= 0.65f; // Nerf head angles alittle... head pitch 90 degrees is not reasonable. + + // only show a fraction of the pitch angle in the torso + if ( headAngles[ PITCH ] > 180.0f ) + { + torsoAngles[PITCH] = ( -360.0f + headAngles[ PITCH ] ) * bendTorsoMult; + } + else + { + torsoAngles[PITCH] = headAngles[ PITCH ] * bendTorsoMult; + } + + if ( _headWatchAllowed ) + AcquireHeadTarget(); + else + head_target = NULL; + + // Adjust head and torso angles for the target if needed + if ( head_target ) + { + //newAimAngles = GetAngleToTarget( head_target, "Bip01 Head", 60, 45, torsoAngles ); + newAimAngles = GetAngleToTarget( head_target, "Bip01 Head", 40.0f, 30.0f, torsoAngles ); + + headAimAngles[PITCH] = LerpAngle( headAimAngles[PITCH], newAimAngles[PITCH], 0.25f ); + headAimAngles[YAW] = LerpAngle( headAimAngles[YAW], newAimAngles[YAW], 0.25f ); + + torsoAimAngles[PITCH] = LerpAngle( torsoAimAngles[PITCH], 0.0f, 0.5f ); + torsoAimAngles[YAW] = LerpAngle( torsoAimAngles[YAW], 0.0f, 0.5f ); + } + else // Otherwise return to them to 0 + { + headAimAngles[PITCH] = LerpAngle( headAimAngles[PITCH], 0.0f, 0.5f ); + headAimAngles[YAW] = LerpAngle( headAimAngles[YAW], 0.0f, 0.1f ); + + torsoAimAngles[PITCH] = LerpAngle( torsoAimAngles[PITCH], 0.0f, 0.5f ); + torsoAimAngles[YAW] = LerpAngle( torsoAimAngles[YAW], 0.0f, 0.5f ); + } + + //if ( !multiplayerManager.inMultiplayer() ) + { + // pull the head angles back out of the hierarchial chain + AnglesSubtract( headAngles, torsoAngles, temp ); + + // Add in the aim angles for the head + VectorAdd( temp, headAimAngles, temp ); + + // Update the head controller + SetControllerAngles( HEAD_TAG, temp ); + + // pull the torso angles back out of the hierarchial chain + AnglesSubtract( torsoAngles, angles, temp ); + + // Add in the aim angles for the torso + VectorAdd( temp, torsoAimAngles, temp ); + + // Update the torso controller + SetControllerAngles( TORSO_TAG, temp ); + } + + // Set the rest (legs) + setAngles( angles ); +} + +void Player::FinishMove( void ) +{ + // + // If the origin or velocity have changed since ClientThink(), + // update the pmove values. This will happen when the client + // is pushed by a bmodel or kicked by an explosion. + // + // If it wasn't updated here, the view position would lag a frame + // behind the body position when pushed -- "sinking into plats" + // + if ( !( client->ps.pm_flags & PMF_FROZEN ) && !( client->ps.pm_flags & PMF_NO_MOVE )) + { + origin.copyTo( client->ps.origin ); + velocity.copyTo( client->ps.velocity ); + } + + if ( !( client->ps.pm_flags & PMF_FROZEN ) ) + { + PlayerAngles(); + } + + // burn from lava, etc + WorldEffects(); + + // determine the view offsets + DamageFeedback(); + CalcBlend(); +} + +void Player::UpdateStats( void ) +{ + int i,count; + float healthToDisplay; + float armorToDisplay; + + // Deathmatch stats for arena mode + if ( multiplayerManager.inMultiplayer() ) + { + /* + // Arena name configstring index + if ( current_arena ) + { + client->ps.stats[STAT_ARENA] = CS_ARENA_INFO + current_arena->getID(); + } + else + { + // CS_ARENA_INFO index holds the "No Arena" string + client->ps.stats[STAT_ARENA] = CS_ARENA_INFO; + } + + if ( current_team ) + { + client->ps.stats[STAT_TEAM] = current_team->getIndex(); + } + else + { + // CS_TEAM_INFO index holds the "No Team" string + client->ps.stats[STAT_TEAM] = CS_TEAM_INFO; + } + */ + + //client->ps.stats[STAT_KILLS] = multiplayerManager.getPoints( this ); + //client->ps.stats[STAT_DEATHS] = multiplayerManager.getDeaths( this ); + //client->ps.stats[STAT_ARENA] = multiplayerManager.getTeamPoints( this ); + + client->ps.stats[STAT_RED_TEAM_SCORE] = multiplayerManager.getTeamPoints( "Red" ); + client->ps.stats[STAT_BLUE_TEAM_SCORE] = multiplayerManager.getTeamPoints( "Blue" ); + + client->ps.stats[STAT_SCORE] = multiplayerManager.getPoints( this ); + client->ps.stats[STAT_KILLS] = multiplayerManager.getKills( this ); + client->ps.stats[STAT_DEATHS] = multiplayerManager.getDeaths( this ); + + client->ps.stats[STAT_MP_GENERIC1] = multiplayerManager.getStat( this, STAT_MP_GENERIC1 ); + client->ps.stats[STAT_MP_GENERIC2] = multiplayerManager.getStat( this, STAT_MP_GENERIC2 ); + client->ps.stats[STAT_MP_GENERIC3] = multiplayerManager.getStat( this, STAT_MP_GENERIC3 ); + client->ps.stats[STAT_MP_GENERIC4] = multiplayerManager.getStat( this, STAT_MP_GENERIC4 ); + client->ps.stats[STAT_MP_GENERIC5] = multiplayerManager.getStat( this, STAT_MP_GENERIC5 ); + client->ps.stats[STAT_MP_GENERIC6] = multiplayerManager.getStat( this, STAT_MP_GENERIC6 ); + client->ps.stats[STAT_MP_GENERIC7] = multiplayerManager.getStat( this, STAT_MP_GENERIC7 ); + client->ps.stats[STAT_MP_GENERIC8] = multiplayerManager.getStat( this, STAT_MP_GENERIC8 ); + + client->ps.stats[STAT_MP_SPECTATING_ENTNUM] = multiplayerManager.getStat( this, STAT_MP_SPECTATING_ENTNUM ); + + client->ps.stats[STAT_MP_MODE_ICON] = multiplayerManager.getIcon( this, STAT_MP_MODE_ICON ); + client->ps.stats[STAT_MP_TEAM_ICON] = multiplayerManager.getIcon( this, STAT_MP_TEAM_ICON ); + client->ps.stats[STAT_MP_TEAMHUD_ICON] = multiplayerManager.getIcon( this, STAT_MP_TEAMHUD_ICON ); + client->ps.stats[STAT_MP_SPECIALTY_ICON] = multiplayerManager.getIcon( this, STAT_MP_SPECIALTY_ICON ); + + client->ps.stats[STAT_MP_OTHERTEAM_ICON] = multiplayerManager.getIcon( this, STAT_MP_OTHERTEAM_ICON ); + + if ( multiplayerManager.inMultiplayer() ) + edict->s.infoIcon = multiplayerManager.getInfoIcon( this, last_ucmd.buttons ); + else + edict->s.infoIcon = 0; + + client->ps.stats[STAT_MP_AWARD_ICON] = multiplayerManager.getIcon( this, STAT_MP_AWARD_ICON ); + client->ps.stats[STAT_MP_AWARD_COUNT] = multiplayerManager.getStat( this, STAT_MP_AWARD_COUNT ); + + client->ps.stats[STAT_MP_STATE] = multiplayerManager.getStat( this, STAT_MP_STATE ); + + if ( _holdableItem ) + client->ps.stats[STAT_MP_HOLDABLEITEM_ICON] = _holdableItem->getIcon(); + else + client->ps.stats[STAT_MP_HOLDABLEITEM_ICON] = -1; + + if ( _rune ) + client->ps.stats[STAT_MP_RUNE_ICON] = _rune->getIcon(); + else + client->ps.stats[STAT_MP_RUNE_ICON] = -1; + + if ( _powerup ) + client->ps.stats[STAT_MP_POWERUP_ICON] = _powerup->getIcon(); + else + client->ps.stats[STAT_MP_POWERUP_ICON] = -1; + + /* client->ps.stats[STAT_WON_MATCHES] = num_won_matches; + client->ps.stats[STAT_LOST_MATCHES] = num_lost_matches; */ + + // Get queue position + //client->ps.stats[STAT_QUEUE_PLACE] = 0; + /* + if ( current_arena ) + { + client->ps.stats[STAT_QUEUE_PLACE] = current_arena->GetLinePosition( this ); + } + */ +// multiplayerManager.getStat( this, STAT_TIMELEFT_MINUTES ); + client->ps.stats[STAT_TIMELEFT_SECONDS] = multiplayerManager.getStat( this, STAT_TIMELEFT_SECONDS ); + } + + // + // Health + // + + // Use my health/armor as the default to display + + healthToDisplay = health; + armorToDisplay = GetArmorValue(); + + if ( multiplayerManager.inMultiplayer() && multiplayerManager.isPlayerSpectator( this, SPECTATOR_TYPE_FOLLOW ) ) + { + Player *playerSpectating; + + playerSpectating = multiplayerManager.getPlayerSpectating( this ); + + if ( playerSpectating ) + { + // Display the health/armor of the player we are spectating instead of ours + + healthToDisplay = playerSpectating->getHealth(); + armorToDisplay = playerSpectating->GetArmorValue(); + } + } + + // Round health a little so that it doesn't mislead the player when it gets changed to an int + + if ( ( healthToDisplay < 1.0f ) && ( healthToDisplay > 0.0f ) ) + { + client->ps.stats[ STAT_HEALTH ] = 1; + } + else + { + client->ps.stats[ STAT_HEALTH ] = (int)healthToDisplay; + } + + + if ( health <= 0.0f ) + { + Vector damageAngles = damage_from * -1; + damageAngles = damageAngles.toAngles(); + client->ps.stats[ STAT_DEAD_YAW ] = (int) damageAngles[ YAW ]; + } + else + { + client->ps.stats[ STAT_DEAD_YAW ] = 0; + } + + //ArmorValue Stat Update + client->ps.stats[ STAT_ARMOR_LEVEL ] = (int) armorToDisplay; + + Weapon *leftweapon = GetActiveWeapon( WEAPON_LEFT ); + Weapon *rightweapon = GetActiveWeapon( WEAPON_RIGHT ); + Weapon *dualweapon = GetActiveWeapon( WEAPON_DUAL ); + + client->ps.stats[STAT_AMMO_LEFT] = 0; + client->ps.stats[STAT_AMMO_RIGHT] = 0; + client->ps.stats[STAT_CLIPAMMO_LEFT] = 0; + client->ps.stats[STAT_CLIPAMMO_RIGHT] = 0; + client->ps.stats[STAT_NUM_SHOTS_LEFT] = 0; + client->ps.stats[STAT_MAX_NUM_SHOTS_LEFT] = 0; + client->ps.stats[STAT_NUM_SHOTS_RIGHT] = 0; + client->ps.stats[STAT_MAXAMMO_LEFT] = 0; + client->ps.stats[STAT_MAXAMMO_RIGHT] = 0; + client->ps.stats[STAT_MAXCLIPAMMO_LEFT] = 0; + client->ps.stats[STAT_MAXCLIPAMMO_RIGHT] = 0; + + client->ps.stats[STAT_AMMO_TYPE1] = 0; + client->ps.stats[STAT_AMMO_TYPE2] = 0; + client->ps.stats[STAT_AMMO_TYPE3] = 0; + client->ps.stats[STAT_AMMO_TYPE4] = 0; + + if ( _powerup ) + client->ps.stats[ STAT_POWERUPTIME ] = static_cast( _powerup->getTimeLeft() ); + else + client->ps.stats[ STAT_POWERUPTIME ] = 0; + + client->ps.stats[ STAT_ACCUMULATED_PAIN] = (int) accumulated_pain; + + client->ps.activeItems[ ITEM_NAME_AMMO_LEFT ] = -1; + client->ps.activeItems[ ITEM_NAME_AMMO_RIGHT ] = -1; + client->ps.activeItems[ ITEM_NAME_WEAPON_LEFT ] = -1; + client->ps.activeItems[ ITEM_NAME_WEAPON_RIGHT ] = -1; + client->ps.activeItems[ ITEM_NAME_WEAPON_DUAL ] = -1; +#ifndef DEDICATED + // + // mission objectives + // + client->ps.stats[STAT_NUM_OBJECTIVES] = gi.MObjective_GetNumActiveObjectives(); + client->ps.stats[STAT_COMPLETE_OBJECTIVES] = gi.MObjective_GetNumCompleteObjectives(); + client->ps.stats[STAT_FAILED_OBJECTIVES] = gi.MObjective_GetNumFailedObjectives(); + client->ps.stats[STAT_INCOMPLETE_OBJECTIVES] = gi.MObjective_GetNumIncompleteObjectives(); +#endif + + // misc stats + if ( client->ps.stats[ STAT_SHOTS_FIRED ] > 0 ) + client->ps.stats[STAT_ACCURACY] = (int)((float)( (float)client->ps.stats[ STAT_SHOTS_HIT ] / (float)client->ps.stats[ STAT_SHOTS_FIRED ] ) * 100.0f); + else + client->ps.stats[STAT_ACCURACY] = 100; + + client->ps.stats[STAT_MISSION_DURATION] = (int) level.timeInLevel; + + client->ps.stats[ STAT_WEAPON_GENERIC1 ] = 0; + client->ps.stats[ STAT_WEAPON_GENERIC2 ] = 0; + + if ( dualweapon ) + { + // Left is PRIMARY + if ( dualweapon->GetClipSize( FIRE_MODE1 ) > 0 ) + { + //set the max ammo to contain the number of clips and the max number of clips. + client->ps.stats[STAT_AMMO_LEFT] = AmmoCount( dualweapon->GetAmmoType( FIRE_MODE1 ) ) / dualweapon->GetClipSize( FIRE_MODE1 ); + client->ps.stats[STAT_MAXAMMO_LEFT] = MaxAmmoCount( dualweapon->GetAmmoType( FIRE_MODE1 ) ) / dualweapon->GetClipSize( FIRE_MODE1 ); + + client->ps.stats[STAT_CLIPAMMO_LEFT] = dualweapon->ClipAmmo( FIRE_MODE1 ); + client->ps.stats[STAT_MAXCLIPAMMO_LEFT] = dualweapon->GetClipSize( FIRE_MODE1 ); + + if( dualweapon->ammorequired[FIRE_MODE1] != 0) + { + client->ps.stats[STAT_NUM_SHOTS_LEFT] = dualweapon->ClipAmmo( FIRE_MODE1 ) / dualweapon->ammorequired[FIRE_MODE1]; + client->ps.stats[STAT_MAX_NUM_SHOTS_LEFT] = dualweapon->GetClipSize( FIRE_MODE1 ) / dualweapon->ammorequired[FIRE_MODE1]; + } + } + else + { + client->ps.stats[STAT_AMMO_LEFT] = AmmoCount( dualweapon->GetAmmoType( FIRE_MODE1 ) ); + client->ps.stats[STAT_MAXAMMO_LEFT] = MaxAmmoCount( dualweapon->GetAmmoType( FIRE_MODE1 ) ); + + client->ps.stats[STAT_CLIPAMMO_LEFT] = AmmoCount( dualweapon->GetAmmoType( FIRE_MODE1 ) ); + client->ps.stats[STAT_MAXCLIPAMMO_LEFT] = MaxAmmoCount( dualweapon->GetAmmoType( FIRE_MODE1 ) ); + + if( dualweapon->ammorequired[FIRE_MODE1] != 0) + { + client->ps.stats[STAT_NUM_SHOTS_LEFT] = AmmoCount( dualweapon->GetAmmoType( FIRE_MODE1 ) ) / dualweapon->ammorequired[FIRE_MODE1]; + client->ps.stats[STAT_MAX_NUM_SHOTS_LEFT] = MaxAmmoCount( dualweapon->GetAmmoType( FIRE_MODE1 ) ) / dualweapon->ammorequired[FIRE_MODE1]; + } + + + } + + + + client->ps.activeItems[ITEM_NAME_AMMO_LEFT] = AmmoIndex( dualweapon->GetAmmoType( FIRE_MODE1 ) ); + + // Right is AlTERNATE + client->ps.stats[STAT_AMMO_RIGHT] = AmmoCount( dualweapon->GetAmmoType( FIRE_MODE2 ) ); + client->ps.stats[STAT_MAXAMMO_RIGHT] = MaxAmmoCount( dualweapon->GetAmmoType( FIRE_MODE2 ) ); + + if ( dualweapon->GetClipSize( FIRE_MODE2 ) > 0 ) + { + client->ps.stats[STAT_CLIPAMMO_RIGHT] = dualweapon->ClipAmmo( FIRE_MODE2 ); + client->ps.stats[STAT_MAXCLIPAMMO_RIGHT] = dualweapon->GetClipSize( FIRE_MODE2 ); + + if(dualweapon->ammorequired[FIRE_MODE2] != 0) + client->ps.stats[STAT_NUM_SHOTS_RIGHT] = dualweapon->ClipAmmo( FIRE_MODE2 ) / dualweapon->ammorequired[FIRE_MODE2]; + } + else + { + client->ps.stats[STAT_CLIPAMMO_RIGHT] = AmmoCount( dualweapon->GetAmmoType( FIRE_MODE2 ) ); + client->ps.stats[STAT_MAXCLIPAMMO_RIGHT] = MaxAmmoCount( dualweapon->GetAmmoType( FIRE_MODE2 ) ); + + if(dualweapon->ammorequired[FIRE_MODE2] != 0) + client->ps.stats[STAT_NUM_SHOTS_RIGHT] = AmmoCount( dualweapon->GetAmmoType( FIRE_MODE2 ) ) / dualweapon->ammorequired[FIRE_MODE2]; + + } + + + + + client->ps.activeItems[ITEM_NAME_AMMO_RIGHT] = AmmoIndex( dualweapon->GetAmmoType( FIRE_MODE2 ) ); + + client->ps.activeItems[ITEM_NAME_WEAPON_DUAL] = dualweapon->getIndex(); + + //Send the # for all ammo groups + //This should be fixed since its a poor implementation. + + for( int i = 1; i <= ammo_inventory.NumObjects(); i++ ) + { + if(ammo_inventory.ObjectAt(i)->getName() == "Plasma") + { + client->ps.stats[STAT_AMMO_TYPE1] = ammo_inventory.ObjectAt(i)->getAmount(); + } + else if(ammo_inventory.ObjectAt(i)->getName() == "Fed") + { + client->ps.stats[STAT_AMMO_TYPE2] = ammo_inventory.ObjectAt(i)->getAmount(); + } + else if(ammo_inventory.ObjectAt(i)->getName() == "Idryll") + { + client->ps.stats[STAT_AMMO_TYPE3] = ammo_inventory.ObjectAt(i)->getAmount(); + } + else if( ammo_inventory.ObjectAt(i)->getName() == "Phaser" ) + { + client->ps.stats[STAT_AMMO_TYPE4] = ammo_inventory.ObjectAt(i)->getAmount(); + } + } + + client->ps.stats[ STAT_WEAPON_GENERIC1 ] = dualweapon->getStat( STAT_WEAPON_GENERIC1 ); + client->ps.stats[ STAT_WEAPON_GENERIC2 ] = dualweapon->getStat( STAT_WEAPON_GENERIC2 ); + } + else + { + if ( leftweapon ) + { + client->ps.stats[STAT_AMMO_LEFT] = AmmoCount( leftweapon->GetAmmoType( FIRE_MODE1 ) ); + client->ps.stats[STAT_MAXAMMO_LEFT] = MaxAmmoCount( leftweapon->GetAmmoType( FIRE_MODE1 ) ); + client->ps.stats[STAT_CLIPAMMO_LEFT] = leftweapon->ClipAmmo( FIRE_MODE1 ); + client->ps.stats[STAT_MAXCLIPAMMO_LEFT] = leftweapon->GetClipSize( FIRE_MODE1 ); + client->ps.activeItems[ITEM_NAME_AMMO_LEFT] = AmmoIndex( leftweapon->GetAmmoType( FIRE_MODE1 ) ); + client->ps.activeItems[ITEM_NAME_WEAPON_LEFT] = leftweapon->getIndex(); + + } + + if ( rightweapon ) + { + client->ps.stats[STAT_AMMO_RIGHT] = AmmoCount( rightweapon->GetAmmoType( FIRE_MODE1 ) ); + client->ps.stats[STAT_MAXAMMO_RIGHT] = MaxAmmoCount( rightweapon->GetAmmoType( FIRE_MODE1 ) ); + client->ps.stats[STAT_CLIPAMMO_RIGHT] = rightweapon->ClipAmmo( FIRE_MODE1 ); + client->ps.stats[STAT_MAXCLIPAMMO_RIGHT] = rightweapon->GetClipSize( FIRE_MODE1 ); + client->ps.activeItems[ITEM_NAME_AMMO_RIGHT] = AmmoIndex( rightweapon->GetAmmoType( FIRE_MODE1 ) ); + client->ps.activeItems[ITEM_NAME_WEAPON_RIGHT] = rightweapon->getIndex(); + + } + } + + + // + // set boss health and name + // + + if ( bosshealth->value > 0.0f ) + { + client->ps.stats[ STAT_BOSSHEALTH ] = (int)(bosshealth->value * 100.0f); + + if ( ( ( bosshealth->value * 100.0f ) > 0 ) && ( client->ps.stats[ STAT_BOSSHEALTH ] == 0 ) ) + { + client->ps.stats[ STAT_BOSSHEALTH ] = 1; + } + + client->ps.stats[ STAT_BOSSNAME_CONFIGINDEX ] = G_FindConfigstringIndex( bossname->string, CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ) + CS_GENERAL_STRINGS; + //client->ps.stats[ STAT_BOSSNAME_CONFIGINDEX ] = gi.soundindex( bossname->string ) + CS_SOUNDS; + } + else + { + client->ps.stats[ STAT_BOSSHEALTH ] = 0; + } + + if ( _itemText.length() > 0 ) + { + client->ps.stats[ STAT_ITEMICON ] = _itemIcon; + + client->ps.stats[ STAT_ITEMTEXT ] = G_FindConfigstringIndex( _itemText.c_str(), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ) + CS_GENERAL_STRINGS; + //client->ps.stats[ STAT_BOSSNAME_CONFIGINDEX ] = gi.soundindex( bossname->string ) + CS_SOUNDS; + } + else + { + client->ps.stats[ STAT_ITEMTEXT ] = -1; + } + + if ( _voteText.length() > 0 ) + { + client->ps.stats[ STAT_VOTETEXT ] = G_FindConfigstringIndex( _voteText.c_str(), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ) + CS_GENERAL_STRINGS; + } + else + { + client->ps.stats[ STAT_VOTETEXT ] = -1; + } + + if ( specialMoveEndTime > 0.0f ) + { + float beginTime = specialMoveEndTime - specialMoveChargeTime; + float timeElapsed = specialMoveCharge - beginTime; + float percentElapsed = timeElapsed / specialMoveChargeTime; + float finalValue = percentElapsed * 100.0f; + if ( finalValue > 100.0f ) + finalValue = 100.0f; + client->ps.stats[ STAT_SPECIALMOVETIMER ] = (int) finalValue; + } + else + client->ps.stats[ STAT_SPECIALMOVETIMER ] = 0; + + client->ps.stats[ STAT_POINTS ] = points; + + client->ps.stats[ STAT_SECRETS_TOTAL ] = level.total_secrets; + client->ps.stats[ STAT_SECRETS_FOUND ] = level.found_secrets; + client->ps.stats[ STAT_ITEMS_TOTAL ] = level.total_specialItems; + client->ps.stats[ STAT_ITEMS_FOUND ] = level.found_specialItems; + + + // Set cinematic stuff + + client->ps.stats[ STAT_CINEMATIC ] = 0; + + if ( level.cinematic ) + client->ps.stats[ STAT_CINEMATIC ] = (1<<0); + + if ( actor_camera ) + client->ps.stats[ STAT_CINEMATIC ] += (1<<1); + + // Go through all the player's weapons and send over the indexes of the names + memset( client->ps.inventory_name_index, 0, sizeof( client->ps.inventory_name_index ) ); + memset( client->ps.ammo_in_clip, 0, sizeof(client->ps.ammo_in_clip)); + + count = inventory.NumObjects(); + + if ( count > MAX_INVENTORY ) + { + count = MAX_INVENTORY; + warning( "Player::UpdateStats", "Max inventory exceeded\n" ); + } + + Weapon* weapon; + Ammo* ammo; + int ammoRequired; + for ( i=1; i<=count; i++ ) + { + int entnum = inventory.ObjectAt( i ); + Item *item = ( Item * )G_GetEntity( entnum ); + + if ( item ) + { + client->ps.inventory_name_index[i-1] = item->getIndex(); + if( item->isSubclassOf(Weapon) ) + { + weapon = (Weapon*) item; + ammo = FindAmmoByName(weapon->GetAmmoType(FIRE_MODE1)); + if( ammo == 0) + continue; + client->ps.inventory_weapon_ammo_index[ i - 1 ] = ammo->getIndex(); + + ammoRequired = weapon->GetRequiredAmmo(FIRE_MODE1); + client->ps.inventory_weapon_required_ammo[i - 1] = ammoRequired; + + client->ps.ammo_in_clip[ i - 1 ] = weapon->getAmmoInClip(FIRE_MODE1); + } + } + } + + // Go through all the player's ammo and send over the names/amounts + memset( client->ps.ammo_amount, 0, sizeof( client->ps.ammo_amount ) ); + count = ammo_inventory.NumObjects(); + + assert( count < MAX_AMMO ); + if ( count > MAX_AMMO ) + { + gi.Error( ERR_DROP, "Player::UpdateStats : Exceeded MAX_AMMO\n" ); + } + + for ( i=1; i<=count; i++ ) + { + Ammo *ammo = ammo_inventory.ObjectAt( i ); + + if ( ammo ) + { + client->ps.ammo_amount[i-1] = ammo->getAmount(); + client->ps.max_ammo_amount[i-1] = ammo->getMaxAmount(); + client->ps.ammo_name_index[i-1] = ammo->getIndex(); + } + } + + // Do letterbox + + // Check for letterbox fully out + if ( ( level.m_letterbox_time <= 0.0f ) && ( level.m_letterbox_dir == letterbox_in ) ) + { + client->ps.stats[STAT_LETTERBOX] = (int)(level.m_letterbox_fraction * MAX_LETTERBOX_SIZE); + return; + } + else if ( ( level.m_letterbox_time <= 0.0f ) && ( level.m_letterbox_dir == letterbox_out ) ) + { + client->ps.stats[STAT_LETTERBOX] = 0; + return; + } + + float frac; + + level.m_letterbox_time -= level.frametime; + + frac = level.m_letterbox_time / level.m_letterbox_time_start; + + if ( frac > 1.0f ) + frac = 1.0f; + if ( frac < 0.0f ) + frac = 0.0f; + + if ( level.m_letterbox_dir == letterbox_in ) + frac = 1.0f - frac; + + client->ps.stats[STAT_LETTERBOX] = (int)((float)( frac * level.m_letterbox_fraction ) * MAX_LETTERBOX_SIZE); + + // Stats for Mission Status +} + +//----------------------------------------------------- +// +// Name: UpdateObjectiveStatus +// Class: Player +// +// Description: Updates the player state of the mission objectives. +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void Player::UpdateObjectiveStatus( void ) +{ + client->ps.objectiveStates = _objectiveStates; + client->ps.informationStates = _informationStates; + client->ps.objectiveNameIndex = _objectiveNameIndex; +} + +void Player::UpdateMusic( void ) +{ + + if ( !g_allowActionMusic->integer || !_allowActionMusic ) + { + action_level = 0.0f; + } + + if ( music_forced ) + { + client->ps.current_music_mood = music_current_mood; + client->ps.fallback_music_mood = music_fallback_mood; + } + else if ( action_level > 30.0f ) + { + client->ps.current_music_mood = mood_action; + client->ps.fallback_music_mood = mood_normal; + } + else if ( action_level < 15.0f ) + { + client->ps.current_music_mood = music_current_mood; + client->ps.fallback_music_mood = music_fallback_mood; + } + + if ( action_level > 0.0f ) + { + if ( multiplayerManager.inMultiplayer() ) + { + action_level -= level.fixedframetime * 2.0f; + } + else + { + action_level -= level.fixedframetime * 15.0f; + } + + if ( action_level > 80.0f ) + { + action_level = 80.0f; + } + } + else + { + action_level = 0.0f; + } + + // + // set the music + // naturally decay the action level + // + if ( s_debugmusic->integer ) + { + gi.DPrintf( "%s's action_level = %4.2f\n", client->pers.netname, action_level ); + } + + // Copy music volume and fade time to player state + client->ps.music_volume = music_current_volume; + client->ps.music_volume_fade_time = music_volume_fade_time; + client->ps.allowMusicDucking = _allowMusicDucking; +} + +void Player::SetReverb( int type, float level ) +{ + reverb_type = type; + reverb_level = level; +} + +void Player::SetReverb( const str &type, float level ) +{ + reverb_type = EAXMode_NameToNum( type ); + reverb_level = level; +} + +void Player::SetReverb( Event *ev ) +{ + if ( ev->NumArgs() < 2 ) + return; + + SetReverb( ev->GetInteger( 1 ), ev->GetFloat( 2 ) ); +} + +void Player::UpdateReverb( void ) +{ + client->ps.reverb_type = reverb_type; + client->ps.reverb_level = reverb_level; +} + +void Player::EndAnim_Legs( Event *ev ) +{ + if ( vehicle ) + return; + + animdone_Legs = true; + animate->SetAnimDoneEvent( EV_Player_AnimLoop_Legs, legs ); + EvaluateState(); +} + +void Player::EndAnim_Torso( Event *ev ) +{ + if ( vehicle ) + return; + + animdone_Torso = true; + animate->SetAnimDoneEvent( EV_Player_AnimLoop_Torso, torso ); + EvaluateState(); +} + +void Player::SetAnim( const char *anim, bodypart_t part, bool force ) +{ + assert( anim ); + + if ( !force && ( part != all ) && ( partAnim[ part ] == anim ) ) + { + return; + } + + if ( !force && ( part == all ) && ( partAnim[ legs ] == anim ) && ( partAnim[ torso ] == anim ) ) + { + return; + } + + if ( getMoveType() == MOVETYPE_NOCLIP ) + { + anim = "idle"; + } + + if ( part != all ) + { + partAnim[ part ] = anim; + } + else + { + partAnim[ legs ] = anim; + partAnim[ torso ] = anim; + } + + switch( part ) + { + default : + case all : + animate->RandomAnimate( anim, EV_Player_AnimLoop_Legs, part ); + break; + + case legs : + animate->RandomAnimate( anim, EV_Player_AnimLoop_Legs, part ); + break; + + case torso : + animate->RandomAnimate( anim, EV_Player_AnimLoop_Torso, part ); + break; + } + + if ( ( part == legs ) || ( part == all ) ) + { + Vector animmove; + float time; + float len; + + time = gi.Anim_Time( edict->s.modelindex, CurrentAnim() ); + gi.Anim_Delta( edict->s.modelindex, CurrentAnim(), animmove ); + len = animmove.length(); + if ( ( len == 0.0f ) || ( time == 0.0f ) ) + { + animspeed = 0; + } + else + { + animspeed = len / time; + } + } +} + +void Player::CheckReloadWeapons( void ) +{ + Weapon *weap; + + weap = GetActiveWeapon( WEAPON_DUAL ); + + if ( weap ) + { + weap->CheckReload(); + } + else + { + weap = GetActiveWeapon( WEAPON_LEFT ); + if ( weap ) + { + weap->CheckReload(); + } + + weap = GetActiveWeapon( WEAPON_RIGHT ); + if ( weap ) + { + weap->CheckReload(); + } + } +} + +void Player::UpdateMisc( void ) +{ + // + // clear out the level exit flag + // + client->ps.pm_flags &= ~PMF_LEVELEXIT; + + // + // see if our camera is the level exit camera + // + if ( camera && camera->IsLevelExit() ) + { + client->ps.pm_flags |= PMF_LEVELEXIT; + } + else if ( level.near_exit ) + { + client->ps.pm_flags |= PMF_LEVELEXIT; + } + // + // do anything special for level exits + // + if ( client->ps.pm_flags & PMF_LEVELEXIT ) + { + // + // change music + // + if ( music_current_mood != mood_success ) + { + ChangeMusic( "success", "normal", false ); + } + } + + if(_updateGameFrames == true) + _totalGameFrames++; + + //Update the dialog information. + client->ps.dialogEntnum = _dialogEntnum; + client->ps.dialogSoundIndex = _dialogSoundIndex; + client->ps.dialogTextSoundIndex = _dialogTextSoundIndex; +} + +/* +================= +EndFrame + +Called for each player at the end of the server frame +and right after spawning +================= +*/ +void Player::EndFrame( Event *ev ) +{ + AutoAim(); + + if ( meleeAttackFlags & MELEE_ATTACK_LEFT ) + AdvancedMeleeAttack(WEAPON_LEFT); + + if ( meleeAttackFlags & MELEE_ATTACK_RIGHT ) + AdvancedMeleeAttack(WEAPON_RIGHT); + + if ( finishableList.NumObjects() > 0 ) + HandleFinishableList(); + else + { + //G_EnableWidgetOfPlayer( edict, "ActionIcon_Kick", false ); // Temporary + _doingFinishingMove = false; + _finishActor = NULL; + _finishState = ""; + } + + FinishMove(); + CheckReloadWeapons(); + UpdateStats(); + UpdateMusic(); + UpdateReverb(); + UpdateMisc(); + SetupView(); + UpdateObjectiveStatus(); + + if ( _powerup ) + _powerup->update( level.frametime ); + + if ( _rune ) + _rune->update( level.frametime ); + + if ( _holdableItem ) + _holdableItem->update( level.frametime ); + + if ( multiplayerManager.inMultiplayer() && ( _lastPainShaderMod != MOD_NONE ) && ( _nextPainShaderTime < level.time ) ) + { + str painShader = getPainShader( _lastPainShaderMod, false ); + + clearCustomShader( "ArmorDeflection" ); + + if ( painShader.length() > 0 ) + { + clearCustomShader( painShader ); + + _lastPainShaderMod = MOD_NONE; + } + } + + if ( needToSendAllHudsToClient() ) + { + sendAllHudsToClient(); + } + + if( _needToSendBranchDialog == true && _started ) + { + _needToSendBranchDialog = false; + if(_branchDialogActor) + _branchDialogActor->setBranchDialog(); + } + + _cameraCutThisFrame = false; +} + + +void Player::GibEvent( Event *ev ) +{ + qboolean hidemodel; + + hidemodel = !ev->GetInteger( 1 ); + + if ( com_blood->integer ) + { + if ( hidemodel ) + { + gibbed = true; + takedamage = DAMAGE_NO; + setSolidType( SOLID_NOT ); + hideModel(); + } + + CreateGibs( this, health, 0.75f, 3 ); + } +} + +void Player::GotKill( Event *ev ) +{ +} + +/* void Player::SetPowerupTimer + ( + Event *ev + ) + + { + Event *event; + + poweruptimer = ev->GetInteger( 1 ); + poweruptype = (powerup_t)ev->GetInteger( 2 ); + event = new Event( EV_Player_UpdatePowerupTimer ); + PostEvent ( event, 1.0f ); + + if ( p_heuristics ) + p_heuristics->IncrementItemsPickedUp(); + } */ + +/* void Player::UpdatePowerupTimer + ( + Event *ev + ) + + { + poweruptimer -= 1; + if ( poweruptimer > 0 ) + { + Event *event = new Event( EV_Player_UpdatePowerupTimer ); + PostEvent ( event, 1.0f ); + } + else + { + // Reset any flags as necessary, powerup is done + if ( poweruptype == POWERUP_STEALTH && (flags & FL_NOTARGET) ) + flags ^= FL_NOTARGET; + if ( poweruptype == POWERUP_PROTECTION && (flags & FL_GODMODE) ) + flags ^= FL_GODMODE; + + if ( poweruptype == POWERUP_ACCURACY ) + { + Weapon *weap; + weap = GetActiveWeapon( WEAPON_DUAL ); + // Tell the weapon we lost the accuracy powerup + if ( weap ) + weap->SetAccuracyPowerup( false ); + } + + poweruptype = POWERUP_NONE; + } + } */ + +void Player::ChangeMusic( const char * current, const char * fallback, qboolean force ) +{ + int current_mood_num; + int fallback_mood_num; + + music_forced = force; + if ( str( current ) == "normal" ) + { + music_forced = false; + } + + // We no longer let any music be forced + //music_forced = false; + + // zero out action_level so that we do get a change + // + action_level = 0; + + if ( current ) + { + current_mood_num = MusicMood_NameToNum( current ); + if ( current_mood_num < 0 ) + { + gi.DPrintf( "current music mood %s not found", current ); + } + else + { + music_current_mood = current_mood_num; + } + } + + if ( fallback ) + { + fallback_mood_num = MusicMood_NameToNum( fallback ); + if ( fallback_mood_num < 0 ) + { + gi.DPrintf( "fallback music mood %s not found", fallback ); + fallback = NULL; + } + else + { + music_fallback_mood = fallback_mood_num; + } + } +} + +void Player::ChangeMusicVolume( float volume, float fade_time ) +{ + music_volume_fade_time = fade_time; + music_saved_volume = music_current_volume; + music_current_volume = volume; +} + +void Player::RestoreMusicVolume( float fade_time ) +{ + music_volume_fade_time = fade_time; + music_current_volume = music_saved_volume; + music_saved_volume = -1.0f; +} + +//---------------------------------------------------------------- +// Name: allowMusicDucking +// Class: Player +// +// Description: Specifies whether or not music ducking is allowed +// +// Parameters: bool allowMusicDucking - whether or not music ducking is allowed +// +// Returns: none +//---------------------------------------------------------------- + +void Player::allowMusicDucking( bool allowMusicDucking ) +{ + _allowMusicDucking = allowMusicDucking; +} + +//---------------------------------------------------------------- +// Name: allowActionMusic +// Class: Player +// +// Description: Specifies whether or not action music is allowed +// +// Parameters: bool allowActionMusic - whether or not action music is allowed +// +// Returns: none +//---------------------------------------------------------------- + +void Player::allowActionMusic( bool allowActionMusic ) +{ + _allowActionMusic = allowActionMusic; +} + +void Player::GiveOxygen( float time ) +{ + air_finished = level.time + time; +} + +void Player::Jump( Event *ev ) +{ + float maxheight; + + maxheight = ev->GetFloat( 1 ); + + if ( maxheight > 16.0f ) + { + // v^2 = 2ad + velocity[ 2 ] += sqrt( 2.0f * sv_currentGravity->integer * maxheight ); + + // make sure the player leaves the ground + client->ps.walking = false; + } +} + +void Player::JumpXY( Event *ev ) +{ + float forwardmove; + float sidemove; + float distance; + float time; + float speed; + + forwardmove = ev->GetFloat( 1 ); + sidemove = ev->GetFloat( 2 ); + speed = ev->GetFloat( 3 ); + + velocity = ( yaw_forward * forwardmove ) - ( yaw_left * sidemove ); + distance = velocity.length(); + velocity *= speed / distance; + time = distance / speed; + velocity[ 2 ] = sv_currentGravity->integer * time * 0.5f; + + airspeed = distance; + + // make sure the player leaves the ground + client->ps.walking = false; +} + +void Player::StartFakePlayer( void ) +{ + Actor * fake; + + // + // if we don't have a fakePlayer active, no need to check + // + if ( !fakePlayer_active ) + { + return; + } + + fakePlayer_active = false; + + fakePlayer = new Actor; + + if ( !fakePlayer ) + return; + + fake = fakePlayer; + + CloneEntity( fake, this ); + + fake->SetTargetName( "fakeplayer" ); + fake->ProcessEvent( EV_Actor_AIOff ); + + // make sure it thinks so that it can fall when necessary + fake->turnThinkOn(); + + // Make the fake player a stepsize shorter to prevent some collision issues + + fake->maxs[2] -= STEPSIZE; + fake->setSize( mins, maxs ); + + fake->takedamage = DAMAGE_NO; + + // hide the player + this->hideModel(); + this->ProcessEvent( EV_Sentient_TurnOffShadow ); + // + // immobolize the player + // + this->flags |= FL_IMMOBILE; + // make the player not solid + setSolidType( SOLID_NOT ); + + // let the scripts now we are ready + PostEvent( EV_Player_Done, 0.0f ); +} + +void Player::FakePlayer( qboolean holster ) +{ + // + // make sure we don't have one already + // + if ( fakePlayer ) + { + return; + } + + fakePlayer_active = true; + + // if we are in the holster state, wait until next frame + // if we aren't process immediately + if ( !holster ) + { + StartFakePlayer(); + } + else + { + if ( WeaponsOut() ) + { + SafeHolster( true ); + } + } +} + +void Player::RemoveFakePlayer( void ) +{ + Actor * fake; + + // + // make sure we have one + // + if ( !fakePlayer ) + { + return; + } + fake = fakePlayer; + + // + // warp the real player to the fakeplayer location + // + this->setOrigin( fake->origin ); + this->setAngles( fake->angles ); + this->SetViewAngles( fake->angles ); + // show the player + this->showModel(); + this->ProcessEvent( EV_Sentient_TurnOnShadow ); + // allow the player to move + this->flags &= ~FL_IMMOBILE; + // make the player solid + setSolidType( SOLID_BBOX ); + // remove the fake + fake->PostEvent ( EV_Remove, 0.f ); + // null out the fake player + fakePlayer = NULL; + SafeHolster( false ); +} + +void Player::SetViewAngles( Vector newViewangles ) +{ + // set the delta angle + client->ps.delta_angles[0] = ANGLE2SHORT( newViewangles.x - client->cmd_angles[0] ); + client->ps.delta_angles[1] = ANGLE2SHORT( newViewangles.y - client->cmd_angles[1] ); + client->ps.delta_angles[2] = ANGLE2SHORT( newViewangles.z - client->cmd_angles[2] ); + + v_angle = newViewangles; + + // get the pitch and roll from our leg angles + newViewangles.x = angles.x; + newViewangles.z = angles.z; + AnglesToMat( newViewangles, orientation ); + yaw_forward = orientation[ 0 ]; + yaw_left = orientation[ 1 ]; + + //MatrixTransformVector( base_righthand_pos, orientation, righthand_pos ); + //MatrixTransformVector( base_lefthand_pos, orientation, lefthand_pos ); + MatrixTransformVector( base_rightfoot_pos, orientation, rightfoot_pos ); + MatrixTransformVector( base_leftfoot_pos, orientation, leftfoot_pos ); + //righthand_pos += origin; + //lefthand_pos += origin; + rightfoot_pos += origin; + leftfoot_pos += origin; +} + +void Player::DumpState( Event *ev ) +{ + gi.DPrintf( "Legs: %s Torso: %s\n", currentState_Legs ? currentState_Legs->getName() : "NULL", currentState_Torso->getName() ); +} + +void Player::ForceTorsoState( Event *ev ) +{ + State *ts = statemap_Torso->FindState( ev->GetString( 1 ) ); + EvaluateState( ts ); +} + +void Player::TouchedUseAnim( Entity * ent ) +{ + toucheduseanim = ent; +} + +void Player::ClearTarget( Event *ev ) +{ + targetEnemy = NULL; +} + +void Player::AdjustTorso( Event *ev ) +{ + adjust_torso = ev->GetBoolean( 1 ); +} + +void Player::UseDualWield( Event *ev ) +{ + // This is triggered by the state machine. + // If there is a weapon in the dual wield list, use it, then remove it from the list. + if ( dual_wield_weaponlist.NumObjects() ) + { + WeaponSetItem *dw; + + dw = dual_wield_weaponlist.ObjectAt( 1 ); + + useWeapon( dw->name, dw->hand ); + dual_wield_weaponlist.RemoveObjectAt( 1 ); + delete dw; + } + else + { + dual_wield_active = false; // We are done wielding all the weapons + } +} + +void Player::DualWield( Event *ev ) +{ + str leftweap, rightweap; + Weapon *leftactweap, *rightactweap, *dualactweap; + + leftweap = ev->GetString( 1 ); + rightweap = ev->GetString( 2 ); + + // Set the putaway flags on any active weapons + leftactweap = GetActiveWeapon( WEAPON_LEFT ); + rightactweap = GetActiveWeapon( WEAPON_RIGHT ); + dualactweap = GetActiveWeapon( WEAPON_DUAL ); + + // Check for any dual handed weapon being out, and mark it for putaway + if ( dualactweap ) + { + dualactweap->SetPutAway( true ); + } + + // if the left and right weapons are already out, then holster them both + if ( + ( leftactweap && !leftweap.icmp( leftactweap->getName() ) ) && + ( rightactweap && !rightweap.icmp( rightactweap->getName() ) ) + ) + { + leftactweap->SetPutAway( true ); + rightactweap->SetPutAway( true ); + return; + } + + WeaponSetItem *dualweap; + + // Putaway the old weapons, and add the new ones to the dual_wield list + if ( !leftactweap ) + { + dualweap = new WeaponSetItem; + dualweap->name = leftweap; + dualweap->hand = WEAPON_LEFT; + dual_wield_weaponlist.AddObject( dualweap ); + } + else if ( leftweap.icmp( leftactweap->getName() ) ) + { + leftactweap->SetPutAway( true ); + + dualweap = new WeaponSetItem; + dualweap->name = leftweap; + dualweap->hand = WEAPON_LEFT; + dual_wield_weaponlist.AddObject( dualweap ); + } + + if ( !rightactweap ) + { + dualweap = new WeaponSetItem; + dualweap->name = rightweap; + dualweap->hand = WEAPON_RIGHT; + dual_wield_weaponlist.AddObject( dualweap ); + } + else if ( rightweap.icmp( rightactweap->getName() ) ) + { + rightactweap->SetPutAway( true ); + + dualweap = new WeaponSetItem; + dualweap->name = rightweap; + dualweap->hand = WEAPON_RIGHT; + dual_wield_weaponlist.AddObject( dualweap ); + } + + dual_wield_active = true; +} + +void Player::EvaluateTorsoAnim( Event *ev ) +{ + str torsoAnim( currentState_Torso->getTorsoAnim( *this, &torso_conditionals ) ); + if ( !animate->HasAnim(torsoAnim) ) + torsoAnim = getGameplayAnim(torsoAnim); + + if ( torsoAnim == "" ) + { + partAnim[ torso ] = ""; + animate->ClearTorsoAnim(); + } + else if ( torsoAnim != "none" ) + { + SetAnim( torsoAnim.c_str(), torso ); + } +} + +void Player::NextPainTime( Event *ev ) +{ + nextpaintime = level.time + ev->GetFloat( 1 ); + pain_type = MOD_NONE; + pain = 0; +} + +void Player::SetMouthAngle( Event *ev ) +{ + int tag_num; + float angle_percent; + Vector mouth_angles; + + + angle_percent = ev->GetFloat( 1 ); + + if ( angle_percent < 0.0f ) + angle_percent = 0.0f; + + if ( angle_percent > 1.0f ) + angle_percent = 1.0f; + + tag_num = gi.Tag_NumForName( edict->s.modelindex, "tag_mouth" ); + + if ( tag_num != -1 ) + { + SetControllerTag( MOUTH_TAG, tag_num ); + + mouth_angles = vec_zero; + mouth_angles[PITCH] = max_mouth_angle * angle_percent; + + SetControllerAngles( MOUTH_TAG, mouth_angles ); + } +} + +void Player::EnterVehicle( Event *ev ) +{ + Entity *ent; + + ent = ev->GetEntity( 1 ); + if ( ent && ent->isSubclassOf( Vehicle ) ) + { + flags |= FL_PARTIAL_IMMOBILE; + viewheight = STAND_EYE_HEIGHT; + velocity = vec_zero; + vehicle = ( Vehicle * )ent; + if ( vehicle->IsDrivable() ) + setMoveType( MOVETYPE_VEHICLE ); + else + setMoveType( MOVETYPE_NOCLIP ); + } +} + +void Player::ExitVehicle( Event *ev ) +{ + flags &= ~FL_PARTIAL_IMMOBILE; + setMoveType( MOVETYPE_WALK ); + vehicle = NULL; +} + +qboolean Player::WeaponsOut( void ) +{ + return ( GetActiveWeapon( WEAPON_LEFT ) || GetActiveWeapon( WEAPON_RIGHT ) || GetActiveWeapon( WEAPON_DUAL ) ); +} + +qboolean Player::IsDualWeaponActive( void ) +{ + if(GetActiveWeapon( WEAPON_LEFT ) || GetActiveWeapon( WEAPON_RIGHT )) + { + return qfalse; + } + + return qtrue; +} + + +void Player::Holster( qboolean putaway ) +{ +// if(client->ps.pm_flags & PMF_DISABLE_INVENTORY) +// return; + + Weapon *leftWeap, *rightWeap, *dualWeap; + + leftWeap = GetActiveWeapon( WEAPON_LEFT ); + rightWeap = GetActiveWeapon( WEAPON_RIGHT ); + dualWeap = GetActiveWeapon( WEAPON_DUAL ); + + // Holster + if ( leftWeap || rightWeap || dualWeap ) + { + if ( putaway ) + { + if ( leftWeap ) + { + leftWeap->SetPutAway( true ); + holsteredWeapons[WEAPON_LEFT] = leftWeap; + } + if ( rightWeap ) + { + rightWeap->SetPutAway( true ); + holsteredWeapons[WEAPON_RIGHT] = rightWeap; + } + if ( dualWeap ) + { + dualWeap->SetPutAway( true ); + holsteredWeapons[WEAPON_DUAL] = dualWeap; + } + + // Set a level var so the script can know if the player is going to holster + levelVars.SetVariable( "holster_active", 1 ); + } + } + else + { + if ( !putaway ) + { + // Unholster + if ( holsteredWeapons[WEAPON_DUAL] ) + { + useWeapon( holsteredWeapons[WEAPON_DUAL], WEAPON_DUAL ); + } + else if ( holsteredWeapons[WEAPON_LEFT] && holsteredWeapons[WEAPON_RIGHT] ) + { + Event *ev1; + + ev1 = new Event( EV_Player_DualWield ); + ev1->AddString( holsteredWeapons[WEAPON_LEFT]->getName() ); + ev1->AddString( holsteredWeapons[WEAPON_RIGHT]->getName() ); + ProcessEvent( ev1 ); + } + else if ( holsteredWeapons[WEAPON_RIGHT] ) + { + useWeapon( holsteredWeapons[WEAPON_RIGHT], WEAPON_RIGHT ); + } + else if ( holsteredWeapons[WEAPON_LEFT] ) + { + useWeapon( holsteredWeapons[WEAPON_LEFT], WEAPON_LEFT ); + } + + holsteredWeapons[WEAPON_LEFT] = NULL; + holsteredWeapons[WEAPON_RIGHT] = NULL; + holsteredWeapons[WEAPON_DUAL] = NULL; + // Set a level var to let the script know there is no holstering + levelVars.SetVariable( "holster_active", 0 ); + } + } +} + +void Player::SafeHolster( qboolean putaway ) +{ + if ( WeaponsOut() ) + { + if ( putaway ) + { + weapons_holstered_by_code = true; + Holster( true ); + } + } + else + { + if ( putaway ) + { + if ( !fakePlayer_active ) + { + WeaponsHolstered(); + } + } + else + { + if ( weapons_holstered_by_code ) + { + weapons_holstered_by_code = false; + Holster( false ); + } + } + } +} + +void Player::WeaponsNotHolstered( void ) +{ +} + +void Player::WeaponsHolstered( void ) +{ +} + +void Player::setTargeted( bool targeted ) +{ + if ( targeted ) + { + gi.SendServerCommand( entnum, "stufftext \"ui_addhud targetedhud\"\n"); + } + else + { + gi.SendServerCommand( entnum, "stufftext \"ui_removehud targetedhud\"\n"); + } +} + +void Player::NightvisionToggle( Event *ev ) +{ + if ( level.cinematic ) + return; + + if ( ( multiplayerManager.isPlayerSpectator( this ) ) && ( client->ps.pm_flags ^ PMF_NIGHTVISION ) ) + return; + + client->ps.pm_flags ^= PMF_NIGHTVISION; + + Event *newEvent; + newEvent = new Event( EV_Sentient_SetViewMode ); + if (client->ps.pm_flags & PMF_NIGHTVISION) + newEvent->AddString("nightvision"); + else + newEvent->AddString("normal"); + ProcessEvent( newEvent ); +} + +void Player::HolsterToggle( Event *ev ) +{ + if ( WeaponsOut() ) + { + Holster( true ); + } + else + { + Holster( false ); + } +} + +void Player::Holster( Event *ev ) +{ + + SafeHolster( ev->GetBoolean( 1 ) ); +} + +void Player::IncreaseActionLevel( float action_level_increase ) +{ + action_level += action_level_increase; +} + +void Player::WatchEntity( Event *ev ) +{ + if ( camera || ( currentState_Torso->getCameraType() != CAMERA_BEHIND ) ) + return; + + watchEntityForEntireDuration = false; + + if ( ev->NumArgs() > 1 ) + { + Event *stopWatchingEvent = new Event( EV_Player_StopWatchingEntity ); + stopWatchingEvent->AddEntity( ev->GetEntity( 1 ) ); + const float timeToWatch = ev->GetFloat( 2 ); + PostEvent( stopWatchingEvent, timeToWatch ); + + maximumAngleToWatchedEntity = ev->GetFloat( 3 ); + + if ( ev->NumArgs() > 3 ) + { + watchEntityForEntireDuration = ev->GetBoolean( 4 ); + } + + } + + entity_to_watch = ev->GetEntity( 1 ); +} + +void Player::StopWatchingEntity( Event *ev ) +{ + StopWatchingEntity(); +} + +void Player::setAngles( const Vector &ang ) +{ + Vector new_ang; + + new_ang = ang; + + // set the angles normally + Entity::setAngles( new_ang ); + + // set the orientation based off of the current view, also update our yaw_forward and yaw_left + new_ang[ YAW ] = v_angle[ YAW ]; + AnglesToMat( new_ang, orientation ); + yaw_forward = orientation[ 0 ]; + yaw_left = orientation[ 1 ]; +} + +void Player::WeaponCommand( Event *ev ) +{ + weaponhand_t hand; + Weapon *weap; + int i; + + if ( ev->NumArgs() < 2 ) + return; + + hand = WeaponHandNameToNum( ev->GetString( 1 ) ); + weap = GetActiveWeapon( hand ); + + if ( !weap ) + return; + + Event *e; + e = new Event( ev->GetToken( 2 ) ); + + for( i=3; i<=ev->NumArgs(); i++ ) + e->AddToken( ev->GetToken( i ) ); + + weap->ProcessEvent( e ); +} + +qboolean TryPush( int entnum, vec3_t move_origin, vec3_t move_end ) +{ + Actor *act; + Vector dir; + Vector dir2; + Entity *ent; + + if ( entnum == ENTITYNUM_NONE ) + return false; + + ent = G_GetEntity( entnum ); + + if ( ent->isSubclassOf( Actor ) ) + { + act = (Actor *) ent; + + dir = act->origin - move_origin; + dir.z = 0.0f; + dir.normalize(); + + dir2 = move_end; + dir2 -= move_origin; + + if ( act->flags & FL_FLY ) + { + dir *= dir2.length() / 2.0f; + + if ( act->movementSubsystem->Push( dir ) ) + return true; + } + else + { + dir *= dir2.length(); + + Event *event = new Event( EV_Actor_Push ); + event->AddVector( dir ); + act->PostEvent( event, 0.0f ); + } + } + + return false; +} + +void Player::PlayerDone( Event *ev ) +{ + // This is used to let scripts know that the player is done doing something + + // let any threads waiting on us know they can go ahead + Director.PlayerSpawned(); +} + +painDirection_t Player::Pain_string_to_int( const str &pain ) +{ + if ( !pain.icmp( pain, "Front" ) ) + return PAIN_FRONT; + else if ( !pain.icmp( pain, "Left" ) ) + return PAIN_LEFT; + else if ( !pain.icmp( pain, "Right" ) ) + return PAIN_RIGHT; + else if ( !pain.icmp( pain, "Rear" ) ) + return PAIN_REAR; + else + return PAIN_NONE; +} + +void Player::ArchivePersistantData( Archiver &arc, qboolean sublevelTransition ) +{ + int i; + str model_name; + bool anglesArchived; + + Sentient::ArchivePersistantData( arc, sublevelTransition ); + + model_name = g_playermodel->string; + + arc.ArchiveString( &model_name ); + + if ( arc.Loading() ) + { + // set the cvar + gi.cvar_set( "g_playermodel", model_name.c_str() ); + + model_name += ".tik"; + setModel( model_name.c_str() ); + } + + for( i = 0; i < MAX_ACTIVE_WEAPONS; i++ ) + { + str name; + if ( arc.Saving() ) + { + if ( holsteredWeapons[ i ] ) + { + name = holsteredWeapons[ i ]->getName(); + } + else + { + name = "none"; + } + } + arc.ArchiveString( &name ); + if ( arc.Loading() ) + { + if ( name != "none" ) + { + holsteredWeapons[ i ] = ( Weapon * )FindItem( name ); + } + } + } + + if ( arc.Saving() ) + { + if ( sublevelTransition && level._saveOrientation ) + { + anglesArchived = true; + arc.ArchiveBool( &anglesArchived ); + arc.ArchiveVector( &angles ); + arc.ArchiveVector( &v_angle ); + } + else + { + anglesArchived = false; + arc.ArchiveBool( &anglesArchived ); + } + } + else + { + arc.ArchiveBool( &anglesArchived ); + + if ( anglesArchived ) + { + arc.ArchiveVector( &angles ); + arc.ArchiveVector( &v_angle ); + + setAngles( angles ); + SetViewAngles( v_angle ); + } + } + + + // Force a re-evaluation of the player's state + LoadStateTable(); + + arc.ArchiveInteger(&_totalGameFrames); + _updateGameFrames = true; + + arc.ArchiveInteger( &_secretsFound ); + + arc.ArchiveInteger( &_skillLevel ); + + arc.ArchiveRaw( client->ps.stats, sizeof( client->ps.stats ) ); +} + +const str Player::getPainShader( meansOfDeath_t mod, bool takeArmorIntoAccount ) +{ + if ( ( takeArmorIntoAccount ) && ( GetArmorValue() >= 100 ) ) + return "ArmorDeflection"; + else + return getPainShader( MOD_NumToName( mod ) ); +} + +const str Player::getPainShader( const char *MODName ) +{ + GameplayManager *gpm; + str modName; + const char *defaultName = "default"; + + gpm = GameplayManager::getTheGameplayManager(); + + if ( !gpm ) + return str(""); + + modName = "MOD"; + modName += MODName; + + if ( !gpm->hasObject( modName ) ) + { + if ( stricmp( MODName, defaultName ) == 0 ) + return ""; + else + return getPainShader( defaultName ); + } + + return gpm->getStringValue( modName, "PainShaderName" ); + + /* switch ( mod ) + { + case MOD_NONE: + return "none"; + break; + default: + return "electriclines"; + break; + } */ +} + +void Player::SpawnDamageEffect( meansOfDeath_t mod ) +{ + /* switch ( mod ) + { + case MOD_ELECTRIC: + // lint -fallthrough + case MOD_ELECTRICWATER: + //SpawnEffect( "fx_elecstrike.tik", origin ); + //Sound( "sound/weapons/sword/electric/hitmix2.wav", 0 ); + + break; + + default: + break; + } */ + + if ( multiplayerManager.inMultiplayer() ) + { + if ( ( mod != MOD_NONE ) && ( mod != MOD_DEATH_QUAD ) && ( _nextPainShaderTime < level.time ) && !hasCustomShader() ) + { + str painShader = getPainShader( mod, true ); + + if ( painShader.length() > 0 ) + { + setCustomShader( painShader ); + + _lastPainShaderMod = mod; + _nextPainShaderTime = level.time + 0.25f; + } + } + } +} + +void Player::ActivateDualWeapons( Event *ev ) +{ + int i; + + Weapon *weapon = 0; + for ( i=dual_wield_weaponlist.NumObjects(); i>=1; i-- ) + { + WeaponSetItem *dw; + + dw = dual_wield_weaponlist.ObjectAt( i ); + + weapon = ( Weapon * )FindItem( dw->name, weapon ); + + // Check to see if player has the weapon + if ( !weapon ) + { + warning( "Player::ActivateDualWeapons", "Player does not have weapon %s", dw->name.c_str() ); + return; + } + + ChangeWeapon( weapon, dw->hand ); + dual_wield_weaponlist.RemoveObjectAt( i ); + delete dw; + } + + // Clear out the newActiveWeapon + ClearNewActiveWeapon(); + + // Clear out the holstered weapons + holsteredWeapons[WEAPON_LEFT] = NULL; + holsteredWeapons[WEAPON_RIGHT] = NULL; + holsteredWeapons[WEAPON_DUAL] = NULL; + + // let the player know that our weapons are not holstered + WeaponsNotHolstered(); +} + +void Player::VelocityModified( void ) +{ + if ( velocity.z > 32.0f ) + { + do_rise = true; + } +} + +int Player::GetKnockback( int original_knockback, qboolean blocked ) +{ + int new_knockback; + + new_knockback = original_knockback; + + // If blocked, absorb some of the knockback + + if ( blocked ) + { + if ( LargeShieldActive() ) + new_knockback -= 150; + else + new_knockback -= 50; + } + + // See if we still have enough knockback to knock the player down + + if ( ( new_knockback >= 200.0f ) && take_pain ) + { + knockdown = true; + + if ( blocked ) + { + float damage; + + damage = new_knockback / 50; + + if ( damage > 10.0f ) + damage = 10.0f; + + Damage( world, world, damage, origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_CRUSH ); + } + } + + // Make sure knockback is still at least 0 + + if ( new_knockback < 0 ) + new_knockback = 0; + + return new_knockback; +} + +void Player::ResetHaveItem( Event *ev ) +{ + str fullname; + ScriptVariable * var; + + fullname = str( "playeritem_" ) + ev->GetString( 1 ); + + var = gameVars.GetVariable( fullname.c_str() ); + + if ( var ) + var->setIntValue( 0 ); +} + +void Player::ReceivedItem( Item * item ) +{ + qboolean forced; + qboolean first; + str fullname; + str dialog; + str anim; + ScriptVariable * var; + + // + // set our global game variables + // + + if ( item->isSubclassOf( Weapon ) ) + { + setItemText( item->getIcon(), va( "$$PickedUpThe$$ $$Weapon-%s$$\n", item->getName().c_str() ) ); + //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$PickedUpThe$$ $$%s$$\n", item->getName() ); + } + else if ( item->getAmount() > 1 ) + { + if ( item->getName() == "health" ) // I know this is horrible :( + setItemText( item->getIcon(), va( "$$PickedUp$$ %d $$Item-%s$$\n", (int)item->getAmount(), item->getName().c_str() ) ); + //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$PickedUp$$ %d %s\n", (int)item->getAmount(), item->getName() ); + else + setItemText( item->getIcon(), va( "$$PickedUp$$ %d $$Item-%s$$s\n", (int)item->getAmount(), item->getName().c_str() ) ); + //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$PickedUp$$ %d %ss\n", (int)item->getAmount(), item->getName() ); + } + else + { + setItemText( item->getIcon(), va( "$$PickedUpThe$$ $$Item-%s$$\n", item->getName().c_str() ) ); + //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$PickedUpThe$$ %s\n", item->getName() ); + } + + fullname = str( "playeritem_" ) + item->getName(); + + first = true; + + var = gameVars.GetVariable( fullname.c_str() ); + if ( !var ) + { + gameVars.SetVariable( fullname.c_str(), 1 ); + } + else + { + int amount; + + amount = var->intValue() + 1; + var->setIntValue( amount ); + // + // if we just received it, let the player know + // + if ( amount > 1 ) + { + first = false; + } + } + + var = levelVars.GetVariable( fullname.c_str() ); + if ( !var ) + { + levelVars.SetVariable( fullname.c_str(), 1 ); + } + else + { + int amount; + + amount = var->intValue() + 1; + var->setIntValue( amount ); + } + + if ( item->IsItemCool( &dialog, &anim, &forced ) ) + { + if ( first || forced ) + { + cool_item = item; + cool_dialog = dialog; + cool_anim = anim; + } + } +} + +void Player::RemovedItem( Item * item ) +{ + str fullname; + ScriptVariable * var; + + // + // set our global game variables if client + // + + fullname = str( "playeritem_" ) + item->getName(); + + var = levelVars.GetVariable( fullname.c_str() ); + if ( var ) + { + int amount; + + amount = var->intValue() - 1; + if ( amount < 0 ) + amount = 0; + var->setIntValue( amount ); + } + + var = gameVars.GetVariable( fullname.c_str() ); + if ( var ) + { + int amount; + + amount = var->intValue() - 1; + if ( amount < 0 ) + amount = 0; + var->setIntValue( amount ); + } +} + +void Player::AmmoAmountChanged( Ammo * ammo, int ammo_in_clip ) +{ + str fullname; + ScriptVariable * var; + + // + // set our level variables + // + fullname = str( "playerammo_" ) + ammo->getName(); + + var = levelVars.GetVariable( fullname.c_str() ); + if ( !var ) + { + levelVars.SetVariable( fullname.c_str(), ammo->getAmount() + ammo_in_clip ); + } + else + { + var->setIntValue( ammo->getAmount() + ammo_in_clip ); + } +} + +void Player::StartCoolItem( Event *ev ) +{ + // turn off ai off during the cinematic + level.ai_on = false; + // make sure we don't take damage during this time + takedamage = DAMAGE_NO; + // freeze the player + level.playerfrozen = true; + // turn on cinematic mode + G_StartCinematic(); + + assert( ( Camera * )cool_camera == NULL ); + // start playing the success music + if ( music_current_mood != mood_success ) + { + ChangeMusic( "success", MusicMood_NumToName( music_current_mood ), false ); + } + + // create an orbit cam + cool_camera = new Camera(); + cool_camera->SetOrbitHeight( 150.0f ); + cool_camera->Orbit( this, 200.0f, this, -90.0f ); + cool_camera->Cut( NULL ); + SetCamera( cool_camera, 1.0f ); +} + +void Player::ShowCoolItem( Event *ev ) +{ + Entity *fx; + Vector org; + + org = origin; + org.z += 128.0f; + + fx = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + fx->setOrigin( org ); + fx->setModel( "fx_coolitem.tik" ); + fx->animate->RandomAnimate( "idle" ); + fx->PostEvent( EV_Remove, 1.0f ); + + if ( cool_item ) + { + cool_item->setOrigin( org ); + cool_item->PostEvent( EV_Show, 0.1f ); + // place a lens flare on the object + cool_item->edict->s.renderfx |= RF_VIEWLENSFLARE; + + if ( cool_dialog.length() ) + { + Sound( cool_dialog, CHAN_DIALOG ); + } + } +} + +void Player::HideCoolItem( Event *ev ) +{ + Entity *fx; + Vector org; + + org = origin; + org.z += 128.0f; + + fx = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + fx->setOrigin( org ); + fx->setModel( "fx_coolitem_reverse.tik" ); + fx->animate->RandomAnimate( "idle" ); + fx->PostEvent( EV_Remove, 1.0f ); + + if ( cool_item ) + { + Event * event; + + cool_item->PostEvent( EV_Hide, 1.0f ); + + event = new Event( EV_SetOrigin ); + event->AddVector( vec_zero ); + cool_item->PostEvent( event, 1.0f ); + // remove the lens flare on the object + cool_item->edict->s.renderfx &= ~RF_VIEWLENSFLARE; + } +} + +void Player::StartCoolItemAnim( void ) +{ + movecontrol = MOVECONTROL_ABSOLUTE; + + if ( cool_item && cool_anim.length() ) + { + SetAnim( cool_anim, legs ); + // clear out anim till next time + cool_anim = ""; + } +} + +void Player::StopCoolItem( Event *ev ) +{ + if ( cool_item && cool_anim.length() ) + { + State * newState; + + newState = statemap_Torso->FindState( "DO_COOL_ITEM_ANIM" ); + if ( newState ) + { + currentState_Torso = newState; + return; + } + } + + // turn ai back on + level.ai_on = true; + + // turn damage back on + takedamage = DAMAGE_AIM; + + // unfreeze the player + level.playerfrozen = false; + + // turn off cinematic mode + G_StopCinematic(); + + cool_item = NULL; + + // delete our camera + if ( cool_camera ) + { + SetCamera( NULL, 1.0f ); + delete cool_camera; + cool_camera = NULL; + } +} + +void Player::WaitForState( Event *ev ) +{ + waitForState = ev->GetString( 1 ); +} + + +void Player::SetDamageMultiplier( Event *ev ) +{ + damage_multiplier = ev->GetFloat( 1 ); +} + +void Player::SetTakePain( Event *ev ) +{ + take_pain = ev->GetBoolean( 1 ); +} + +void Player::Loaded( void ) +{ +} + +void Player::PlayerShowModel( Event *ev ) +{ + Entity::showModel(); +} + +void Player::showModel( void ) +{ + Entity::showModel(); +} + +void Player::WarpToPoint( const Entity *spawnpoint ) +{ + if ( !spawnpoint ) + return; + + NoLerpThisFrame(); + setOrigin( spawnpoint->origin + Vector( "0 0 1" ) ); + origin.copyTo( edict->s.origin2 ); + setAngles( spawnpoint->angles ); + SetViewAngles( angles ); + CameraCut(); + + oldvelocity = vec_zero; + velocity = vec_zero; +} + +void Player::Gib( void ) +{ + /* str gib_name; + int number_of_gibs; + float scale; + Entity *ent; + str real_gib_name; + + if ( !com_blood->integer ) + return; + + gib_name = "fx_rgib"; + number_of_gibs = 5; + scale = 1.3; + + // Spawn the gibs + real_gib_name = gib_name; + real_gib_name += number_of_gibs; + real_gib_name += ".tik"; + + ent = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + ent->setModel( real_gib_name.c_str() ); + ent->setScale( scale ); + ent->setOrigin( centroid ); + ent->animate->RandomAnimate( "idle" ); + ent->PostEvent( EV_Remove, 1.0f ); + + this->hideModel(); + Sound( "snd_decap", CHAN_BODY, 1.0f, 300.0f ); + gibbed = true; */ +} + +void Player::ArmorDamage( Event *ev ) +{ + float oldHealth; + + ::Damage damage(ev); + + // Protect the player from errant damage before fighting + if ( multiplayerManager.inMultiplayer() && !multiplayerManager.isFightingAllowed() ) + return; + + if ( multiplayerManager.inMultiplayer() && multiplayerManager.isPlayerSpectator( this ) ) + return; + + // Quick dirty hack to do no damage when you have the shield up. + if ( shield_active ) + { + // Code to be implemented soon. + Weapon *weapon = GetActiveWeapon(WEAPON_RIGHT); + if ( !weapon ) + return; + + Vector attack_angle; + float yaw_diff; + attack_angle = damage.attacker->angles; + yaw_diff = angles[YAW] - attack_angle[YAW] + 180.0f; + yaw_diff = AngleNormalize180( yaw_diff ); + if ( ( yaw_diff > -45.0f ) && ( yaw_diff < 45.0f ) ) + { + int tagnum = 0; + tagnum = gi.Tag_NumForName( weapon->edict->s.modelindex, "tag_swipe1" ); + if ( tagnum >= 0) + { + Vector pos, pos2, sparkpos; + weapon->GetActorMuzzlePosition(&pos, NULL, NULL, NULL, "tag_swipe1"); + weapon->GetActorMuzzlePosition(&pos2, NULL, NULL, NULL, "tag_swipe2"); + sparkpos = (pos + pos2) / 2.0f; // Spark is halfway between the two points + WeaponEffectsAndSound( weapon, "Parry", sparkpos ); + } + + if ( damage.attacker->isSubclassOf( Sentient ) ) + { + Sentient *sent = ( Sentient * )damage.attacker; + sent->SetAttackBlocked( true ); + } + + //attack_blocked = true; + return; + } + } + + if ( _powerup ) + damage.damage = _powerup->getDamageTaken( damage.attacker, damage.damage, damage.meansofdeath ); + + if ( _rune ) + damage.damage = _rune->getDamageTaken( damage.attacker, damage.damage, damage.meansofdeath ); + + if ( multiplayerManager.inMultiplayer() && damage.attacker->isSubclassOf( Player ) ) + { + damage.damage = multiplayerManager.playerDamaged( this, (Player *)damage.attacker, damage.damage, damage.meansofdeath ); + + damage.knockback = (int)multiplayerManager.getModifiedKnockback( this, (Player *)damage.attacker, damage.knockback ); + } + + if ( !multiplayerManager.inMultiplayer() && ( damage.meansofdeath != MOD_FALLING ) ) + { + GameplayManager *gpm; + float damageMultiplier; + int skillLevel; + + skillLevel = getSkill(); + + gpm = GameplayManager::getTheGameplayManager(); + + if ( gpm->hasObject( "SkillLevel-PlayerDamage" ) ) + { + if ( skillLevel == 0 ) + damageMultiplier = gpm->getFloatValue( "SkillLevel-PlayerDamage", "Easy" ); + else if ( skillLevel == 1 ) + damageMultiplier = gpm->getFloatValue( "SkillLevel-PlayerDamage", "Normal" ); + else if ( skillLevel == 2 ) + damageMultiplier = gpm->getFloatValue( "SkillLevel-PlayerDamage", "Hard" ); + else + damageMultiplier = gpm->getFloatValue( "SkillLevel-PlayerDamage", "VeryHard" ); + + + damage.damage *= damageMultiplier; + } + } + + oldHealth = health; + + Sentient::ArmorDamage(damage); + + if ( multiplayerManager.inMultiplayer() ) + { + float damageTaken; + + damageTaken = oldHealth - health; + + if ( damageTaken > 0.0f ) + { + // Increase victim's action level + + if ( damage.meansofdeath > MOD_LAST_SELF_INFLICTED ) + { + IncreaseActionLevel( damageTaken ); + } + + if ( damage.attacker->isSubclassOf( Player ) ) + { + Player *attackingPlayer = (Player *)damage.attacker; + + // Tell the multiplayer system that the player took damage + + multiplayerManager.playerTookDamage( this, attackingPlayer, damageTaken, damage.meansofdeath ); + + // Increase attacker's action level + + if ( attackingPlayer != this ) + { + attackingPlayer->IncreaseActionLevel( damageTaken ); + } + } + } + } + + // If we're dead, go ahead and gib completely on Projectiles + if ( ( multiplayerManager.inMultiplayer() ) && ( health <= 0.0f ) && damage.inflictor->isSubclassOf( Projectile ) ) + { + Gib(); + } +} + +void Player::DeadBody( Event *ev ) +{ + // Spawn a dead body at the spot + Body *body; + int surfaceNum; + + if ( ( pain_type == MOD_VAPORIZE ) || ( pain_type == MOD_VAPORIZE_COMP ) || + ( pain_type == MOD_VAPORIZE_DISRUPTOR ) || ( pain_type == MOD_VAPORIZE_PHOTON ) || ( pain_type == MOD_SNIPER )) + return; + + body = new Body; + + if ( gibbed ) + return; + + body->setModel( this->model ); + body->ProcessInitCommands( body->edict->s.modelindex ); + body->edict->s.anim = this->edict->s.anim; + body->edict->s.frame = this->edict->s.frame; + + //body->edict->s.torso_anim = this->edict->s.anim; + //body->edict->s.torso_frame = this->edict->s.frame; + + body->edict->s.torso_anim = this->edict->s.torso_anim; + body->edict->s.torso_frame = this->edict->s.torso_frame; + body->edict->s.scale = this->edict->s.scale; + + body->setOrigin( this->origin ); + body->setAngles( this->angles ); + + // Copy over all of the surface data from the player to the body + + for( surfaceNum = 0 ; surfaceNum < numsurfaces ; surfaceNum++ ) + { + body->edict->s.surfaces[ surfaceNum ] = edict->s.surfaces[ surfaceNum ]; + } +} + +void Player::ShowHeuristics( Event *ev ) +{ + + if ( p_heuristics ) + p_heuristics->ShowHeuristics( this ); +} + +void Player::FireWeapon( Event *ev ) +{ + Sentient::FireWeapon(ev); +} + +void Player::ReleaseFireWeapon( Event *ev ) +{ + Sentient::ReleaseFireWeapon(ev); +} + +void Player::SetAimType( Event *ev ) +{ + Weapon* weapon = 0; + weapon = GetActiveWeapon( WEAPON_DUAL ); + if (!weapon ) + return; + + // Forward the event to the weapon itself + weapon->SetAimType( ev ); +} + +void Player::ReloadWeapon( Event *ev ) +{ + Weapon* weapon = 0; + weapon = GetActiveWeapon( WEAPON_DUAL ); + if (!weapon ) + return; + + // Reload + weapon->ForceReload(); +} + +void Player::AnimateWeapon( Event *ev ) +{ + Weapon* weapon = 0; + weaponhand_t hand = WEAPON_DUAL; + bool animatingFlag = true; + + if ( ev->NumArgs() > 1 ) + hand = WeaponHandNameToNum(ev->GetString( 2 )); + + if ( ev->NumArgs() > 2 ) + animatingFlag = ev->GetBoolean( 3 ); + + weapon = GetActiveWeapon( hand ); + if ( !weapon ) + return; + + weapon->playAnim( ev->GetString( 1 ), animatingFlag ); +} + +void Player::SwitchWeaponMode( Event *ev ) +{ + Weapon* weapon = 0; + weapon = GetActiveWeapon( WEAPON_DUAL ); + if (!weapon ) + return; + + // Switch Modes + weapon->SwitchMode(); +} + +qboolean Player::GetCrouch( void ) +{ + if ( last_ucmd.upmove < 0 ) // check for downward movement + return true; + return false; +} + +void Player::ReloadTiki( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + int frame, anim, animstate; + Viewthing *viewthing; + + viewthing = ( Viewthing * )( ( Entity * )Viewmodel.current_viewthing ); + if ( !viewthing ) + return; + + // Save off info about the current viewspawn + frame = viewthing->frame; + anim = viewthing->CurrentAnim(); + Vector vieworigin(viewthing->origin.x, viewthing->origin.y, viewthing->origin.z); + Vector viewangles(viewthing->angles.x, viewthing->angles.y, viewthing->angles.z); + + // We're actually going to call ToggleAnimationState, so we + // set the animstate here one less than the real state + animstate = viewthing->animstate-1; + if ( animstate < 0 ) + animstate = -1; + + // Process the event that deletes the old viewmodel and spawns the new one + Event *ev2 = new Event(EV_ViewThing_SpawnFromTS); + ev2->AddString(ev->GetString(1)); + ev2->AddString(viewthing->model); + Viewmodel.ProcessEvent(ev2); + + // Re-get the new viewthing pointer + viewthing = ( Viewthing * )( ( Entity * )Viewmodel.current_viewthing ); + + // Update all its info to be the same to the one we deleted + ev2 = new Event; + ev2->AddFloat(vieworigin.x); + ev2->AddFloat(vieworigin.y); + ev2->AddFloat(vieworigin.z); + viewthing->ChangeOrigin(ev2); + if ( ev2 ) delete ev2; + + ev2 = new Event; + ev2->AddFloat(viewangles.x); + ev2->AddFloat(viewangles.y); + ev2->AddFloat(viewangles.z); + viewthing->SetAnglesEvent(ev2); + + viewthing->frame = frame; + viewthing->SetAnim(anim); + viewthing->animstate = animstate; + if ( ev2 ) delete ev2; + ev2 = new Event; + viewthing->ToggleAnimateEvent(ev2); +} + +void Player::SetViewAnglesEvent( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + SetViewAngles(ev->GetVector(1)); +} + +void Player::ProjDetonate(Event *ev) +{ + if ( ev->NumArgs() > 0 ) + projdetonate = ev->GetBoolean(1); +} + +void Player::SetProjDetonate(qboolean value) +{ + projdetonate = value; +} + +qboolean Player::GetProjDetonate() +{ + Weapon *weapon; + + if ( projdetonate ) + return true; + + weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( !weapon ) + return false; + + if ( ( weapon->GetFireType( FIRE_MODE1 ) == FT_TRIGGER_PROJECTILE ) && ( isButtonDown( BUTTON_ATTACKLEFT ) ) ) + return true; + + if ( ( weapon->GetFireType( FIRE_MODE2 ) == FT_TRIGGER_PROJECTILE ) && ( isButtonDown( BUTTON_ATTACKRIGHT ) ) ) + return true; + + return false; +} + +void Player::PassEventToVehicle( Event *ev ) +{ + if ( vehicle ) + vehicle->HandleEvent( ev ); +} + +void Player::UseSpecifiedEntity( Event *ev ) +{ + Event *event = NULL; + Entity *ent = NULL; + + // Event with a param is old functionality + if ( ev->NumArgs() > 0 ) + { + ent = ev->GetEntity( 1 ); + if ( ent == 0 ) + { + Com_Error ( ERR_DROP , "UseSpecifiedEntity(): NULL entity referenced\n" ); + return; + } + + event = new Event( EV_Use ); + event->AddEntity( this ); + ent->ProcessEvent( event ); + return; + } + else // No params, new functionality + { + if ( !atobject ) + return; + ent = (Entity *)atobject; + if ( !ent->hasUseData() ) + return; + + ent->useData->useMe(); + + if ( ent->useData->getUseAnim().length() > 0 && animate->HasAnim(ent->useData->getUseAnim()) ) + { + // We are assuming we have a valid anim that + // will manually trigger the use on a specific frame + // using the "douseentity" call + + SetAnim( ent->useData->getUseAnim(), legs ); + movecontrol = MOVECONTROL_ABSOLUTE; + _usingEntity = true; + _useEntityStartTimer = level.time + 2.5f; + } + else + { + // No animation, just call the thread and notify the entity + // he's been used. + if ( ent->useData->getUseThread().length() > 0 ) + ExecuteThread(ent->useData->getUseThread().c_str(), true, ent); + + _usingEntity = false; // No anim, so we can just leave the state immediately. + + // If it's an item, we're going to pick it up, so no need to call + // it's use event. Check the gameplay manager to make sure this item + // cannot be auto-picked up. + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( ent->isSubclassOf(Item) && gpm->hasProperty(ent->getArchetype(), "noautopickup")) + { + Item *item = (Item *)ent; + handlePickupItem(item); + return; + } + + event = new Event( EV_Use ); + event->AddEntity( this ); + ent->ProcessEvent( event ); + } + } +} + +//-------------------------------------------------------------- +// +// Name: handlePickupItem +// Class: Player +// +// Description: Called when the user has clicked on a useentity that +// happens to be an item. The default behavior of this +// action is to put the item in the inventory and remove +// it from the world. +// +// Parameters: Item *item -- The item to pick up +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::handlePickupItem( Item *item ) +{ + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str type = item->getArchetype(); + str invslot = gpm->getStringValue(type, "invslot"); + if ( invslot.length() ) + { + float quantity = gpm->getFloatValue(invslot, "quantity"); + quantity += 1.0f; + gpm->setFloatValue(invslot, "quantity", quantity); + gpm->setStringValue(invslot, "name", type); + } + else + { + str slotName = getFreeInventorySlot(); + if ( !slotName.length() ) + return; + + gpm->setStringValue(slotName, "name", type); + } + + str snd = gpm->getStringValue(type + ".Pickup", "wav"); + if ( snd.length() ) + { + int channel = CHAN_BODY; + float volume = -1.0f; + float mindist = -1.0f; + if ( gpm->hasProperty(type + ".Pickup","channel") ) + channel = (int)gpm->getFloatValue(type + ".Pickup", "channel"); + if ( gpm->hasProperty(getArchetype() + ".Pickup","volume") ) + volume = (int)gpm->getFloatValue(getArchetype() + ".Pickup", "volume"); + if ( gpm->hasProperty(getArchetype() + ".Pickup","mindist") ) + mindist = (int)gpm->getFloatValue(getArchetype() + ".Pickup", "mindist"); + item->Sound(snd, channel, volume, mindist); + } + + // Remove the item from the world + item->ProcessEvent(EV_Remove); +} + +//-------------------------------------------------------------- +// +// Name: doUseEntity +// Class: Player +// +// Description: Called from the tiki to do the actual useentity +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::doUseEntity( Event *ev ) +{ + Entity *ent = NULL; + Event *event = NULL; + + if ( !atobject ) + return; + + ent = (Entity *)atobject; + if ( !ent->hasUseData() ) + return; + + if ( ent->useData->getUseThread().length() > 0 ) + ExecuteThread(ent->useData->getUseThread().c_str(), true, ent); + + // If it's an item, we're going to pick it up, so no need to call + // it's use event. Check the gameplay manager to make sure this item + // cannot be auto-picked up. + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( ent->isSubclassOf(Item) && gpm->hasProperty(ent->getArchetype(), "noautopickup")) + { + Item *item = (Item *)ent; + handlePickupItem(item); + return; + } + + event = new Event( EV_Use ); + event->AddEntity( this ); + ent->ProcessEvent( event ); +} + +//-------------------------------------------------------------- +// +// Name: doneUseEntity +// Class: Player +// +// Description: Called when the useentity animation is done +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::doneUseEntity( Event *ev ) +{ + _usingEntity = false; + level.playerfrozen = false; + _useEntityStartTimer = 0.0f; + SetState("STAND", "STAND"); +} + +void Player::SetupDialog( Event *ev ) +{ + Entity *entity; + str soundName; + + entity = ev->GetEntity( 1 ); + soundName = ev->GetString( 2 ); + + SetupDialog( entity, soundName ); +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::SetupDialog( Entity *entity, const str &soundName ) +{ + if( gi.SoundLength( soundName.c_str() ) <= 0 ) + return; + + //If we have an entity, then we are dealing with Dialog events. + if ( entity ) + { + handleDialogSetup(entity, soundName); + } + else + { + handleTextDialogSetup( soundName ); + } +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::handleDialogSetup( Entity* entity, const str& soundName ) +{ + // Set the correct info and post the clear event when done + // Make sure all current clears are canceled + CancelEventsOfType( EV_Player_ClearDialog ); + + // Make sure current stuff is cleared properly + + ClearDialog(); + + if ( gi.SoundLength( soundName.c_str() ) >= 0 ) + { + _dialogEntnum = entity->entnum; + _dialogSoundIndex = gi.soundindex( soundName.c_str() ); + + PostEvent( EV_Player_ClearDialog, gi.SoundLength( soundName.c_str() ) ); + } + else + { + ProcessEvent( EV_Player_ClearDialog ); + } +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::handleTextDialogSetup( const str& soundName ) +{ + // Set the correct info and post the clear event when done + // Make sure all current clears are canceled + CancelEventsOfType( EV_Player_ClearTextDialog ); + + // Make sure current stuff is cleared properly + ClearTextDialog(); + + if ( gi.SoundLength( soundName.c_str() ) >= 0 ) + { + _dialogTextSoundIndex = gi.soundindex( soundName.c_str() ); + PostEvent( EV_Player_ClearTextDialog, gi.SoundLength( soundName.c_str() ) ); + } + else + { + ProcessEvent( EV_Player_ClearTextDialog ); + } +} + + +void Player::ClearDialog( Event *ev ) +{ + ClearDialog(); +} + +void Player::ClearDialog( void ) +{ + // Clear the dialog info + _dialogEntnum = ENTITYNUM_NONE; + _dialogSoundIndex = -1; +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::ClearTextDialog( Event* ev ) +{ + ClearTextDialog(); +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::ClearTextDialog( void ) +{ + _dialogTextSoundIndex = -1; +} + + +// +// Objective Functions +// +void Player::SetObjectiveComplete( Event* ev ) +{ + int ObjIndex; + str ObjName; + qboolean Complete; + + ObjName = ev->GetString( 1 ); + Complete = ev->GetBoolean( 2 ); + + ObjIndex = gi.MObjective_GetIndexFromName( ObjName.c_str() ); + if ( ObjIndex == 0 ) + return; + + switch ( ObjIndex ) + { + case OBJECTIVE1: + if ( Complete ) + _objectiveStates |= OBJECTIVE1_COMPLETE; + else + _objectiveStates &= ~(OBJECTIVE1_COMPLETE); + break; + + case OBJECTIVE2: + if ( Complete ) + _objectiveStates |= OBJECTIVE2_COMPLETE; + else + _objectiveStates &= ~(OBJECTIVE2_COMPLETE); + break; + + case OBJECTIVE3: + if ( Complete ) + _objectiveStates |= OBJECTIVE3_COMPLETE; + else + _objectiveStates &= ~(OBJECTIVE3_COMPLETE); + + break; + + case OBJECTIVE4: + if ( Complete ) + _objectiveStates |= OBJECTIVE4_COMPLETE; + else + _objectiveStates &= ~(OBJECTIVE4_COMPLETE); + + break; + + case OBJECTIVE5: + if ( Complete ) + _objectiveStates |= OBJECTIVE5_COMPLETE; + else + _objectiveStates &= ~(OBJECTIVE5_COMPLETE); + + break; + + case OBJECTIVE6: + if ( Complete ) + _objectiveStates |= OBJECTIVE6_COMPLETE; + else + _objectiveStates &= ~(OBJECTIVE6_COMPLETE); + + break; + + case OBJECTIVE7: + if ( Complete ) + _objectiveStates |= OBJECTIVE7_COMPLETE; + else + _objectiveStates &= ~(OBJECTIVE7_COMPLETE); + + break; + + case OBJECTIVE8: + if ( Complete ) + _objectiveStates |= OBJECTIVE8_COMPLETE; + else + _objectiveStates &= ~(OBJECTIVE8_COMPLETE); + + break; + + default: + break; + } + + + // gi.MObjective_SetObjectiveComplete( ObjName.c_str() , Complete ); +} + + + +void Player::SetObjectiveFailed( Event *ev ) +{ + int ObjIndex; + str ObjName; + qboolean Failed; + + ObjName = ev->GetString( 1 ); + Failed = ev->GetBoolean( 2 ); + + ObjIndex = gi.MObjective_GetIndexFromName( ObjName.c_str() ); + if ( ObjIndex == 0 ) + return; + + switch ( ObjIndex ) + { + case OBJECTIVE1: + if ( Failed ) + _objectiveStates |= OBJECTIVE1_FAILED; + else + _objectiveStates &= ~(OBJECTIVE1_FAILED); + break; + + case OBJECTIVE2: + if ( Failed ) + _objectiveStates |= OBJECTIVE2_FAILED; + else + _objectiveStates &= ~(OBJECTIVE2_FAILED); + + break; + + case OBJECTIVE3: + if ( Failed ) + _objectiveStates |= OBJECTIVE3_FAILED; + else + _objectiveStates &= ~(OBJECTIVE3_FAILED); + + break; + + case OBJECTIVE4: + if ( Failed ) + _objectiveStates |= OBJECTIVE4_FAILED; + else + _objectiveStates &= ~(OBJECTIVE4_FAILED); + + break; + + case OBJECTIVE5: + if ( Failed ) + _objectiveStates |= OBJECTIVE5_FAILED; + else + _objectiveStates &= ~(OBJECTIVE5_FAILED); + + break; + + case OBJECTIVE6: + if ( Failed ) + _objectiveStates |= OBJECTIVE6_FAILED; + else + _objectiveStates &= ~(OBJECTIVE6_FAILED); + + break; + + case OBJECTIVE7: + if ( Failed ) + _objectiveStates |= OBJECTIVE7_FAILED; + else + _objectiveStates &= ~(OBJECTIVE7_FAILED); + + + case OBJECTIVE8: + if ( Failed ) + _objectiveStates |= OBJECTIVE8_FAILED; + else + _objectiveStates &= ~(OBJECTIVE8_FAILED); + + break; + + default: + break; + } + + // gi.MObjective_SetObjectiveFailed( ObjName.c_str() , Failed ); +} + + +//----------------------------------------------------- +// +// Name: LoadObjectives +// Class: Player +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::LoadObjectives(Event* ev) +{ + loadObjectives(ev->GetString(1)); +} + + +//----------------------------------------------------- +// +// Name: loadObjectives +// Class: Player +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::loadObjectives( const str& objectiveName ) +{ + _objectiveStates = 0; + _informationStates = 0; + gi.MObjective_Update(objectiveName); + + _objectiveNameIndex = gi.objectivenameindex(objectiveName); +} + + +//----------------------------------------------------- +// +// Name: SetObjectiveShow +// Class: Player +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::SetObjectiveShow( Event* ev ) +{ + int ObjIndex; + str ObjName; + qboolean Show; + bool playSound; + + playSound = false; + + ObjName = ev->GetString( 1 ); + Show = ev->GetBoolean( 2 ); + + + ObjIndex = gi.MObjective_GetIndexFromName( ObjName.c_str() ); + if ( ObjIndex == 0 ) + return; + + //Update the objective show flag. + gi.MObjective_SetShowObjective(ObjName.c_str(), Show); + + + //Set the appropriate bit on the flag passed to the client. + unsigned int bitToChange; + + switch ( ObjIndex ) + { + case OBJECTIVE1: + bitToChange = OBJECTIVE1_SHOW; + break; + case OBJECTIVE2: + bitToChange = OBJECTIVE2_SHOW; + break; + case OBJECTIVE3: + bitToChange = OBJECTIVE3_SHOW; + break; + case OBJECTIVE4: + bitToChange = OBJECTIVE4_SHOW; + break; + case OBJECTIVE5: + bitToChange = OBJECTIVE5_SHOW; + break; + case OBJECTIVE6: + bitToChange = OBJECTIVE6_SHOW; + break; + case OBJECTIVE7: + bitToChange = OBJECTIVE7_SHOW; + break; + case OBJECTIVE8: + bitToChange = OBJECTIVE8_SHOW; + break; + default: + bitToChange = 0; + break; + } + + if ( Show && !(_objectiveStates & bitToChange) ) + { + _objectiveStates |= bitToChange; + playSound = true; + } + + // Else is removed because if we call setobjectiveshow on the same objective twice, we will + // actually hide it + //else + //{ + // _objectiveStates &= ~bitToChange; + //} + + if ( playSound ) + Sound( "snd_objectivechanged", CHAN_LOCAL ); +} + +void Player::SpecialMoveChargeStart( Event* ev ) +{ + specialMoveCharge = level.time; + specialMoveEndTime = level.time + specialMoveChargeTime; +} + +void Player::SpecialMoveChargeEnd( Event* ev ) +{ + specialMoveCharge = 0.0f; + specialMoveChargeTime = 0.0f; + specialMoveEndTime = 0.0f; +} + +void Player::SpecialMoveChargeTime( Event* ev ) +{ + if ( ev->NumArgs() > 0 ) + specialMoveChargeTime = ev->GetFloat( 1 ); +} + +void Player::SetInformationShow( Event* ev ) +{ + int InfoIndex; + str InfoName; + qboolean Show; + + InfoName = ev->GetString( 1 ); + Show = ev->GetBoolean( 2 ); + + InfoIndex = gi.MI_GetIndexFromName( InfoName.c_str() ); + if ( InfoIndex == 0 ) + return; + + + switch ( InfoIndex ) + { + case INFORMATION1: + if ( Show ) + _informationStates |= INFORMATION1_SHOW; + else + _informationStates &= ~(INFORMATION1_SHOW); + break; + + case INFORMATION2: + if ( Show ) + _informationStates |= INFORMATION2_SHOW; + else + _informationStates &= ~(INFORMATION2_SHOW); + + break; + + case INFORMATION3: + if ( Show ) + _informationStates |= INFORMATION3_SHOW; + else + _informationStates &= ~(INFORMATION3_SHOW); + + break; + + case INFORMATION4: + if ( Show ) + _informationStates |= INFORMATION4_SHOW; + else + _informationStates &= ~(INFORMATION4_SHOW); + + break; + + case INFORMATION5: + if ( Show ) + _informationStates |= INFORMATION5_SHOW; + else + _informationStates &= ~(INFORMATION5_SHOW); + + break; + + case INFORMATION6: + if ( Show ) + _informationStates |= INFORMATION6_SHOW; + else + _informationStates &= ~(INFORMATION6_SHOW); + + break; + + case INFORMATION7: + if ( Show ) + _informationStates |= INFORMATION7_SHOW; + else + _informationStates &= ~(INFORMATION7_SHOW); + + + case INFORMATION8: + if ( Show ) + _informationStates |= INFORMATION8_SHOW; + else + _informationStates &= ~(INFORMATION8_SHOW); + + break; + + default: + break; + } +} + +void Player::MissionFailed( Event* ev ) +{ + str reason = "DefaultFailure"; + if ( ev->NumArgs() > 0 ) + reason = ev->GetString( 1 ); + + G_MissionFailed(reason); +} + +void Player::setMissionFailed( void ) +{ + client->ps.missionStatus |= MISSION_FAILED; +} + +void Player::SetStat( Event *ev ) +{ + int stat_index; + int stat_value; + + stat_index = PlayerStat_NameToNum( ev->GetString( 1 ) ); + + if ( stat_index < 0 ) + { + gi.Printf( "Couldn't find player stat %s\n", ev->GetString( 1 ) ); + return; + } + + stat_value = ev->GetInteger( 2 ); + + client->ps.stats[ stat_index ] = stat_value; +} + +void Player::SetStateFile( Event *ev ) +{ + str stateFileName(ev->GetString(1)); + gi.cvar_set( "g_statefile", stateFileName ); + LoadStateTable(); +} + +//---------------------------------------------------------------- +// Name: ShouldSendToClient +// Class: Player +// +// Description: Decides whether or not we should send this entity to the client +// +// Parameters: Entity *entityToSend - entity that we deciding about +// +// Returns: qboolean - whether or not we should send this entity +//---------------------------------------------------------------- + +qboolean Player::ShouldSendToClient( Entity *entityToSend ) +{ + // For now, early out if we don't have care about any view modes + + if ( multiplayerManager.inMultiplayer() && client->pers.mp_lowBandwidth && entityToSend->isNetworkDetail() ) + return false; + + if ( !entityToSend->_affectingViewModes ) + return true; + + + // Check to see if we should send this entity based on the player's current view mode + + if ( entityToSend->_affectingViewModes & _viewMode ) + return gi.GetViewModeSendInMode( entityToSend->_affectingViewModes & _viewMode ); + else + return gi.GetViewModeSendNotInMode( entityToSend->_affectingViewModes & (~_viewMode) ); +} + +void Player::UpdateEntityStateForClient( entityState_t *state ) +{ + int i; + + if ( !state ) + return; + + // Only update entity in multiplayer + + if ( !multiplayerManager.inMultiplayer() ) + return; + + // Only update entity in low bandwidth mode + + if ( !client->pers.mp_lowBandwidth ) + return; + + // Clear net angles if a client + + if ( state->clientNum != ENTITYNUM_NONE ) + { + VectorClear( state->netangles ); + } + + // Clear bone angles + + for ( i = 0 ; i < NUM_BONE_CONTROLLERS ; i++ ) + { + VectorClear( state->bone_angles[ i ] ); + } +} + +void Player::UpdatePlayerStateForClient( playerState_t *state ) +{ + if ( !state ) + return; + + // Only update playerstate in multiplayer + + if ( !multiplayerManager.inMultiplayer() ) + return; + + if ( !multiplayerManager.isPlayerSpectator( this, SPECTATOR_TYPE_FOLLOW ) && !mp_savingDemo ) + { + VectorClear( state->viewangles ); + } +} + +//---------------------------------------------------------------- +// Name: ExtraEntitiesToSendToClient +// Class: Player +// +// Description: Adds extra entities to send over to the client +// +// Parameters: int *numExtraEntities - the number of extra entities we added +// int *extraEntities - the list of entity numbers that we added +// +// Returns: None +//---------------------------------------------------------------- + +void Player::ExtraEntitiesToSendToClient( int *numExtraEntities, int *extraEntities ) +{ + *numExtraEntities = 0; + + // Add in our current dialog person (if any) + if ( _dialogEntnum != ENTITYNUM_NONE ) + { + extraEntities[ *numExtraEntities ] = _dialogEntnum; + (*numExtraEntities)++; + + // Make sure we haven't added too many entities to the list + + if ( *numExtraEntities == MAX_EXTRA_ENTITIES_FROM_GAME ) + return; + } +} + +//---------------------------------------------------------------- +// Name: setViewMode +// Class: Player +// +// Description: Sets the players current view mode +// +// Parameters: const str &viewModeName - the name of the view mode to go to +// +// Returns: None +//---------------------------------------------------------------- + +void Player::setViewMode( const str &viewModeName ) +{ + Sentient::setViewMode( viewModeName ); + client->ps.viewMode = getViewMode(); +} + +//---------------------------------------------------------------- +// Name: AwardPoints +// Class: Player +// +// Description: Gives the player points +// +// Parameters: int numPoints -- the number of points to give +// +// Returns: int -- The new total number of points +//---------------------------------------------------------------- +int Player::AwardPoints(int numPoints) +{ + points += numPoints; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm ) return points ; + + float maxPoints = 0.0f ; + float experience = 0.0f ; + + if ( gpm->hasProperty( "CurrentPlayer", "maxPoints" ) ) + { + maxPoints = gpm->getFloatValue( "CurrentPlayer", "maxPoints" ); + experience = 100.0f * (points / maxPoints) ; + gpm->setFloatValue( "CurrentPlayer", "experience", experience ); + + if ( experience >= 100.0f ) + { + float level = gpm->getFloatValue( "CurrentPlayer", "level" ); + float skillPoints = gpm->getFloatValue( "CurrentPlayer", "SkillPoints" ); + + level += 1.0 ; + skillPoints += level ; + maxPoints = level * level * 100.0f ; + experience = 0.0f ; + + gpm->setFloatValue( "CurrentPlayer", "level", level ); + gpm->setFloatValue( "CurrentPlayer", "SkillPoints", skillPoints ); + gpm->setFloatValue( "CurrentPlayer", "maxPoints", maxPoints ); + gpm->setFloatValue( "CurrentPlayer", "experience", experience ); + points = 0 ; + + G_EnableWidgetOfPlayer( edict, "level_up", true ); + } + } + + return points; +} + +//---------------------------------------------------------------- +// Name: TakePoints +// Class: Player +// +// Description: Takes points away (never below 0) +// +// Parameters: int numPoints -- the number of points to take +// +// Returns: int -- The new total number of points +//---------------------------------------------------------------- +int Player::TakePoints(int numPoints) +{ + points -= numPoints; + if ( points < 0 ) + points = 0; + + return points; +} + +//---------------------------------------------------------------- +// Name: GivePointsEvent +// Class: Player +// +// Description: Gives the player points (from script) +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::GivePointsEvent( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + AwardPoints(ev->GetInteger( 1 )); +} + +//---------------------------------------------------------------- +// Name: SetPointsEvent +// Class: Player +// +// Description: Sets the players total number of points +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::SetPointsEvent( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + points = ev->GetInteger( 1 ); +} + +//---------------------------------------------------------------- +// Name: ChangeChar +// Class: Player +// +// Description: Starts the screen fading out. When it fades to black +// we switch characters. +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::ChangeChar( Event *ev ) +{ + if ( changingChar ) // In the process of changing don't allow this event AGAIN until we're done. + return; + + // Don't switch characters if we're already that character. + str curChar = gi.cvar("g_playermodel","",0)->string; + str curStateFile = gi.cvar("g_statefile","",0)->string; + if ((ev->GetString( 1 ) == curStateFile) && (ev->GetString( 2 ) == curChar)) + return; + + Actor *act = Actor::FindActorByName(ev->GetString( 3 )); + if ( act ) + { + changingChar = true; + G_FadeOut(1.0f); + Event *ev2 = new Event(EV_Player_ChangeCharFadeIn); + ev2->AddString(ev->GetString( 1 )); + ev2->AddString(ev->GetString( 2 )); + ev2->AddString(ev->GetString( 3 )); + ev2->AddString(ev->GetString( 4 )); + ev2->AddEntity(act); + PostEvent(ev2, 1.0f); + } +} + +//---------------------------------------------------------------- +// Name: ChangeCharFadeIn +// Class: Player +// +// Description: Changes to the specified state machine and model +// as the screen fades in. +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::ChangeCharFadeIn( Event *ev ) +{ + str statemachine = ev->GetString( 1 ); + str newmodel = ev->GetString( 2 ); + str replacemodel = ev->GetString( 3 ); + str spawnNPC = ev->GetString( 4 ); + Actor *act = (Actor*)ev->GetEntity( 5 ); + + ProcessEvent(EV_DetachAllChildren); + + gi.cvar_set( "g_statefile", statemachine.c_str() ); + gi.cvar_set( "g_playermodel", newmodel.c_str() ); + InitModel(); + LoadStateTable(); + + Event *e = new Event( EV_Player_SpawnActor ); + e->AddString( spawnNPC.c_str() ); + e->AddString( "origin" ); + e->AddVector(origin); + e->AddString( "angles" ); + e->AddVector(angles); + // Spawn the actor where the player is currently positioned + SpawnActor(e); + + // Set the player angles and origin to the new location + origin = act->origin; + SetViewAngles(act->angles); + act->ProcessEvent( EV_Remove ); + + G_AutoFadeIn(); + changingChar = false; +} + +//---------------------------------------------------------------- +// Name: SetPlayerChar +// Class: Player +// +// Description: Sets the player character +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::SetPlayerChar( Event *ev ) +{ + str statemachine = ev->GetString( 1 ); + str newmodel = ev->GetString( 2 ); + + // Don't switch characters if we're already that character. + str curChar = gi.cvar("g_playermodel","",0)->string; + str curStateFile = gi.cvar("g_statefile","",0)->string; + if ((statemachine == curStateFile) && (newmodel == curChar)) + return; + + gi.cvar_set( "g_statefile", statemachine.c_str() ); + gi.cvar_set( "g_playermodel", newmodel.c_str() ); + ProcessEvent(EV_DetachAllChildren); + InitModel(); + LoadStateTable(); +} + +//---------------------------------------------------------------- +// Name: PlayerKnockback +// Class: Player +// +// Description: Sets the player knockback value. +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::PlayerKnockback(Event *ev) +{ + playerKnockback = ev->GetInteger( 1 ); +} + +//---------------------------------------------------------------- +// Name: KnockbackMultiplier +// Class: Player +// +// Description: Sets the player knockback multiplier +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::KnockbackMultiplier(Event *ev) +{ + knockbackMultiplier = ev->GetInteger( 1 ); +} + +//---------------------------------------------------------------- +// Name: MeleeEvent +// Class: Player +// +// Description: Performs a weaponless melee attack. +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::MeleeEvent( Event *ev ) +{ + Vector melee_pos; + Vector melee_end; + float damage = 20; + str means_of_death_string; + meansOfDeath_t attack_means_of_death; + float knockback; + + // Get all of the parameters + + if ( ev->NumArgs() > 0 ) + damage = ev->GetFloat( 1 ); + + if ( ev->NumArgs() > 1 ) + means_of_death_string = ev->GetString( 2 ); + + if ( ev->NumArgs() > 2 ) + knockback = ev->GetFloat( 3 ); + else + knockback = damage * 8.0f; + + melee_pos = centroid; + melee_end = centroid + Vector( orientation[0] ) * 96.0f; + + if ( means_of_death_string.length() > 0 ) + attack_means_of_death = (meansOfDeath_t)MOD_NameToNum( means_of_death_string ); + else + attack_means_of_death = MOD_CRUSH; + + // Do the actual attack + MeleeAttack( melee_pos, melee_end, damage, this, attack_means_of_death, 15.0f, -45.0f, 45.0f, knockback ); +} + +//---------------------------------------------------------------- +// Name: MeleeDamageStart +// Class: Player +// +// Description: Sets flags to perform melee damage with the weapon +// in the hand specified (defaults to right hand) +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::MeleeDamageStart( Event *ev ) +{ + Weapon *weapon; + weaponhand_t hand = WEAPON_RIGHT; + + if ( ev->NumArgs() > 0 ) + hand = WeaponHandNameToNum(ev->GetString( 1 )); + + weapon = GetActiveWeapon( hand ); + if ( !weapon ) + return; + + // Make sure the tags are there on the weapon, or it will crash. + int tag_num = 0; + tag_num += gi.Tag_NumForName( weapon->edict->s.modelindex, "tag_swipe1" ); + tag_num += gi.Tag_NumForName( weapon->edict->s.modelindex, "tag_swipe2" ); + if ( tag_num < 0 ) + return; + + weapon->ClearMeleeVictims(); + if ( hand == WEAPON_RIGHT ) + meleeAttackFlags |= MELEE_ATTACK_RIGHT; + if ( hand == WEAPON_LEFT ) + meleeAttackFlags |= MELEE_ATTACK_LEFT; +} + +//---------------------------------------------------------------- +// Name: MeleeDamageEnd +// Class: Player +// +// Description: Stops doing damage with the melee weapon in the hand +// specified. +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::MeleeDamageEnd( Event *ev ) +{ + weaponhand_t hand = WEAPON_RIGHT; + + if ( ev->NumArgs() > 0 ) + hand = WeaponHandNameToNum(ev->GetString( 1 )); + + if ( hand == WEAPON_RIGHT ) + meleeAttackFlags &= ~MELEE_ATTACK_RIGHT; + if ( hand == WEAPON_LEFT ) + meleeAttackFlags &= ~MELEE_ATTACK_LEFT; +} + +//---------------------------------------------------------------- +// Name: ChangeStance +// Class: Player +// +// Description: Changes to the stance specified +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::ChangeStance(Event *ev) +{ + if ( ev->NumArgs() < 1 ) + return; + + changedStanceTorso = true; + changedStanceLegs = true; + stanceNumber = ev->GetInteger( 1 ); +} + +//---------------------------------------------------------------- +// Name: ClearStanceTorso +// Class: Player +// +// Description: Clears internal torso stance data +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::ClearStanceTorso(Event *ev) +{ + changedStanceTorso = false; +} + +//---------------------------------------------------------------- +// Name: ClearStanceLegs +// Class: Player +// +// Description: Clears internal legs stance data +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::ClearStanceLegs(Event *ev) +{ + changedStanceLegs = false; +} + +//---------------------------------------------------------------- +// Name: ClearIncomingMelee +// Class: Player +// +// Description: Clears the incoming melee flag +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::ClearIncomingMelee(Event *ev) +{ + incomingMeleeAttack = false; +} + +//---------------------------------------------------------------- +// Name: AddMeleeAttacker +// Class: Player +// +// Description: Adds the entity to the melee attacker container +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::AddMeleeAttacker( Event *ev) +{ + Entity *ent = ev->GetEntity( 1 ); + meleeAttackerList.AddUniqueObject( ent ); +} + +//---------------------------------------------------------------- +// Name: SetBendTorso +// Class: Player +// +// Description: Sets the torso bending for the player +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::SetBendTorso(Event *ev) +{ + if ( ev->NumArgs() > 0 ) + bendTorsoMult = ev->GetFloat( 1 ); + else + bendTorsoMult = standardTorsoMult; +} + +//---------------------------------------------------------------- +// Name: HeadWatchAllowed +// Class: Player +// +// Description: Sets whether to headwatch or not, defaults to true +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Player::HeadWatchAllowed(Event *ev) +{ + Sentient::HeadWatchAllowed( ev ); +} + +void Player::SetCurrentCallVolume( const str &volumeName ) +{ + currentCallVolume = volumeName; +} + +str Player::GetCurrentCallVolume() +{ + return currentCallVolume; +} + +// +// Multiplayer stuff +// + +void Player::Score( Event *ev ) +{ + multiplayerManager.score( this ); +} + +void Player::joinTeam( Event *ev ) +{ + str teamName; + + teamName = ev->GetString( 1 ); + + multiplayerManager.joinTeam( this, teamName ); +} + +void Player::multiplayerCommand( Event *ev ) +{ + str command; + str parm; + + command = ev->GetString( 1 ); + parm = ev->GetString( 2 ); + + multiplayerManager.playerCommand( this, command, parm ); +} + +void Player::Disconnect( void ) +{ + if ( multiplayerManager.inMultiplayer() ) + { + multiplayerManager.removePlayer( this ); + } +} + +void Player::CallVote( Event *ev ) +{ + str command; + str arg; + + if ( multiplayerManager.inMultiplayer() ) + { + command = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + arg = ev->GetString( 2 ); + + multiplayerManager.callVote( this, command, arg ); + } +} + +void Player::Vote( Event *ev ) +{ + str vote; + + if ( multiplayerManager.inMultiplayer() ) + { + if ( ev->NumArgs() != 1 ) + { + multiplayerManager.HUDPrint( entnum, "$$Usage$$: vote <1|0|y|n>" ); + return; + } + + vote = ev->GetString( 1 ); + + multiplayerManager.vote( this, vote ); + } +} + +// +// Powerup stuff +// + +void Player::addPowerupEffect( PowerupBase *powerup ) +{ + str modelName; + str tagName; + float modelRemoveTime; + str shaderName; + + if ( !powerup ) + return; + + // Attach a model to the player if necessary + + powerup->getModelToAttachOnUse( modelName, tagName, modelRemoveTime ); + + if ( modelName.length() > 0 ) + { + Event *attachModel; + + attachModel = new Event( EV_AttachModel ); + + attachModel->AddString( modelName ); + attachModel->AddString( tagName ); + attachModel->AddFloat( 1.0f ); + attachModel->AddString( "" ); + attachModel->AddInteger( 0 ); + attachModel->AddFloat( modelRemoveTime ); + + ProcessEvent( attachModel ); + } +} + +void Player::removePowerupEffect( PowerupBase *powerup ) +{ + str modelName; + str tagName; + float modelRemoveTime; + str shaderName; + + if ( !powerup ) + return; + + // Remove the attached model from the player if attached before + + powerup->getModelToAttachOnUse( modelName, tagName, modelRemoveTime ); + + if ( modelName.length() > 0 ) + { + Event *removeAttachedModel; + + removeAttachedModel = new Event( EV_RemoveAttachedModel ); + + removeAttachedModel->AddString( tagName ); + removeAttachedModel->AddFloat( 0.0f ); + removeAttachedModel->AddString( modelName ); + + ProcessEvent( removeAttachedModel ); + } + + // Remove the custom shader from the player if set before + + powerup->getShaderToDisplayOnUse( shaderName ); + + if ( shaderName.length() > 0 ) + { + clearCustomShader( shaderName.c_str() ); + } +} + +void Player::setPowerup( Powerup *powerup ) +{ + // If this is the same powerup type, stack them + + if ( _powerup && powerup && ( _powerup->getName() == powerup->getName() ) && _powerup->canStack() && powerup->canStack() ) + { + float newTimeLeft; + newTimeLeft = _powerup->getTimeLeft() + powerup->getTimeLeft(); + _powerup->setTimeLeft( newTimeLeft ); + } + else + { + removePowerup(); + + CancelEventsOfType( EV_Player_RemovePowerup ); + + _powerup = powerup; + + if ( !_powerup ) + return; + + addPowerupEffect( _powerup ); + } + + setItemText( _powerup->getIcon(), va( "$$Using$$ $$Item-%s$$", _powerup->getName().c_str() ) ); + //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$Using$$ %s", _powerup->getRealName() ); +} + +void Player::removePowerup( void ) +{ + if ( _powerup ) + { + removePowerupEffect( _powerup ); + + delete _powerup; + _powerup = NULL; + } +} + +void Player::removePowerupEvent( Event *ev ) +{ + removePowerup(); +} + +void Player::dropPowerup( void ) +{ + if ( _powerup ) + { + if ( _powerup->canDrop() ) + { + _powerup->spawn( centroid ); + } + + removePowerup(); + } +} + +// +// Rune stuff +// + +bool Player::hasRune( void ) +{ + if ( _rune ) + return true; + else + return false; +} + +void Player::setRune( Rune *rune ) +{ + removeRune(); + + _rune = rune; + + if ( !_rune ) + return; + + addPowerupEffect( _rune ); + + // Tell the player they are now using this rune + + setItemText( rune->getIcon(), va( "$$Using$$ $$Item-%s$$", _rune->getName().c_str() ) ); + //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$Using$$ %s", _rune->getRealName() ); +} + +void Player::removeRune( void ) +{ + if ( _rune ) + { + removePowerupEffect( _rune ); + + delete _rune; + _rune = NULL; + } +} + +// +// Holdable item stuff +// + +void Player::setHoldableItem( HoldableItem *holdableItem ) +{ + removeHoldableItem(); + + _holdableItem = holdableItem; +} + +void Player::removeHoldableItem( void ) +{ + if ( _holdableItem ) + { + _holdableItem->PostEvent( EV_Remove, 0.0f ); + + _holdableItem = NULL; + } +} + +void Player::useHoldableItem( void ) +{ + if ( _holdableItem ) + { + if ( multiplayerManager.inMultiplayer() ) + { + multiplayerManager.playerEventNotification( "use-HoldableItem", _holdableItem->getName(), this ); + } + + if ( _holdableItem->use() ) + { + // Must check again if holdable item exists, because use might have caused the holdable item to be destroyed + + if ( _holdableItem ) + { + addPowerupEffect( _holdableItem ); + + setItemText( _holdableItem->getIcon(), va( "$$Used$$ $$Item-%s$$", _holdableItem->getName().c_str() ) ); + //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$Using$$ $$Item-%s$$", _holdableItem->getName() ); + removeHoldableItem(); + } + } + } +} + +HoldableItem* Player::getHoldableItem( void ) +{ + return _holdableItem; +} + +float Player::getDamageDone( float damage, int meansOfDeath, bool inMelee ) +{ + float damageDone; + + + damageDone = damage; + + if ( inMelee ) + damageDone *= damage_multiplier; + + if ( _powerup ) + damageDone = _powerup->getDamageDone( damageDone, meansOfDeath ); + + if ( _rune ) + damageDone = _rune->getDamageDone( damageDone, meansOfDeath ); + + return damageDone; +} + +meansOfDeath_t Player::changetMeansOfDeath( meansOfDeath_t meansOfDeath ) +{ + meansOfDeath_t realMeansOfDeath; + + realMeansOfDeath = meansOfDeath; + + if ( _powerup ) + realMeansOfDeath = _powerup->changetMeansOfDeath( realMeansOfDeath ); + + if ( _rune ) + realMeansOfDeath = _rune->changetMeansOfDeath( realMeansOfDeath ); + + if ( _finishActor && _doingFinishingMove ) + realMeansOfDeath = MOD_REDEMPTION; // Finishing move MOD... oh sweet irony + + return realMeansOfDeath; +} + +void Player::dropRune( Event *ev ) +{ + if ( _rune ) + { + dropRune(); + } +} + +void Player::dropRune( void ) +{ + if ( _rune ) + { + setItemText( _rune->getIcon(), va( "$$Dropping$$ $$Item-%s$$", _rune->getName().c_str() ) ); + //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$Dropping$$ $$Item-%s$$", _rune->getName() ); + + _rune->spawn( centroid ); + + removeRune(); + } + else + { + multiplayerManager.playerCommand( this, "dropItem", "" ); + } +} + +//---------------------------------------------------------------- +// Name: setCanTransferEnergy +// Class: Player +// +// Description: Makes the player allowed to transfer energy from ammo to armor +// +// Parameters: Event * +// +// Returns: none +//---------------------------------------------------------------- + +void Player::setCanTransferEnergy( Event * ) +{ + _canTransferEnergy = true; +} + +//---------------------------------------------------------------- +// Name: transferEnergy +// Class: Player +// +// Description: Transfers some energy from ammo to the armor +// +// Parameters: none +// +// Returns: none +//---------------------------------------------------------------- + +void Player::transferEnergy( void ) +{ + Event *armorEvent; + + // Make sure we are allowed to transfer energy + + if ( !_canTransferEnergy ) + return; + + // Make sure enough time has gone by to transfer more energy + + if ( _nextEnergyTransferTime > level.time ) + return; + + _nextEnergyTransferTime = level.time + level.frametime; + + // Make sure we have enough energy + + if ( AmmoCount( "Plasma" ) < 1 ) + return; + + // Make sure we still need more armor + + if ( GetArmorValue() >= 100 ) + return; + + // Give ourselves a little bit of armor + + armorEvent = new Event( EV_Sentient_GiveArmor ); + + armorEvent->AddString( "BasicArmor" ); + armorEvent->AddFloat( 1.0f ); + + ProcessEvent( armorEvent ); + + // Use up some energy + + UseAmmo( "Plasma", 1 ); +} + +//-------------------------------------------------------------- +// +// Name: AdvancedMeleeAttack +// Class: Player +// +// Description: Gets the waepon in the hand specified and calls +// it's AdvancedMeleeAttack function. +// +// Parameters: weaponhand_t hand -- weapon hand +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::AdvancedMeleeAttack(weaponhand_t hand) +{ + Weapon *weapon; + bool critical = false ; + + if ( hand == WEAPON_ERROR ) + return; + + weapon = GetActiveWeapon( hand ); + if ( !weapon ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( gpm->hasProperty( "CurrentPlayer.Criticals", "value" ) ) + { + float criticalSkill = gpm->getFloatValue( "CurrentPlayer.Criticals", "value" ); + float criticalBaseChance = gpm->getFloatValue( "Criticals", "baseChance" ); + float criticalChance = criticalSkill * criticalBaseChance ; + if ( G_Random() < criticalChance ) + { + critical = true ; + } + } + + weapon->AdvancedMeleeAttack("tag_swipe1", "tag_swipe2", critical ); +} + +//-------------------------------------------------------------- +// Name: setDoDamageScreenFlash +// Class: Player +// +// Description: Sets whether or not we want to flash the player's screen when he is damaged +// +// Parameters: Event *ev - optionally contains a bool specifying on or off +// +// Returns: None +//-------------------------------------------------------------- + +void Player::setDoDamageScreenFlash( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + _doDamageScreenFlash = ev->GetBoolean( 1 ); + else + _doDamageScreenFlash = true; +} + +//-------------------------------------------------------------- +// Name: pointOfView +// Class: Player +// +// Description: Forces the player into a different point of view +// +// Parameters: Event *ev - 1 = 1st person, 0 = 3rd person +// +// Returns: None +//-------------------------------------------------------------- + +void Player::pointOfView( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + { + // More than one argument, we are attempting to SET + // the point of view specifically. + bool pov = ev->GetBoolean( 1 ); + if ( pov == _isThirdPerson ) + return; + } + + // Toggle the point of view + if ( _isThirdPerson ) + { + G_SendCommandToPlayer(edict, "cg_3rd_person 0"); + _isThirdPerson = false; + } + else + { + G_SendCommandToPlayer(edict, "cg_3rd_person 1"); + _isThirdPerson = true; + } +} + +void Player::HandleFinishableList() +{ + int i,j; + for ( i=1; i<=finishableList.NumObjects(); i++ ) + { + Actor *act = finishableList.ObjectAt(i); + for ( j=1; j<=finishingMoveList.NumObjects(); j++ ) + { + FinishingMove *fm = finishingMoveList.ObjectAt(j); + float fovdot = cos( DEG2RAD( fm->coneangle * 0.5f) ); + Vector delta = ( act->origin ) - origin; + if ( delta.length() > fm->distance ) + continue; + + // Enemy Angles not yet used... + // Chance not yet used... + + delta.z = 0.0f; + delta.normalize(); + float dot = 0.0f; + switch ( fm->direction ) + { + case 0 : dot = DotProduct( yaw_forward, delta ); break; + case 1 : dot = DotProduct( yaw_forward*-1.0f, delta ); break; + case 2 : dot = DotProduct( yaw_left, delta ); break; + case 3 : dot = DotProduct( yaw_left*-1.0f, delta ); break; + } + + if ( dot > fovdot ) + { + // G_EnableWidgetOfPlayer( edict, "ActionIcon_Kick", true ); + _finishActor = act; + _finishState = fm->statename; + return; + } + } + } + + // G_EnableWidgetOfPlayer( edict, "ActionIcon_Kick", false ); + _finishActor = NULL; + _finishState = ""; +} + +//-------------------------------------------------------------- +// +// Name: addFinishingMove +// Class: Player +// +// Description: Add a finishing move to the list +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::addFinishingMove( Event *ev ) +{ + str state, dirstr; + float coneangle = 45.0f; + float dist = 64.0f; + float eyaw = 0.0f; + float chance = 1.0f; + + state = ev->GetString( 1 ); + dirstr = ev->GetString( 2 ); + + if ( ev->NumArgs() > 2 ) + coneangle = ev->GetFloat( 3 ); + if ( ev->NumArgs() > 3 ) + dist = ev->GetFloat( 4 ); + if ( ev->NumArgs() > 4 ) + eyaw = ev->GetFloat( 5 ); + if ( ev->NumArgs() > 5 ) + chance = ev->GetFloat( 6 ); + + FinishingMove *fm = new FinishingMove(); + fm->statename = state; + if ( dirstr == "front" ) + fm->direction = 0; + else if ( dirstr == "behind" ) + fm->direction = 1; + else if ( dirstr == "left" ) + fm->direction = 2; + else if ( dirstr = "right" ) + fm->direction = 3; + else + fm->direction = 0; // Front default + fm->coneangle = coneangle; + fm->distance = dist; + fm->enemyyaw = eyaw; + fm->chance = chance; + + finishingMoveList.AddObject(fm); +} + +//-------------------------------------------------------------- +// +// Name: clearFinishingMove +// Class: Player +// +// Description: Clears the finishing move list. +// +// Parameters: Event *ev -- no params +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::clearFinishingMove( Event *ev ) +{ + int i; + for ( i=1; i<=finishingMoveList.NumObjects(); i++ ) + { + FinishingMove *fm = finishingMoveList.ObjectAt(i); + delete fm; + } + finishingMoveList.FreeObjectList(); +} + +//-------------------------------------------------------------- +// +// Name: doFinishingMove +// Class: Player +// +// Description: Fires off the finishing move that's currently prepared +// +// Parameters: Event *ev -- no params +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::doFinishingMove( Event *ev ) +{ + movecontrol = MOVECONTROL_ABSOLUTE; + + if ( _finishState.length() ) + { + State* newState = statemap_Torso->FindState( _finishState ); + if ( newState ) + { + EvaluateState( newState ); + _doingFinishingMove = true; + _finishActor->SetState("FINISH_ME"); + } + else + gi.WDPrintf( "Could not find state %s on doFinishingMove\n", _finishState.c_str() ); + } +} + +//-------------------------------------------------------------- +// +// Name: forceTimeScale +// Class: Player +// +// Description: Forces timescale for the game +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::forceTimeScale( Event *ev ) +{ + float timescale = 1.0f; + if ( ev->NumArgs() > 0 ) + timescale = ev->GetFloat( 1 ); + + char tmp[15]; + sprintf(tmp, "timescale %f", timescale); + + G_SendCommandToPlayer(edict, tmp); +} + +//-------------------------------------------------------------- +// +// Name: freezePlayer +// Class: Player +// +// Description: Freezes the player +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::freezePlayer( Event *ev ) +{ + bool freeze = true; + if ( ev->NumArgs() > 0 ) + freeze = ev->GetBoolean( 1 ); + + level.playerfrozen = freeze; +} + +//-------------------------------------------------------------- +// +// Name: immobilizePlayer +// Class: Player +// +// Description: Immobilizes the player (can only look around) +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::immobilizePlayer( Event *ev ) +{ + bool freeze = true; + + if ( ev->NumArgs() > 0 ) + freeze = ev->GetBoolean( 1 ); + + if ( freeze ) + flags |= FL_IMMOBILE; + else + flags &= ~FL_IMMOBILE; + +} + +//-------------------------------------------------------------- +// +// Name: setAttackType +// Class: Player +// +// Description: Sets the attack type of the attack +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::setAttackType( Event *ev ) +{ + _attackType = ev->GetString( 1 ); +} + + +//-------------------------------------------------------------- +// +// Name: getGameplayAnim +// Class: Player +// +// Description: Gets the anim name from the gameplay database +// based on the current gameplayAnimIdx +// +// Parameters: const str& objname -- The object +// +// Returns: const str +// +//-------------------------------------------------------------- +const str Player::getGameplayAnim(const str& objname) +{ + if ( !objname.length() || !getArchetype().length() ) + return ""; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str scopestr = getArchetype() + "." + objname; + if ( !gpm->hasObject(scopestr) ) + return ""; + + char tmpstr[6]; + sprintf(tmpstr, "%d", _gameplayAnimIdx); + return gpm->getStringValue(scopestr, tmpstr); +} + + +//-------------------------------------------------------------- +// +// Name: nextGameplayAnim +// Class: Player +// +// Description: Increment the the gameplay animation up to +// the maxchain value in the database. Wrap +// around afterwards. +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::nextGameplayAnim( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + str objname = ev->GetString( 1 ); + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str scopestr = "CurrentPlayer.ChainedMoves"; + if ( !gpm->hasObject(scopestr) ) + return; + + int max = (int)gpm->getFloatValue(scopestr, "value"); + max++; // Increment to compensate for 0 based skill system, we always have to play the first anim + if ( _gameplayAnimIdx < max ) + _gameplayAnimIdx++; + else + _gameplayAnimIdx = 1; + + // Max of 4 chains deep. + if ( _gameplayAnimIdx > 4 ) + _gameplayAnimIdx = 1; +} + +//-------------------------------------------------------------- +// +// Name: setGameplayAnim +// Class: Player +// +// Description: Sets the gameplayAnimIdx to the number specified +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::setGameplayAnim( Event *ev ) +{ + int val = 1; + if ( ev->NumArgs() > 0) + val = ev->GetInteger( 1 ); + + _gameplayAnimIdx = val; +} + + +//----------------------------------------------------- +// +// Name: SetDisableUseWeapon +// Class: Player +// +// Description: Can disable the use of a new weapon +// +// Parameters: ev - the event that sets the disableUseWeapon +// +// Returns: None +//----------------------------------------------------- +void Player::setDisableUseWeapon( Event* ev) +{ + if(ev->NumArgs() > 0) + { + _disableUseWeapon = ev->GetBoolean(1); + } + else + { + _disableUseWeapon = true; + } + + + Com_Printf("DisableUseWeapon %d", _disableUseWeapon); + +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::setDisableInventory( Event* ev ) +{ + if(ev->NumArgs() > 0) + { + disableInventory(); + return; + } + else if( ev->GetFloat(1)) + { + enableInventory(); + return; + } + + disableInventory(); +} + +//-------------------------------------------------------------- +// +// Name: skillChanged +// Class: Player +// +// Description: Handles skills when they change +// +// Parameters: const str& objname -- Object in the database that has changed +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::skillChanged(const str& objname) +{ + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(objname) ) + return; + + float value = gpm->getFloatValue(objname, "value"); + if ( objname == "CurrentPlayer.BonusHP" && value > 0.0f ) + { + if ( gpm->hasFormula("BonusHP") ) + { + GameplayFormulaData fd(this); + float hp = gpm->calculate("BonusHP", fd); + float maxhp = max_health + ( max_health * hp ); + setMaxHealth(maxhp); + } + } +} + + +//-------------------------------------------------------------- +// +// Name: EquipItems +// Class: Player +// +// Description: Equips items from the database +// +// Parameters: Event *ev -- not used +// +// Returns: None +// +//-------------------------------------------------------------- +void Player::equipItems( Event *ev ) +{ + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + + str itemName, modelName; + + itemName = gpm->getStringValue("CurrentPlayer.DualWield", "name"); + modelName = gpm->getStringValue(itemName, "model"); + if ( modelName.length() ) + giveItem(modelName); + + itemName = gpm->getStringValue("CurrentPlayer.TwoHanded", "name"); + modelName = gpm->getStringValue(itemName, "model"); + if ( modelName.length() ) + giveItem(modelName); + + itemName = gpm->getStringValue("CurrentPlayer.Ranged", "name"); + modelName = gpm->getStringValue(itemName, "model"); + if ( modelName.length() ) + giveItem(modelName); + + itemName = gpm->getStringValue("CurrentPlayer.Ring", "name"); + modelName = gpm->getStringValue(itemName, "model"); + if ( modelName.length() ) + giveItem(modelName); + + itemName = gpm->getStringValue("CurrentPlayer.Amulet", "name"); + modelName = gpm->getStringValue(itemName, "model"); + if ( modelName.length() ) + giveItem(modelName); + + useWeapon("DualWield", WEAPON_RIGHT); +} + + +//-------------------------------------------------------------- +// +// Name: getFreeInventorySlot +// Class: Player +// +// Description: Finds a free inventory slot from the database +// +// Parameters: None +// +// Returns: Object name of the free slot, or "" if the inventory +// is full. +// +//-------------------------------------------------------------- +const str Player::getFreeInventorySlot() +{ + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + + int i; + char tmpstr[128]; + for ( i=1; i<15; i++ ) + { + sprintf(tmpstr,"PartyInventory.Slot%d",i); + str invitem = gpm->getStringValue(tmpstr, "name"); + if ( invitem == "Empty" ) + return tmpstr; + } + + return ""; +} + +void Player::usePlayer( Event *ev ) +{ + Player *usingPlayer = NULL; + Equipment *equipment = NULL; + Entity *owner; + + if ( multiplayerManager.inMultiplayer() ) + { + Entity *entity; + + entity = ev->GetEntity( 1 ); + + if ( entity->isSubclassOf( Player ) ) + { + usingPlayer = (Player *)entity; + } + else if ( entity->isSubclassOf( Equipment ) ) + { + equipment = (Equipment *)entity; + + owner = equipment->GetOwner(); + + if ( owner && owner->isSubclassOf( Player ) ) + { + usingPlayer = (Player *)owner; + } + } + + multiplayerManager.playerUsed( this, usingPlayer, equipment ); + } +} + + +//----------------------------------------------------- +// +// Name: addRosterTeammate1 +// Class: Player +// +// Description: Sets the third teammate in the roster. +// +// Parameters: ev +// +// Returns: None +//----------------------------------------------------- +void Player::addRosterTeammate1( Event* ev ) +{ +} + + +//----------------------------------------------------- +// +// Name: addRosterTeammate2 +// Class: Player +// +// Description: Adds the second teammate from the roster. +// +// Parameters: ev +// +// Returns: None +//----------------------------------------------------- +void Player::addRosterTeammate2( Event* ev ) +{ +} + + +//----------------------------------------------------- +// +// Name: addRosterTeammate3 +// Class: Player +// +// Description: Adds the third teammate from the roster. +// +// Parameters: ev +// +// Returns: None +//----------------------------------------------------- +void Player::addRosterTeammate3( Event* ev ) +{ + +} + + +//----------------------------------------------------- +// +// Name: addRosterTeammate4 +// Class: Player +// +// Description: Adds the fourth teammate from the roster. +// +// Parameters: ev +// +// Returns: None +//----------------------------------------------------- +void Player::addRosterTeammate4( Event* ev ) +{ + +} + + +//----------------------------------------------------- +// +// Name: removeRosterTeammate1 +// Class: Player +// +// Description: Removes the first teammate from the roster. +// +// Parameters: ev +// +// Returns: None +//----------------------------------------------------- +void Player::removeRosterTeammate1( Event* ev ) +{ +} + + +//----------------------------------------------------- +// +// Name: removeRosterTeammate2 +// Class: Player +// +// Description: Removes the second teammate from the roster. +// +// Parameters: ev +// +// Returns: None +//----------------------------------------------------- +void Player::removeRosterTeammate2( Event* ev ) +{ +} + + +//----------------------------------------------------- +// +// Name: removeRosterTeammate3 +// Class: Player +// +// Description: Removes the third teammate from the roster. +// +// Parameters: ev +// +// Returns: None +//----------------------------------------------------- +void Player::removeRosterTeammate3( Event* ev ) +{ +} + + +//----------------------------------------------------- +// +// Name: removeRosterTeammate4 +// Class: Player +// +// Description: Removes the fourth teammate from the roster. +// +// Parameters: ev +// +// Returns: None +//----------------------------------------------------- +void Player::removeRosterTeammate4( Event* ev ) +{ +} + + +bool Player::isButtonDown( int button ) +{ + if ( last_ucmd.buttons & button ) + return true; + else + return false; +} + +void Player::notifyPlayerOfMultiplayerEvent( const char *eventName, const char *eventItemName, Player *eventPlayer ) +{ + // Put stuff here :) +} + +void Player::touchingLadder( Trigger *ladder, const Vector &normal, float top ) +{ + // Make sure we are allowed to get on the ladder time wise + + if ( level.time < _nextLadderTime ) + return; + + // If we are near the top of the ladder and we have a groundentity don't get on the ladder + + if ( groundentity && origin.z > top - 48 ) + return; + + // Save off all necessary info + + _onLadder = true; + _ladderNormal = normal; + _ladderTop = top; +} + +void Player::warp( Event *ev ) +{ + setOrigin( ev->GetVector( 1 ) ); + NoLerpThisFrame(); + client->ps.pm_flags |= PMF_TIME_TELEPORT; + CameraCut(); +} + +void Player::hudPrint( Event *ev ) +{ + hudPrint( ev->GetString( 1 ) ); +} + +void Player::hudPrint( const str &string ) +{ + str command; + + // Build the hud print command to send to the client + + command = "hudprint \""; + command += string; + command += "\"\n"; + + // Send the HUD print command + + gi.SendServerCommand( edict - g_entities, command.c_str() ); +} + +void Player::setItemText( int itemIcon, const str &itemText ) +{ + CancelEventsOfType( EV_Player_ClearItemText ); + + _itemIcon = itemIcon; + _itemText = itemText; + + PostEvent( EV_Player_ClearItemText, 2.0f ); +} + +void Player::clearItemText( void ) +{ + _itemIcon = 0; + _itemText = ""; + + CancelEventsOfType( EV_Player_ClearItemText ); +} + +void Player::clearItemText( Event *ev ) +{ + clearItemText(); +} + +void Player::shotFired( void ) +{ + if ( p_heuristics ) + { + p_heuristics->IncrementShotsFired(); + } + + ++client->ps.stats[ STAT_SHOTS_FIRED ]; + +} + +void Player::shotHit( void ) +{ + if ( p_heuristics ) + { + p_heuristics->IncrementShotsHit(); + } + + ++client->ps.stats[ STAT_SHOTS_HIT ]; +} + + +void Player::cinematicStarted( void ) +{ + + // Turn off any viewmodes + + setViewMode( "normal" ); + + // Kill the zoom if any + + Weapon *weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( weapon ) + { + weapon->ProcessEvent( "endZoom" ); + + // Force the weapon to idle + + weapon->ForceIdle(); + } +} + +void Player::cinematicStopped( void ) +{ + Weapon *weapon; + Equipment *equipment; + + weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( weapon && weapon->isSubclassOf( Equipment ) ) + { + equipment = (Equipment *)weapon; + + equipment->updateMode(); + } + + // Force the player's state back to idle + + SetAnim( "stand_idle", legs, true ); + SetAnim( "stand_idle", torso, true ); + LoadStateTable(); +} + +void Player::loadUseItem( const str &item ) +{ + Event *event; + str itemName; + + itemName = item; + + if ( stricmp( item.c_str(), "tricorder" ) == 0 ) + { + if ( HasItem( "Tricorder-stx" ) ) + itemName = "Tricorder-stx"; + else if ( HasItem( "Tricorder-rom" ) ) + itemName = "Tricorder-rom"; + } + + if ( itemName.length() ) + { + event = new Event( EV_Player_UseItem ); + event->AddString( itemName ); + ProcessEvent( event ); + } +} + +void Player::setValidPlayerModel( Event *ev ) +{ + _validPlayerModel = true; +} + +void Player::setVoteText( const str &voteText ) +{ + _voteText = voteText; +} + +void Player::clearVoteText( void ) +{ + _voteText = ""; +} + +void Player::incrementSecretsFound( void ) +{ + _secretsFound++; + + // Update the cvar if we are greater + + if ( _secretsFound > g_secretCount->integer ) + { + gi.cvar_set( "g_secretCount", va( "%d", _secretsFound ) ); + } +} + +void Player::addHud( Event *ev ) +{ + addHud( ev->GetString( 1 ) ); +} + +void Player::addHud( const str &hudName ) +{ + if ( !_hudList.ObjectInList( hudName ) ) + { + addHudToClient( hudName ); + + _hudList.AddObject( hudName ); + } +} + +void Player::removeHud( Event *ev ) +{ + removeHud( ev->GetString( 1 ) ); +} + +void Player::removeHud( const str &hudName ) +{ + if ( _hudList.ObjectInList( hudName ) ) + { + removeHudFromClient( hudName ); + + _hudList.RemoveObject( hudName ); + } +} + +bool Player::needToSendAllHudsToClient( void ) +{ + if ( _needToSendHuds && _started ) + return true; + else + return false; +} + +void Player::sendAllHudsToClient( void ) +{ + int i; + str commandString; + str hudName; + + if ( _needToSendHuds ) + { + for( i = 1 ; i <= _hudList.NumObjects() ; i++ ) + { + hudName = _hudList.ObjectAt( i ); + + addHudToClient( hudName ); + } + } + + _needToSendHuds = false; +} + +void Player::clearAllHuds( void ) +{ + int i; + str commandString; + str hudName; + + for( i = 1 ; i <= _hudList.NumObjects() ; i++ ) + { + hudName = _hudList.ObjectAt( i ); + + removeHudFromClient( hudName ); + } + + _hudList.ClearObjectList(); +} + +void Player::addHudToClient( const str &hudName ) +{ + str commandString; + + commandString = "stufftext \"ui_addhud "; + commandString += hudName; + commandString += "\"\n"; + + gi.SendServerCommand( entnum, commandString.c_str() ); +} + +void Player::removeHudFromClient( const str &hudName ) +{ + str commandString; + + commandString = "stufftext \"ui_removehud "; + commandString += hudName; + commandString += "\"\n"; + + gi.SendServerCommand( entnum, commandString.c_str() ); +} + +void Player::killAllDialog( Event *ev ) +{ + killAllDialog(); +} + +void Player::killAllDialog() +{ + Actor *theActor; + int i; + + for ( i = 1; i <= SleepList.NumObjects(); i++ ) + { + theActor = (Actor*)SleepList.ObjectAt( i ); + theActor->StopDialog(); + } + + for ( i = 1; i <= ActiveList.NumObjects(); i++ ) + { + theActor = (Actor*)ActiveList.ObjectAt( i ); + theActor->StopDialog(); + } +} + +void Player::clearTempAttachments( void ) +{ + int i; + Entity *child; + + if ( bind_info ) + { + for( i = 0; i < MAX_MODEL_CHILDREN ; i++ ) + { + if ( bind_info->children[ i ] == ENTITYNUM_NONE ) + continue; + + child = ( Entity * )G_GetEntity( bind_info->children[ i ] ); + + if ( child ) + { + if ( child->isSubclassOf( Projectile ) || child->CancelEventsOfType( EV_Remove ) ) + { + child->PostEvent( EV_Remove, 0.0f ); + } + } + } + } +} + +void Player::setSkill( int skill ) +{ + _skillLevel = skill; + + if ( _skillLevel < 0 ) + _skillLevel = 0; + else if ( _skillLevel > 3 ) + _skillLevel = 3; + + +} + +int Player::getSkill( void ) +{ + return _skillLevel; +} + +void Player::forceMoveType( Event *ev ) +{ + str moveTypeName; + + moveTypeName = ev->GetString( 1 ); + + if ( stricmp( moveTypeName, "secret" ) == 0 ) + _forcedMoveType = PM_SECRET_MOVE_MODE; + else if ( stricmp( moveTypeName, "3rdPerson" ) == 0 ) + _forcedMoveType = PM_3RD_PERSON; + else if ( stricmp( moveTypeName, "none" ) == 0 ) + _forcedMoveType = PM_NONE; +} + +void Player::isPlayerOnGround( Event *ev ) +{ + float onGround; + + if ( client->ps.walking ) + { + onGround = 1.0f; + } + else + { + onGround = 0.0f; + } + + + ev->ReturnFloat( onGround ); +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::setBranchDialogActor( const Actor* actor) +{ + if( _branchDialogActor != 0) + _branchDialogActor->clearBranchDialog(); + + _branchDialogActor = (Actor*)actor; +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Player::clearBranchDialogActor( void ) +{ + if( _branchDialogActor != 0) + _branchDialogActor->clearBranchDialog( ); + + _branchDialogActor = 0; +} + +void Player::setBackpackAttachOffset( Event *ev ) +{ + _backpackAttachOffset = ev->GetVector( 1 ); +} + +void Player::setBackpackAttachAngles( Event *ev ) +{ + _backpackAttachAngles = ev->GetVector( 1 ); +} + +Vector Player::getBackpackAttachOffset( void ) +{ + return _backpackAttachOffset; +} + +Vector Player::getBackpackAttachAngles( void ) +{ + return _backpackAttachAngles; +} + +void Player::setFlagAttachOffset( Event *ev ) +{ + _flagAttachOffset = ev->GetVector( 1 ); +} + +void Player::setFlagAttachAngles( Event *ev ) +{ + _flagAttachAngles = ev->GetVector( 1 ); +} + +Vector Player::getFlagAttachOffset( void ) +{ + return _flagAttachOffset; +} + +Vector Player::getFlagAttachAngles( void ) +{ + return _flagAttachAngles; +} + +bool Player::canRegenerate( void ) +{ + if ( _powerup && !_powerup->canOwnerRegenerate() ) + return false; + + if ( _rune && !_rune->canOwnerRegenerate() ) + return false; + + return true; +} + +void Player::modelChanged( void ) +{ + if ( _powerup ) + { + removePowerupEffect( _powerup ); + addPowerupEffect( _powerup ); + } + + if ( _rune ) + { + removePowerupEffect( _rune ); + addPowerupEffect( _rune ); + } +} + +void Player::setBackupModel( Event *ev ) +{ + setBackupModel( ev->GetString( 1 ) ); +} + +void Player::setBackupModel( const str &modelName ) +{ + gi.setviewmodel( edict, modelName ); +} diff --git a/dlls/game/player.h b/dlls/game/player.h new file mode 100644 index 0000000..1673e28 --- /dev/null +++ b/dlls/game/player.h @@ -0,0 +1,1687 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/player.h $ +// $Revision:: 242 $ +// $Author:: Steven $ +// $Date:: 9/18/03 9:02a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Class definition of the player. +// + +//============================== +// Forward Declarations +//============================== +class Player; +class Powerup; +class PowerupBase; +class Rune; +class HoldableItem; + +#ifndef __PLAYER_H__ +#define __PLAYER_H__ + +#include "g_local.h" +#include "vector.h" +#include "entity.h" +#include "weapon.h" +#include "WeaponDualWield.h" +#include "sentient.h" +#include "navigate.h" +#include "misc.h" +#include "bspline.h" +#include "camera.h" +#include "specialfx.h" +#include "characterstate.h" +#include "actor.h" +#include "vehicle.h" +#include "playerheuristics.h" + +extern Event EV_Player_EndLevel; +extern Event EV_Player_GiveCheat; +extern Event EV_Player_GodCheat; +extern Event EV_Player_NoTargetCheat; +extern Event EV_Player_NoClipCheat; +extern Event EV_Player_GameVersion; +extern Event EV_Player_Fov; +extern Event EV_Player_WhatIs; +extern Event EV_Player_Respawn; +extern Event EV_Player_WatchActor; +extern Event EV_Player_StopWatchingActor; +extern Event EV_Player_DoStats; +extern Event EV_Player_DeadBody; +extern Event EV_Weapon_DonePutaway; +extern Event EV_Weapon_TargetIdleThink; +extern Event EV_Driver_AnimDone; +extern Event EV_Weapon_DoneAnimating; +extern Event EV_Player_DoUse; +extern Event EV_ViewThing_SpawnFromTS; +extern Event EV_Player_MissionFailed; +extern Event EV_Weapon_Shoot; +extern Event EV_DetachAllChildren; +extern Event EV_Player_RemovePowerup; +extern Event EV_Player_UseItem; +extern Event EV_Player_SetStat; +extern Event EV_Player_Weapon; + +#define HEAD_TAG 0 +#define TORSO_TAG 1 +#define L_ARM_TAG 2 +#define R_ARM_TAG 3 +#define MOUTH_TAG 4 + +enum painDirection_t + { + PAIN_NONE, + PAIN_FRONT, + PAIN_LEFT, + PAIN_RIGHT, + PAIN_REAR + }; + +typedef enum +{ + NONE, + FRIEND, + ENEMY, + OJBECT +} TargetTypes; + +enum PlayerCameraModes +{ + PLAYER_CAMERA_MODE_NORMAL, + PLAYER_CAMERA_MODE_ACTOR, + PLAYER_CAMERA_MODE_ENTITY_WATCHING, + PLAYER_CAMERA_MODE_NO_CLIP, + PLAYER_CAMERA_MODE_CINEMATIC, +}; + +typedef void ( Player::*movecontrolfunc_t )( void ); + + +//------------------------- CLASS ------------------------------ +// +// Name: FinishingMove +// Base Class: Class +// +// Description: Finishing move class +// +// Method of Use: +//-------------------------------------------------------------- +class FinishingMove : public Class + { + public: + FinishingMove() { } + ~FinishingMove() { } + + str statename; + int direction; + float coneangle; + float distance; + float chance; + float enemyyaw; + void Archive( Archiver &arc ); + }; + +inline void FinishingMove::Archive( Archiver &arc ) + { + Class::Archive( arc ); + + arc.ArchiveString( &statename ); + arc.ArchiveInteger( &direction ); + arc.ArchiveFloat( &coneangle ); + arc.ArchiveFloat( &distance ); + arc.ArchiveFloat( &chance ); + arc.ArchiveFloat( &enemyyaw ); + } + +//------------------------- CLASS ------------------------------ +// +// Name: WeaponSetItem +// Base Class: Class +// +// Description: This is a dual weapon +// +// Method of Use: +//-------------------------------------------------------------- +class WeaponSetItem : public Class + { + public: + str name; + weaponhand_t hand; + void Archive( Archiver &arc ); + }; + +inline void WeaponSetItem::Archive( Archiver &arc ) + { + Class::Archive( arc ); + + arc.ArchiveString( &name ); + ArchiveEnum( hand, weaponhand_t ); + } + + +//------------------------- CLASS ------------------------------ +// +// Name: Player +// Base Class: Sentient +// +// Description: This is the player. +// +// Method of Use: +//-------------------------------------------------------------- +class Player : public Sentient + { + public: + CLASS_PROTOTYPE( Player ); + Player(); + ~Player(); + + static Condition Conditions[]; + + char lastTeam[ 16 ]; + qboolean mp_savingDemo; + float userFov; + + private: + friend class Camera; + friend class Vehicle; + friend class HorseVehicle; + + static movecontrolfunc_t MoveStartFuncs[]; + + StateMap *statemap_Legs; + StateMap *statemap_Torso; + + State *currentState_Legs; + State *currentState_Torso; + str last_torso_anim_name; + str last_leg_anim_name; + movecontrol_t movecontrol; + int last_camera_type; + + ActiveWeapon newActiveWeapon; + EntityPtr head_target; + float look_at_time; + EntityPtr targetEnemy; + bool targetEnemyLocked; + + qboolean shield_active; + + qboolean dual_wield_active; + Container dual_wield_weaponlist; + + WeaponPtr holsteredWeapons[MAX_ACTIVE_WEAPONS]; + + str partAnim[ 3 ]; + + bool animdone_Legs; + bool animdone_Torso; + + Vector oldvelocity; + Vector old_v_angle; + Vector oldorigin; + float animspeed; + float airspeed; + + // Call Volume Stuff + str currentCallVolume; + + // Armor Stuff + //float ArmorValue; + + // blend + float blend[ 4 ]; // rgba full screen effect + float fov; // horizontal field of view + float _userFovChoice; + + // vehicle stuff + VehiclePtr vehicle; + + // aiming direction + Vector v_angle; + + int buttons; + int new_buttons; + float respawn_time; + + int last_attack_button; + + // damage blend + float damage_blood; + float damage_alpha; + Vector damage_blend; + Vector damage_from; // the direciton of incoming damage + Vector damage_angles; // the damage angle adjustment that should be applied to the player + float damage_count; // incoming damage which is decayed over time + float bonus_alpha; + float next_drown_time; // how long until we drown again + float next_painsound_time; // when we should make a pain sound again + float air_finished; + int old_waterlevel; + float drown_damage; + float _flashAlpha; + Vector _flashBlend; + float _flashMaxTime; + float _flashMinTime; + + str waitForState; // if not null, than player will clear waitforplayer when this state is hit + CameraPtr camera; + CameraPtr actor_camera; + CameraPtr cool_camera; + EntityPtr entity_to_watch; + float maximumAngleToWatchedEntity; + bool watchEntityForEntireDuration; + int playerCameraMode; + + // Music Stuff + float action_level; + int music_current_mood; + int music_fallback_mood; + float music_current_volume; + float music_saved_volume; + float music_volume_fade_time; + int reverb_type; + float reverb_level; + qboolean music_forced; + bool _allowMusicDucking; + bool _allowActionMusic; + + qboolean gibbed; + float pain; + painDirection_t pain_dir; + meansOfDeath_t pain_type; + bool take_pain; + float accumulated_pain; + float nextpaintime; + bool knockdown; + + bool canfall; + bool falling; + int feetfalling; + Vector falldir; + + bool hardimpact; + usercmd_t last_ucmd; + + // Movement Variables + //Vector base_righthand_pos; + //Vector base_lefthand_pos; + //Vector righthand_pos; + //Vector lefthand_pos; + Vector base_rightfoot_pos; + Vector base_leftfoot_pos; + Vector rightfoot_pos; + Vector leftfoot_pos; + + bool _onLadder; + Vector _ladderNormal; + float _ladderTop; + float _nextLadderTime; + + int pm_lastruntime; // the last runtime before Pmove + + float animheight; + + Vector yaw_forward; + Vector yaw_left; + + EntityPtr atobject; + float atobject_dist; + Vector atobject_dir; + + EntityPtr toucheduseanim; + int useanim_numloops; // number of times to loop the animation + EntityPtr useitem_in_use; // used so that we can trigger targets after the useitem is finished + EntityPtr cool_item; // we picked up a cool item and are waiting to show it off + str cool_dialog; // dialog to play when we get it + str cool_anim; // anim to play after the cinematic + + int moveresult; + qboolean do_rise; // whether or not the player should go into the rise animation + qboolean weapons_holstered_by_code; // whether or not we initiated a holstering for a specific animation + + qboolean projdetonate; + + // leg angles + qboolean yawing; + qboolean yawing_left; + qboolean yawing_right; + qboolean adjust_torso; + + Vector torsoAngles; + Vector headAngles; + Vector headAimAngles; + Vector torsoAimAngles; + + float damage_multiplier; + + // dummyPlayer stuff + qboolean fakePlayer_active; + ActorPtr fakePlayer; + + bool dont_turn_legs; + float specialMoveCharge; + float specialMoveEndTime; + float specialMoveChargeTime; + int points; + float playerKnockback; + float knockbackMultiplier; + bool changedStanceTorso; + bool changedStanceLegs; + int stanceNumber; + bool incomingMeleeAttack; + float bendTorsoMult; + str lastActionType; + int meleeAttackFlags; + bool changingChar; + + + Container meleeAttackerList; + Container legs_conditionals; + Container torso_conditionals; + Container finishableList; + Container finishingMoveList; + + SafePtr _powerup; + SafePtr _rune; + SafePtr _holdableItem; + + float _nextEnergyTransferTime; + bool _canTransferEnergy; + + bool _doDamageScreenFlash; + bool _isThirdPerson; + + Actor* _finishActor; + str _finishState; + bool _doingFinishingMove; + + bool _autoSwitchWeapons; + + bool _usingEntity; + str _attackType; + int _gameplayAnimIdx; + bool _disableUseWeapon; + EntityPtr _targetSelectedHighlight; + EntityPtr _targetLockedHighlight; + + bool _infoHudOn; + int _nextRegenTime; + float _useEntityStartTimer; + + int _objectiveNameIndex; + unsigned int _objectiveStates; + unsigned int _informationStates; + + EntityPtr _targetedEntity; + + int _dialogEntnum; + int _dialogSoundIndex; + int _dialogTextSoundIndex; + + float _crossHairXOffset; + float _crossHairYOffset; + + float _lastDamagedTimeFront; + float _lastDamagedTimeBack; + float _lastDamagedTimeLeft; + float _lastDamagedTimeRight; + + bool _updateGameFrames; + int _totalGameFrames; + + float _nextPainShaderTime; + meansOfDeath_t _lastPainShaderMod; + + int _itemIcon; + str _itemText; + + str _voteText; + + bool _validPlayerModel; + + int _secretsFound; + + Container _hudList; + bool _needToSendHuds; + + bool _started; + + int _skillLevel; + + pmtype_t _forcedMoveType; + + ActorPtr _branchDialogActor; + bool _needToSendBranchDialog; + + Vector _backpackAttachOffset; + Vector _backpackAttachAngles; + + Vector _flagAttachOffset; + Vector _flagAttachAngles; + + bool _cameraCutThisFrame; + + // Conditional Functions + public: + qboolean returntrue( Conditional &condition ); + qboolean checkturnleft( Conditional &condition ); + qboolean checkturnright( Conditional &condition ); + qboolean checkforward( Conditional &condition ); + qboolean checkbackward( Conditional &condition ); + qboolean checkstrafeleft( Conditional &condition ); + qboolean checkstraferight( Conditional &condition ); + qboolean checkduck( Conditional &condition ); + qboolean checkleanleft(Conditional &condition); + qboolean checkleanright(Conditional &condition); + qboolean checkjump( Conditional &condition ); + qboolean checkcrouch( Conditional &condition ); + qboolean checkjumpflip( Conditional &condition ); + qboolean checkanimdone_legs( Conditional &condition ); + qboolean checkanimdone_torso( Conditional &condition ); + qboolean checkattackleft( Conditional &condition ); + qboolean checkattackright( Conditional &condition ); + qboolean checkattackbuttonleft( Conditional &condition ); + qboolean checkattackbuttonright( Conditional &condition ); + qboolean checksneak( Conditional &condition ); + qboolean checkrun( Conditional &condition ); + qboolean checkwasrunning( Conditional &condition ); + qboolean checkholsterweapon( Conditional &condition ); + qboolean checkuse( Conditional &condition ); + qboolean checkcanturn( Conditional &condition ); + qboolean checkcanwallhug( Conditional &condition ); + qboolean checkblocked( Conditional &condition ); + qboolean checkhangatwall( Conditional &condition ); + qboolean checkonground( Conditional &condition ); + qboolean check22degreeslope( Conditional &condition ); + qboolean check45degreeslope( Conditional &condition ); + qboolean checkrightleghigh( Conditional &condition ); + qboolean checkleftleghigh( Conditional &condition ); + qboolean checkfacingupslope( Conditional &condition ); + qboolean checkfacingdownslope( Conditional &condition ); + qboolean checkcanfall( Conditional &condition ); + qboolean checkatdoor( Conditional &condition ); + qboolean checkfalling( Conditional &condition ); + qboolean checkgroundentity( Conditional &condition ); + qboolean checkhardimpact( Conditional &condition ); + qboolean checkdead( Conditional &condition ); + qboolean checkhealth( Conditional &condition ); + qboolean checkpain( Conditional &condition ); + qboolean checkpaindirection( Conditional &condition ); + qboolean checkaccumulatedpain( Conditional &condition ); + qboolean checkpaintype( Conditional &condition ); + qboolean checkpainthreshold( Conditional &condition ); + qboolean checkknockdown( Conditional &condition ); + qboolean checklegsstate( Conditional &condition ); + qboolean checktorsostate( Conditional &condition ); + qboolean checkatuseanim( Conditional &condition ); + qboolean checkatuseentity( Conditional &condition ); + qboolean checktouchuseanim( Conditional &condition ); + qboolean checkatuseobject( Conditional &condition ); + qboolean checkloopuseobject( Conditional &condition ); + qboolean checkuseweaponleft( Conditional &condition ); + qboolean checknewweapon( Conditional &condition ); + qboolean checkuseweapon( Conditional &condition ); + qboolean checkhasweapon( Conditional &condition ); + qboolean checkweaponactive( Conditional &condition ); + qboolean checkhasdualweapon( Conditional &condition ); + qboolean checkweaponreload( Conditional &condition ); + qboolean checkweaponswitchmode( Conditional &condition ); + qboolean checkweaponinmode( Conditional &condition ); + qboolean checkputawayleft( Conditional &condition ); + qboolean checkputawayright( Conditional &condition ); + qboolean checkputawayboth( Conditional &condition ); + qboolean checktargetacquired( Conditional &condition ); + qboolean checkanyweaponactive( Conditional &condition ); + qboolean checkstatename( Conditional &condition ); + qboolean checkattackblocked( Conditional &condition ); + qboolean checkblockdelay( Conditional &condition ); + qboolean checkcanstand( Conditional &condition ); + qboolean checkpush( Conditional &condition ); + qboolean checkpull( Conditional &condition ); + qboolean checkOnLadder( Conditional &condition ); + qboolean checkdualwield( Conditional &condition ); + qboolean checkdualweapons( Conditional &condition ); + qboolean checkuseanimfinished( Conditional &condition ); + qboolean checkchance( Conditional &condition ); + qboolean checkturnedleft( Conditional &condition ); + qboolean checkturnedright( Conditional &condition ); + qboolean checkinvehicle( Conditional &condition ); + qboolean checksolidforward( Conditional &condition ); + qboolean checkholstercomplete( Conditional &condition ); + qboolean checkweaponhasammo( Conditional &condition ); + qboolean checkweaponhasfullammo( Conditional &condition ); + qboolean checkweaponhasinvammo( Conditional &condition ); + qboolean checkrise( Conditional &condition ); + qboolean checkweaponsholstered( Conditional &condition ); + qboolean checkdualweaponreadytofire( Conditional &condition ); + qboolean checkweaponreadytofire( Conditional &condition ); + qboolean checkfakeplayeractive( Conditional &condition ); + qboolean checkfakeplayerholster( Conditional &condition ); + qboolean checkweaponlowered( Conditional &condition ); + qboolean checkweaponfiretimer( Conditional &condition ); + qboolean checkweaponforcereload( Conditional &condition ); + qboolean checkweaponcanreload( Conditional &condition ); + qboolean checkweaponfullclip( Conditional &condition ); + qboolean checkweapondonefiring( Conditional &condition ); + qboolean checkspecialmovecharge( Conditional &condition ); + qboolean checkcharavailable( Conditional &condition ); + qboolean checkstancechangedtorso( Conditional &condition ); + qboolean checkstancechangedlegs( Conditional &condition ); + qboolean checkstance( Conditional &condition ); + qboolean checkpoints( Conditional &condition ); + qboolean checkincomingmeleeattack( Conditional &condition ); + qboolean checkfinishingmove( Conditional &condition ); + qboolean checkusingentity( Conditional &condition ); + qboolean checkthirdperson( Conditional &condition ); + qboolean checkPropChance( Conditional &condition ); + qboolean checkPropExists( Conditional &condition ); + qboolean checkEndAnimChain( Conditional &condition ); + qboolean checkWeaponType( Conditional &condition ); + qboolean checkHasAnim( Conditional &condition ); + qboolean checkIsWeaponControllingProjectile( Conditional &condition ); + + // Movecontrol Functions + void StartPush( void ); + void StartUseAnim( void ); + void StartLoopUseAnim( void ); + void SetupUseObject( void ); + void StartUseObject( Event *ev ); + void FinishUseObject( Event *ev ); + void FinishUseAnim( Event *ev ); + void StepUp( Event *ev ); + void Turn( Event *ev ); + void turnTowardsEntity( Event *ev ); + void TurnUpdate( Event *ev ); + void TurnLegs( Event *ev ); + void DontTurnLegs( Event *ev ); + void PassEventToVehicle( Event *ev ); + + // Init Functions + void Init( void ); + void InitSound( void ); + void InitEdict( void ); + void InitClient( void ); + void InitPhysics( void ); + //void InitPowerups( void ); + void InitWorldEffects( void ); + void InitWeapons( void ); + void InitView( void ); + void InitModel( const char *modelName = NULL ); + void InitState( void ); + void InitHealth( void ); + void InitInventory( void ); + void InitStats( void ); + void ChooseSpawnPoint( void ); + void EndLevel( Event *ev ); + void Respawn( Event *ev ); + void LevelCleanup( void ); + + void SetDeltaAngles( void ); + virtual void setAngles( const Vector &ang ); + void SetTargetedEntity(EntityPtr entity); + EntityPtr GetTargetedEntity(void) { return _targetedEntity; } + + void CheckForTargetedEntity( void ); + void ProcessTargetedEntity( void ); + + void DoUse( Event *ev ); + Actor * getBestActorToUse( int *entityList, int count ); + void Killed( Event *ev ); + + void Dead( Event *ev ); + void Pain( Event *ev ); + + void TouchStuff( const pmove_t *pm ); + + void disableInventory( void ); + void enableInventory( void ); + + usercmd_t GetLastUcmd(void); + void GetMoveInfo( pmove_t *pm ); + void SetMoveInfo( pmove_t *pm, const usercmd_t *ucmd ); + pmtype_t GetMovePlayerMoveType( void ); + void ClientMove( usercmd_t *ucmd ); + void ApplyPowerupEffects(int &moveSpeed ); + void ClientMoveLadder( usercmd_t *ucmd ); + void ClientMoveDuck( usercmd_t *ucmd ); + void ClientMoveLean(usercmd_t *ucmd); + void CheckMoveFlags( void ); + void ClientMoveFlagsAndSpeeds( int moveSpeed, int noclipSpeed, int crouchSpeed, int airSpeed ); + void ClientMoveMisc( usercmd_t *ucmd ); + void ClientThink( Event *ev ); + + void LoadStateTable( void ); + void SetState(const str& legsstate, const str& torsostate); + void ResetState( Event *ev ); + void EvaluateState( State *forceTorso=NULL, State *forceLegs=NULL ); + + void CheckGround( void ); + void UpdateViewAngles( usercmd_t *cmd ); + qboolean AnimMove( const Vector &move, Vector *endpos = NULL ); + qboolean TestMove( const Vector &pos, Vector *endpos = NULL ); + qboolean CheckMove( const Vector &move, Vector *endpos = NULL ); + + void EndAnim_Legs( Event *ev ); + void EndAnim_Torso( Event *ev ); + void SetAnim( const char *anim, bodypart_t part = legs, bool force = false ); + + void EventUseItem( Event *ev ); + void ChangeStance( Event *ev ); + void ClearStanceTorso( Event *ev ); + void ClearStanceLegs( Event *ev ); + void TouchedUseAnim( Entity * ent ); + + void GiveCheat( Event *ev ); + void GiveWeaponCheat( Event *ev ); + void GiveAllCheat( Event *ev ); + void GodCheat( Event *ev ); + void NoTargetCheat( Event *ev ); + void NoclipCheat( Event *ev ); + void Kill( Event *ev ); + void GibEvent( Event *ev ); + void SpawnEntity( Event *ev ); + void SpawnActor( Event *ev ); + void ListInventoryEvent( Event *ev ); + void SetViewAnglesEvent( Event *ev ); + void GivePointsEvent( Event *ev ); + void SetPointsEvent( Event *ev ); + void SetCurrentCallVolume( const str &volumeName ); + str GetCurrentCallVolume(); + + void GameVersion( Event *ev ); + void Fov( Event *ev ); + + void GetPlayerView( Vector *pos, Vector *angle ); + + float CalcRoll( void ); + void WorldEffects( void ); + void AddBlend( float r, float g, float b, float a ); + void SetBlend( float r, float g, float b, float a, int additive ); + void SetBlend( int additive ); + void CalcBlend( void ); + + void setFlash( const Vector &color, float alpha, float minTime, float maxTime ); + + void DamageFeedback( void ); + + void UpdateStats( void ); + void UpdateMusic( void ); + void UpdateReverb( void ); + void UpdateMisc( void ); + void UpdateObjectiveStatus( void ); + + void SetReverb( const str &type, float level ); + void SetReverb( int type, float level ); + void SetReverb( Event *ev ); + + Camera *CurrentCamera( void ); + void SetCamera( Camera *ent, float switchTime ); + void CameraCut( void ); + void CameraCut( Camera *ent ); + + void SetClientViewAngles( const Vector &position, const float cameraoffset, const Vector &ang, const Vector &vel, const float camerafov ) const; + void ShakeCamera( void ); + void SetPlayerViewUsingNoClipController( void ); + void SetPlayerViewUsingActorController( Camera *camera ); + void SetPlayerViewUsingCinematicController( Camera* camera ); + void SetPlayerViewUsingEntityWatchingController( void ); + void SetPlayerViewNormal( void ); + void StopWatchingEntity( void ); + void DestroyActorCamera( void ); + void SetupView( void ); + void AutomaticallySelectedNewCamera( void ); + + void ProcessPmoveEvents( int event ); + + void SwingAngles( float destination, float swingTolerance, float clampTolerance, float speed, float *angle, qboolean *swinging ); + Entity const * GetTarget( void ) const; + bool GetProjectileLaunchAngles( Vector &launchAngles, const Vector &launchPoint, const float initialSpeed, const float gravity ) const; + void AcquireTarget( void ); + void AutoAim( void ); + void PlayerAngles( void ); + void FinishMove( void ); + void EndFrame( Event *ev ); + + void TestThread( Event *ev ); + bool useWeapon( const char *weaponname, weaponhand_t hand ); + bool useWeapon( Weapon *weapon, weaponhand_t hand ); + + void GotKill( Event *ev ); + void SetPowerupTimer( Event *ev ); + void UpdatePowerupTimer( Event *ev ); + + void WhatIs( Event *ev ); + void ActorInfo( Event *ev ); + void Taunt( Event *ev ); + + void ChangeMusic( const char * current, const char * fallback, qboolean force ); + void ChangeMusicVolume( float volume , float fade_time); + void RestoreMusicVolume( float fade_time ); + + void allowMusicDucking( bool allowMusicDucking ); + void allowActionMusic( bool allowActionMusic ); + + void GravityNodes( void ); + virtual void Archive( Archiver &arc ); + virtual void ArchivePersistantData( Archiver &arc, qboolean sublevelTransition ); + + void GiveOxygen( float time ); + + void KillEnt( Event *ev ); + void RemoveEnt( Event *ev ); + void KillClass( Event *ev ); + void RemoveClass( Event *ev ); + + void Jump( Event *ev ); + void JumpXY( Event *ev ); + + void ActivateNewWeapon( Event *ev ); + void ActivateDualWeapons( Event *ev ); + void DeactivateWeapon( Event *ev ); + void PutawayWeapon( Event *ev ); + void DonePutaway( Event *ev ); + + void ActivateShield( Event *ev ); + void DeactivateShield( Event *ev ); + qboolean ShieldActive( void ); + qboolean LargeShieldActive( void ); + + void StartFakePlayer( void ); + void FakePlayer( qboolean holster ); + void RemoveFakePlayer( void ); + void SetViewAngles( Vector angles ); + Vector getViewAngles( void ) { return v_angle; } + void SetFov( float newFov, bool forced = false ); + float getDefaultFov( void ); + + bool IsNewActiveWeapon( void ); + Weapon *GetNewActiveWeapon( void ); + weaponhand_t GetNewActiveWeaponHand( void ); + void ClearNewActiveWeapon( void ); + + void DumpState( Event *ev ); + void ForceTorsoState( Event *ev ); + + void SetHeadTarget( Event *ev ); + Vector GetAngleToTarget( const Entity *ent, const str &tag, float yawclamp, float pitchclamp, const Vector &baseangles ); + + void AcquireHeadTarget( void ); + + void SetCurrentCombo( Event *ev ); + + qboolean GetTagPositionAndOrientation( const str &tagname, orientation_t *new_or ); + qboolean GetTagPositionAndOrientation( int tagnum, orientation_t *new_or ); + + void DebugWeaponTags( int controller_tag, Weapon *weapon, const str &weapon_tagname ); + void ClearTarget( Event *ev ); + void AdjustTorso( Event *ev ); + void UseDualWield( Event *ev ); + void DualWield( Event *ev ); + void EvaluateTorsoAnim( Event *ev ); + void CheckReloadWeapons( void ); + void NextPainTime( Event *ev ); + void SetTakePain( Event *ev ); + + void SetMouthAngle( Event *ev ); + + void EnterVehicle( Event *ev ); + void ExitVehicle( Event *ev ); + void Holster( Event *ev ); + void HolsterToggle( Event *ev ); + void NightvisionToggle (Event *ev); + + void IncreaseActionLevel( float action_level_increase ); + + void WatchEntity( Event *ev ); + void StopWatchingEntity( Event *ev ); + void WeaponCommand( Event *ev ); + void PlayerDone( Event *ev ); + painDirection_t Pain_string_to_int( const str &pain ); + inline Vector GetTorsoAngles( void ) { return torsoAngles; }; + inline Vector GetVAngles( void ){ return v_angle; } + + + const str getPainShader( meansOfDeath_t mod, bool takeArmorIntoAccount ); + const str getPainShader( const char *MODName ); + + void SpawnDamageEffect( meansOfDeath_t mod ); + + virtual void GetStateAnims( Container *c ); + virtual void SetStateFile( Event *ev ); + + virtual void VelocityModified( void ); + int GetKnockback( int original_knockback, qboolean blocked ); + virtual void ReceivedItem( Item * item ); + virtual void RemovedItem( Item * item ); + virtual void AmmoAmountChanged( Ammo * ammo, int inclip = 0 ); + qboolean WeaponsOut( void ); + qboolean IsDualWeaponActive( void ); + void Holster( qboolean putaway ); + void SafeHolster( qboolean putaway ); + inline float GetFov() { return fov; } + inline bool GetTargetEnemyLocked( void ) const { return targetEnemyLocked; } + + void StartCoolItem( Event *ev ); + void StartCoolItemAnim( void ); + void ShowCoolItem( Event *ev ); + void HideCoolItem( Event *ev ); + void StopCoolItem( Event *ev ); + void WaitForState( Event *ev ); + void SkipCinematic( Event *ev ); + void SetDamageMultiplier( Event *ev ); + void LogStats( Event *ev ); + void WeaponsHolstered( void ); + void WeaponsNotHolstered( void ); + void Loaded( void ); + void PlayerShowModel( Event *ev ); + virtual void showModel( void ); + void ResetHaveItem( Event *ev ); + qboolean GetCrouch(); + + int getTotalGameFrames() { return _totalGameFrames; } + + // Deathmatch arena stuff + void Score( Event *ev ); + + void WarpToPoint( const Entity *ent ); + void ArmorDamage( Event *ev ); + void Disconnect( void ); + void CallVote( Event *ev ); + void Vote( Event *ev ); + void joinTeam( Event *ev ); + void multiplayerCommand( Event *ev ); + void DeadBody( Event *ev ); + void Gib( void ); + + void FireWeapon(Event *ev); + void ReleaseFireWeapon(Event *ev); + void SetAimType(Event *ev); + void ReloadWeapon(Event *ev); + void AnimateWeapon(Event *ev ); + void SwitchWeaponMode(Event *ev); + + void UseSpecifiedEntity( Event *ev ); + + void SetupDialog( Event *ev ); + void SetupDialog( Entity *entity, const str &soundName ); + + void handleTextDialogSetup( const str& soundName ); + void handleDialogSetup( Entity* entity, const str& soundName ); + + void ClearDialog( Event *ev ); + void ClearDialog( void ); + + void ClearTextDialog( Event* ev ); + void ClearTextDialog( void ); + + // Interface to the projdetonate member variable + // Used for the grenade launcher, which has a detonate trigger + qboolean GetProjDetonate(); + void SetProjDetonate(qboolean value); + void ProjDetonate(Event *ev); + + //Player Heuristics + PlayerHeuristics *p_heuristics; + void ShowHeuristics( Event *ev ); + + void ReloadTiki( Event *ev ); + + //Objective Updates + void LoadObjectives( Event* ev ); + void SetObjectiveComplete( Event *ev ); + void SetObjectiveFailed( Event *ev ); + void SetObjectiveShow( Event *ev ); + + void loadObjectives( const str& objectiveName ); + + void MissionFailed(Event* ev); + void setMissionFailed( void ); + + //Information Updates + void SetInformationShow( Event *ev ); + void SetStat( Event *ev ); + qboolean ShouldSendToClient( Entity *entityToSend ); + void UpdateEntityStateForClient( entityState_t *state ); + void UpdatePlayerStateForClient( playerState_t *state ); + void ExtraEntitiesToSendToClient( int *numExtraEntities, int *extraEntities ); + + virtual void setViewMode( const str &viewModeName ); + + void applyWeaponSpeedModifiers( int *moveSpeed ); + + float GetDamageMultiplier() { return damage_multiplier; } + + // Special Moves + void SpecialMoveChargeStart( Event *ev ); + void SpecialMoveChargeEnd( Event *ev ); + void SpecialMoveChargeTime( Event *ev ); + + // Point System + int AwardPoints(int numPoints); + int TakePoints(int numPoints); + void ClearPoints() { points = 0; } + int GetNumPoints() { return points; } + + void ChangeChar( Event *ev ); + void SetPlayerChar( Event *ev ); + void PlayerKnockback( Event *ev ); + void KnockbackMultiplier( Event *ev ); + void MeleeEvent( Event *ev ); + float GetKnockbackMultiplier() { return knockbackMultiplier; } + float GetPlayerKnockback() { return playerKnockback; } + + Vector getWeaponViewShake( void ); + void MeleeDamageStart( Event *ev ); + void MeleeDamageEnd( Event *ev ); + void AdvancedMeleeAttack( weaponhand_t hand ); + void ChangeCharFadeIn( Event *ev ); + void AddMeleeAttacker( Event *ev ); + void SetIncomingMeleeAttack( bool flag ) { incomingMeleeAttack = flag; } + void ClearIncomingMelee( Event *ev ); + void SetBendTorso( Event *ev ); + void HeadWatchAllowed( Event *ev ); + + float getDamageDone( float damage, int meansOfDeath, bool inMelee ); + meansOfDeath_t changetMeansOfDeath( meansOfDeath_t meansOfDeath ); + + // Powerup stuff + void setPowerup( Powerup *powerup ); + void removePowerup( void ); + void removePowerupEvent( Event *ev ); + void dropPowerup( void ); + + // Rune Stuff + bool hasRune( void ); + void setRune( Rune *rune ); + void removeRune( void ); + void dropRune( Event *ev ); + void dropRune( void ); + + // Holdable item Stuff + void setHoldableItem( HoldableItem *holdableItem ); + void removeHoldableItem( void ); + void useHoldableItem( void ); + HoldableItem* getHoldableItem(void); + + // Energy Transfer + void transferEnergy( void ); + void setCanTransferEnergy( Event * ); + + void setDoDamageScreenFlash( Event *ev ); + void pointOfView( Event *ev ); + + Entity * FindClosestEntityInRadius( const float horizontalFOVDegrees, const float verticalFOVDegrees, const float maxDistance ); + Entity* FindHeadTarget( const Vector &origin, const Vector &forward, const float fov, const float maxdist ); + void HandleFinishableList(); + void addFinishingMove( Event *ev ); + void clearFinishingMove( Event *ev ); + void doFinishingMove( Event *ev ); + void forceTimeScale( Event *ev ); + void freezePlayer( Event *ev ); + void immobilizePlayer( Event *ev ); + void doUseEntity( Event *ev ); + void doneUseEntity( Event *ev ); + void showObjectInfo(); + + void setAutoSwitchWeapons( bool autoSwitchWeapons ) { _autoSwitchWeapons = autoSwitchWeapons; } + bool getAutoSwitchWeapons( void ) { return _autoSwitchWeapons; } + + void handleUseObject(UseObject *uo); + void handleUseEntity(Entity *ent, float v_dist); + void handlePickupItem(Item *item); + void clearActionType(); + void setAttackType(Event *ev); + + const str& getAttackType() { return _attackType; } + const str getGameplayAnim(const str& objname); + + void nextGameplayAnim( Event *ev ); + void setGameplayAnim( Event *ev ); + void setDisableUseWeapon( Event* ev ); + void setDisableInventory( Event* ev ); + void skillChanged( const str& objname ); + void equipItems( Event *ev ); + const str getFreeInventorySlot(); + + void usePlayer( Event * ); + + + void addRosterTeammate1( Event* ev ); + void addRosterTeammate2( Event* ev ); + void addRosterTeammate3( Event* ev ); + void addRosterTeammate4( Event* ev ); + + void removeRosterTeammate1( Event* ev ); + void removeRosterTeammate2( Event* ev ); + void removeRosterTeammate3( Event* ev ); + void removeRosterTeammate4( Event* ev ); + + bool isButtonDown( int button ); + + void notifyPlayerOfMultiplayerEvent( const char *eventName, const char *eventItemName, Player *eventPlayer ); + + void touchingLadder( Trigger *ladder, const Vector &normal, float top ); + +// Vector GetViewEndPoint( void ); + void GetViewTrace( trace_t& trace, int contents, float maxDistance = 3000.0f ); + + void addPowerupEffect( PowerupBase *powerup ); + void removePowerupEffect( PowerupBase *powerup ); + + void warp( Event *ev ); + + void hudPrint( Event *ev ); + void hudPrint( const str &string ); + + void setTargeted( bool targeted ); + + void setItemText( int itemIcon, const str &itemText ); + void clearItemText( void ); + void clearItemText( Event *ev ); + + void setVoteText( const str &voteText ); + void clearVoteText( void ); + + void shotFired( void ); + void shotHit( void ); + + void cinematicStarted( void ); + void cinematicStopped( void ); + + void loadUseItem( const str &item ); + + void setValidPlayerModel( Event *ev ); + + void incrementSecretsFound( void ); + void isPlayerOnGround( Event *ev ); + + void addHud( Event *ev ); + void addHud( const str &hudName ); + void removeHud( Event *ev ); + void removeHud( const str &hudName ); + bool needToSendAllHudsToClient( void ); + void sendAllHudsToClient( void ); + void clearAllHuds( void ); + void addHudToClient( const str &hudName ); + void removeHudFromClient( const str &hudName ); + + void killAllDialog(Event *ev); + void killAllDialog(); + + void clearTempAttachments( void ); + + void setSkill( int skill ); + int getSkill( void ); + + void forceMoveType( Event *ev ); + + void setBranchDialogActor( const Actor* actor); + void clearBranchDialogActor( void ); + + void setBackpackAttachOffset( Event *ev ); + void setBackpackAttachAngles( Event *ev ); + + Vector getBackpackAttachOffset( void ); + Vector getBackpackAttachAngles( void ); + + void setFlagAttachOffset( Event *ev ); + void setFlagAttachAngles( Event *ev ); + + Vector getFlagAttachOffset( void ); + Vector getFlagAttachAngles( void ); + + bool canRegenerate( void ); + + void modelChanged( void ); + + void setBackupModel( Event *ev ); + void setBackupModel( const str &modelName ); + }; + +inline bool Player::IsNewActiveWeapon() + { + return ( newActiveWeapon.weapon != NULL ); + } + +inline weaponhand_t Player::GetNewActiveWeaponHand() + { + return newActiveWeapon.hand; + } + +inline Weapon *Player::GetNewActiveWeapon() + { + return newActiveWeapon.weapon; + } + +inline void Player::ClearNewActiveWeapon() + { + newActiveWeapon.weapon = NULL; + newActiveWeapon.hand = WEAPON_ERROR; + } + +inline void Player::Archive( Archiver &arc ) + { + str tempStr; + int i, num; + WeaponSetItem *tempDualWeapon; + + Sentient::Archive( arc ); + + // Don't archive + //static Condition Conditions[]; + //static movecontrolfunc_t MoveStartFuncs[]; + + // make sure we have the state machine loaded up + if ( arc.Loading() ) + { + LoadStateTable(); + } + + if ( arc.Saving() ) + { + if ( currentState_Legs ) + tempStr = currentState_Legs->getName(); + else + tempStr = "NULL"; + + arc.ArchiveString( &tempStr ); + + if ( currentState_Torso ) + tempStr = currentState_Torso->getName(); + else + tempStr = "NULL"; + + arc.ArchiveString( &tempStr ); + } + else + { + arc.ArchiveString( &tempStr ); + + if ( tempStr != "NULL" ) + currentState_Legs = statemap_Legs->FindState( tempStr ); + else + currentState_Legs = NULL; + + arc.ArchiveString( &tempStr ); + + if ( tempStr != "NULL" ) + currentState_Torso = statemap_Torso->FindState( tempStr ); + else + currentState_Torso = NULL; + } + + arc.ArchiveString( &last_torso_anim_name ); + arc.ArchiveString( &last_leg_anim_name ); + + ArchiveEnum( movecontrol, movecontrol_t ); + + arc.ArchiveInteger( &last_camera_type ); + if ( arc.Loading() ) + { + // make sure the camera gets reset + last_camera_type = -1; + } + + newActiveWeapon.Archive( arc ); + + arc.ArchiveSafePointer( &head_target ); + arc.ArchiveFloat( &look_at_time ); + + arc.ArchiveSafePointer( &targetEnemy ); + arc.ArchiveBool( &targetEnemyLocked ); + + arc.ArchiveBoolean( &shield_active ); + + arc.ArchiveBoolean( &dual_wield_active ); + + if ( arc.Saving() ) + num = dual_wield_weaponlist.NumObjects(); + else + dual_wield_weaponlist.ClearObjectList(); + + arc.ArchiveInteger( &num ); + + for( i = 1; i <= num; i++ ) + { + if ( arc.Saving() ) + { + tempDualWeapon = dual_wield_weaponlist.ObjectAt( i ); + } + else + { + tempDualWeapon = new WeaponSetItem; + dual_wield_weaponlist.AddObject( tempDualWeapon ); + } + tempDualWeapon->Archive( arc ); + } + + for( i = 0; i < MAX_ACTIVE_WEAPONS; i++ ) + { + arc.ArchiveSafePointer( &holsteredWeapons[ i ] ); + } + + arc.ArchiveString( &partAnim[ 0 ] ); + arc.ArchiveString( &partAnim[ 1 ] ); + arc.ArchiveString( &partAnim[ 2 ] ); + + arc.ArchiveBool( &animdone_Legs ); + arc.ArchiveBool( &animdone_Torso ); + + arc.ArchiveVector( &oldvelocity ); + arc.ArchiveVector( &old_v_angle ); + arc.ArchiveVector( &oldorigin ); + arc.ArchiveFloat( &animspeed ); + arc.ArchiveFloat( &airspeed ); + + arc.ArchiveString( ¤tCallVolume ); + + arc.ArchiveRaw( blend, sizeof( blend ) ); + arc.ArchiveFloat( &fov ); + arc.ArchiveFloat( &_userFovChoice ); + + arc.ArchiveSafePointer( &vehicle ); + arc.ArchiveVector( &v_angle ); + + arc.ArchiveInteger( &buttons ); + arc.ArchiveInteger( &new_buttons ); + arc.ArchiveFloat( &respawn_time ); + + arc.ArchiveInteger( &last_attack_button ); + + arc.ArchiveFloat( &damage_blood ); + arc.ArchiveFloat( &damage_alpha ); + arc.ArchiveVector( &damage_blend ); + arc.ArchiveVector( &damage_from ); + arc.ArchiveVector( &damage_angles ); + arc.ArchiveFloat( &damage_count ); + + arc.ArchiveFloat( &bonus_alpha ); + + arc.ArchiveFloat( &next_drown_time ); + arc.ArchiveFloat( &next_painsound_time ); + arc.ArchiveFloat( &air_finished ); + + arc.ArchiveInteger( &old_waterlevel ); + arc.ArchiveFloat( &drown_damage ); + + arc.ArchiveFloat( &_flashAlpha ); + arc.ArchiveVector( &_flashBlend ); + arc.ArchiveFloat( &_flashMaxTime ); + arc.ArchiveFloat( &_flashMinTime ); + + arc.ArchiveString( &waitForState ); + + arc.ArchiveSafePointer( &camera ); + arc.ArchiveSafePointer( &actor_camera ); + arc.ArchiveSafePointer( &cool_camera ); + arc.ArchiveSafePointer( &entity_to_watch ); + arc.ArchiveFloat( &maximumAngleToWatchedEntity ); + arc.ArchiveBool( &watchEntityForEntireDuration ); + arc.ArchiveInteger( &playerCameraMode ); + + arc.ArchiveFloat( &action_level ); + arc.ArchiveInteger( &music_current_mood ); + arc.ArchiveInteger( &music_fallback_mood ); + arc.ArchiveFloat( &music_current_volume ); + arc.ArchiveFloat( &music_saved_volume ); + arc.ArchiveFloat( &music_volume_fade_time ); + arc.ArchiveInteger( &reverb_type ); + arc.ArchiveFloat( &reverb_level ); + arc.ArchiveBoolean( &music_forced ); + arc.ArchiveBool( &_allowMusicDucking ); + arc.ArchiveBool( &_allowActionMusic ); + + arc.ArchiveBoolean( &gibbed ); + arc.ArchiveFloat( &pain ); + + ArchiveEnum( pain_dir, painDirection_t ); + ArchiveEnum( pain_type, meansOfDeath_t ); + arc.ArchiveBool( &take_pain ); + + arc.ArchiveFloat( &accumulated_pain ); + arc.ArchiveFloat( &nextpaintime ); + + arc.ArchiveBool( &knockdown ); + arc.ArchiveBool( &canfall ); + arc.ArchiveBool( &falling ); + arc.ArchiveInteger( &feetfalling ); + arc.ArchiveVector( &falldir ); + + arc.ArchiveBool( &hardimpact ); + + arc.ArchiveRaw( &last_ucmd, sizeof( last_ucmd ) ); + + //arc.ArchiveVector( &base_righthand_pos ); + //arc.ArchiveVector( &base_lefthand_pos ); + //arc.ArchiveVector( &righthand_pos ); + //arc.ArchiveVector( &lefthand_pos ); + + arc.ArchiveVector( &base_rightfoot_pos ); + arc.ArchiveVector( &base_leftfoot_pos ); + arc.ArchiveVector( &rightfoot_pos ); + arc.ArchiveVector( &leftfoot_pos ); + + arc.ArchiveBool( &_onLadder ); + arc.ArchiveVector( &_ladderNormal ); + arc.ArchiveFloat( &_ladderTop ); + arc.ArchiveFloat( &_nextLadderTime ); + + arc.ArchiveInteger( &pm_lastruntime ); + arc.ArchiveFloat( &animheight ); + + arc.ArchiveVector( &yaw_forward ); + arc.ArchiveVector( &yaw_left ); + + arc.ArchiveSafePointer( &atobject ); + arc.ArchiveFloat( &atobject_dist ); + arc.ArchiveVector( &atobject_dir ); + + arc.ArchiveSafePointer( &toucheduseanim ); + arc.ArchiveInteger( &useanim_numloops ); + arc.ArchiveSafePointer( &useitem_in_use ); + arc.ArchiveSafePointer( &cool_item ); + arc.ArchiveString( &cool_dialog ); + arc.ArchiveString( &cool_anim ); + + arc.ArchiveInteger( &moveresult ); + + arc.ArchiveBoolean( &do_rise ); + arc.ArchiveBoolean( &weapons_holstered_by_code ); + + arc.ArchiveBoolean( &projdetonate ); + + arc.ArchiveBoolean( &yawing ); + arc.ArchiveBoolean( &yawing_left ); + arc.ArchiveBoolean( &yawing_right ); + arc.ArchiveBoolean( &adjust_torso ); + + arc.ArchiveVector( &torsoAngles ); + arc.ArchiveVector( &headAngles ); + arc.ArchiveVector( &headAimAngles ); + arc.ArchiveVector( &torsoAimAngles ); + + arc.ArchiveFloat( &damage_multiplier ); + + // Don't save multiplayer stuff + + arc.ArchiveBoolean( &fakePlayer_active ); + + arc.ArchiveSafePointer( &fakePlayer ); + + arc.ArchiveBool( &dont_turn_legs ); + arc.ArchiveFloat(&specialMoveCharge); + arc.ArchiveFloat(&specialMoveEndTime); + arc.ArchiveFloat(&specialMoveChargeTime); + arc.ArchiveInteger(&points); + arc.ArchiveFloat(&playerKnockback); + arc.ArchiveFloat(&knockbackMultiplier); + arc.ArchiveBool(&changedStanceTorso); + arc.ArchiveBool(&changedStanceLegs); + arc.ArchiveInteger(&stanceNumber); + arc.ArchiveBool(&incomingMeleeAttack); + arc.ArchiveFloat(&bendTorsoMult); + arc.ArchiveString(&lastActionType); + arc.ArchiveInteger(&meleeAttackFlags); + arc.ArchiveBool(&changingChar); + + int numEntries; + if ( arc.Saving() ) + { + numEntries = meleeAttackerList.NumObjects(); + arc.ArchiveInteger( &numEntries ); + + EntityPtr eptr; + for ( int i = 1 ; i <= numEntries ; i++ ) + { + eptr = meleeAttackerList.ObjectAt( i ); + arc.ArchiveSafePointer( &eptr ); + } + } + else + { + EntityPtr eptr; + EntityPtr *eptrptr; + arc.ArchiveInteger( &numEntries ); + + meleeAttackerList.Resize( numEntries ); + + for ( int i = 1 ; i <= numEntries ; i++ ) + { + meleeAttackerList.AddObject( eptr ); + eptrptr = &meleeAttackerList.ObjectAt( i ); + arc.ArchiveSafePointer( eptrptr ); + } + } + + // Don't archive + //Container legs_conditionals; + //Container torso_conditionals; + + if ( arc.Saving() ) + { + numEntries = finishableList.NumObjects(); + arc.ArchiveInteger( &numEntries ); + + ActorPtr eptr; + for ( int i = 1 ; i <= numEntries ; i++ ) + { + eptr = finishableList.ObjectAt( i ); + arc.ArchiveSafePointer( &eptr ); + } + } + else + { + ActorPtr eptr; + ActorPtr *eptrptr; + arc.ArchiveInteger( &numEntries ); + + finishableList.Resize( numEntries ); + + for ( int i = 1 ; i <= numEntries ; i++ ) + { + finishableList.AddObject( eptr ); + eptrptr = &finishableList.ObjectAt( i ); + arc.ArchiveSafePointer( eptrptr ); + } + } + + if ( arc.Saving() ) + { + numEntries = finishingMoveList.NumObjects(); + arc.ArchiveInteger( &numEntries ); + + FinishingMove * eptr; + for ( int i = 1 ; i <= numEntries ; i++ ) + { + eptr = finishingMoveList.ObjectAt( i ); + arc.ArchiveObjectPointer( (Class **)&eptr ); + } + } + else + { + FinishingMove *eptr = NULL; + FinishingMove **eptrptr; + arc.ArchiveInteger( &numEntries ); + + finishingMoveList.Resize( numEntries ); + + for ( int i = 1 ; i <= numEntries ; i++ ) + { + finishingMoveList.AddObject( eptr ); + eptrptr = &finishingMoveList.ObjectAt( i ); + arc.ArchiveObjectPointer( (Class **)eptrptr ); + } + } + + arc.ArchiveSafePointer( &_powerup ); + arc.ArchiveSafePointer( &_rune ); + arc.ArchiveSafePointer( &_holdableItem ); + + arc.ArchiveFloat( &_nextEnergyTransferTime ); + arc.ArchiveBool( &_canTransferEnergy ); + arc.ArchiveBool( &_doDamageScreenFlash ); + arc.ArchiveBool( &_isThirdPerson ); + arc.ArchiveObjectPointer( (Class **)&_finishActor ); + arc.ArchiveString( &_finishState ); + arc.ArchiveBool( &_doingFinishingMove ); + + arc.ArchiveBool( &_autoSwitchWeapons ); + + arc.ArchiveBool( &_usingEntity ); + arc.ArchiveString( &_attackType ); + arc.ArchiveInteger( &_gameplayAnimIdx ); + + arc.ArchiveBool( &_disableUseWeapon ); + arc.ArchiveSafePointer( &_targetSelectedHighlight ); + arc.ArchiveSafePointer( &_targetLockedHighlight ); + arc.ArchiveBool( &_infoHudOn ); + arc.ArchiveInteger( &_nextRegenTime ); + arc.ArchiveFloat( &_useEntityStartTimer ); + + //mission objective stuff + str objectiveName; + if( arc.Saving() ) + { + objectiveName = gi.getConfigstring( CS_OBJECTIVE_NAME + _objectiveNameIndex); + arc.ArchiveString( &objectiveName ); + } + + if( arc.Loading() ) + { + arc.ArchiveString( &objectiveName ); + loadObjectives(objectiveName); + } + + arc.ArchiveUnsigned( &_objectiveStates ); + arc.ArchiveUnsigned( &_informationStates ); + + arc.ArchiveSafePointer( &_targetedEntity); + + //dialog entity string and dialog num + str dialogSound; + str dialogTextSound; + if(arc.Saving() ) + { + arc.ArchiveInteger(&_dialogEntnum); + dialogSound = gi.getConfigstring( CS_SOUNDS + _dialogSoundIndex); + arc.ArchiveString( &dialogSound ); + + dialogTextSound = gi.getConfigstring( CS_SOUNDS + _dialogTextSoundIndex ); + arc.ArchiveString( &dialogTextSound ); + } + + if( arc.Loading() ) + { + arc.ArchiveInteger(&_dialogEntnum); + arc.ArchiveString( &dialogSound ); + _dialogSoundIndex = gi.soundindex( dialogSound ); + + arc.ArchiveString( &dialogTextSound ); + _dialogTextSoundIndex = gi.soundindex( dialogTextSound ); + } + + + arc.ArchiveFloat( &_crossHairXOffset ); + arc.ArchiveFloat( &_crossHairYOffset ); + + arc.ArchiveFloat( &_lastDamagedTimeFront ); + arc.ArchiveFloat( &_lastDamagedTimeBack ); + arc.ArchiveFloat( &_lastDamagedTimeLeft ); + arc.ArchiveFloat( &_lastDamagedTimeRight ); + + arc.ArchiveInteger(&_totalGameFrames); + + arc.ArchiveFloat( &_nextPainShaderTime ); + ArchiveEnum( _lastPainShaderMod, meansOfDeath_t ); + + arc.ArchiveInteger( &_itemIcon ); + arc.ArchiveString( &_itemText ); + + arc.ArchiveString( &_voteText ); + + arc.ArchiveBool( &_validPlayerModel ); + + arc.ArchiveInteger( &_secretsFound ); + + _hudList.Archive( arc ); + + // _needToSendHuds is not archived but set directly + + if ( arc.Loading() && ( _hudList.NumObjects() > 0 ) ) + _needToSendHuds = true; + else + _needToSendHuds = false; + + // _started is not archived but set directly + _started = false; + + arc.ArchiveInteger( &_skillLevel ); + + ArchiveEnum( _forcedMoveType, pmtype_t ); + + arc.ArchiveSafePointer( &_branchDialogActor); + _needToSendBranchDialog = true; + + // Don't save these 4, only used in multiplayer + + //Vector _backpackAttachOffset; + //Vector _backpackAttachAngles; + //Vector _flagAttachOffset; + //Vector _flagAttachAngles; + + arc.ArchiveBool( &_cameraCutThisFrame ); + } + +inline Camera *Player::CurrentCamera() + { + return camera; + } + +inline void Player::CameraCut() + { + // toggle the camera cut bit + if ( !client ) + return; + + if ( _cameraCutThisFrame ) + return; + + client->ps.camera_flags = + ( ( client->ps.camera_flags & CF_CAMERA_CUT_BIT ) ^ CF_CAMERA_CUT_BIT ) | + ( client->ps.camera_flags & ~CF_CAMERA_CUT_BIT ); + + _cameraCutThisFrame = true; + } + +inline void Player::CameraCut( Camera * ent ) + { + if ( ent == camera ) + { + // if the camera we are currently looking through cut, than toggle the cut bits + CameraCut(); + } + } + +inline void Player::SetCamera( Camera *ent, float switchTime ) + { + if ( !client ) + return; + + camera = ent; + client->ps.camera_time = switchTime; + if ( switchTime <= 0.0f ) + { + CameraCut(); + } + } + +#endif /* player.h */ diff --git a/dlls/game/player_combat.cpp b/dlls/game/player_combat.cpp new file mode 100644 index 0000000..b859964 --- /dev/null +++ b/dlls/game/player_combat.cpp @@ -0,0 +1,518 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/player_combat.cpp $ +// $Revision:: 40 $ +// $Author:: Steven $ +// $Date:: 5/17/03 3:09p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Player combat system and combat utility functions +// + +#include "_pch_cpp.h" +#include "player.h" +#include "weaputils.h" +#include +#include "mp_manager.hpp" + + +Entity * Player::FindClosestEntityInRadius( const float horizontalFOVDegrees, const float verticalFOVDegrees, const float maxDistance ) +{ + Vector torsoForward; + Vector torsoRight; + Vector torsoUp; + + torsoAngles.AngleVectors( &torsoForward, &torsoRight, &torsoUp ); + + const float horizontalFOVComponent = static_cast( sin( DEG2RAD( horizontalFOVDegrees / 2.0f ) ) ); + const float verticalFOVComponent = static_cast( sin( DEG2RAD( verticalFOVDegrees / 2.0f ) ) ); + + Entity *bestEntity=NULL; + float bestDistance = maxDistance; + + // Find closest enemy in radius + Entity *currentEntity = NULL; + + while( ( currentEntity = findradius( currentEntity, centroid, maxDistance ) ) != NULL ) + { + bool validEntity = false; + + if ( currentEntity->flags & FL_AUTOAIM ) + { + validEntity = true; + } + else if ( currentEntity->isSubclassOf( Actor ) && !currentEntity->deadflag ) + { + Actor *actor = static_cast( currentEntity ); + + if ( ( actor->actortype == IS_ENEMY ) && actor->CanTarget() && !( actor->bind_info && actor->bind_info->bindmaster ) && ( actor->edict->s.parent == ENTITYNUM_NONE ) ) + validEntity = true; + } + + if ( validEntity ) + { + // Check to see if the enemy is closest to us + Vector delta( currentEntity->centroid - centroid ); + + const float dist = delta.length(); + + if ( dist < bestDistance ) + { + delta.normalize(); + + // It's close, now check to see if it's in our FOV. + const float forwardDot = DotProduct( torsoForward, delta ); + const float horizontalDot = DotProduct( torsoRight, delta ); + const float verticalDot = DotProduct( torsoUp, delta ); + + if ( + forwardDot > 0.0f && + fabs(horizontalDot) < horizontalFOVComponent && + ( (fabs(verticalDot) < verticalFOVComponent) || ( dist < 96.0f ) ) + ) + { + trace_t trace; + // Do a trace to see if we can get to it + trace = G_Trace( centroid, + vec_zero, + vec_zero, + currentEntity->centroid, + NULL, + MASK_OPAQUE, + false, + "FindClosestEntityInRadius" ); + + if ( trace.ent || ( trace.fraction == 1 ) ) + { + // dir = delta; + bestEntity = currentEntity; + bestDistance = dist; + } + } + } + } + } + return bestEntity; +} + +const bool IsValidHeadTarget( const Entity &entity) +{ + if ( entity.isSubclassOf( Actor ) && !entity.deadflag ) + { + const Actor &actor = static_cast( entity ); + if ( ( actor.actortype == IS_ENEMY ) && actor.CanTarget() && ! ( actor.bind_info && actor.bind_info->bindmaster ) && ( actor.edict->s.parent == ENTITYNUM_NONE ) ) + { + return true; + } + } + else if ( entity.isSubclassOf( Item ) ) + { + const Item &item = static_cast( entity ); + + if ( !item.GetOwner() && !item.has_been_looked_at && entity.look_at_me) + { + return true; + } + } + return false; +} + +Entity* Player::FindHeadTarget( const Vector &origin, const Vector &forward, const float fov, const float maxdist ) +{ + const int maximumNumberOfCandidates = 10; + int numberOfCandidates=0; + float validTargetRadiusSquared = maxdist * maxdist; + float fovdot = cos( DEG2RAD( fov * 0.5f) ); + + if ( multiplayerManager.inMultiplayer() ) + return NULL; + + Container possibleHeadTargets; + finishableList.FreeObjectList(); + + for( gentity_t *currentEdict = active_edicts.next; currentEdict != &active_edicts && numberOfCandidates < maximumNumberOfCandidates; currentEdict = currentEdict->next ) + { + Entity ¤tEntity = *currentEdict->entity; + if ( IsValidHeadTarget( currentEntity ) ) + { + Vector delta = ( currentEntity.centroid ) - origin; + float lengthOfDeltaSquared = delta.lengthSquared(); + if ( lengthOfDeltaSquared < validTargetRadiusSquared ) + { + // Headwatch Stuff + delta.normalize(); + float dot = DotProduct( forward, delta ); + if ( dot > fovdot ) + { + int insertionPoint = 1; + const float distanceSquaredToCurrentEntity = Vector::DistanceSquared( origin, currentEntity.centroid ); + for ( ; insertionPoint < possibleHeadTargets.NumObjects(); insertionPoint++ ) + { + if ( distanceSquaredToCurrentEntity < Vector::DistanceSquared( origin, possibleHeadTargets.ObjectAt( insertionPoint )->centroid ) ) + { + break; + } + } + numberOfCandidates++; + possibleHeadTargets.InsertObjectAt( insertionPoint, ¤tEntity ); + } + + // Finishable List -- If there's a finishable guy within 5 feet, + // add him to the finishable list. + if ( lengthOfDeltaSquared < 6400.0f ) // 5 feet + { + if ( currentEntity.isSubclassOf(Actor) ) + { + Actor *act = (Actor*)¤tEntity; + if ( act->IsFinishable() ) + finishableList.AddObject(act); + } + } + } + } + } + + Entity *closestValidEntity = NULL; + + for (int i = 1; i <= possibleHeadTargets.NumObjects(); i++ ) + { + Entity *currentCandidate = possibleHeadTargets.ObjectAt( i ); + trace_t trace = G_Trace( origin, vec_zero, vec_zero, currentCandidate->centroid, NULL, MASK_OPAQUE, false, "FindHeadTarget" ); + if ( ( trace.ent && trace.entityNum == currentCandidate->entnum ) || ( trace.fraction == 1.0f ) ) + { + closestValidEntity = currentCandidate; + break; + } + } + return closestValidEntity; +} + +//==================== +//ActivateNewWeapon +//==================== +void Player::ActivateNewWeapon( Event *ev ) +{ + // Change the weapon to the currently active weapon as specified by useWeapon + ChangeWeapon( newActiveWeapon.weapon, newActiveWeapon.hand ); + + // Clear out the newActiveWeapon + ClearNewActiveWeapon(); + + // Clear out the holstered weapons + holsteredWeapons[WEAPON_LEFT] = NULL; + holsteredWeapons[WEAPON_RIGHT] = NULL; + holsteredWeapons[WEAPON_DUAL] = NULL; + + // let the player know that our weapons are not holstered + WeaponsNotHolstered(); +} + +//==================== +//DeactivateWeapon +//==================== +void Player::DeactivateWeapon( Event *ev ) +{ + // Deactivate the weapon + weaponhand_t hand; + str side; + + side = ev->GetString( 1 ); + + hand = WeaponHandNameToNum( side ); + + if ( hand == WEAPON_ERROR ) + return; + + Sentient::DeactivateWeapon( hand ); + + if ( !GetActiveWeapon( WEAPON_LEFT ) && !GetActiveWeapon( WEAPON_RIGHT ) && !GetActiveWeapon( WEAPON_DUAL ) ) + { + // let the player know our weapons are holstered + WeaponsHolstered(); + } +} + +//==================== +//PutawayWeapon +//==================== +void Player::PutawayWeapon( Event *ev ) +{ + Weapon * weapon; + weaponhand_t hand; + str side; + + side = ev->GetString( 1 ); + + hand = WeaponHandNameToNum( side ); + + if ( hand == WEAPON_ERROR ) + return; + + weapon = GetActiveWeapon( hand ); + if ( !weapon ) return; + + if ( weapon->isSubclassOf( Weapon ) ) + { + weapon->targetidleflag = false; + CancelEventsOfType( EV_Weapon_TargetIdleThink ); + + weapon->PutAway(); + + if ( gi.Anim_NumForName( weapon->edict->s.modelindex, "putaway" ) != -1 ) + weapon->SetAnim( "putaway", EV_Weapon_DonePutaway ); + else + weapon->PostEvent( EV_Weapon_DonePutaway, 0.0f ); + } +} + + +//-------------------------------------------------------------- +// +// Name: useWeapon +// Class: Player +// +// Description: Find the weapon by name and use it in the specifed +// hand. +// +// Parameters: const char *weaponname -- name of the weapon to use +// weaponhand_t hand -- Hand to use it in +// +// Returns: None +// +//-------------------------------------------------------------- +bool Player::useWeapon( const char *weaponname, weaponhand_t hand ) +{ + Weapon *weapon; + str name(weaponname); + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( gpm ) + { + str objectName("CurrentPlayer."); + objectName += name ; + if ( gpm->hasProperty( objectName, "name" ) ) + { + name = gpm->getStringValue( objectName, "name" ); + } + } + + weapon = ( Weapon * )FindItem( name ); + + // Check to see if player has the weapon + if ( !weapon ) + { + warning( "Player::useWeapon", "Player does not have weapon %s", weaponname ); + return false; + } + + return useWeapon( weapon, hand ); +} + +bool Player::useWeapon( Weapon *weapon, weaponhand_t hand ) +{ + Weapon * activeWeapon; + + if ( !weapon ) + { + warning( "Player::useWeapon", "Null weapon used.\n" ); + return false; + } + + // Check to see if we are already in the process of using a new weapon. + //if ( newActiveWeapon.weapon ) + //{ + // return false; + //} + + // Check to see if weapon has ammo and if useNoAmmo is allowed + if ( !weapon->HasAmmo( FIRE_MODE1 ) && !weapon->HasAmmo( FIRE_MODE2 ) && !weapon->GetUseNoAmmo() ) + { + Sound( "snd_noammo" ); + return false; + } + + // Check to see if the hand is allowed to have that weapon + + // WEAPON_ANY can be used in WEAPON_LEFT or WEAPON_RIGHT but not as a WEAPON_DUAL, so check for that first + if ( ( hand == WEAPON_DUAL ) && ( weapon->GetHand() != hand ) ) + { + warning( "Player::useWeapon", "Weapon %s is not allowed in %s", weapon->getName().c_str(), WeaponHandNumToName( hand ) ); + return false; + } + else if ( ( weapon->GetHand() != WEAPON_ANY ) && ( weapon->GetHand() != hand ) ) + { + warning( "Player::useWeapon", "Weapon %s is not allowed in %s", weapon->getName().c_str(), WeaponHandNumToName( hand ) ); + return false; + } + + // If the weapon we are wielding is a WEAPON_DUAL, then put away the left and right ones + if ( weapon->GetHand() == WEAPON_DUAL ) + { + activeWeapon = GetActiveWeapon( WEAPON_LEFT ); + if ( activeWeapon ) + activeWeapon->PutAway(); + activeWeapon = GetActiveWeapon( WEAPON_RIGHT ); + if ( activeWeapon ) + activeWeapon->PutAway(); + } + + // Check to see if a WEAPON_DUAL is being used and put it away if needed + activeWeapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( activeWeapon && activeWeapon != weapon ) + { + activeWeapon->PutAway(); + // we just want to put the dual handed weapon away + if ( activeWeapon == weapon ) + { + return false; + } + } + + // Now get the active weapon in the specified hand + activeWeapon = GetActiveWeapon( hand ); + + // Check to see if this weapon is already being used in this hand and just put it away and return + if ( ( activeWeapon == weapon ) && ( !newActiveWeapon.weapon || newActiveWeapon.weapon == weapon ) ) + { + // Set the putaway flag to true. The state machine will then play the correct animation to put away the active weapon + //activeWeapon->PutAway(); + return true; + } + + // If activeWeapon is set, and it's not == weapon then put away this weapon + /* + if ( activeWeapon ) + { + // Set the putaway flag to true. The state machine will then play the correct animation to put away the active weapon + activeWeapon->PutAway(); + } + */ + + // Check to see if this weapon is being used in a different hand and put it away as well (if it's in a different hand) + if ( IsActiveWeapon( weapon ) ) + { + weapon->PutAway(); + } + + // Set the newActiveWeapon as the weapon specified, the state machine will play the appropriate animation and + // trigger when to attach it to the player model. + newActiveWeapon.weapon = weapon; + newActiveWeapon.hand = hand; + return true; +} + +//==================== +//ActivateShield +//==================== +void Player::ActivateShield( Event *ev ) +{ + shield_active = true; +} + +//==================== +//DeactivateShield +//==================== +void Player::DeactivateShield( Event *ev ) +{ + shield_active = false; +} + +//==================== +//ShieldActive +//==================== +qboolean Player::ShieldActive( void ) +{ + return shield_active; +} + +//==================== +//LargeShieldActive +//==================== +qboolean Player::LargeShieldActive( void ) +{ + Weapon *weapon; + qboolean large_shield_active=false; + + weapon = GetActiveWeapon( WEAPON_LEFT ); + if ( weapon && !str::icmp( weapon->item_name, "LargeShield" ) ) + large_shield_active = true; + + return shield_active && large_shield_active; +} + +void Player::AcquireHeadTarget( void ) +{ + vec3_t mat[3]; + Entity *new_head_target; + Entity *ent; + Item *item; + + // Find a good target + + if ( targetEnemy ) + head_target = targetEnemy; + else + { + AnglesToAxis( headAngles, mat ); + + // Make sure not to look at items too long + + if ( ( look_at_time <= level.time ) && head_target && head_target->isSubclassOf( Item ) ) + { + item = (Item *)(Entity *)head_target; + item->has_been_looked_at = true; + } + + // Get the new head target + new_head_target = FindHeadTarget( this->centroid, mat[0], 160.0f, 1000.0f ); + + if ( !new_head_target ) + { + head_target = NULL; + return; + } + + if ( new_head_target != head_target ) + { + // If we were looking at an item and are not now, mark it as has been looked at + + if ( head_target && head_target->isSubclassOf( Item ) ) + { + item = (Item *)(Entity *)head_target; + item->has_been_looked_at = true; + } + + // Set up new head target + + head_target = new_head_target; + + look_at_time = level.time + 5.0f; + + // Mark items near this one as looked at + + if ( head_target && head_target->isSubclassOf( Item ) ) + { + ent = findradius( NULL, head_target->origin, 50.0f ); + + while( ent ) + { + if ( ent != head_target && ent->isSubclassOf( Item ) ) + { + item = (Item *)ent; + item->has_been_looked_at = true; + } + + ent = findradius( ent, head_target->origin, 50.0f ); + } + } + } + } +} diff --git a/dlls/game/player_util.cpp b/dlls/game/player_util.cpp new file mode 100644 index 0000000..5c1f08c --- /dev/null +++ b/dlls/game/player_util.cpp @@ -0,0 +1,556 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/player_util.cpp $ +// $Revision:: 15 $ +// $Author:: Steven $ +// $Date:: 10/05/02 9:18p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// This file is used to hold the utility functions that are issued by the +// player at the console. Most of these are developer commands + +#include "_pch_cpp.h" +#include "player.h" +#include "object.h" + +//==================== +//Player::ActorInfo +//==================== +void Player::ActorInfo( Event *ev ) +{ + int num; + Entity *ent; + + if ( ev->NumArgs() != 1 ) + { + gi.SendServerCommand( edict-g_entities, "print \"Usage: actorinfo \n\"" ); + return; + } + + num = ev->GetInteger( 1 ); + if ( ( num < 0 ) || ( num >= globals.max_entities ) ) + { + gi.SendServerCommand( edict-g_entities, "print \"Value out of range. Possible values range from 0 to %d.\n\"", globals.max_entities ); + return; + } + + ent = G_GetEntity( num ); + if ( !ent || !ent->isSubclassOf( Actor ) ) + { + gi.SendServerCommand( edict-g_entities, "print \"Entity not an Actor.\n\"" ); + } + else + { + ( ( Actor * )ent )->ShowInfo(); + } +} + +//==================== +//Player::WhatIs +//==================== +void Player::WhatIs( Event *ev ) +{ + int num; + Entity *ent; + + if ( ev->NumArgs() != 1 ) + { + gi.SendServerCommand( edict-g_entities, "print \"Usage: whatis \n\"" ); + return; + } + + num = ev->GetInteger( 1 ); + if ( ( num < 0 ) || ( num >= globals.max_entities ) ) + { + gi.SendServerCommand( edict-g_entities, "print \"Value out of range. Possible values range from 0 to %d.\n\"", globals.max_entities ); + return; + } + + ent = G_GetEntity( num ); + if ( !ent ) + { + gi.SendServerCommand( edict-g_entities, "print \"Entity not in use.\n\"", globals.max_entities ); + } + else + { + const char * animname; + + animname = NULL; + if ( gi.IsModel( ent->edict->s.modelindex ) ) + { + animname = gi.Anim_NameForNum( ent->edict->s.modelindex, ent->edict->s.anim & ANIM_MASK ); + } + + if ( !animname ) + { + animname = "( N/A )"; + } + + gi.SendServerCommand( edict-g_entities, "print \"" + "Entity # : %d\n" + "Class ID : %s\n" + "Classname : %s\n" + "Targetname : %s\n" + "Modelname : %s\n" + "Animname : %s\n" + "Origin : ( %f, %f, %f )\n" + "Angles : ( %f, %f, %f )\n" + "Bounds : Mins( %.2f, %.2f, %.2f ) Maxs( %.2f, %.2f, %.2f )\n" + "Velocity : ( %f, %f, %f )\n" + "SVFLAGS : %x\n" + "Movetype : %i\n" + "Solidtype : %i\n" + "Contents : %x\n" + "Areanum : %i\n" + "Parent : %i\n" + "Health : %.1f\n" + "Max Health : %.1f\n" + "Edict Owner: %i\n\"", + num, + ent->getClassID(), + ent->getClassname(), + ent->TargetName(), + ent->model.c_str(), + animname, + ent->origin.x, ent->origin.y, ent->origin.z, + ent->angles.x, ent->angles.y, ent->angles.z, + ent->mins.x, ent->mins.y, ent->mins.z, ent->maxs.x, ent->maxs.y, ent->maxs.z, + ent->velocity.x, ent->velocity.y, ent->velocity.z, + ent->edict->svflags, + ent->movetype, + ent->edict->solid, + ent->edict->contents, + ent->edict->areanum, + ent->edict->s.parent, + ent->health, + ent->max_health, + ent->edict->ownerNum + ); + } +} + +//==================== +//Player::KillEnt +//==================== +void Player::KillEnt( Event * ev ) +{ + int num; + Entity *ent; + + if ( ev->NumArgs() != 1 ) + { + gi.SendServerCommand( edict-g_entities, "print \"Usage: killent \n\"" ); + return; + } + + num = ev->GetInteger( 1 ); + if ( ( num < 0 ) || ( num >= globals.max_entities ) ) + { + gi.SendServerCommand( edict-g_entities, "print \"Value out of range. Possible values range from 0 to %d.\n\"", globals.max_entities ); + return; + } + + ent = G_GetEntity( num ); + ent->Damage( world, world, ent->max_health + 25.0f, origin, vec_zero, vec_zero, 0, 0, 0 ); +} + +//==================== +//Player::RemoveEnt +//==================== +void Player::RemoveEnt( Event * ev ) +{ + int num; + Entity *ent; + + if ( ev->NumArgs() != 1 ) + { + gi.SendServerCommand( edict-g_entities, "print \"Usage: removeent \n\"" ); + return; + } + + num = ev->GetInteger( 1 ); + if ( ( num < 0 ) || ( num >= globals.max_entities ) ) + { + gi.SendServerCommand( edict-g_entities, "print \"Value out of range. Possible values range from 0 to %d.\n\"", globals.max_entities ); + return; + } + + ent = G_GetEntity( num ); + ent->PostEvent( Event( EV_Remove ), 0.0f ); +} + +//==================== +//Player::KillClass +//==================== +void Player::KillClass( Event * ev ) +{ + int except; + str classname; + gentity_t * from; + Entity *ent; + + if ( ev->NumArgs() < 1 ) + { + gi.SendServerCommand( edict-g_entities, "print \"Usage: killclass [except entity number]\n\"" ); + return; + } + + classname = ev->GetString( 1 ); + + except = 0; + if ( ev->NumArgs() == 2 ) + { + except = ev->GetInteger( 1 ); + } + + for ( from = this->edict + 1; from < &g_entities[ globals.num_entities ]; from++ ) + { + if ( !from->inuse ) + { + continue; + } + + assert( from->entity ); + + ent = from->entity; + + if ( ent->entnum == except ) + { + continue; + } + + if ( ent->inheritsFrom( classname.c_str() ) ) + { + ent->Damage( world, world, ent->max_health + 25.0f, origin, vec_zero, vec_zero, 0, 0, 0 ); + } + } +} + +//==================== +//Player::RemoveClass +//==================== +void Player::RemoveClass( Event * ev ) +{ + int except; + str classname; + gentity_t * from; + Entity *ent; + + if ( ev->NumArgs() < 1 ) + { + gi.SendServerCommand( edict-g_entities, "print \"Usage: removeclass [except entity number]\n\"" ); + return; + } + + classname = ev->GetString( 1 ); + + except = 0; + if ( ev->NumArgs() == 2 ) + { + except = ev->GetInteger( 1 ); + } + + for ( from = this->edict + 1; from < &g_entities[ globals.num_entities ]; from++ ) + { + if ( !from->inuse ) + { + continue; + } + + assert( from->entity ); + + ent = from->entity; + + if ( ent->entnum == except ) + continue; + + if ( ent->inheritsFrom( classname.c_str() ) ) + { + ent->PostEvent( Event( EV_Remove ), 0.0f ); + } + } +} + +//==================== +//Player::TestThread +//==================== +void Player::TestThread( Event *ev ) +{ + const char *label; + CThread *thread; + + if ( ev->NumArgs() < 1 ) + { + gi.SendServerCommand( edict-g_entities, "print \"Syntax: testthread label.\n\"" ); + return; + } + + label = ev->GetString( 1 ); + thread = Director.CreateThread( label ); + if ( thread ) + { + // start right away + thread->Start(); + } +} + +//==================== +//Player::SpawnEntity +//==================== +void Player::SpawnEntity( Event *ev ) +{ + Entity *ent; + str name; + ClassDef *cls; + str text; + Vector forward; + Vector up; + Vector delta; + Vector v; + int n; + int i; + Event *e; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "Usage: spawn entityname [keyname] [value]..." ); + return; + } + + name = ev->GetString( 1 ); + if ( !name.length() ) + { + ev->Error( "Must specify an entity name" ); + return; + } + + // create a new entity + SpawnArgs args; + + args.setArg( "classname", name.c_str() ); + args.setArg( "model", name.c_str() ); + + cls = args.getClassDef(); + if ( !cls ) + { + cls = &Entity::ClassInfo; + } + + if ( !checkInheritance( &Entity::ClassInfo, cls ) ) + { + ev->Error( "%s is not a valid Entity", name.c_str() ); + return; + } + + ent = ( Entity * )cls->newInstance(); + + e = new Event( EV_Model ); + e->AddString( name.c_str() ); + ent->PostEvent( e, EV_SPAWNARG ); + + angles.AngleVectors( &forward, NULL, &up ); + v = origin + ( ( forward + up ) * 128.0f ); + + e = new Event( EV_SetOrigin ); + e->AddVector( v ); + ent->PostEvent( e, EV_SPAWNARG ); + + delta = origin - v; + v.x = 0; + v.y = delta.toYaw(); + v.z = 0; + + e = new Event( EV_SetAngles ); + e->AddVector( v ); + ent->PostEvent( e, EV_SPAWNARG ); + + if ( ev->NumArgs() > 2 ) + { + n = ev->NumArgs(); + for( i = 2; i <= n; i += 2 ) + { + e = new Event( ev->GetString( i ) ); + e->AddToken( ev->GetString( i + 1 ) ); + ent->PostEvent( e, EV_SPAWNARG ); + } + } + + e = new Event( EV_Anim ); + e->AddString( "idle" ); + ent->PostEvent( e, EV_SPAWNARG ); +} + +//==================== +//Player::SpawnActor +//==================== +void Player::SpawnActor( Event *ev ) +{ + Entity *ent; + str name; + str text; + Vector forward; + Vector up; + Vector delta; + Vector v; + int n; + int i; + ClassDef *cls; + Event *e; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "Usage: actor [modelname] [keyname] [value]..." ); + return; + } + + name = ev->GetString( 1 ); + if ( !name[ 0 ] ) + { + ev->Error( "Must specify a model name" ); + return; + } + + if ( !strstr( name.c_str(), ".tik" ) ) + { + name += ".tik"; + } + + // create a new entity + SpawnArgs args; + + args.setArg( "model", name.c_str() ); + + cls = args.getClassDef(); + + if ( cls == &Object::ClassInfo ) + { + cls = &Actor::ClassInfo; + } + + if ( !cls || !checkInheritance( &Actor::ClassInfo, cls ) ) + { + ev->Error( "%s is not a valid Actor", name.c_str() ); + return; + } + + ent = ( Entity * )cls->newInstance(); + e = new Event( EV_Model ); + e->AddString( name.c_str() ); + ent->PostEvent( e, EV_SPAWNARG ); + + angles.AngleVectors( &forward, NULL, &up ); + v = origin + ( ( forward + up ) * 40.0f ); + + e = new Event( EV_SetOrigin ); + e->AddVector( v ); + ent->PostEvent( e, EV_SPAWNARG ); + + delta = origin - v; + v = delta.toAngles(); + + e = new Event( EV_SetAngle ); + e->AddFloat( v[ 1 ] ); + ent->PostEvent( e, EV_SPAWNARG ); + + if ( ev->NumArgs() > 2 ) + { + n = ev->NumArgs(); + for( i = 2; i <= n; i += 2 ) + { + e = new Event( ev->GetString( i ) ); + e->AddToken( ev->GetString( i + 1 ) ); + ent->PostEvent( e, EV_SPAWNARG ); + } + } +} + +void Player::ListInventoryEvent( Event *ev ) +{ + ListInventory(); +} + +void Player::GetStateAnims( Container *c ) +{ + statemap_Legs->GetAllAnims( c ); + statemap_Torso->GetAllAnims( c ); +} + +static fileHandle_t logfile=NULL; + +static void OpenPlayerLogFile( void ) +{ + str s,filename; + + s = "playlog_"; + s += level.mapname; + filename = gi.GetArchiveFileName( NULL, s, "log", qtrue ); + + logfile = gi.FS_FOpenFileWrite( filename.c_str() ); +} + +void Player::LogStats( Event *ev ) +{ + str s; + + if ( !logfile ) + { + OpenPlayerLogFile(); + } + + if ( !logfile ) + { + return; + } + + int b = AmmoCount( "Bullet" ); + int p = AmmoCount( "Plasma" ); + int g = AmmoCount( "Gas" ); + int r = AmmoCount( "Rocket" ); + int f = AmmoCount( "Flashbangs" ); + int m = AmmoCount( "Meteor" ); + int gp = AmmoCount( "Gas Pod" ); + + s = va( "%.2f\t", level.time ); + s += va( "(%.2f %.2f %.2f)\t", origin.x, origin.y, origin.z ); + s += va( "%.2f\t", health ); + s += va( "%d\t%d\t%d\t%d\t%d\t%d\t%d\n", b,p,g,r,f,m,gp ); + + gi.FS_Write( s, s.length(), logfile ); + gi.FS_Flush( logfile ); + + Event *ev1 = new Event( ev ); + PostEvent( ev1, 1.0f ); +} + +void ClosePlayerLogFile( void ) +{ + if ( logfile ) + { + gi.FS_FCloseFile( logfile ); + logfile = NULL; + } +} + +void Player::SkipCinematic( Event *ev ) +{ + if ( level.cinematic && ( world->skipthread.length() > 1 ) ) + { + str skipthread; + G_ClearFade(); + + skipthread = world->skipthread; + // now that we have executed it, lets kill it so we don't call it again + world->skipthread = ""; + ExecuteThread( skipthread ); + // reset the roll on our view just in case + v_angle.z = 0; + SetViewAngles( v_angle ); + } +} diff --git a/dlls/game/playerheuristics.cpp b/dlls/game/playerheuristics.cpp new file mode 100644 index 0000000..af49063 --- /dev/null +++ b/dlls/game/playerheuristics.cpp @@ -0,0 +1,592 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/playerheuristics.cpp $ +// $Revision:: 14 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: + +#include "_pch_cpp.h" +#include "playerheuristics.h" + +static fileHandle_t heuristicFile=NULL; +static str heuristicFileName=NULL; + +PlayerHeuristics::PlayerHeuristics() +{ + //Single Player Stats + shotsFired = 0; + shotsHit = 0; + numberOfDeaths = 0; + enemiesKilled = 0; + timeOnLevel = 0; + levelStartTime = 0; + levelEndTime = 0; + playerHealth = 0.0f; + lastLevel = ""; + currentLevel = ""; + + shotsFiredInLevel = 0; + shotsHitInLevel = 0; + enemiesKilledInLevel = 0; + + + //Multiplayer Stats + ping = 0; + itemsPickedUp = 0; + specialEvents = 0; + + + //Skill Level + skillLevel = 0; + + //Mission Objectives + numObjectives = 0; + numCompleteObjectives = 0; + numFailedObjectives = 0; + +} + +PlayerHeuristics::PlayerHeuristics( const PlayerHeuristics &pHeuristic ) +{ + //Single Player Stats + shotsFired = pHeuristic.shotsFired; + shotsHit = pHeuristic.shotsHit; + numberOfDeaths = pHeuristic.numberOfDeaths; + enemiesKilled = pHeuristic.enemiesKilled; + timeOnLevel = pHeuristic.timeOnLevel; + levelStartTime = pHeuristic.levelStartTime; + levelEndTime = pHeuristic.levelEndTime; + playerHealth = pHeuristic.playerHealth; + lastLevel = pHeuristic.lastLevel; + currentLevel = pHeuristic.currentLevel; + + + shotsFiredInLevel = pHeuristic.shotsFiredInLevel; + shotsHitInLevel = pHeuristic.shotsHitInLevel; + enemiesKilledInLevel = pHeuristic.enemiesKilledInLevel; + teammatesKilledInLevel = pHeuristic.teammatesKilledInLevel; + + //Multiplayer Stats + ping = pHeuristic.ping; + itemsPickedUp = pHeuristic.itemsPickedUp; + specialEvents = pHeuristic.specialEvents; + + //Skill Level + skillLevel = pHeuristic.skillLevel; + + //Mission Objectives + numObjectives = pHeuristic.numObjectives; + numCompleteObjectives = pHeuristic.numCompleteObjectives; + numFailedObjectives = pHeuristic.numFailedObjectives; + +} + +PlayerHeuristics::~PlayerHeuristics() +{ + +} + +void PlayerHeuristics::ShowHeuristics( const Player *player ) +{ + //Show Player Information in the console + + levelEndTime = level.time; + timeOnLevel+= levelEndTime; + playerHealth = player->health; + + ping = player->client->ping; + + float accuracy = 0; + + if ( shotsFired != 0 ) + { + accuracy = (float)shotsHit / (float)shotsFired; + accuracy *= 100.0f; + } + + gi.Printf( "\nPlayer Heuristics:\n"); + gi.Printf( "Shots Fired: %d\n", shotsFired ); + gi.Printf( "Shots Hit: %d\n", shotsHit ); + gi.Printf( "Accuracy: %%%.2f\n", accuracy ); + gi.Printf( "Number of Deaths: %d\n", numberOfDeaths ); + gi.Printf( "Time On Level: %f\n", timeOnLevel ); + gi.Printf( "Player Health: %d\n", playerHealth ); + gi.Printf( "Ping: %d\n", ping ); + gi.Printf( "Items Picked Up: %d\n", itemsPickedUp ); + gi.Printf( "Special Events: %d\n", specialEvents ); + gi.Printf( "Enemies Killed %d\n", enemiesKilled ); + gi.Printf( "\n" ); + gi.Printf( "Skill Level: %f\n", CalculateSkillLevel() ); + + gi.Printf( "\n" ); + +} + +void PlayerHeuristics::SaveHeuristics( const Player *player ) +{ + str token; + str levelName; + Script script; + qboolean currentHeuristicsWritten; + PlayerHeuristics *pHeuristic; + + //Precalculation for stats + levelEndTime = level.time; + timeOnLevel+= levelEndTime; + playerHealth = player->health; + ping = player->client->ping; + skillLevel = CalculateSkillLevel(); + + script.LoadFile( heuristicFileName ); + + OpenPlayerHeuristicFile(); + if ( !heuristicFile ) + { + script.Close(); + return; + } + + //Save off a copy of our current stats + pHeuristic = new PlayerHeuristics( *this ); + + currentHeuristicsWritten = false; + while( script.TokenAvailable( true ) ) + { + token = script.GetToken(false); + + //The reason we saved off our old stats, and are reading through + //is that in order to keep all the stats for all the other levels + //we need to read them all in and write them back out, just replacing + //the information we need for our current level + if ( !Q_stricmp( token.c_str(), "Level:" ) ) + { + levelName = script.GetToken(false); + if ( Q_stricmp( levelName.c_str(), level.mapname.c_str() ) ) //Not Equal + { + script.UnGetToken(); + ReadInHeuristicData(script, levelName); + } + else + { + + //Restore Values + shotsFired = pHeuristic->shotsFired; + shotsHit = pHeuristic->shotsHit; + numberOfDeaths = pHeuristic->numberOfDeaths; + enemiesKilled = pHeuristic->enemiesKilled; + timeOnLevel = pHeuristic->timeOnLevel; + levelStartTime = pHeuristic->levelStartTime; + levelEndTime = pHeuristic->levelEndTime; + playerHealth = pHeuristic->playerHealth; + lastLevel = pHeuristic->lastLevel; + currentLevel = pHeuristic->currentLevel; + + //Multiplayer Stats + ping = pHeuristic->ping; + itemsPickedUp = pHeuristic->itemsPickedUp; + specialEvents = pHeuristic->specialEvents; + + + //Skill Level + skillLevel = pHeuristic->skillLevel; + + teammatesKilledInLevel = pHeuristic->teammatesKilledInLevel; + shotsFiredInLevel = pHeuristic->shotsFiredInLevel; + shotsHitInLevel = pHeuristic->shotsFiredInLevel; + + numObjectives = pHeuristic->numObjectives; + numCompleteObjectives = pHeuristic->numCompleteObjectives; + numFailedObjectives = pHeuristic->numFailedObjectives; + + + + currentHeuristicsWritten = true; + } + + WriteOutHeuristicData(levelName); + } + } + + if (!currentHeuristicsWritten) + { + currentHeuristicsWritten = true; + WriteOutHeuristicData(level.mapname); + } + + ClosePlayerHeuristicFile(); +} + +void PlayerHeuristics::LoadHeuristics() +{ + str token; + Script script; + + CheckForHeuristicFile(); + + //Clear Heuristic Data Structure; + shotsFired = 0; + shotsHit = 0; + numberOfDeaths = 0; + enemiesKilled = 0; + timeOnLevel = 0; + levelStartTime = 0; + levelEndTime = 0; + playerHealth = 0; + + ping = 0; + itemsPickedUp = 0; + specialEvents = 0; + + //skillLevel = 0; + + script.LoadFile( heuristicFileName ); + + ReadInHeuristicData(script, level.mapname); + + //level.setSkill(skillLevel); + + script.Close(); + ClosePlayerHeuristicFile(); +} + +void PlayerHeuristics::ReadInHeuristicData( Script& script, str& levelName ) +{ + str token; + str precedingToken ; + + //Fill Structure from File + while( script.TokenAvailable( true ) ) + { + str heuristic; + str endSection; + token = script.GetToken( false ); + + if ( !Q_stricmp( token.c_str(), levelName.c_str() ) && ( precedingToken == "Level:" ) ) + { + endSection = levelName + "_end"; + + while ( Q_stricmp( token.c_str(), endSection.c_str() ) ) + { + token = script.GetToken( false ); + if ( !Q_stricmp( token.c_str(), "ShotsFired:" ) ) + { + heuristic = script.GetToken(false); + shotsFired = atol(heuristic.c_str()); + } + else if ( !Q_stricmp( token.c_str(), "ShotsHit:" ) ) + { + heuristic = script.GetToken(false); + shotsHit = atol(heuristic.c_str()); + } + else if ( !Q_stricmp( token.c_str(), "NumberOfDeaths:" ) ) + { + heuristic = script.GetToken(false); + numberOfDeaths = atol(heuristic.c_str()); + } + else if ( !Q_stricmp( token.c_str(), "EnemiesKilled:" ) ) + { + heuristic = script.GetToken(false); + enemiesKilled = atol(heuristic.c_str()); + } + else if ( !Q_stricmp( token.c_str(), "TimeOnLevel:" ) ) + { + heuristic = script.GetToken(false); + timeOnLevel = (float)atof(heuristic.c_str()); + } + else if ( !Q_stricmp( token.c_str(), "PlayerHealth:" ) ) + { + heuristic = script.GetToken(false); + playerHealth = atoi(heuristic.c_str()); + } + else if ( !Q_stricmp( token.c_str(), "Ping:" ) ) + { + heuristic = script.GetToken(false); + ping = atoi(heuristic.c_str()); + } + else if ( !Q_stricmp( token.c_str(), "ItemsPickedUp:" ) ) + { + heuristic = script.GetToken(false); + itemsPickedUp = atoi(heuristic.c_str()); + } + else if ( !Q_stricmp( token.c_str(), "SpecialEvents:" ) ) + { + heuristic = script.GetToken(false); + specialEvents = atoi(heuristic.c_str()); + } + else if ( !Q_stricmp( token.c_str(), "SkillLevel:" ) ) + { + heuristic = script.GetToken(false); + skillLevel = atof(heuristic.c_str()); + } + } + return; + } + precedingToken = token ; + } +} + +void PlayerHeuristics::WriteOutHeuristicData( const str &levelName ) +{ + str s; + str stat; + + //Build File String + s = "Level: " + levelName + "\n"; + + stat = va("%l", shotsFired); + s+= "ShotsFired: " + stat + "\n"; + + stat = va("%l", shotsHit); + s+= "ShotsHit: " + stat + "\n"; + + stat = va("%l", numberOfDeaths); + s+= "NumberOfDeaths: " + stat + "\n"; + + stat = va("%l", enemiesKilled); + s+= "EnemiesKilled: " + stat + "\n"; + + stat = timeOnLevel; + s+= "TimeOnLevel: " + stat + "\n"; + + stat = playerHealth; + s+= "PlayerHealth: " + stat + "\n"; + + stat = ping; + s+= "Ping: " + stat + "\n"; + + stat = itemsPickedUp; + s+= "ItemsPickedUp: " + stat + "\n"; + + stat = specialEvents; + s+= "SpecialEvents: " + stat + "\n"; + + stat = skillLevel; + s+= "SkillLevel: " + stat + "\n"; + + s+= levelName + "_end"; + s+= "\n\n"; + + gi.FS_Write( s, s.length(), heuristicFile ); + gi.FS_Flush( heuristicFile ); + +} + +void PlayerHeuristics::CreateInitialHeuristicFile() +{ + //Clear Heuristic Data Structure; + shotsFired = 0; + shotsHit = 0; + numberOfDeaths = 0; + enemiesKilled = 0; + timeOnLevel = 0; + levelStartTime = 0; + levelEndTime = 0; + playerHealth = 0; + + ping = 0; + itemsPickedUp = 0; + specialEvents = 0; + + skillLevel = 0; + + OpenPlayerHeuristicFile(); + if ( !heuristicFile ) + { + return; + } + + WriteOutHeuristicData(level.mapname); + ClosePlayerHeuristicFile(); +} + + + +void PlayerHeuristics::OpenPlayerHeuristicFile() +{ + if ( !heuristicFile ) + { + heuristicFile = gi.FS_FOpenFileWrite( heuristicFileName.c_str() ); + } +} + +void PlayerHeuristics::ClosePlayerHeuristicFile() +{ + if ( heuristicFile ) + { + gi.FS_FCloseFile( heuristicFile ); + heuristicFile = NULL; + } +} + +void PlayerHeuristics::CheckForHeuristicFile() +{ + str s,filename; + int filesize; + + s = "heuristics"; + filename = gi.GetArchiveFileName( NULL, s, "log", qtrue ); + heuristicFileName = filename.c_str(); + + filesize = gi.FS_ReadFile( heuristicFileName.c_str(), NULL, true ); + if ( filesize <= 0 ) //File Does Not Exsist + CreateInitialHeuristicFile(); +} + +float PlayerHeuristics::CalculateSkillLevel() +{ + // Current Skill Level Formula + // (((Accuracy * 100) * .75 ) + (1 - (Deaths * .25))) / 10 + // Returns a floating point number between 0 and 10 + + // I expect this function to change a lot during the course of development + // That is why it is written so verbosely. + + float Accuracy; + float Deaths; + float SkillLevel; + + Accuracy = (float)shotsHit / (float)shotsFired; + Deaths = numberOfDeaths; + + SkillLevel = (((Accuracy * 100.0f) * .75f ) + (1.0f - (Deaths * .25f))) / 10.0f; + if (SkillLevel < 0.0f ) + { + SkillLevel = 0.0f; + } + + return SkillLevel; + +} + + + +//----------------------------------------------------- +// +// Name: ClearLevelStatistics +// Class: PlayerHeuristics +// +// Description: Clears the variables that are level specific. +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void PlayerHeuristics::ClearLevelStatistics(void) +{ + shotsFiredInLevel = 0; + shotsHitInLevel = 0; + enemiesKilledInLevel = 0; + numCompleteObjectives = 0; + numObjectives = 0; + numFailedObjectives = 0; + teammatesKilledInLevel = 0; +} + + +void PlayerHeuristics::SetShotsFired( long int shots ) +{ + shotsFired = shots; +} + +void PlayerHeuristics::IncrementShotsFired( void ) +{ + shotsFired++; + shotsFiredInLevel++; +} + +void PlayerHeuristics::SetShotsHit( long int shots ) +{ + shotsHit = shots; +} + +void PlayerHeuristics::IncrementShotsHit() +{ + shotsHit++; + shotsHitInLevel++; +} + +void PlayerHeuristics::SetNumberOfDeaths( long int deaths ) +{ + numberOfDeaths = deaths; +} + +void PlayerHeuristics::IncrementNumberOfDeaths() +{ + numberOfDeaths++; +} + +void PlayerHeuristics::SetTimeOnLevel( float levelTime ) +{ + timeOnLevel = levelTime; +} + +void PlayerHeuristics::SetPlayerHealth( int health ) +{ + playerHealth = health; +} + +void PlayerHeuristics::SetPing( int ping_value ) +{ + ping = ping_value; +} + +void PlayerHeuristics::SetItemsPickedUp( int items ) +{ + itemsPickedUp = items; +} + +void PlayerHeuristics::IncrementItemsPickedUp() +{ + itemsPickedUp++; +} + +void PlayerHeuristics::SetSpecialEvents( int specialevent ) +{ + specialEvents = specialevent; +} + +void PlayerHeuristics::IncrementSpecialEvents() +{ + specialEvents++; +} + +void PlayerHeuristics::UpdateShotsFired( void ) +{ + IncrementShotsFired(); +} + + +void PlayerHeuristics::SetObjectives(int numMissionObjectives) +{ + numObjectives = numMissionObjectives; +} + +void PlayerHeuristics::IncrementCompleteObjectives() +{ + numCompleteObjectives++; +} + +void PlayerHeuristics::IncrementFailedObjectives() +{ + numFailedObjectives++; +} + +void PlayerHeuristics::IncrementEnemiesKilled(void) +{ + enemiesKilled++; + enemiesKilledInLevel++; +} + +void PlayerHeuristics::IncrementTeammatesKilled(void) +{ + teammatesKilledInLevel++; +} diff --git a/dlls/game/playerheuristics.h b/dlls/game/playerheuristics.h new file mode 100644 index 0000000..0385518 --- /dev/null +++ b/dlls/game/playerheuristics.h @@ -0,0 +1,131 @@ + +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/playerheuristics.h $ +// $Revision:: 10 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:11a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// PlayerHeuristics Class Definition. +// + +class PlayerHeuristics; + +// PlayerHeuristics class +// +// This class and file are in a state of transition, and currently, is messy. +// I have, at least, managed to pull most of the heuristic stuff out of player.cpp and place it here, +// for now. +// +// Future Plans: +// Where this mess is headed -- Eventually, I would like to create a new system where we have a +// Heuristics manager that is seperate from anything in the game ( like Player, or Weapon ) and +// Thus could be used by anyone to track their own stats. +// +// PlayerHeuristics would still be around, though its role would change to that of an intermediary +// between the new Heuristics Manager and Player. It would control what stats it's tracking and +// manage any _PLAYER_ specific heuristic data. +// + +#ifndef __PLAYER_HEURISTICS_H__ +#define __PLAYER_HEURISTICS_H__ + +#include "player.h" + +class PlayerHeuristics : public Class +{ + public: + + PlayerHeuristics(); + PlayerHeuristics( const PlayerHeuristics &pHeuristic ); + ~PlayerHeuristics(); + + void ShowHeuristics( const Player *player ); + void SaveHeuristics( const Player *player ); + void LoadHeuristics(); + void ReadInHeuristicData(Script& script, str& levelName); + void WriteOutHeuristicData( const str& levelName ); + void CreateInitialHeuristicFile(); + void OpenPlayerHeuristicFile(); + void ClosePlayerHeuristicFile(); + void CheckForHeuristicFile(); + float CalculateSkillLevel(); + void ClearLevelStatistics(void); + + void SetShotsFired( long int shots); + void IncrementShotsFired( void ); + void SetShotsHit( long int shots); + void IncrementShotsHit(); + void SetNumberOfDeaths( long int deaths); + void IncrementNumberOfDeaths(); + void SetTimeOnLevel( float levelTime ); + void SetPlayerHealth( int health ); + void SetPing (int ping_value ); + void SetItemsPickedUp ( int items ); + void IncrementItemsPickedUp (); + + void IncrementTeammatesKilled(void); + + void SetSpecialEvents( int specialevent ); + void IncrementSpecialEvents(); + void UpdateShotsFired( void ); + void SetEnemiesKilled( long int enemies ); + void IncrementEnemiesKilled(void); + + //Mission objectives + void SetObjectives(int numMissionObjectives); + void IncrementCompleteObjectives(); + void IncrementFailedObjectives(); + + + + //Single Player Stats Persistent + long int shotsFired; + long int shotsHit; + long int numberOfDeaths; + long int enemiesKilled; + + //level properties... + //these are the statistics for that instance of the level + long int shotsFiredInLevel; + long int shotsHitInLevel; + long int enemiesKilledInLevel; + long int teammatesKilledInLevel; + + int numObjectives; + int numCompleteObjectives; + int numFailedObjectives; + + + float timeOnLevel; + float levelStartTime; + float levelEndTime; + float playerHealth; + str lastLevel; + str currentLevel; + + //Multiplayer Stats + int ping; + int itemsPickedUp; + int specialEvents; + + //Weapon Stats + + + //Skill Level + float skillLevel; + +}; + + + + +#endif /* __PLAYER_HEURISTICS_H__ */ diff --git a/dlls/game/portal.cpp b/dlls/game/portal.cpp new file mode 100644 index 0000000..c05a501 --- /dev/null +++ b/dlls/game/portal.cpp @@ -0,0 +1,150 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/portal.cpp $ +// $Revision:: 6 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Portals - surfaces that are mirrors or cameras + +#include "_pch_cpp.h" +#include "portal.h" + +/*QUAKED portal_surface (1 0 1) (-8 -8 -8) (8 8 8) +The portal surface nearest this entity will show a view from the targeted portal_camera, or a mirror view if untargeted. +*/ + +Event EV_Portal_LocateCamera +( + "locatecamera", + EV_CODEONLY, + NULL, + NULL, + "Locates the camera position." +); + +CLASS_DECLARATION( Entity, PortalSurface, "portal_surface" ) +{ + { &EV_Portal_LocateCamera, &PortalSurface::LocateCamera }, + + { NULL, NULL } +}; + +void PortalSurface::LocateCamera( Event *ev ) +{ + Entity *owner; + Entity *target; + Vector dir; + + owner = G_FindTarget( NULL, Target() ); + + if ( !owner ) + { + // No target, just a mirror + VectorCopy( edict->s.origin, edict->s.origin2 ); + return; + } + + // frame holds the rotate speed + if ( owner->spawnflags & 1 ) + { + edict->s.frame = 25; + } + else if ( owner->spawnflags & 2 ) + { + edict->s.frame = 75; + } + + // skinNum holds the rotate offset + edict->s.skinNum = owner->edict->s.skinNum; + + VectorCopy( owner->origin, edict->s.origin2 ); + + // see if the portal_camera has a target + target = G_FindTarget( NULL, owner->Target() ); + + if ( target ) + { + dir = target->origin - owner->origin; + dir.normalize(); + setAngles( dir.toAngles() ); + } + else + { + setAngles( owner->angles ); + dir = owner->orientation[ 0 ]; + } +} + +PortalSurface::PortalSurface() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + VectorClear( edict->mins ); + VectorClear( edict->maxs ); + + gi.linkentity( edict ); + + edict->svflags = SVF_PORTAL | SVF_SENDPVS; + edict->s.eType = ET_PORTAL; + + PostEvent( EV_Portal_LocateCamera, EV_POSTSPAWN ); +} + +/*QUAKED portal_camera (1 0 1) (-8 -8 -8) (8 8 8) slowrotate fastrotate +The target for a portal_surface. You can set either angles or target another entity to determine the direction of view. +"roll" an angle modifier to orient the camera around the target vector; +*/ + +Event EV_Portal_Roll +( + "roll", + EV_SCRIPTONLY, + "f", + "roll", + "Sets the portal camera's roll." +); + +CLASS_DECLARATION( Entity, PortalCamera, "portal_camera" ) +{ + { &EV_Portal_Roll, &PortalCamera::Roll }, + + { NULL, NULL } +}; + +void PortalCamera::Roll( Event *ev ) +{ + float roll = ev->GetFloat( 1 ); + + // skinNum holds the roll + edict->s.skinNum = ( roll / 360.0f ) * 256.0f; +} + +PortalCamera::PortalCamera() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + VectorClear( edict->mins ); + VectorClear( edict->maxs ); + + // No roll on the camera by default + edict->s.skinNum = 0; + + gi.linkentity( edict ); +} diff --git a/dlls/game/portal.h b/dlls/game/portal.h new file mode 100644 index 0000000..33c614c --- /dev/null +++ b/dlls/game/portal.h @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/portal.h $ +// $Revision:: 2 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __PORTAL_H__ +#define __PORTAL_H__ + +#include "g_local.h" + +class PortalSurface : public Entity + { + public: + CLASS_PROTOTYPE( PortalSurface ); + PortalSurface(); + + void LocateCamera( Event *ev ); + }; + +class PortalCamera : public Entity + { + public: + CLASS_PROTOTYPE( PortalCamera ); + PortalCamera(); + void Roll( Event *ev ); + }; + +#endif // __PORTAL_H__ diff --git a/dlls/game/powerups.cpp b/dlls/game/powerups.cpp new file mode 100644 index 0000000..e69662b --- /dev/null +++ b/dlls/game/powerups.cpp @@ -0,0 +1,1227 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/powerups.cpp $ +// $Revision:: 58 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: + +#include "_pch_cpp.h" +#include "powerups.h" +#include "player.h" +#include "mp_manager.hpp" +#include "weaputils.h" + + +Event EV_Powerup_ModelToAttach +( + "powerup_modelToAttach", + EV_DEFAULT, + "sSF", + "modelName tagName removeTime", + "Sets the model info to use when the powerup is used." +); +Event EV_Powerup_Shader +( + "powerup_shader", + EV_DEFAULT, + "s", + "shaderName", + "Sets the shader use when the powerup is used." +); +Event EV_Powerup_ModelToSpawn +( + "powerup_modelToSpawn", + EV_DEFAULT, + "s", + "modelName", + "Sets the model to spawn when the powerup is used (only works on certain models)." +); + +CLASS_DECLARATION( Item, PowerupBase, NULL ) +{ + { &EV_Item_SetAmount, &PowerupBase::amountEvent }, + + { &EV_Powerup_ModelToAttach, &PowerupBase::setModelToAttachOnUse }, + { &EV_Powerup_ModelToSpawn, &PowerupBase::setModelToSpawnOnUse }, + { &EV_Powerup_Shader, &PowerupBase::setShaderToDisplayOnUse }, + + { NULL, NULL } +}; + +PowerupBase::PowerupBase() +{ + /* if ( multiplayerManager.checkFlag( MP_FLAG_NO_POWERUPS ) ) + { + PostEvent( EV_Remove, EV_REMOVE ); + return; + } */ + + if ( LoadingSavegame ) + { + // all data will be setup by the archive function + return; + } + + setName( "UnknownItem" ); + amount = 30; +} + +void PowerupBase::init( const str &modelName, Sentient *owner ) +{ + _modelName = modelName; + _owner = owner; + + setModel( modelName ); + + CancelEventsOfType( EV_ProcessInitCommands ); + ProcessInitCommands( gi.modelindex( modelName.c_str() ) ); + + CancelEventsOfType( EV_Item_DropToFloor ); + + setSolidType( SOLID_NOT ); + hideModel(); +} + +void PowerupBase::amountEvent( Event *ev ) +{ + amount = ev->GetInteger( 1 ); +} + +void PowerupBase::setModelToAttachOnUse( Event *ev ) +{ + _modelToAttachOnUse = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + _modelToAttachOnUseTag = ev->GetString( 2 ); + else + _modelToAttachOnUseTag = "Bip01"; + + if ( ev->NumArgs() > 2 ) + _modelToAttachOnUseRemoveTime = ev->GetFloat( 3 ); + else + _modelToAttachOnUseRemoveTime = 0.0f; +} + +void PowerupBase::setModelToSpawnOnUse( Event *ev ) +{ + _modelToSpawn = ev->GetString( 1 ); +} + +void PowerupBase::setShaderToDisplayOnUse( Event *ev ) +{ + _shaderToDisplayOnUse = ev->GetString( 1 ); +} + +void PowerupBase::getModelToAttachOnUse( str &modelName, str &tagName, float &modelRemoveTime ) +{ + modelName = _modelToAttachOnUse; + tagName = _modelToAttachOnUseTag; + modelRemoveTime = _modelToAttachOnUseRemoveTime; +} + +void PowerupBase::getShaderToDisplayOnUse( str &modelName ) +{ + modelName = _shaderToDisplayOnUse; +} + +Item *PowerupBase::ItemPickup( Entity *other, qboolean add_to_inventory, qboolean ) +{ + Player *player; + str realname; + + if ( !other->isSubclassOf( Player ) ) + return NULL; + + if ( !Pickupable( other ) ) + return NULL; + + if ( multiplayerManager.inMultiplayer() ) + { + if ( !multiplayerManager.canPickup( (Player *)other, getMultiplayerItemType(), item_name ) ) + return NULL; + } + + player = ( Player * )other; + + // Play pickup sound + realname = GetRandomAlias( "snd_pickup" ); + if ( realname.length() > 1 ) + player->Sound( realname, CHAN_ITEM ); + + // Cancel some events + CancelEventsOfType( EV_Item_DropToFloor ); + CancelEventsOfType( EV_Item_Respawn ); + CancelEventsOfType( EV_FadeOut ); + + // Hide the model + setSolidType( SOLID_NOT ); + + if ( _missingSkin ) + { + ChangeSkin( _missingSkin, true ); + } + else + { + hideModel(); + } + + // Respawn? + if ( !Respawnable() ) + PostEvent( EV_Remove, FRAMETIME ); + else + PostEvent( EV_Item_Respawn, RespawnTime() ); + + // fire off any pickup_thread's + if ( pickup_thread.length() ) + { + ExecuteThread( pickup_thread ); + } + + givePlayerItem( player ); + + if ( multiplayerManager.inMultiplayer() ) + { + multiplayerManager.pickedupItem( (Player *)other, MP_ITEM_TYPE_POWERUP, item_name ); + } + + return NULL; // This doesn't create any items +} + +float PowerupBase::RespawnTime( void ) +{ + if ( multiplayerManager.inMultiplayer() ) + return respawntime * multiplayerManager.getPowerupRespawnMultiplayer(); + else + return respawntime; +} + +CLASS_DECLARATION( PowerupBase, Powerup, NULL ) +{ + //{ &EV_Item_IconName, iconNameEvent }, + { &EV_Item_SetAmount, &Powerup::amountEvent }, + { NULL, NULL } +}; + +Powerup::Powerup() +{ + if ( LoadingSavegame ) + { + // all data will be setup by the archive function + return; + } + + setRespawn( true ); + setRespawnTime( 60 ); + + _mpItemType = MP_ITEM_TYPE_POWERUP; + + _timeLeft = 0.0f; +} + +void Powerup::amountEvent( Event *ev ) +{ + _timeLeft = ev->GetInteger( 1 ); + amount = _timeLeft; +} + +void Powerup::givePlayerItem( Player *player ) +{ + Powerup *powerup; + const char *modelName; + + modelName = gi.NameForNum( edict->s.modelindex ); + + if ( !modelName ) + return; + + powerup = Powerup::CreatePowerup( item_name, modelName, player ); + + Event *event = new Event( EV_Item_SetAmount ); + event->AddFloat( amount ); + powerup->ProcessEvent( event ); + + player->setPowerup( powerup ); +} + +Powerup *Powerup::CreatePowerup( const str &className, const str &modelName, Sentient *sentient ) +{ + str fullname; + SpawnArgs args; + ClassDef *cls; + Powerup *powerup; + + + fullname = "Powerup"; + fullname += className; + + args.setArg( "classname", fullname.c_str() ); + + cls = args.getClassDef(); + + if ( !cls ) + return NULL; + + if ( !checkInheritance( &Powerup::ClassInfo, cls ) ) + return NULL; + + powerup = (Powerup *)cls->newInstance(); + + if ( !powerup ) + return NULL; + + powerup->init( modelName, sentient ); + + return powerup; +} + +void Powerup::update( float frameTime ) +{ + _timeLeft -= frameTime; + + amount = _timeLeft; + + if ( _timeLeft <= 0 && _owner ) + { + if ( _owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)_owner; + player->setItemText( getIcon(), va( "$$Item-%s$$ $$TimeRanOut$$", getName().c_str() ) ); + } + + //gi.centerprintf ( _owner->edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$Item-%s$$ $$TimeRanOut$$", getName() ); + _owner->PostEvent( EV_Player_RemovePowerup, 0.0f ); + } + + // Display custom shader if needed and possible + + if ( _owner && _owner->isSubclassOf( Player ) ) + { + str shaderName; + Player *player = (Player *)_owner; + + getShaderToDisplayOnUse( shaderName ); + + if ( ( shaderName.length() > 0 ) && !player->hasCustomShader() ) + { + if ( !player->hasCustomShader() ) + { + player->setCustomShader( shaderName.c_str() ); + } + } + } + + specificUpdate( frameTime ); +} + +void Powerup::spawn( const Vector &origin ) +{ + SpawnArgs args; + Entity *ent; + Item *item; + + if ( _timeLeft <= 0.0f ) + return; + + args.setArg( "model", _modelName ); + + ent = args.Spawn(); + + if ( !ent || !ent->isSubclassOf( Item ) ) + return; + + item = (Item *)ent; + + item->setOrigin( origin ); + + item->ProcessPendingEvents(); + + item->PlaceItem(); + item->setOrigin( origin ); + //item->velocity = Vector( G_CRandom( 100.0f ), G_CRandom( 100.0f ), 200.0f + G_Random( 200.0f ) ); + item->edict->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + item->_nextPickupTime = level.time + 1.0f; + + item->setAmount( (int)( _timeLeft + 1.0f ) ); + + item->setRespawn( false ); +} + +void Powerup::cacheStrings( void ) +{ + G_FindConfigstringIndex( va( "$$Using$$ $$Item-%s$$", getName().c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); + G_FindConfigstringIndex( va( "$$Item-%s$$ $$TimeRanOut$$", getName().c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); +} + +CLASS_DECLARATION( Powerup, PowerupSpeed, NULL ) +{ + { NULL, NULL } +}; + +CLASS_DECLARATION( Powerup, PowerupStrength, NULL ) +{ + { NULL, NULL } +}; + +CLASS_DECLARATION( Powerup, PowerupProtection, NULL ) +{ + { NULL, NULL } +}; + +float PowerupProtection::getDamageTaken( Entity *attacker, float damage, int meansOfDeath ) +{ + // Always take telefrag damage + + if ( meansOfDeath == MOD_TELEFRAG ) + { + _timeLeft = 0.0f; + return damage; + } + + return 0.0f; +} + +CLASS_DECLARATION( PowerupProtection, PowerupProtectionTemp, NULL ) +{ + { NULL, NULL } +}; + +float PowerupProtectionTemp::getDamageDone( float damage, int meansOfDeath ) +{ + _timeLeft = 0.0f; + + if ( owner ) + { + owner->Sound( "snd_powerupFizzle", CHAN_ITEM ); + } + + return damage; +} + +CLASS_DECLARATION( Powerup, PowerupRegen, NULL ) +{ + { NULL, NULL } +}; + +const float PowerupRegen::REGEN_SPEED = 10.0f; + +void PowerupRegen::specificUpdate( float frameTime ) +{ + if ( _owner ) + { + if ( _owner->getHealth() > 0.0f ) + { + if ( _owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)(Sentient *)_owner; + + if ( !player->canRegenerate() ) + return; + } + + _owner->AddHealth( frameTime * REGEN_SPEED, 200.0f ); + } + } +} + +CLASS_DECLARATION( Powerup, PowerupInvisibility, NULL ) +{ + { NULL, NULL } +}; + +PowerupInvisibility::PowerupInvisibility() +{ + _started = false; +} + +PowerupInvisibility::~PowerupInvisibility() +{ + if ( _owner && _started ) + { + // Stop the invisibility effect + + Event *event; + + event = new Event( EV_DisplayEffect ); + event->AddString( "stop_invisibility" ); + _owner->ProcessEvent( event ); + } +} + +void PowerupInvisibility::specificUpdate( float frameTime ) +{ + if ( _owner ) + { + // Start the invisibility effect + + Event *event; + + event = new Event( EV_DisplayEffect ); + event->AddString( "start_invisibility" ); + _owner->ProcessEvent( event ); + + _started = true; + } +} + +// +// RUNES +// + +Event EV_Rune_Respawn +( + "powerup_respawn", + EV_CODEONLY, + NULL, + NULL, + "Makes the rune respawn in its original position." +); + +CLASS_DECLARATION( PowerupBase, Rune, NULL ) +{ + { &EV_Rune_Respawn, &Rune::respawnAtOriginalOrigin }, + + { NULL, NULL } +}; + +Rune::Rune() +{ + if ( LoadingSavegame ) + { + // all data will be setup by the archive function + return; + } + + setRespawn( false ); + + _mpItemType = MP_ITEM_TYPE_RUNE; + + _originalOriginSet = false; +} + +void Rune::setOrigin( const Vector &point ) +{ + PowerupBase::setOrigin( point ); + + setOriginalOrigin( point, false ); +} + +void Rune::setOriginalOrigin( const Vector &point, bool force ) +{ + if ( !_originalOriginSet || force ) + { + _originalOrigin = point; + _originalOriginSet = true; + } +} + + +Item *Rune::ItemPickup( Entity *other, qboolean add_to_inventory, qboolean ) +{ + Player *player; + + if ( !Pickupable( other ) ) + return NULL; + + if ( !other->isSubclassOf( Player ) ) + return NULL; + + player = ( Player * )other; + + if ( player->hasRune() ) + return NULL; + + return PowerupBase::ItemPickup( other, add_to_inventory, false ); +} + +void Rune::givePlayerItem( Player *player ) +{ + Rune *rune; + + rune = Rune::CreateRune( item_name, gi.NameForNum( edict->s.modelindex ), player ); + + player->setRune( rune ); + + rune->setOriginalOrigin( getOriginalOrigin(), true ); +} + +Rune *Rune::CreateRune( const str &className, const str &modelName, Sentient *sentient ) +{ + str fullname; + SpawnArgs args; + ClassDef *cls; + Rune *rune; + + + fullname = "Rune"; + fullname += className; + + args.setArg( "classname", fullname.c_str() ); + + cls = args.getClassDef(); + + if ( !cls ) + return NULL; + + if ( !checkInheritance( &Rune::ClassInfo, cls ) ) + return NULL; + + rune = (Rune *)cls->newInstance(); + + if ( !rune ) + return NULL; + + rune->init( modelName, sentient ); + + return rune; +} + +void Rune::spawn( const Vector &origin ) +{ + SpawnArgs args; + Entity *ent; + Item *item; + + args.setArg( "model", _modelName ); + + ent = args.Spawn(); + + if ( !ent || !ent->isSubclassOf( Item ) ) + return; + + item = (Item *)ent; + + item->setOrigin( origin ); + + item->ProcessPendingEvents(); + + item->PlaceItem(); + item->setOrigin( origin ); + //item->velocity = Vector( G_CRandom( 100.0f ), G_CRandom( 100.0f ), 200.0f + G_Random( 200.0f ) ); + item->edict->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + item->_nextPickupTime = level.time + 1.0f; + + item->animate->RandomAnimate( "idle" ); + + if ( item->isSubclassOf( Rune ) ) + { + Rune *rune = (Rune *)item; + + rune->setOriginalOrigin( getOriginalOrigin(), true ); + + rune->PostEvent( EV_Rune_Respawn, 60.0f ); + } +} + +void Rune::cacheStrings( void ) +{ + G_FindConfigstringIndex( va( "$$Using$$ $$Item-%s$$", getName().c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); + G_FindConfigstringIndex( va( "$$Dropping$$ $$Item-%s$$", getName().c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); +} + +void Rune::respawnAtOriginalOrigin( Event *ev ) +{ + if ( _originalOriginSet ) + { + NoLerpThisFrame(); + setOrigin( _originalOrigin ); + ProcessEvent( EV_Item_DropToFloor ); + } +} + +CLASS_DECLARATION( Rune, RuneDeathQuad, NULL ) +{ + { NULL, NULL } +}; + +void RuneDeathQuad::specificUpdate( float frameTime ) +{ + Event *event; + + if ( _owner ) + { + event = new Event( EV_Hurt ); + event->AddFloat( frameTime * 5.0f ); + event->AddString( "deathQuad" ); + _owner->ProcessEvent( event ); + } +} + +CLASS_DECLARATION( Rune, RuneAmmoRegen, NULL ) +{ + { NULL, NULL } +}; + +RuneAmmoRegen::RuneAmmoRegen() +{ + _nextGiveTime = 0.0f; +} + +void RuneAmmoRegen::specificUpdate( float frameTime ) +{ + str ammoType; + Weapon *weapon; + + if ( _owner && ( level.time > _nextGiveTime ) && _owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)_owner; + + weapon = player->GetActiveWeapon( WEAPON_DUAL ); + + if ( weapon ) + { + ammoType = weapon->GetAmmoType( FIRE_MODE1 ); + + player->GiveAmmo( ammoType, 1, false ); + } + + _nextGiveTime = level.time + 1.0f; + } +} + +CLASS_DECLARATION( Rune, RuneEmpathyShield, NULL ) +{ + { NULL, NULL } +}; + +float RuneEmpathyShield::getDamageTaken( Entity *attacker, float damage, int meansOfDeath ) +{ + float realDamage; + Event *event; + + realDamage = damage; + + if ( attacker && ( meansOfDeath != MOD_EMPATHY_SHIELD ) ) + { + realDamage *= 0.5; + + event = new Event( EV_Hurt ); + event->AddFloat( realDamage ); + event->AddString( "empathyShield" ); + attacker->ProcessEvent( event ); + } + + return realDamage; +} + +CLASS_DECLARATION( Rune, RuneArmorPiercing, NULL ) +{ + { NULL, NULL } +}; + + +// +// Holdable items +// + +CLASS_DECLARATION( PowerupBase, HoldableItem, NULL ) +{ + { NULL, NULL } +}; + +HoldableItem::HoldableItem() +{ + if ( LoadingSavegame ) + { + // all data will be setup by the archive function + return; + } + + amount = 0; + + setRespawn( true ); + setRespawnTime( 60 ); +} + +void HoldableItem::givePlayerItem( Player *player ) +{ + HoldableItem *holdableItem; + + holdableItem = HoldableItem::createHoldableItem( item_name, model, player ); + + if ( holdableItem ) + { + player->setItemText( holdableItem->getIcon(), va( "$$PickedUp$$ $$Item-%s$$", holdableItem->getName().c_str() ) ); + //gi.centerprintf ( player->edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$PickedUp$$ %s", holdableItem->getRealName() ); + + player->setHoldableItem( holdableItem ); + } +} + +HoldableItem *HoldableItem::createHoldableItem( const str &className, const str &modelName, Sentient *sentient ) +{ + str fullname; + SpawnArgs args; + ClassDef *cls; + HoldableItem *holdableItem; + + + fullname = "HoldableItem"; + fullname += className; + + args.setArg( "classname", fullname.c_str() ); + + cls = args.getClassDef(); + + if ( !cls ) + return NULL; + + if ( !checkInheritance( &HoldableItem::ClassInfo, cls ) ) + return NULL; + + holdableItem = (HoldableItem *)cls->newInstance(); + + if ( !holdableItem ) + return NULL; + + holdableItem->init( modelName, sentient ); + + return holdableItem; +} + +void HoldableItem::cacheStrings( void ) +{ + G_FindConfigstringIndex( va( "$$Using$$ $$Item-%s$$", getName().c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); + G_FindConfigstringIndex( va( "$$PickedUp$$ $$Item-%s$$", getName().c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); +} + +CLASS_DECLARATION( HoldableItem, HoldableItemHealth, NULL ) +{ + { NULL, NULL } +}; + +bool HoldableItemHealth::use( void ) +{ + if ( _owner ) + { + if ( _owner->getHealth() < _owner->getMaxHealth() ) + { + _owner->addHealth( amount ); + return true; + } + } + + return false; +} + +CLASS_DECLARATION( HoldableItem, HoldableItemProtection, NULL ) +{ + { NULL, NULL } +}; + +bool HoldableItemProtection::use( void ) +{ + Event *event; + + if ( _owner && !multiplayerManager.checkFlag( MP_FLAG_NO_POWERUPS ) ) + { + if ( !_owner->isSubclassOf( Player ) ) + return false; + + Powerup *powerup; + Player *player; + + player = (Player *)_owner; + + powerup = Powerup::CreatePowerup( "Protection", "models/item/powerup_protection.tik", player ); + + event = new Event( EV_Item_SetAmount ); + event->AddFloat( 15.0f ); + powerup->ProcessEvent( event ); + + if ( powerup ) + { + player->setPowerup( powerup ); + return true; + } + } + + return false; +} + +CLASS_DECLARATION( HoldableItem, HoldableItemTransporter, NULL ) +{ + { NULL, NULL } +}; + +bool HoldableItemTransporter::use( void ) +{ + Entity *spawnPoint; + + if ( _owner ) + { + if ( !_owner->isSubclassOf( Player ) ) + return false; + + Player *player; + + player = (Player *)_owner; + + if ( multiplayerManager.inMultiplayer() ) + { + spawnPoint = multiplayerManager.getSpawnPoint( player ); + + if ( spawnPoint ) + { + if ( _modelToSpawn.length() > 0 ) + { + SpawnEffect( _modelToSpawn, player->origin, vec_zero, 1.0f ); + } + + player->WarpToPoint( spawnPoint ); + + KillBox( player ); + return true; + } + } + } + + return false; +} + +CLASS_DECLARATION( HoldableItem, HoldableItemExplosive, NULL ) +{ + { NULL, NULL } +}; + +HoldableItemExplosive::HoldableItemExplosive() +{ + _explosiveArmed = false; + _explosiveAlive = true; + + _explosiveArmTime = 0.0f; + + _explosive = NULL; + + _lastSoundTime = 0.0f; + + _nextProximitySoundTime = 0.0f; +} + +HoldableItemExplosive::~HoldableItemExplosive() +{ + if ( _explosiveAlive && _explosive ) + { + // Spawn a small effect + + _explosive->SpawnEffect( "fx/fx-explosion-debris-rocks-dust-brown.tik", _explosive->origin, _explosive->angles, 2.0f ); + + // Destroy the explosive + + _explosive->PostEvent( EV_Remove, 0.0f ); + } +} + +bool HoldableItemExplosive::use( void ) +{ + if ( _owner ) + { + if ( !_owner->isSubclassOf( Player ) ) + return false; + + Player *player; + + player = (Player *)_owner; + + // If explosive armed + + if ( _explosiveArmed && ( _explosiveArmTime + 1.0f < level.time ) ) + { + if ( _explosiveAlive && _explosive ) + { + // Blow up the explosive + + ExplosionAttack( _explosive->origin, _owner, "models/weapons/explosion-holdable.tik" ); + + // Must check again if _explosive exists, because ExplosionAttack might have caused the holdable item to be destroyed + + if ( _explosive ) + { + _explosive->PostEvent( EV_Remove, 0.0f ); + _explosive = NULL; + } + + _explosiveAlive = false; + } + + return true; + } + else if ( !_explosiveArmed ) + { + Vector newOrigin; + Vector newAngles; + + // Explosive hasn't been armed yet + + if ( !findPlaceToSet( newOrigin, newAngles ) ) + { + if ( _lastSoundTime + 0.5f < level.time ) + { + Sound( "snd_noammo" ); + + _lastSoundTime = level.time; + } + + return false; + } + + _explosiveArmTime = level.time; + _explosiveArmed = true; + + // Spawn the item into the world + + _explosive = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + _explosive->setModel( "models/item/holdable_explosive.tik" ); + + _explosive->CancelEventsOfType( EV_ProcessInitCommands ); + _explosive->ProcessInitCommands( _explosive->edict->s.modelindex ); + + _explosive->setSolidType( SOLID_BBOX ); + _explosive->setContents( CONTENTS_SHOOTABLE_ONLY ); + + _explosive->setSize( Vector( -16, -16, 0 ), Vector( 16, 16, 32 ) ); + + _explosive->setHealth( 5.0f ); + + _explosive->takedamage = DAMAGE_YES; + + _explosive->setOrigin( newOrigin ); + _explosive->setAngles( newAngles ); + _explosive->animate->RandomAnimate( "idle_placed" ); + } + } + + return false; +} + +bool HoldableItemExplosive::findPlaceToSet( Vector &newOrigin, Vector &newAngles ) +{ + Player *player; + trace_t viewTrace; + Vector forward; + Vector left; + Vector up; + float axis[3][3]; + vec3_t newAnglesVec; + + if ( !_owner || !_owner->isSubclassOf( Player ) ) + return false; + + player = (Player *)_owner; + + memset( &viewTrace, 0, sizeof(trace_t) ); + + player->GetViewTrace( viewTrace, MASK_SHOT, 16.0f * 7.0f ); + + if ( ( viewTrace.fraction < 1.0f ) && ( viewTrace.ent && viewTrace.entityNum == ENTITYNUM_WORLD ) ) + { + newOrigin = viewTrace.endpos; + + up = viewTrace.plane.normal; + PerpendicularVector( forward, viewTrace.plane.normal ); + left.CrossProduct( up, forward ); + + forward.copyTo( axis[ AXIS_FORWARD_VECTOR ] ); + left.copyTo( axis[ AXIS_RIGHT_VECTOR ] ); + up.copyTo( axis[ AXIS_UP_VECTOR ] ); + + AxisToAngles( axis, newAnglesVec ); + + newAngles = newAnglesVec; + + return true; + } + + return false; +} + +void HoldableItemExplosive::specificUpdate( float frameTime ) +{ + if ( _explosiveArmed ) + { + if ( _explosiveAlive ) + { + // See if the explosive has been destroyed + + if ( !_explosive || ( _explosive->getHealth() <= 0.0f ) ) + { + // The explosive has been destroyed + + _explosiveAlive = false; + + // Spawn a small effect + + _explosive->SpawnEffect( "fx/fx-explosion-debris-rocks-dust-brown.tik", _explosive->origin, _explosive->angles, 2.0f ); + + // Destroy the explosive + + _explosive->PostEvent( EV_Remove, 0.0f ); + _explosive = NULL; + + if ( _owner && _owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)_owner; + + player->removeHoldableItem(); + } + } + else + { + if ( level.time > _nextProximitySoundTime ) + { + _nextProximitySoundTime = level.time + 1.0f; + + if ( isPlayerInRange( _explosive->centroid, 300.0f ) ) + { + if ( _owner ) + { + _owner->Sound( "snd_proximity", CHAN_AUTO, DEFAULT_VOL, DEFAULT_MIN_DIST, NULL, 1.0f, true ); + } + } + } + } + } + } +} + +bool HoldableItemExplosive::isPlayerInRange( const Vector &position, float maxDistance ) +{ + int i; + Entity *entity; + gentity_t *edict; + trace_t trace; + Vector dir; + float distance; + + for( i = 0; i < game.maxclients; i++ ) + { + edict = &g_entities[ i ]; + + if ( !edict->inuse || !edict->entity || !edict->client ) + { + continue; + } + + entity = edict->entity; + + if ( entity->health < 0.0f ) + { + continue; + } + + dir = entity->centroid - position; + distance = dir.length(); + + if ( distance < maxDistance ) + { + trace = G_Trace( position, vec_zero, vec_zero, entity->centroid, _explosive, MASK_SHOT, false, "HoldableItemExplosive::isPlayerInRange" ); + + if ( ( trace.ent ) && ( trace.ent->entity == entity ) ) + return true; + } + } + + return false; +} + +Event EV_HoldableItem_PowerupToSpawn +( + "powerupToSpawn", + EV_TIKIONLY, + "s", + "powerupName", + "Sets the powerup to spawn when used." +); + +CLASS_DECLARATION( HoldableItem, HoldableItemSpawnPowerup, NULL ) +{ + + { &EV_HoldableItem_PowerupToSpawn, &HoldableItemSpawnPowerup::powerupToSpawn }, + + { NULL, NULL } +}; + +void HoldableItemSpawnPowerup::powerupToSpawn( Event *ev ) +{ + _powerupToSpawn = ev->GetString( 1 ); +} + +bool HoldableItemSpawnPowerup::use( void ) +{ + SpawnArgs args; + Entity *ent; + Item *item; + + + if ( _powerupToSpawn.length() == 0 ) + return true; + + if ( _owner ) + { + args.setArg( "model", _powerupToSpawn ); + + ent = args.Spawn(); + + if ( !ent || !ent->isSubclassOf( Item ) ) + return true; + + item = (Item *)ent; + + item->setOrigin( _owner->centroid ); + + item->ProcessPendingEvents(); + + item->PlaceItem(); + item->setOrigin( _owner->centroid ); + + if ( _owner->isSubclassOf( Player ) ) + { + Vector viewAngles; + Vector viewDir; + Player *player = (Player *)_owner; + Vector pos; + + player->GetPlayerView( &pos, &viewAngles ); + + //viewAngles = player->GetVAngles(); + viewAngles.AngleVectors( &viewDir ); + + viewDir.normalize(); + viewDir *= 500.0f; + + item->velocity = viewDir; + + item->setOrigin( pos ); + } + else + { + item->velocity = Vector( G_CRandom( 100.0f ), G_CRandom( 100.0f ), 200.0f + G_Random( 200.0f ) ); + } + + item->edict->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + item->_nextPickupTime = level.time + 1.0f; + + item->setRespawn( false ); + + // Powerup is only gets half time + + item->setAmount( item->getAmount() / 2.0f ); + + // Get rid of the spawned powerup in 10 seconds + + item->PostEvent( EV_Remove, 10.0f ); + + return true; + } + + return false; +} diff --git a/dlls/game/powerups.h b/dlls/game/powerups.h new file mode 100644 index 0000000..44b7631 --- /dev/null +++ b/dlls/game/powerups.h @@ -0,0 +1,466 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/powerups.h $ +// $Revision:: 30 $ +// $Author:: Steven $ +// $Date:: 5/16/03 8:41p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: + +#ifndef __POWERUP_H__ +#define __POWERUP_H__ + +class Powerup; +class PowerupBase; +class Rune; +class HoldableItem; + +#include "g_local.h" +#include "item.h" + + +class PowerupBase : public Item +{ + protected: + + Sentient *_owner; + str _modelName; + + str _modelToAttachOnUse; + str _modelToAttachOnUseTag; + float _modelToAttachOnUseRemoveTime; + str _modelToSpawn; + str _shaderToDisplayOnUse; + + void init( const str &modelname, Sentient *owner ); + + + public: + CLASS_PROTOTYPE( PowerupBase ); + + PowerupBase(); + virtual ~PowerupBase() {} + + void realNameEvent( Event *ev ); + virtual void amountEvent( Event *ev ); + + virtual void update( float frameTime ) { specificUpdate( frameTime ); } + virtual void specificUpdate( float frameTime ) {}; + + virtual float getMoveMultiplier( void ) { return 1.0f; }; + virtual float getDamageDone( float damage, int meansOfDeath ) { return damage; }; + virtual float getDamageTaken( Entity *attacker, float damage, int meansOfDeath ) { return damage; }; + + virtual meansOfDeath_t changetMeansOfDeath( meansOfDeath_t meansOfDeath ) { return meansOfDeath; }; + + virtual void spawn( const Vector &origin ) {}; + + /* virtual */ Item * ItemPickup( Entity *other, qboolean add_to_inventory, qboolean ); + + virtual void givePlayerItem( Player *player ) {}; + + /* virtual */ float RespawnTime( void ); + + void setModelToAttachOnUse( Event *ev ); + void setModelToSpawnOnUse( Event *ev ); + void setShaderToDisplayOnUse( Event *ev ); + + void getModelToAttachOnUse( str &modelName, str &tagName, float &modelRemoveTime ); + void getShaderToDisplayOnUse( str &modelName ); + + virtual bool canDrop( void ) { return true; } + + virtual bool canOwnerRegenerate( void ) { return true; } + + /* virtual */ void Archive( Archiver &arc ); +}; + +inline void PowerupBase::Archive( Archiver &arc ) +{ + Item::Archive( arc ); + + arc.ArchiveObjectPointer( ( Class ** )&_owner ); + arc.ArchiveString( &_modelName ); + arc.ArchiveString( &_modelToAttachOnUse ); + arc.ArchiveString( &_modelToAttachOnUseTag ); + arc.ArchiveFloat( &_modelToAttachOnUseRemoveTime ); + arc.ArchiveString( &_shaderToDisplayOnUse ); +} + +class Powerup : public PowerupBase +{ + protected: + + float _timeLeft; + + public: + CLASS_PROTOTYPE( Powerup ); + + Powerup(); + virtual ~Powerup() {} + + /* virtual */ void amountEvent( Event *ev ); + + float getTimeLeft( void ) const { return _timeLeft; }; + + /* virtual */ void update( float frameTime ); + + /* virtual */ void spawn( const Vector &origin ); + + /* virtual */ void givePlayerItem( Player *player ); + + static Powerup * CreatePowerup( const str &className, const str &modelName, Sentient *sentient ); + + /* virtual */ void cacheStrings( void ); + + float getTimeLeft( void ) { return _timeLeft; } + void setTimeLeft( float timeLeft ) { _timeLeft = timeLeft; } + + virtual bool canStack( void ) { return true; } + + /* virtual */ void Archive( Archiver &arc ); +}; + +inline void Powerup::Archive( Archiver &arc ) +{ + PowerupBase::Archive( arc ); + + arc.ArchiveFloat( &_timeLeft ); +} + +class PowerupSpeed : public Powerup +{ + private: + + protected: + + public: + CLASS_PROTOTYPE( PowerupSpeed ); + + PowerupSpeed() {}; + ~PowerupSpeed() {}; + + virtual float getMoveMultiplier( void ) { return 1.5f; }; +}; + +class PowerupStrength : public Powerup +{ + private: + + protected: + + public: + CLASS_PROTOTYPE( PowerupStrength ); + + PowerupStrength() {}; + ~PowerupStrength() {}; + + /* virtual */ float getDamageDone( float damage, int meansOfDeath ) { return damage * 2.0f; }; +}; + +class PowerupProtection : public Powerup +{ + private: + + protected: + + public: + CLASS_PROTOTYPE( PowerupProtection ); + + PowerupProtection() {}; + ~PowerupProtection() {}; + + /* virtual */ float getDamageTaken( Entity *attacker, float damage, int meansOfDeath ); +}; + +class PowerupProtectionTemp : public PowerupProtection +{ + private: + + protected: + + public: + CLASS_PROTOTYPE( PowerupProtectionTemp ); + + PowerupProtectionTemp() {}; + ~PowerupProtectionTemp() {}; + + /* virtual */ float getDamageDone( float damage, int meansOfDeath ); + /* virtual */ bool canDrop( void ) { return false; } + /* virtual */ bool canStack( void ) { return false; } +}; + +class PowerupRegen : public Powerup +{ + private: + + static const float REGEN_SPEED; + + protected: + + public: + CLASS_PROTOTYPE( PowerupRegen ); + + PowerupRegen() {}; + ~PowerupRegen() {}; + + /* virtual */ void specificUpdate( float frameTime ); +}; + +class PowerupInvisibility : public Powerup +{ + private: + bool _started; + + protected: + + public: + CLASS_PROTOTYPE( PowerupInvisibility ); + + PowerupInvisibility(); + ~PowerupInvisibility(); + + /* virtual */ void specificUpdate( float frameTime ); + + /* virtual */ void Archive( Archiver &arc ); +}; + +inline void PowerupInvisibility::Archive( Archiver &arc ) +{ + Powerup::Archive( arc ); + + arc.ArchiveBool( &_started ); +} + +class Rune : public PowerupBase +{ + protected: + Vector _originalOrigin; + bool _originalOriginSet; + + public: + CLASS_PROTOTYPE( Rune ); + + Rune(); + virtual ~Rune() {} + + /* virtual */ void setOrigin( const Vector &point ); + void setOriginalOrigin( const Vector &point, bool force ); + Vector getOriginalOrigin( void) { return _originalOrigin;} + + /* virtual */ void spawn( const Vector &origin ); + + /* virtual */ Item * ItemPickup( Entity *other, qboolean add_to_inventory, qboolean ); + + /* virtual */ void givePlayerItem( Player *player ); + + static Rune * CreateRune( const str &className, const str &modelName, Sentient *sentient ); + + /* virtual */ void cacheStrings( void ); + + void respawnAtOriginalOrigin( Event *ev ); + + /* virtual */ void Archive( Archiver &arc ); +}; + +inline void Rune::Archive( Archiver &arc ) +{ + PowerupBase::Archive( arc ); + + arc.ArchiveVector( &_originalOrigin ); + arc.ArchiveBool( &_originalOriginSet ); +} + +class RuneDeathQuad : public Rune +{ + private: + + protected: + + public: + CLASS_PROTOTYPE( RuneDeathQuad ); + + RuneDeathQuad() {}; + ~RuneDeathQuad() {}; + + /* virtual */ void specificUpdate( float frameTime ); + /* virtual */ float getDamageDone( float damage, int meansOfDeath ) { return damage * 4.0f; }; + /* virtual */ bool canOwnerRegenerate( void ) { return false; } + +}; + +class RuneAmmoRegen : public Rune +{ + private: + float _nextGiveTime; + + protected: + + public: + CLASS_PROTOTYPE( RuneAmmoRegen ); + + RuneAmmoRegen(); + ~RuneAmmoRegen() {}; + + /* virtual */ void specificUpdate( float frameTime ); + + /* virtual */ void Archive( Archiver &arc ); +}; + +inline void RuneAmmoRegen::Archive( Archiver &arc ) +{ + Rune::Archive( arc ); + + arc.ArchiveFloat( &_nextGiveTime ); +} + +class RuneEmpathyShield : public Rune +{ + private: + + protected: + + public: + CLASS_PROTOTYPE( RuneEmpathyShield ); + + RuneEmpathyShield() {}; + ~RuneEmpathyShield() {}; + + /* virtual */ float getDamageTaken( Entity *attacker, float damage, int meansOfDeath ); +}; + +class RuneArmorPiercing : public Rune +{ + private: + + protected: + + public: + CLASS_PROTOTYPE( RuneArmorPiercing ); + + RuneArmorPiercing() {}; + ~RuneArmorPiercing() {}; + + /* virtual */ meansOfDeath_t changetMeansOfDeath( meansOfDeath_t meansOfDeath ) { return MOD_ARMOR_PIERCING; }; +}; + +class HoldableItem : public PowerupBase +{ + protected: + + public: + CLASS_PROTOTYPE( HoldableItem ); + + HoldableItem(); + virtual ~HoldableItem() {} + + virtual bool use( void ) { return true; } + + /* virtual */ void givePlayerItem( Player *player ); + + static HoldableItem *createHoldableItem( const str &className, const str &modelName, Sentient *sentient ); + + /* virtual */ void cacheStrings( void ); +}; + +class HoldableItemHealth : public HoldableItem +{ +public: + CLASS_PROTOTYPE( HoldableItemHealth ); + + HoldableItemHealth() {}; + ~HoldableItemHealth() {}; + + /* virtual */ bool use( void ); +}; + +class HoldableItemProtection : public HoldableItem +{ +public: + CLASS_PROTOTYPE( HoldableItemProtection ); + + HoldableItemProtection() {}; + ~HoldableItemProtection() {}; + + /* virtual */ bool use( void ); +}; + +class HoldableItemTransporter : public HoldableItem +{ +public: + CLASS_PROTOTYPE( HoldableItemTransporter ); + + HoldableItemTransporter() {}; + ~HoldableItemTransporter() {}; + + /* virtual */ bool use( void ); +}; + +class HoldableItemExplosive : public HoldableItem +{ +private: + bool _explosiveArmed; + bool _explosiveAlive; + float _explosiveArmTime; + EntityPtr _explosive; + float _lastSoundTime; + float _nextProximitySoundTime; + + bool findPlaceToSet( Vector &newOrigin, Vector &newAngles ); + bool isPlayerInRange( const Vector &position, float maxDistance ); + +public: + CLASS_PROTOTYPE( HoldableItemExplosive ); + + HoldableItemExplosive(); + ~HoldableItemExplosive(); + + /* virtual */ bool use( void ); + /* virtual */ void specificUpdate( float frameTime ); + + /* virtual */ void Archive( Archiver &arc ); +}; + +inline void HoldableItemExplosive::Archive( Archiver &arc ) +{ + HoldableItem::Archive( arc ); + + arc.ArchiveBool( &_explosiveArmed ); + arc.ArchiveBool( &_explosiveAlive ); + arc.ArchiveFloat( &_explosiveArmTime ); + arc.ArchiveSafePointer( &_explosive ); + + arc.ArchiveFloat( &_lastSoundTime ); + arc.ArchiveFloat( &_nextProximitySoundTime ); +} + +class HoldableItemSpawnPowerup : public HoldableItem +{ +private: + str _powerupToSpawn; + +public: + CLASS_PROTOTYPE( HoldableItemSpawnPowerup ); + + /* virtual */ bool use( void ); + + void powerupToSpawn( Event *ev ); + + /* virtual */ void Archive( Archiver &arc ); +}; + +inline void HoldableItemSpawnPowerup::Archive( Archiver &arc ) +{ + HoldableItem::Archive( arc ); + + arc.ArchiveString( &_powerupToSpawn ); +} + +#endif /* Powerup.h */ diff --git a/dlls/game/program.cpp b/dlls/game/program.cpp new file mode 100644 index 0000000..fe67001 --- /dev/null +++ b/dlls/game/program.cpp @@ -0,0 +1,1121 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/program.cpp $ +// $Revision:: 23 $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// + +#include "_pch_cpp.h" +#include "program.h" +#include "compiler.h" + +inline void type_t::Archive( Archiver &arc ) +{ + int i; + + Class::Archive( arc ); + + ArchiveEnum( type, etype_t ); + + if ( arc.Loading() ) + { + bool onList; + + arc.ArchiveBool( &onList ); + + if ( !onList ) + { + def = new def_t; + + arc.ArchiveObject( ( Class * )def ); + } + else + { + arc.ArchiveObjectPointer( ( Class ** )&def ); + } + + } + else + { + arc.ArchiveBool( &def->_onDefList ); + + if ( !def->_onDefList ) + { + arc.ArchiveObject( ( Class * )def ); + } + else + { + arc.ArchiveObjectPointer( ( Class ** )&def ); + } + } + + /* if ( arc.Loading() ) + def = new def_t; + + arc.ArchiveObject( ( Class * )def ); */ + + arc.ArchiveObjectPointer( ( Class ** )&def ); + + arc.ArchiveObjectPointer( ( Class ** )&aux_type ); + + arc.ArchiveInteger( &num_parms ); + arc.ArchiveInteger( &min_parms ); + + for ( i = 0; i < num_parms; i++ ) + arc.ArchiveObjectPointer( ( Class ** )&parm_types[i] ); +} + + +inline void def_t::Archive( Archiver &arc ) +{ + Class::Archive( arc ); + + arc.ArchiveObjectPointer( ( Class ** )&type ); + arc.ArchiveString( &name ); +// arc.ArchiveObjectPointer( ( Class ** )&next ); + arc.ArchiveInteger( &ofs ); + arc.ArchiveInteger( &localofs ); + arc.ArchiveObjectPointer( ( Class ** )&scope ); + arc.ArchiveInteger( &initialized ); + arc.ArchiveBool( &caseSensitive ); + + arc.ArchiveBool( &_onDefList ); + //arc.ArchiveObjectPointer( ( Class ** )&type ); +} + +inline void dfunction_t::Archive( Archiver &arc ) +{ + Class::Archive( arc ); + + arc.ArchiveInteger( &eventnum ); + arc.ArchiveInteger( &first_statement ); + arc.ArchiveInteger( &parm_start ); + arc.ArchiveInteger( &parm_total ); + arc.ArchiveInteger( &locals ); + + arc.ArchiveInteger( &profile ); + + arc.ArchiveString( &s_name ); + arc.ArchiveString( &s_file ); + + arc.ArchiveInteger( &numparms ); + arc.ArchiveInteger( &minparms ); + + if ( arc.Loading() ) + { + memset( &parm_size, 0, sizeof( parm_size[0] ) * MAX_PARMS ); + memset( &parm_type, 0, sizeof( parm_type[0] ) * MAX_PARMS ); + } + + arc.ArchiveRaw( parm_size, sizeof( parm_size[0] ) * numparms ); + arc.ArchiveRaw( parm_type, sizeof( parm_type[0] ) * numparms ); +} + +// These two pointers are dummy pointers for load/save games. They MUST stay global because of how +// loadgames change the pointer at a later time, hence if they are on the stack it will corrupt the stack. + +//type_t *forceTypeSave; +//def_t *forceDefSave; + +inline void Program::Archive( Archiver &arc ) +{ + int i, num; + type_t *curtype, *newtype; + def_t *curdef, *newdef; + + Class::Archive( arc ); + + /* // Force all of the defs to have indexes + + forceDefSave = &def_void; + arc.ArchiveObjectPointer( ( Class ** )&forceDefSave ); + forceDefSave = &def_string; + arc.ArchiveObjectPointer( ( Class ** )&forceDefSave ); + forceDefSave = &def_float; + arc.ArchiveObjectPointer( ( Class ** )&forceDefSave ); + forceDefSave = &def_vector; + arc.ArchiveObjectPointer( ( Class ** )&forceDefSave ); + forceDefSave = &def_entity; + arc.ArchiveObjectPointer( ( Class ** )&forceDefSave ); + forceDefSave = &def_function; + arc.ArchiveObjectPointer( ( Class ** )&forceDefSave ); + + // Force all of the types to have indexes + + forceTypeSave = &type_void; + arc.ArchiveObjectPointer( ( Class ** )&forceTypeSave ); + forceTypeSave = &type_string; + arc.ArchiveObjectPointer( ( Class ** )&forceTypeSave ); + forceTypeSave = &type_float; + arc.ArchiveObjectPointer( ( Class ** )&forceTypeSave ); + forceTypeSave = &type_vector; + arc.ArchiveObjectPointer( ( Class ** )&forceTypeSave ); + forceTypeSave = &type_entity; + arc.ArchiveObjectPointer( ( Class ** )&forceTypeSave ); + forceTypeSave = &type_function; + arc.ArchiveObjectPointer( ( Class ** )&forceTypeSave ); */ + + // NOTE: must archive global data for pointer fixups + arc.ArchiveObject( &def_void ); + arc.ArchiveObject( &def_string ); + arc.ArchiveObject( &def_float ); + arc.ArchiveObject( &def_vector ); + arc.ArchiveObject( &def_entity ); + arc.ArchiveObject( &def_function ); + + arc.ArchiveObject( &def_ret ); + arc.ArchiveObject( &junkdef ); + + arc.ArchiveObject( &type_void ); + arc.ArchiveObject( &type_string ); + arc.ArchiveObject( &type_float ); + arc.ArchiveObject( &type_vector ); + arc.ArchiveObject( &type_entity ); + arc.ArchiveObject( &type_function ); + + arc.ArchiveInteger( &numpr_globals ); + + if ( arc.Loading() ) + { + memset( pr_globals, 0, sizeof( pr_globals[0] ) * MAX_REGS ); + } + + arc.ArchiveRaw( pr_globals, sizeof( pr_globals[0] ) * numpr_globals ); + + arc.ArchiveInteger( &locals_start ); + arc.ArchiveInteger( &locals_end ); + + for ( i = 0; i < MAX_STRINGS; i++ ) + { + arc.ArchiveBool( &strings[i].inuse ); + + if ( strings[i].inuse ) + { + arc.ArchiveString( &strings[i].s ); + } + else + { + strings[i].s = ""; + } + } + + arc.ArchiveInteger( &numstatements ); + arc.ArchiveRaw( statements, sizeof( statements[0] ) * numstatements ); + + arc.ArchiveInteger( &numfunctions ); + for ( i = 0; i < numfunctions; i++ ) + arc.ArchiveObject( ( Class * )&functions[i] ); + + // archive types + if ( arc.Saving() ) + { + for ( curtype = types, num = 0; curtype; curtype = curtype->next ) + { + num++; + } + + // Don't count type_function + + num--; + } + + arc.ArchiveInteger( &num ); + + if ( arc.Saving() ) + { + for ( curtype = types; curtype; curtype = curtype->next ) + { + // Skip type_function (we archive it seperately above) + + if ( curtype == &type_function ) + continue; + + arc.ArchiveObject( ( Class * )curtype ); + } + } + else + { + curtype = types; + + for ( i = 0; i < num; i++ ) + { + newtype = new type_t; + arc.ArchiveObject( ( Class * )newtype ); + newtype->next = NULL; + + curtype->next = newtype; + curtype = newtype; + } + } + + // archive defs + if ( arc.Saving() ) { + for ( curdef = def_head.next, num = 0; curdef; curdef = curdef->next ) + num++; + } + + arc.ArchiveInteger( &num ); + + if ( arc.Saving() ) { + for ( curdef = def_head.next; curdef; curdef = curdef->next ) + arc.ArchiveObject( ( Class * )curdef ); + } + else { + def_tail = &def_head; + curdef = def_tail; + + for ( i = 0; i < num; i++ ) { + newdef = new def_t; + arc.ArchiveObject( ( Class * )newdef ); + newdef->next = NULL; + + curdef->next = newdef; + curdef = newdef; + } + } + + arc.ArchiveInteger( &pr_error_count ); + + filenames.Archive( arc ); + arc.ArchiveString( &s_file ); + + if ( arc.Loading() ) + { + memset( pr_global_defs, 0, sizeof( pr_global_defs ) ); + } + + for ( i = 0; i < numpr_globals; i++ ) + { + arc.ArchiveObjectPointer( ( Class ** )&pr_global_defs[i] ); + } +} + +CLASS_DECLARATION( Class, dfunction_t, NULL ) +{ + { NULL, NULL } +}; + +CLASS_DECLARATION( Class, type_t, NULL ) +{ + { NULL, NULL } +}; + +CLASS_DECLARATION( Class, def_t, NULL ) +{ + { NULL, NULL } +}; + +CLASS_DECLARATION( Class, Program, NULL ) +{ + { NULL, NULL } +}; + +// defined as a str so that it is only allocated once +const str complextypestring( "COMPLEX TYPE" ); +const str emptystring; +const str resultstring( "" ); +const str immediatestring( "IMMEDIATE" ); + +def_t::def_t() +{ + type = NULL; + next = NULL; + ofs = 0; + localofs = 0; + scope = NULL; + initialized = false; + caseSensitive = true; +} + +/* +============ +FindType + +Returns a preexisting complex type that matches the parm, or allocates +a new one and copies it out. +============ +*/ +type_t *Program::FindType( const type_t *type ) +{ + def_t *def; + type_t *check; + int i; + + for( check = types; check != NULL; check = check->next ) + { + if ( ( check->type != type->type ) || ( check->aux_type != type->aux_type ) ) + { + continue; + } + + if ( check->min_parms == -1 ) + { + // non-event functions + if ( check->num_parms != type->num_parms ) + { + continue; + } + } + else + { + // event functions + if ( ( check->min_parms != type->min_parms ) || ( check->num_parms != type->num_parms ) ) + { + continue; + } + } + + for( i = 0; i < type->num_parms; i++ ) + { + if ( check->parm_types[ i ] != type->parm_types[ i ] ) + { + break; + } + } + + if ( i == type->num_parms ) + { + return check; + } + } + + // allocate a new one + check = new type_t; + *check = *type; + check->next = types; + types = check; + + // allocate a generic def for the type, so fields can reference it + def = new def_t; + def->name = complextypestring; + def->type = check; + def->_onDefList = false; + check->def = def; + + return check; +} + +/* +============ +GetDef + +If type is NULL, it will match any type +If allocate is true, a new def will be allocated if it can't be found +============ +*/ +def_t *Program::GetDef( type_t *type, const char *name, def_t *scope, bool allocate, Lexer *lex ) +{ + def_t *def; + char element[ MAX_NAME ]; + + // see if the name is already in use + for( def = def_head.next; def; def = def->next ) + { + if ( ( !def->caseSensitive && ( stricmp( def->name, name ) == 0 ) ) || ( strcmp( def->name, name ) == 0 ) ) + { + if ( def->scope && ( def->scope != scope ) ) + { + // in a different function + continue; + } + + if ( type && ( def->type != type ) && lex ) + { + lex->ParseError( "Type mismatch on redeclaration of %s", name ); + } + + return def; + } + } + + if ( !allocate ) + { + return NULL; + } + + // allocate a new def + def = new def_t; + + def->next = NULL; + def_tail->next = def; + def_tail = def; + + def->_onDefList = true; + + def->name = name; + def->type = type; + + def->scope = scope; + + def->ofs = numpr_globals; + if ( scope ) + { + // since we don't know how many local variables there are, + // we have to have them go backwards on the stack + def->localofs = -( numpr_globals - locals_start ) - 1; + } + else + { + def->localofs = def->ofs; + } + + pr_global_defs[ numpr_globals ] = def; + + // + // make automatic defs for the vectors elements + // .origin can be accessed as .origin_x, .origin_y, and .origin_z + // + if ( type->type == ev_vector ) + { + sprintf( element, "%s_x", name ); + GetDef( &type_float, element, scope, true, lex ); + + sprintf( element, "%s_y", name ); + GetDef( &type_float, element, scope, true, lex ); + + sprintf( element, "%s_z", name ); + GetDef( &type_float, element, scope, true, lex ); + } + else + { + numpr_globals += type_size[ type->type ]; + } + + return def; +} + +void Program::CreateDefForEvent( Event *ev ) +{ + type_t newtype; + type_t *type; + const char *name; + def_t *def; + EventArgDef *arg; + int num; + int i; + dfunction_t *df; + qboolean hitoptional; + + num = ev->getNumArgDefs(); + if ( num > MAX_PARMS ) + { + gi.WDPrintf( "Event '%s' has too many arguments for function call.\n", ev->getName() ); + return; + } + + if ( numfunctions >= MAX_FUNCTIONS ) + { + gi.Error( ERR_DROP, "Exceeded max functions while declaring events." ); + } + + df = &functions[ numfunctions ]; + df->parm_total = 0; + df->parm_start = numpr_globals; + + memset( &newtype, 0, sizeof( newtype ) ); + newtype.type = ev_function; + + // set the return type + arg = ev->getReturnType(); + if ( arg ) + { + switch( arg->getType() ) + { + case IS_STRING : + newtype.aux_type = &type_string; + break; + + case IS_VECTOR : + newtype.aux_type = &type_vector; + break; + + case IS_ENTITY : + newtype.aux_type = &type_entity; + break; + + case IS_BOOLEAN : + case IS_INTEGER : + case IS_FLOAT : + default: + newtype.aux_type = &type_float; + break; + } + } + else + { + newtype.aux_type = &type_void; + } + + newtype.num_parms = 0; + newtype.min_parms = 0; + hitoptional = false; + for( i = 1; i <= num; i++ ) + { + arg = ev->getArgDef( i ); + + if ( !hitoptional ) + { + hitoptional = arg->isOptional(); + if ( !hitoptional ) + { + newtype.min_parms++; + } + } + + switch( arg->getType() ) + { + case IS_STRING : + type = &type_string; + break; + + case IS_VECTOR : + type = &type_vector; + break; + + case IS_ENTITY : + type = &type_entity; + break; + + case IS_BOOLEAN : + case IS_INTEGER : + case IS_FLOAT : + default: + type = &type_float; + break; + } + + df->parm_total += type_size[ type->type ]; + df->parm_size[ newtype.num_parms ] = type_size[ type->type ]; + df->parm_type[ newtype.num_parms ] = type->type; + newtype.parm_types[ newtype.num_parms ] = type; + newtype.num_parms++; + } + + type = FindType( &newtype ); + name = ev->getName(); + def = GetDef( type, name, NULL, true, NULL ); + def->initialized = 1; + def->caseSensitive = false; + + setFunction( def->ofs, numfunctions ); + + // fill in the dfunction + df->eventnum = int( *ev ); + df->first_statement = -1; + df->s_name = def->name; + df->s_file = s_file; + df->numparms = def->type->num_parms; + df->minparms = def->type->min_parms; + df->locals = 0; + + numfunctions++; +} + +void Program::CreateEventDefs( void ) +{ + int num; + int i; + + num = Event::NumEventDefs(); + for( i = 1; i <= num; i++ ) + { + CreateDefForEvent( Event::GetEventDef( i ) ); + } +} + +/* +============ +CopyString + +returns an offset from the string heap +============ +*/ +int Program::CopyString( const char *str ) +{ + int idx; + + idx = AllocString(); + + if ( str ) + { + strings[ idx ].s = str; + } + else + { + strings[ idx ].s = emptystring; + } + + return idx; +} + +/* +============ +AllocString + +returns an unused string index +============ +*/ +int Program::AllocString() +{ + int i; + + for ( i = 0; i < MAX_STRINGS; i++ ) + { + if ( !strings[i].inuse ) { + strings[i].inuse = true; + strings[i].s.CapLength( 0 ); + return i; + } + } + + gi.Error( ERR_DROP, "Program::GetFreeString : Too many strings allocated!\n" ); + return 0; +} + +/* +============ +FreeString +============ +*/ +void Program::FreeString( int idx ) +{ + assert( ( idx > 0 ) && ( idx < MAX_STRINGS ) ); + assert( strings[idx].inuse ); + + strings[idx].inuse = false; + strings[idx].s.CapLength( 0 ); +} + +/* +============ +CountStrings +============ +*/ +int Program::CountUsedStrings() +{ + int i, count = 0; + + for ( i = 0; i < MAX_STRINGS; i++ ) { + if ( strings[i].inuse ) + count++; + } + + return count; +} + +/* +============== +BeginCompilation + +called before compiling a batch of files, clears the pr struct +============== +*/ +void Program::BeginCompilation( void ) +{ + int i; + + numpr_globals = RESERVED_OFS; + locals_end = numpr_globals; + locals_start = numpr_globals; + + numstatements = 0; + numfunctions = 1; + + for ( i = 0; i < MAX_STRINGS; i++ ) { + strings[i].inuse = false; + strings[i].s.CapLength( 0 ); + } + strings[0].inuse = true; // used as NULL + strings[1].inuse = true; // always used as the return string + + def_tail = &def_head; + + for( i = 0; i < RESERVED_OFS; i++ ) + { + pr_global_defs[ i ] = &def_void; + } + + // link the function type in so state forward declarations match proper type + types = &type_function; + type_function.next = NULL; + + pr_error_count = 0; + + // define any predefined objects + GetDef( &type_void, "cam", NULL, true, NULL ); +} + +/* +============== +FinishCompilation + +called after all files are compiled to check for errors +Returns false if errors were detected. +============== +*/ +bool Program::FinishCompilation( void ) +{ + def_t *d; + bool errors; + + errors = false; + + // check to make sure all functions prototyped have code + for( d = def_head.next; d; d = d->next ) + { + if ( ( d->type->type == ev_function ) && !d->scope ) + { + // function parms are ok + if (!d->initialized) + { + gi.WPrintf( "function %s was not defined\n", d->name.c_str() ); + errors = true; + } + } + } + + // also bail if there were ANY errors; the above block may be unneccessary with this in place. + if( program.pr_error_count > 0 ) + { + errors = true; + } + + return !errors; +} + +void Program::Compile( const char *filename ) +{ + char *src; + Compiler compiler( *this ); + int filenum; + + str oldfile( s_file ); + + s_file = filename; + if ( gi.FS_ReadFile( filename, ( void ** )&src, true ) < 0 ) + { + s_file = oldfile; + gi.WPrintf( "***\n***\n***\n*** Couldn't load %s\n***\n***\n***\n", filename ); + throw "Error"; + } + + filenum = filenames.AddObject( s_file ); + //if ( !compiler.CompileFile( src, filename, filenum ) ) + if ( !compiler.CompileFile( src, filenum ) ) + { + s_file = oldfile; + gi.FS_FreeFile( src ); + gi.WPrintf( "Compile failed.\n" ); + throw "Error"; + } + + gi.FS_FreeFile( src ); + s_file = oldfile; +} + +void Program::Load( const char *filename ) +{ + FreeData(); + + BeginCompilation(); + CreateEventDefs(); + + try + { + Compile( filename ); + } + + catch( ... ) + { + }; + + if ( !FinishCompilation() ) + { + gi.Error( ERR_DROP, "Compile failed." ); + } +} + +func_t Program::findFunction( const char *name ) +{ + int i; + + assert( name ); + + for( i = 0; i < numfunctions; i++ ) + { + if ( !functions[ i ].s_name.cmp( name ) ) + { + return i; + } + } + + return -1; +} + +void Program::FreeData( void ) +{ + type_t *curtype; + type_t *prevtype; + def_t *curdef; + def_t *prevdef; + int i; + + for( curtype = types; curtype != &type_function; curtype = prevtype ) + { + prevtype = curtype->next; + delete curtype->def; + delete curtype; + } + + // link the function type in so state forward declarations match proper type + types = &type_function; + type_function.next = NULL; + + for( curdef = def_head.next; curdef != NULL; curdef = prevdef ) + { + prevdef = curdef->next; + delete curdef; + } + + def_head.next = NULL; + def_tail = NULL; + + // cause all our strings to share their data + for( i = 0; i < MAX_STRINGS; i++ ) { + strings[ i ].inuse = false; + strings[ i ].s = emptystring; + } + strings[0].inuse = true; // always used as return string + + memset( pr_global_defs, 0, sizeof( pr_global_defs ) ); + memset( pr_globals, 0, sizeof( pr_globals ) ); + numpr_globals = 0; + + locals_start = 0; + locals_end = 0; + + memset( statements, 0, sizeof( statements ) ); + numstatements = 0; + + for( i = 0; i < MAX_FUNCTIONS; i++ ) + { + functions[ i ].eventnum = 0; + functions[ i ].first_statement = 0; + functions[ i ].parm_start = 0; + functions[ i ].parm_total = 0; + functions[ i ].locals = 0; + functions[ i ].profile = 0; + + functions[ i ].s_name = emptystring; + functions[ i ].s_file = emptystring; + + functions[ i ].numparms = 0; + functions[ i ].minparms = 0; + + memset( functions[ i ].parm_size, 0, sizeof( functions[ i ].parm_size ) ); + memset( functions[ i ].parm_type, 0, sizeof( functions[ i ].parm_type ) ); + } + + numfunctions = 1; + + pr_error_count = 0; + s_file = emptystring; +} + +Program::Program() +{ + int i; + str tempstring( "temp" ); + + def_void.type = &type_void; + def_void.name = tempstring; + + def_string.type = &type_string; + def_string.name = tempstring; + + def_float.type = &type_float; + def_float.name = tempstring; + + def_vector.type = &type_vector; + def_vector.name = tempstring; + + def_entity.type = &type_entity; + def_entity.name = tempstring; + + def_function.type = &type_function; + def_function.name = tempstring; + + def_tail = NULL; + + // link the function type in so state forward declarations match proper type + types = &type_function; + type_function.next = NULL; + + // cause all our strings to share their data + for( i = 0; i < MAX_STRINGS; i++ ) { + strings[ i ].inuse = false; + strings[ i ].s = ""; + } + strings[0].inuse = true; // always used as return string + + filenames.FreeObjectList(); + + numpr_globals = 0; + numstatements = 0; + numfunctions = 1; + + locals_start = 0; + locals_end = 0; + + pr_error_count = 0; +} + +Program::~Program() +{ + FreeData(); +} + +// Stuff From .h File +float Program::getFloat( int offset ) +{ + return pr_globals[ offset ]; +} + +int Program::getInteger( int offset ) +{ + return *( int * )&pr_globals[ offset ]; +} + +float *Program::getVector( int offset ) +{ + return &pr_globals[ offset ]; +} + +const char *Program::getString( int offset ) +{ + return strings[ *( int * )&pr_globals[ offset ] ].s.c_str(); + // return ( ( str * )&pr_globals[ offset ] ] ] )->c_str(); + // return ( ( str * )( &pr_globals[ offset ] ) )->getString(); +} + +func_t Program::getFunction( int offset ) +{ + return *( func_t * )&pr_globals[ offset ]; +} + +Entity *Program::getEntity( int offset ) +{ + Entity *ent; + int entnum; + + entnum = *( int * )&pr_globals[ offset ]; + + if ( entnum > 0 ) + { + ent = G_GetEntity( entnum - 1 ); + return ent; + } + + return NULL; +} + +TargetList *Program::getTargetList( int offset ) +{ + int entnum; + + entnum = *( int * )&pr_globals[ offset ]; + + if ( entnum < 0 ) + { + return world->GetTargetList( -entnum ); + } + + return NULL; +} + +gentity_t *Program::getEdict( int offset ) +{ + int entnum; + Entity *ent; + + entnum = *( int * )&pr_globals[ offset ]; + + if ( entnum > 0 ) + { + ent = G_GetEntity( entnum - 1 ); + if ( ent ) + { + return ent->edict; + } + } + else if ( entnum < 0 ) + { + } + + return NULL; +} + +void Program::setFunction( int offset, func_t func ) +{ + *( func_t * )&pr_globals[ offset ] = func; +} + +void Program::setEntity( int offset, const Entity *ent ) +{ + if ( ent ) + { + *( int * )&pr_globals[ offset ] = ent->entnum + 1; + } + else + { + *( int * )&pr_globals[ offset ] = 0; + } +} + +void Program::setTargetList( int offset, const TargetList *list ) +{ + if ( list ) + { + *( int * )&pr_globals[ offset ] = -list->index; + } + else + { + *( int * )&pr_globals[ offset ] = 0; + } +} + +void Program::setString( int offset, const char *text ) +{ + strings[ offset ].s = text; + *( int * )&pr_globals[ offset ] = offset; +} + +void Program::setFloat( int offset, float value ) +{ + pr_globals[ offset ] = value; +} + +void Program::setInteger( int offset, int value ) +{ + *( int * )&pr_globals[ offset ] = value; +} + +void Program::setVector( int offset, const Vector &vec ) +{ + *( Vector * )&pr_globals[ offset ] = vec; +} + +const char *Program::GetFilename( int num ) +{ + return filenames.ObjectAt( num ); +} diff --git a/dlls/game/program.h b/dlls/game/program.h new file mode 100644 index 0000000..17ca916 --- /dev/null +++ b/dlls/game/program.h @@ -0,0 +1,440 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/program.h $ +// $Revision:: 11 $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// +// DESCRIPTION: +// + +#ifndef __PROGRAM_H__ +#define __PROGRAM_H__ + +#include "g_local.h" + +class Lexer; + +#define MAX_STRINGS 4096 +//#define MAX_STATEMENTS 65536 +#define MAX_STATEMENTS 16384 +#define MAX_FUNCTIONS 2560 + +#define MAX_PARMS 16 +#define MAX_NAME 64 // chars long +#define MAX_REGS 16384 + +#define OFS_NULL 0 +#define OFS_RETURN 1 +#define RESERVED_OFS 4 +#define OFS_CAM 4 +#define OFS_END 6 + +extern const str complextypestring; +extern const str emptystring; +extern const str resultstring; +extern const str immediatestring; + +// offsets are always multiplied by 4 before using +typedef int gofs_t; // offset in global data block + +typedef enum + { + ev_error = -1, ev_void, ev_string, ev_float, ev_vector, ev_entity, ev_function + } etype_t; + +typedef int func_t; + +typedef union eval_s + { + int string; + float _float; + float vector[ 3 ]; + func_t function; + int _int; + int entity; + } eval_t; + +class def_t; + +class type_t : public Class +{ +public: + type_t( etype_t t = ev_error, def_t *d = NULL, type_t *n = NULL, type_t *aux = NULL ) : + type( t ), def( d ), next( n ), aux_type( aux ) { num_parms = 0; min_parms = 0; }; + CLASS_PROTOTYPE( type_t ); + + virtual void Archive( Archiver &arc ); + + etype_t type; + def_t *def; // a def that points to this type + type_t *next; + + // function types are more complex + type_t *aux_type; // return type + int num_parms; // -1 = variable args + int min_parms; // for variable arg events + type_t *parm_types[ MAX_PARMS ]; // only [num_parms] allocated +}; + +class def_t : public Class + { + public: + type_t *type; + str name; + def_t *next; + gofs_t ofs; + gofs_t localofs; // equal to ofs for globals, negative for locals + def_t *scope; // function the var was defined in, or NULL + int initialized; // 1 when a declaration included "= immediate" + + bool caseSensitive; + + bool _onDefList; + + def_t(); + + CLASS_PROTOTYPE( def_t ); + + virtual void Archive( Archiver &arc ); + }; + +typedef struct + { + unsigned short type; + unsigned short ofs; + signed short localofs; + unsigned short s_name; + } ddef_t; + +typedef struct statement_s + { + unsigned short op; + short a; + short b; + short c; + unsigned short linenumber; + unsigned short file; + } dstatement_t; + +class dfunction_t : public Class + { +public: + dfunction_t() { }; + CLASS_PROTOTYPE( dfunction_t ); + + virtual void Archive( Archiver &arc ); + + int eventnum; + int first_statement; // negative numbers are builtins + int parm_start; + int parm_total; + int locals; // total ints of parms + locals + + int profile; // runtime + + str s_name; + str s_file; // source file defined in + + int numparms; + int minparms; + byte parm_size[ MAX_PARMS ]; + byte parm_type[ MAX_PARMS ]; + }; + +class localstr_t +{ +public: + localstr_t() : inuse( false ), s() { }; + bool inuse; + str s; +}; + +class TargetList; +class Program : public Class + { + public: + CLASS_PROTOTYPE( Program ); + + virtual void Archive( Archiver &arc ); + + def_t *pr_global_defs[ MAX_REGS ]; // to find def for a global variable + float pr_globals[ MAX_REGS ]; + int numpr_globals; + + int locals_start; + int locals_end; // for tracking local variables vs temps + + localstr_t strings[ MAX_STRINGS ]; + + dstatement_t statements[ MAX_STATEMENTS ]; + int numstatements; + + dfunction_t functions[ MAX_FUNCTIONS ]; + int numfunctions; + + type_t *types; + def_t def_head; // unused head of linked list + def_t *def_tail; // add new defs after this and move it + + int pr_error_count; + + Container filenames; + str s_file; + + type_t *FindType( const type_t *type ); + def_t *GetDef( type_t *type, const char *name, def_t *scope, bool allocate, Lexer *lex ); + void CreateDefForEvent( Event *ev ); + void CreateEventDefs( void ); + + int CopyString( const char *str ); + + Program(); + ~Program(); + + void InitData( void ); + void BeginCompilation( void ); + bool FinishCompilation( void ); + void FreeData( void ); + + const char *GetFilename( int num ); + + void Compile( const char *filename ); + void Load( const char *filename ); + + float getFloat( int offset ); + int getInteger( int offset ); + float *getVector( int offset ); + const char *getString( int offset ); + Entity *getEntity( int offset ); + gentity_t *getEdict( int offset ); + TargetList *getTargetList( int offset ); + func_t getFunction( int offset ); + + void setFloat( int offset, float value ); + void setInteger( int offset, int value ); + void setVector( int offset, const Vector &vec ); + void setString( int offset, const char *text ); + void setFunction( int offset, func_t func ); + void setEntity( int offset, const Entity *ent ); + void setTargetList( int offset, const TargetList *list ); + + func_t findFunction( const char *name ); + + int AllocString(); + void FreeString( int idx ); + int CountUsedStrings(); + }; +/* +inline float Program::getFloat + ( + int offset + ) + + { + return pr_globals[ offset ]; + } + +inline int Program::getInteger + ( + int offset + ) + + { + return *( int * )&pr_globals[ offset ]; + } + +inline float *Program::getVector + ( + int offset + ) + + { + return &pr_globals[ offset ]; + } + +inline const char *Program::getString + ( + int offset + ) + + { + return strings[ *( int * )&pr_globals[ offset ] ].s.c_str(); +// return ( ( str * )&pr_globals[ offset ] ] ] )->c_str(); +// return ( ( str * )( &pr_globals[ offset ] ) )->getString(); + } + +inline func_t Program::getFunction + ( + int offset + ) + + { + return *( func_t * )&pr_globals[ offset ]; + } + +inline Entity *Program::getEntity + ( + int offset + ) + + { + Entity *ent; + int entnum; + + entnum = *( int * )&pr_globals[ offset ]; + + if ( entnum > 0 ) + { + ent = G_GetEntity( entnum - 1 ); + return ent; + } + + return NULL; + } + +inline TargetList *Program::getTargetList + ( + int offset + ) + + { + int entnum; + + entnum = *( int * )&pr_globals[ offset ]; + + if ( entnum < 0 ) + { + return world->GetTargetList( -entnum ); + } + + return NULL; + } + +inline gentity_t *Program::getEdict + ( + int offset + ) + + { + int entnum; + Entity *ent; + + entnum = *( int * )&pr_globals[ offset ]; + + if ( entnum > 0 ) + { + ent = G_GetEntity( entnum - 1 ); + if ( ent ) + { + return ent->edict; + } + } + else if ( entnum < 0 ) + { + } + + return NULL; + } + +inline void Program::setFunction + ( + int offset, + func_t func + ) + + { + *( func_t * )&pr_globals[ offset ] = func; + } + +inline void Program::setEntity + ( + int offset, + Entity *ent + ) + + { + if ( ent ) + { + *( int * )&pr_globals[ offset ] = ent->entnum + 1; + } + else + { + *( int * )&pr_globals[ offset ] = 0; + } + } + +inline void Program::setTargetList + ( + int offset, + TargetList *list + ) + + { + if ( list ) + { + *( int * )&pr_globals[ offset ] = -list->index; + } + else + { + *( int * )&pr_globals[ offset ] = 0; + } + } + +inline void Program::setString + ( + int offset, + const char *text + ) + + { + strings[ offset ].s = text; + } + +inline void Program::setFloat + ( + int offset, + float value + ) + + { + pr_globals[ offset ] = value; + } + +inline void Program::setInteger + ( + int offset, + int value + ) + + { + *( int * )&pr_globals[ offset ] = value; + } + +inline void Program::setVector + ( + int offset, + Vector &vec + ) + + { + *( Vector * )&pr_globals[ offset ] = vec; + } + +inline const char *Program::GetFilename + ( + int num + ) + + { + return filenames.ObjectAt( num ); + } +*/ +#endif diff --git a/dlls/game/puzzleobject.cpp b/dlls/game/puzzleobject.cpp new file mode 100644 index 0000000..1f2ab80 --- /dev/null +++ b/dlls/game/puzzleobject.cpp @@ -0,0 +1,759 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/puzzleobject.cpp $ +// $Revision:: 28 $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// + +#include "_pch_cpp.h" +#include "puzzleobject.hpp" +#include "sentient.h" +#include "player.h" +#include "equipment.h" +#include "gamecmds.h" + + +Event EV_PuzzleObject_SetOpenDistance +( + "puzzleobject_opendistance", + EV_DEFAULT, + "f", + "openDistance", + "Sets the open distance from player that the puzzle object will open or close" +); +Event EV_PuzzleObject_AnimationDone +( + "puzzleobject_animdone", + EV_CODEONLY, + NULL, + NULL, + "Called when the puzzle object's animation is done." +); +Event EV_PuzzleObject_SetItemToUse +( + "puzzleobject_itemtouse", + EV_DEFAULT, + "s", + "item", + "The item to use on the puzzle" +); +Event EV_PuzzleObject_SetItemUsedThread +( + "puzzleobject_itemusedthread", + EV_DEFAULT, + "s", + "threadname", + "The thread to call when the item is used" +); +Event EV_PuzzleObject_SetFailedThread +( + "puzzleobject_failedthread", + EV_DEFAULT, + "s", + "threadname", + "The thread to call when the puzzle fails" +); +Event EV_PuzzleObject_SetSolvedThread +( + "puzzleobject_solvedthread", + EV_DEFAULT, + "s", + "threadname", + "The thread to call when the puzzle is solved" +); +Event EV_PuzzleObject_SetCanceledThread +( + "puzzleobject_canceledthread", + EV_DEFAULT, + "s", + "threadname", + "The thread to call when the puzzle is canceled" +); +Event EV_PuzzleObject_Failed +( + "puzzleobject_failed", + EV_DEFAULT, + NULL, + NULL, + "Received when the puzzle fails" +); +Event EV_PuzzleObject_Canceled +( + "puzzleobject_canceled", + EV_DEFAULT, + NULL, + NULL, + "Received when the puzzle is canceled" +); +Event EV_PuzzleObject_Solved +( + "puzzleobject_solved", + EV_DEFAULT, + NULL, + NULL, + "Received when the puzzle is solved" +); +Event EV_PuzzleObject_Reset +( + "puzzleobject_reset", + EV_DEFAULT, + NULL, + NULL, + "Resets a previously solved puzzle so that it can be triggered again" +); +Event EV_PuzzleObject_TimeToUse +( + "puzzleobject_timeToUse", + EV_DEFAULT, + "f", + "timeToUse", + "Makes the puzzle object solved after the user has used it for long enough." +); +Event EV_PuzzleObject_Activate +( + "puzzleobject_activate", + EV_DEFAULT, + NULL, + NULL, + "Lets the object think and respond to the player again. DOES NOT affect any change in animation." +); +Event EV_PuzzleObject_DeActivate +( + "puzzleobject_deactivate", + EV_DEFAULT, + NULL, + NULL, + "Makes the object unresponsive to input and the player. DOES NOT affect any change in animation." +); +Event EV_PuzzleObject_TimerHudName +( + "puzzleobject_timerHudName", + EV_DEFAULT, + "s", + "hudName", + "Sets the hud name to use for the timer hud." +); +Event EV_PuzzleObject_BecomeModBarInSkill +( + "becomeModBarInSkill", + EV_DEFAULT, + "f", + "skill", + "Tells a puzzleobject to just display a timed modulation bar in any skill less than or equal to the specified one." +); + +//--------------------------------------------------------- +// PUZZLE OBJECT +//--------------------------------------------------------- +/*****************************************************************************/ +/*QUAKED puzzle_object (0 0.5 1) (-16 -16 0) (16 16 32) + +Puzzle object is the basic framework for all the puzzles. It communicates +with the script through events and threads to process the puzzle state. +All logic for the puzzle object exists in the script. + +The puzzle object contains four threads, which are ItemUsed, Solved, Canceled and Failed. +These are explained in the Key Value Pairs section. The Puzzle object also contains an +item used string. This allows the level designer to specify the item the player must +use to activate the puzzle. + +Key Value Pairs: +model - the tiki model to use for the puzzle object +puzzleobject_opendistance - the amount of distance the player should be before the puzzle opens. +puzzleobject_itemtouse - the name of the item the player must use to execute the puzzle +puzzleobject_itemusedthread - the name of the thread called when the item is used. +puzzleobject_failedthread - the name of the thread called when the puzzle fails +puzzleobject_canceledthread - the name of the thread called when the puzzle is canceled +puzzleobject_solvedthread - the name of the thread called when the puzzle is solved. + +******************************************************************************/ +CLASS_DECLARATION( Entity, PuzzleObject, "puzzle_object" ) +{ + { &EV_PuzzleObject_SetOpenDistance, &PuzzleObject::setOpenDistance }, + { &EV_PuzzleObject_AnimationDone, &PuzzleObject::animationDone }, + { &EV_PuzzleObject_SetItemToUse, &PuzzleObject::setItemToUse }, + { &EV_PuzzleObject_SetItemUsedThread, &PuzzleObject::setItemUsedThread }, + { &EV_PuzzleObject_SetFailedThread, &PuzzleObject::setFailedThread }, + { &EV_PuzzleObject_SetSolvedThread, &PuzzleObject::setSolvedThread }, + { &EV_PuzzleObject_SetCanceledThread, &PuzzleObject::setCanceledThread }, + { &EV_PuzzleObject_Failed, &PuzzleObject::failed }, + { &EV_PuzzleObject_Canceled, &PuzzleObject::canceled }, + { &EV_PuzzleObject_Solved, &PuzzleObject::solved }, + { &EV_PuzzleObject_Reset, &PuzzleObject::reset }, + { &EV_PuzzleObject_TimeToUse, &PuzzleObject::setTimeToUse }, + { &EV_Use, &PuzzleObject::useEvent }, + { &EV_PuzzleObject_Activate, &PuzzleObject::activate }, + { &EV_PuzzleObject_DeActivate, &PuzzleObject::deActivate }, + { &EV_PuzzleObject_TimerHudName, &PuzzleObject::setTimerHudName }, + { &EV_PuzzleObject_BecomeModBarInSkill, &PuzzleObject::becomeModBarInSkill }, + + {NULL, NULL} +}; + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +PuzzleObject::PuzzleObject() +{ + animate = new Animate( this ); + + _saveState = PUZZLE_STATE_IDLE; + _puzzleState = PUZZLE_STATE_IDLE; + animate->RandomAnimate( "puzzle_idle" ); + + edict->s.eType = ET_MODELANIM; + _openDistance = 200.0f; + + // setup default bounding box if there is no model + if( model.length() == 0 ) + { + Vector mins( -16, -16, 0 ); + Vector maxs( 16, 16, 32 ); + this->setSize( mins, maxs ); + } + + _timed = false; + _timeToUse = 0.0f; + _hudOn = false; + _lastTimeUsed = 0.0f; + _usedTime = 0.0f; + + _nextNeedToUseTime = 0.0f; + + turnThinkOn(); + + _hudName = "timerhud"; + _minSkill = -1; +} + + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +PuzzleObject::~PuzzleObject() +{ + +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::Think( void ) +{ + bool playerNearby = false; + Player *player; + Vector dir; + float distance; + + // when idle, handle player walking in and out of range + if( (_puzzleState == PUZZLE_STATE_IDLE) || (_puzzleState == PUZZLE_STATE_IDLE_OPEN) ) + { + // get the player's distance from the puzzle + player = (Player *)g_entities[ 0 ].entity; + if ( player ) + { + dir = player->origin - origin; + distance = dir.length(); + + if ( distance < _openDistance ) + { + playerNearby = true; + } + } + + // if the player is near, open up + if( (_puzzleState == PUZZLE_STATE_IDLE) && (playerNearby == true) ) + { + _puzzleState = PUZZLE_STATE_OPENING; + animate->RandomAnimate( "puzzle_opening", EV_PuzzleObject_AnimationDone ); + } + + // if the player has moved away, close up + if( (_puzzleState == PUZZLE_STATE_IDLE_OPEN) && (playerNearby == false) ) + { + _puzzleState = PUZZLE_STATE_CLOSING; + animate->RandomAnimate("puzzle_closing", EV_PuzzleObject_AnimationDone ); + } + } + + if ( ( _timed || (level.getSkill() <= _minSkill) ) + && _hudOn && ( _lastTimeUsed + 0.25 < level.time ) ) + { + _lastTimeUsed = 0.0f; + _usedTime = 0.0f; + + timedPuzzleCanceled(); + } +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::setOpenDistance(Event* event) +{ + _openDistance = event->GetFloat(1); +} + +void PuzzleObject::setItemToUse(Event* event) +{ + _itemToUse = event->GetString(1); +} +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::setItemUsedThread(Event* event) +{ + _itemUsedThread = event->GetString(1); +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::setFailedThread(Event* event) +{ + _failedThread = event->GetString(1); +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::setSolvedThread(Event* event) +{ + _solvedThread = event->GetString(1); +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::setCanceledThread(Event* event) +{ + _canceledThread = event->GetString(1); +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::failed(Event* event) +{ + // close and lapse into locked mode + _puzzleState = PUZZLE_STATE_CLOSING_LOCKED; + animate->RandomAnimate( "puzzle_closing", EV_PuzzleObject_AnimationDone ); + + if(_failedThread.length() != 0) + { + ExecuteThread(_failedThread, true, this); + } + else + { + PostEvent( EV_PuzzleObject_Reset, 1 ); + } + +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::canceled(Event* event) +{ + // close and lapse into normal mode + _puzzleState = PUZZLE_STATE_CLOSING; + animate->RandomAnimate( "puzzle_closing", EV_PuzzleObject_AnimationDone ); + + if(_canceledThread.length() != 0) + { + ExecuteThread( _canceledThread, true, this); + } + else + { + PostEvent( EV_PuzzleObject_Reset, 1 ); + } + +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::solved(Event* event) +{ + // close and lapse into solved mode + _puzzleState = PUZZLE_STATE_CLOSING_SOLVED; + animate->RandomAnimate( "puzzle_closing", EV_PuzzleObject_AnimationDone ); + + if(_solvedThread.length() != 0) + { + ExecuteThread(_solvedThread, true, this); + } + +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::animationDone(Event* event) +{ + if( _puzzleState == PUZZLE_STATE_OPENING ) + { + _puzzleState = PUZZLE_STATE_IDLE_OPEN; + animate->RandomAnimate( "puzzle_waitingopen" ); + } + else if( _puzzleState == PUZZLE_STATE_CLOSING ) + { + _puzzleState = PUZZLE_STATE_IDLE; + animate->RandomAnimate( "puzzle_idle" ); + } + else if( _puzzleState == PUZZLE_STATE_CLOSING_SOLVED ) + { + _puzzleState = PUZZLE_STATE_IDLE_SOLVED; + animate->RandomAnimate( "puzzle_idle" ); + } + else if( _puzzleState == PUZZLE_STATE_CLOSING_LOCKED ) + { + _puzzleState = PUZZLE_STATE_IDLE_LOCKED; + animate->RandomAnimate( "puzzle_idle" ); + } +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::useEvent(Event* event) +{ + Entity* entity; + + if ( event->NumArgs() > 0 ) + entity = event->GetEntity( 1 ); + else + entity = NULL; + + // Don't get retriggered if the puzzle is busy + + if( (_puzzleState != PUZZLE_STATE_IDLE_OPEN) ) + return; + + // Make sure we are being used by the proper thing + + if ( !entity ) + return; + + if ( _itemToUse.length() > 0 ) + { + Equipment *equipment; + + // An item is supposed to be used on us + + if ( !entity->isSubclassOf( Equipment ) ) + { + if ( _nextNeedToUseTime < level.time ) + { + gi.centerprintf ( entity->edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$NeedToUse$$ %s", _itemToUse.c_str() ); + + if ( entity->isSubclassOf( Player ) ) + { + Player *player = (Player *)entity; + + player->loadUseItem( _itemToUse ); + } + + _nextNeedToUseTime = level.time + 1.0f; + } + + return; + } + + equipment = (Equipment*)entity; + + // Make sure this is the correct item + + if ( stricmp( equipment->getTypeName().c_str(), _itemToUse.c_str() ) != 0 ) + return; + } + else + { + // The player is supposed to use us directly + + if ( !entity->isSubclassOf( Player ) ) + { + // We're being used by something other than the player + + return; + } + } + + if ( _timed || (level.getSkill() <= _minSkill) ) + timedUse(); + else + normalUse(); +} + +void PuzzleObject::normalUse( void ) +{ + if ( !_itemUsedThread.length() ) + return; + + // let script take it from here + ExecuteThread( _itemUsedThread, true, this ); + + _puzzleState = PUZZLE_STATE_ACTIVE_OPEN; + animate->RandomAnimate( "puzzle_openon" ); +} + +void PuzzleObject::timedUse( void ) +{ + float percent; + Player *player; + Event *event; + + // Make sure we haven't already been used this frame + + if ( _lastTimeUsed >= level.time ) + return; + + player = ( Player * )g_entities[ 0 ].entity; + + // Turn on the timed hud if we haven't yet + + if ( !_hudOn ) + { + showTimerHud(); + } + + _lastTimeUsed = level.time; + _usedTime += level.frametime; + + percent = _usedTime / _timeToUse * 100.0f; + + event = new Event( EV_Player_SetStat ); + event->AddString( "generic" ); + event->AddInteger( (int)percent ); + player->ProcessEvent( event ); + + if ( percent >= 100.0f ) + { + timedPuzzleSolved(); + } +} + +void PuzzleObject::timedPuzzleSolved( void ) +{ + hideTimerHud(); + ProcessEvent( EV_PuzzleObject_Solved ); +} + +void PuzzleObject::timedPuzzleCanceled( void ) +{ + hideTimerHud(); + + if ( _canceledThread.length() ) + { + ProcessEvent( EV_PuzzleObject_Canceled ); + } +} + +void PuzzleObject::showTimerHud( void ) +{ + Player *player; + str commandString; + + + player = ( Player * )g_entities[ 0 ].entity; + + commandString = "pushmenu "; + commandString += _hudName; + commandString += "\n"; + + G_SendCommandToPlayer( player->edict, commandString.c_str() ); + + _hudOn = true; +} + +void PuzzleObject::hideTimerHud( void ) +{ + Player *player; + str commandString; + + player = ( Player * )g_entities[ 0 ].entity; + + commandString = "popmenu "; + commandString += _hudName; + commandString += " 1\n"; + + G_SendCommandToPlayer( player->edict, commandString.c_str() ); + + _hudOn = false; +} + +//----------------------------------------------------- +// +// Name: reset +// Class: PuzzleObject +// +// Description: Resets the puzzle's state to idle so it will +// animate correctly if played again and can be used again +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void PuzzleObject::reset( Event* event ) +{ + _puzzleState = PUZZLE_STATE_IDLE; + animate->RandomAnimate( "puzzle_idle" ); + + _lastTimeUsed = 0.0f; + _usedTime = 0.0f; +} + +void PuzzleObject::activate( Event* event ) +{ + _puzzleState = _saveState; +} + +void PuzzleObject::deActivate( Event* event ) +{ + _saveState = _puzzleState; + _puzzleState = PUZZLE_STATE_DEACTIVATED; +} + +void PuzzleObject::setTimeToUse( Event * ev ) +{ + _timeToUse = ev->GetFloat( 1 ); + + if ( _timeToUse > 0.0f ) + _timed = true; +} + +void PuzzleObject::setTimerHudName( Event * ev ) +{ + _hudName = ev->GetString( 1 ); +} + +void PuzzleObject::becomeModBarInSkill( Event* ev ) +{ + _minSkill = (int) ev->GetFloat( 1 ); + if( _timeToUse == 0.0f ) + { + _timeToUse = 2.0f; + } +} + diff --git a/dlls/game/puzzleobject.hpp b/dlls/game/puzzleobject.hpp new file mode 100644 index 0000000..d1d5e79 --- /dev/null +++ b/dlls/game/puzzleobject.hpp @@ -0,0 +1,144 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/program.h $ +// $Revision:: 7 $ +// $Date:: 5/07/02 12:02p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// + +#ifndef PUZZLE_OBJECT_HPP +#define PUZZLE_OBJECT_HPP + +#include "entity.h" + + +class PuzzleObject : public Entity +{ + public: + CLASS_PROTOTYPE( PuzzleObject ); + + typedef enum + { + // "idle" anim + PUZZLE_STATE_IDLE, // normal, just waiting for the player + PUZZLE_STATE_IDLE_SOLVED, // player has solved it, it's in afterglow + PUZZLE_STATE_IDLE_LOCKED, // player fucked it up, it's sulking + + // "opening" anim + PUZZLE_STATE_OPENING, // working on getting open + + // "open" anim + PUZZLE_STATE_IDLE_OPEN, // waiting to be used by you + + // "openon" anim + PUZZLE_STATE_ACTIVE_OPEN, // is being used by you + + // "closing" anim + PUZZLE_STATE_CLOSING, // closing on the way to normal idle + PUZZLE_STATE_CLOSING_SOLVED,// closing after being solved + PUZZLE_STATE_CLOSING_LOCKED,// closing after being failed + + PUZZLE_STATE_DEACTIVATED + + }PuzzleState; + + + PuzzleObject(); + virtual ~PuzzleObject(); + + PuzzleState getPuzzleState(void) { return _puzzleState; } + void setPuzzleState(PuzzleState puzzleState) { _puzzleState = puzzleState; } + + void setOpenDistance(Event* event); + + //Thread setting functions + void setItemUsedThread(Event* event); + void setFailedThread(Event* event); + void setSolvedThread(Event* event); + void setCanceledThread(Event* event); + + void failed(Event* event); + void canceled(Event* event); + void solved(Event* event); + void reset( Event* event ); + + void animationDone(Event* event); + void setItemToUse(Event* event); + void useEvent(Event* event); + void activate( Event* event ); + void deActivate( Event* event ); + + void normalUse( void ); + void timedUse( void ); + + void setTimeToUse( Event *ev ); + void timedPuzzleSolved( void ); + void timedPuzzleCanceled( void ); + void showTimerHud( void ); + void hideTimerHud( void ); + + void setTimerHudName( Event * ev ); + + void becomeModBarInSkill( Event* ev ); + + /*virtual*/ void Archive(Archiver &arc); + /*virutal*/ void Think( void ); + + private: + str _itemToUse; + //Animations to use based up the state. + str _itemUsedThread; + str _failedThread; + str _solvedThread; + str _canceledThread; + + PuzzleState _puzzleState; + PuzzleState _saveState; + float _openDistance; + + bool _timed; + float _timeToUse; + float _lastTimeUsed; + float _usedTime; + bool _hudOn; + + float _nextNeedToUseTime; + + str _hudName; + int _minSkill; // if skill level is <= this, the puzzle becomes a mod bar +}; + + +inline void PuzzleObject::Archive( Archiver &arc ) +{ + Entity::Archive( arc ); + + arc.ArchiveString( &_itemToUse ); + arc.ArchiveString( &_itemUsedThread ); + arc.ArchiveString( &_failedThread ); + arc.ArchiveString( &_solvedThread ); + arc.ArchiveString( &_canceledThread ); + ArchiveEnum( _puzzleState, PuzzleState ); + ArchiveEnum( _saveState, PuzzleState ); + + arc.ArchiveFloat( &_openDistance ); + + arc.ArchiveBool( &_timed ); + arc.ArchiveFloat( &_timeToUse ); + arc.ArchiveFloat( &_lastTimeUsed ); + arc.ArchiveFloat( &_usedTime ); + arc.ArchiveBool( &_hudOn ); + + arc.ArchiveFloat( &_nextNeedToUseTime ); + + arc.ArchiveString( &_hudName ); + + arc.ArchiveInteger( &_minSkill ); +} + +#endif diff --git a/dlls/game/q_math.c b/dlls/game/q_math.c new file mode 100644 index 0000000..d4f839e --- /dev/null +++ b/dlls/game/q_math.c @@ -0,0 +1,2567 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/q_math.c $ +// $Revision:: 15 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:11a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// stateless support routines that are included in each code dll +#include +#include "q_shared.h" +#include +#include "float.h" +//intel addition +#if !defined ( MSVC_BUILD ) && !defined( LINUX ) +#include "xmmintrin.h" +#endif +// + + +#define X 0 +#define Y 1 +#define Z 2 +#define W 3 +//#define QUAT_EPSILON 0.00001 + +//intel change to accomodate manual cpu dispatch feature in intel compiler +#if !defined( MSVC_BUILD ) && !defined (LINUX) +__declspec(cpu_dispatch(generic,pentium_4)) +void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ) +{ +}; +__declspec(cpu_dispatch(generic,pentium_4)) +void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ) +{ +}; +#endif + +vec3_t vec3_origin = { 0.0f, 0.0f, 0.0f }; +vec3_t axisDefault[3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }; + + +vec4_t colorBlack = { 0.0f, 0.0f, 0.0f, 1.0f }; +vec4_t colorRed = { 1.0f, 0.0f, 0.0f, 1.0f }; +vec4_t colorGreen = { 1.0f, 1.0f, 0.0f, 1.0f }; +vec4_t colorBlue = { 0.0f, 0.0f, 1.0f, 1.0f }; +vec4_t colorYellow = { 1.0f, 1.0f, 0.0f, 1.0f }; +vec4_t colorMagenta= { 1.0f, 0.0f, 1.0f, 1.0f }; +vec4_t colorCyan = { 0.0f, 1.0f, 1.0f, 1.0f }; +vec4_t colorWhite = { 1.0f, 1.0f, 1.0f, 1.0f }; +vec4_t colorLtGrey = { 0.75f, 0.75f, 0.75f, 1.0f }; +vec4_t colorMdGrey = { 0.5f, 0.5f, 0.5f, 1.0f }; +vec4_t colorDkGrey = { 0.25f, 0.25f, 0.25f, 1.0f }; + +vec4_t g_color_table[8] = +{ + { 0.0f, 0.0f, 0.0f, 1.0f }, + { 1.0f, 0.0f, 0.0f, 1.0f }, + { 0.0f, 1.0f, 0.0f, 1.0f }, + { 1.0f, 1.0f, 0.0f, 1.0f }, + { 0.0f, 0.0f, 1.0f, 1.0f }, + { 0.0f, 1.0f, 1.0f, 1.0f }, + { 1.0f, 0.0f, 1.0f, 1.0f }, + { 1.0f, 1.0f, 1.0f, 1.0f } +}; + + +vec3_t bytedirs[NUMVERTEXNORMALS] = +{ + { -0.525731f, 0.000000f, 0.850651f }, { -0.442863f, 0.238856f, 0.864188f }, + { -0.295242f, 0.000000f, 0.955423f }, { -0.309017f, 0.500000f, 0.809017f }, + { -0.162460f, 0.262866f, 0.951056f }, { 0.000000f, 0.000000f, 1.000000f }, + { 0.000000f, 0.850651f, 0.525731f }, { -0.147621f, 0.716567f, 0.681718f }, + { 0.147621f, 0.716567f, 0.681718f }, { 0.000000f, 0.525731f, 0.850651f }, + { 0.309017f, 0.500000f, 0.809017f }, { 0.525731f, 0.000000f, 0.850651f }, + { 0.295242f, 0.000000f, 0.955423f }, { 0.442863f, 0.238856f, 0.864188f }, + { 0.162460f, 0.262866f, 0.951056f }, { -0.681718f, 0.147621f, 0.716567f }, + { -0.809017f, 0.309017f, 0.500000f }, { -0.587785f, 0.425325f, 0.688191f }, + { -0.850651f, 0.525731f, 0.000000f }, { -0.864188f, 0.442863f, 0.238856f }, + { -0.716567f, 0.681718f, 0.147621f }, { -0.688191f, 0.587785f, 0.425325f }, + { -0.500000f, 0.809017f, 0.309017f }, { -0.238856f, 0.864188f, 0.442863f }, + { -0.425325f, 0.688191f, 0.587785f }, { -0.716567f, 0.681718f, -0.147621f }, + { -0.500000f, 0.809017f, -0.309017f }, { -0.525731f, 0.850651f, 0.000000f }, + { 0.000000f, 0.850651f, -0.525731f }, { -0.238856f, 0.864188f, -0.442863f }, + { 0.000000f, 0.955423f, -0.295242f }, { -0.262866f, 0.951056f, -0.162460f }, + { 0.000000f, 1.000000f, 0.000000f }, { 0.000000f, 0.955423f, 0.295242f }, + { -0.262866f, 0.951056f, 0.162460f }, { 0.238856f, 0.864188f, 0.442863f }, + { 0.262866f, 0.951056f, 0.162460f }, { 0.500000f, 0.809017f, 0.309017f }, + { 0.238856f, 0.864188f, -0.442863f }, { 0.262866f, 0.951056f, -0.162460f }, + { 0.500000f, 0.809017f, -0.309017f }, { 0.850651f, 0.525731f, 0.000000f }, + { 0.716567f, 0.681718f, 0.147621f }, { 0.716567f, 0.681718f, -0.147621f }, + { 0.525731f, 0.850651f, 0.000000f }, { 0.425325f, 0.688191f, 0.587785f }, + { 0.864188f, 0.442863f, 0.238856f }, { 0.688191f, 0.587785f, 0.425325f }, + { 0.809017f, 0.309017f, 0.500000f }, { 0.681718f, 0.147621f, 0.716567f }, + { 0.587785f, 0.425325f, 0.688191f }, { 0.955423f, 0.295242f, 0.000000f }, + { 1.000000f, 0.000000f, 0.000000f }, { 0.951056f, 0.162460f, 0.262866f }, + { 0.850651f, -0.525731f, 0.000000f }, { 0.955423f, -0.295242f, 0.000000f }, + { 0.864188f, -0.442863f, 0.238856f }, { 0.951056f, -0.162460f, 0.262866f }, + { 0.809017f, -0.309017f, 0.500000f }, { 0.681718f, -0.147621f, 0.716567f }, + { 0.850651f, 0.000000f, 0.525731f }, { 0.864188f, 0.442863f, -0.238856f }, + { 0.809017f, 0.309017f, -0.500000f }, { 0.951056f, 0.162460f, -0.262866f }, + { 0.525731f, 0.000000f, -0.850651f }, { 0.681718f, 0.147621f, -0.716567f }, + { 0.681718f, -0.147621f, -0.716567f }, { 0.850651f, 0.000000f, -0.525731f }, + { 0.809017f, -0.309017f, -0.500000f }, { 0.864188f, -0.442863f, -0.238856f }, + { 0.951056f, -0.162460f, -0.262866f }, { 0.147621f, 0.716567f, -0.681718f }, + { 0.309017f, 0.500000f, -0.809017f }, { 0.425325f, 0.688191f, -0.587785f }, + { 0.442863f, 0.238856f, -0.864188f }, { 0.587785f, 0.425325f, -0.688191f }, + { 0.688191f, 0.587785f, -0.425325f }, { -0.147621f, 0.716567f, -0.681718f }, + { -0.309017f, 0.500000f, -0.809017f }, { 0.000000f, 0.525731f, -0.850651f }, + { -0.525731f, 0.000000f, -0.850651f }, { -0.442863f, 0.238856f, -0.864188f }, + { -0.295242f, 0.000000f, -0.955423f }, { -0.162460f, 0.262866f, -0.951056f }, + { 0.000000f, 0.000000f, -1.000000f }, { 0.295242f, 0.000000f, -0.955423f }, + { 0.162460f, 0.262866f, -0.951056f }, { -0.442863f, -0.238856f, -0.864188f }, + { -0.309017f, -0.500000f, -0.809017f },{ -0.162460f, -0.262866f, -0.951056f }, + { 0.000000f, -0.850651f, -0.525731f }, { -0.147621f, -0.716567f, -0.681718f }, + { 0.147621f, -0.716567f, -0.681718f }, { 0.000000f, -0.525731f, -0.850651f }, + { 0.309017f, -0.500000f, -0.809017f }, { 0.442863f, -0.238856f, -0.864188f }, + { 0.162460f, -0.262866f, -0.951056f }, { 0.238856f, -0.864188f, -0.442863f }, + { 0.500000f, -0.809017f, -0.309017f }, { 0.425325f, -0.688191f, -0.587785f }, + { 0.716567f, -0.681718f, -0.147621f }, { 0.688191f, -0.587785f, -0.425325f }, + { 0.587785f, -0.425325f, -0.688191f }, { 0.000000f, -0.955423f, -0.295242f }, + { 0.000000f, -1.000000f, 0.000000f }, { 0.262866f, -0.951056f, -0.162460f }, + { 0.000000f, -0.850651f, 0.525731f }, { 0.000000f, -0.955423f, 0.295242f }, + { 0.238856f, -0.864188f, 0.442863f }, { 0.262866f, -0.951056f, 0.162460f }, + { 0.500000f, -0.809017f, 0.309017f }, { 0.716567f, -0.681718f, 0.147621f }, + { 0.525731f, -0.850651f, 0.000000f }, { -0.238856f, -0.864188f, -0.442863f }, + { -0.500000f, -0.809017f, -0.309017f },{ -0.262866f, -0.951056f, -0.162460f }, + { -0.850651f, -0.525731f, 0.000000f }, { -0.716567f, -0.681718f, -0.147621f }, + { -0.716567f, -0.681718f, 0.147621f }, { -0.525731f, -0.850651f, 0.000000f }, + { -0.500000f, -0.809017f, 0.309017f }, { -0.238856f, -0.864188f, 0.442863f }, + { -0.262866f, -0.951056f, 0.162460f }, { -0.864188f, -0.442863f, 0.238856f }, + { -0.809017f, -0.309017f, 0.500000f }, { -0.688191f, -0.587785f, 0.425325f }, + { -0.681718f, -0.147621f, 0.716567f }, { -0.442863f, -0.238856f, 0.864188f }, + { -0.587785f, -0.425325f, 0.688191f }, { -0.309017f, -0.500000f, 0.809017f }, + { -0.147621f, -0.716567f, 0.681718f }, { -0.425325f, -0.688191f, 0.587785f }, + { -0.162460f, -0.262866f, 0.951056f }, { 0.442863f, -0.238856f, 0.864188f }, + { 0.162460f, -0.262866f, 0.951056f }, { 0.309017f, -0.500000f, 0.809017f }, + { 0.147621f, -0.716567f, 0.681718f }, { 0.000000f, -0.525731f, 0.850651f }, + { 0.425325f, -0.688191f, 0.587785f }, { 0.587785f, -0.425325f, 0.688191f }, + { 0.688191f, -0.587785f, 0.425325f }, { -0.955423f, 0.295242f, 0.000000f }, + { -0.951056f, 0.162460f, 0.262866f }, { -1.000000f, 0.000000f, 0.000000f }, + { -0.850651f, 0.000000f, 0.525731f }, { -0.955423f, -0.295242f, 0.000000f }, + { -0.951056f, -0.162460f, 0.262866f }, { -0.864188f, 0.442863f, -0.238856f }, + { -0.951056f, 0.162460f, -0.262866f }, { -0.809017f, 0.309017f, -0.500000f }, + { -0.864188f, -0.442863f, -0.238856f },{ -0.951056f, -0.162460f, -0.262866f }, + { -0.809017f, -0.309017f, -0.500000f },{ -0.681718f, 0.147621f, -0.716567f }, + { -0.681718f, -0.147621f, -0.716567f },{ -0.850651f, 0.000000f, -0.525731f }, + { -0.688191f, 0.587785f, -0.425325f }, { -0.587785f, 0.425325f, -0.688191f }, + { -0.425325f, 0.688191f, -0.587785f }, { -0.425325f, -0.688191f, -0.587785f }, + { -0.587785f, -0.425325f, -0.688191f },{ -0.688191f, -0.587785f, -0.425325f } +}; + +int Q_rand( int *seed ) { + *seed = (69069 * *seed + 1); + return *seed; +} + +float Q_random( int *seed ) { + return ( Q_rand( seed ) & 0xffff ) / (float)0x10000; +} + +float Q_crandom( int *seed ) { + return 2.0f * ( Q_random( seed ) - 0.5f ); +} + +/* +grealrandom + +This function produces a random number with a gaussian +distribution. This is also known as a normal or bell +curve distribution; it has a mean value of zero and a +standard deviation of one. +*/ +float grealrandom ( void ) { + double v1; + double v2; + double s; + float x1; + static float x2 = 0; + static int toggle = 0; + + if ( toggle ) { + toggle = 0; + return x2; + } + + do { + v1 = -1.0 + ( 2.0 * random () ); + v2 = -1.0 + ( 2.0 * random () ); + s = ( v1 * v1 ) + ( v2 * v2 ); + } + while ( ( s >= 1.0 ) || ( s == 0 ) ); + + s = sqrt ( -2.0 * log ( s ) / s ); + x1 = (float)( v1 * s ); + x2 = (float)( v2 * s ); + toggle = 1; + return x1; +} + + +/* +erandom + +This function produces a random number with a exponential +distribution and the specified mean value. +*/ +float erandom( float mean ) { + float r; + + do { + r = random(); + } while ( r == 0.0f ); + + return -mean * (float)log( r ); +} + +float randomrange( float min, float max ) + { + return min + ( random() * ( max - min ) ); + } + +float crandomrange( float min, float max ) + { + float random_number; + + random_number = crandom(); + + if ( random_number >= 0.0f ) + return min + ( crandom() * ( max - min ) ); + else + return ( crandom() * ( max - min ) ) - min; + } + +float grandom( float average, float deviation ) + { + return average + ( grealrandom() * deviation ); + } + +signed char ClampChar( int i ) { + if ( i < DATATYPE_SCHAR_MIN ) { + return DATATYPE_SCHAR_MIN; + } + if ( i > DATATYPE_SCHAR_MAX ) { + return DATATYPE_SCHAR_MAX; + } + return i; +} + +signed short ClampShort( int i ) { + if ( i < DATATYPE_SSHORT_MIN ) { + return DATATYPE_SSHORT_MIN; + } + if ( i > DATATYPE_SSHORT_MAX ) { + return DATATYPE_SSHORT_MAX; + } + return i; +} + +//=========================================================================== +// +// Global functions base on type double +// +//=========================================================================== +#define SCALAR_EPSILON (0.000001f) +#define SCALAR_IDENTITY (0.0f) + +double dEpsilon( void ) +{ + return (double)SCALAR_EPSILON; +} + +double dIdentity( void ) +{ + return (double)SCALAR_IDENTITY; +} + +double dSign( const double number ) +{ + if (number >= 0.0) + { + return 1; + } + else + { + return -1; + } +} + +double dClamp( const double value, const double min, const double max ) +{ + assert( min <= max ); + if ( value < min ) + { + return min; + } + if ( value > max ) + { + return max; + } + return value; +} + +double dDistance (const double value1, const double value2 ) +{ + return fabs ( value1 - value2); +} + +qboolean dCloseEnough( const double value1, const double value2, const double epsilon ) +{ + return dDistance( value1, value2) < epsilon; +} + +qboolean dSmallEnough( const double value, const double epsilon ) +{ + return dDistance( dIdentity(), value ) < epsilon; +} + +//=========================================================================== +// +// Global functions base on type float +// +//=========================================================================== +float fEpsilon(void) +{ + return SCALAR_EPSILON; +} + +float fIdentity(void) +{ + return SCALAR_IDENTITY; +} + +float fSign( const float number) +{ + if (number >= 0.0f) + { + return 1; + } + else + { + return -1; + } +} + +float fClamp( const float value, const float min, const float max ) +{ + assert( min <= max ); + if ( value < min ) + { + return min; + } + if ( value > max ) + { + return max; + } + return value; +} + +float fDistance (const float value1, const float value2 ) +{ + return fabs ( value1 - value2); +} + +qboolean fCloseEnough(const float value1, const float value2, const float epsilon ) +{ + return fDistance( value1, value2) < epsilon; +} + +qboolean fSmallEnough(const float value, const float epsilon ) +{ + return fDistance( fIdentity(), value ) < epsilon; +} + +//=========================================================================== +// +// Global functions base on type int +// +//=========================================================================== +int iSign( const int number) +{ + if (number >= 0) + { + return 1; + } + else + { + return -1; + } +} + +int iClamp( const int value, const int min, const int max ) +{ + assert( min <= max ); + if ( value < min ) + { + return min; + } + if ( value > max ) + { + return max; + } + return value; +} + +// this isn't a real cheap function to call! +int DirToByte( const vec3_t dir ) { + int i, best; + float d, bestd; + + if ( !dir ) { + return 0; + } + + bestd = 0; + best = 0; + for (i=0 ; i bestd) + { + bestd = d; + best = i; + } + } + + return best; +} + +void ByteToDir( int b, vec3_t dir ) { + if ( ( b < 0 ) || ( b >= NUMVERTEXNORMALS ) ) { + VectorCopy( vec3_origin, dir ); + return; + } + VectorCopy (bytedirs[b], dir); +} + + +unsigned ColorBytes3 (float r, float g, float b) { + unsigned i; + + ( (byte *)&i )[0] = r * 255.0f; + ( (byte *)&i )[1] = g * 255.0f; + ( (byte *)&i )[2] = b * 255.0f; + + return i; +} + +unsigned ColorBytes4 (float r, float g, float b, float a) { + unsigned i; + + ( (byte *)&i )[0] = r * 255.0f; + ( (byte *)&i )[1] = g * 255.0f; + ( (byte *)&i )[2] = b * 255.0f; + ( (byte *)&i )[3] = a * 255.0f; + + return i; +} + +float NormalizeColor( const vec3_t in, vec3_t out ) { + float max; + USES_CLAMP_ZERO; + + max = in[0] - in[1]; + ClampZero ( max ); + max = ( max + in[1] ) - in[2]; + ClampZero ( max ); + max += in[2]; + + if ( !max ) { + VectorClear( out ); + } else { + float oomax = 1.f / max; + + out[0] = in[0] * oomax; + out[1] = in[1] * oomax; + out[2] = in[2] * oomax; + } + return max; +} + + +//============================================================================ + +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, + float degrees ) +{ + float m[3][3]; + float im[3][3]; + float zrot[3][3]; + float tmpmat[3][3]; + float rot[3][3]; + int i; + vec3_t vr, vup, vf; + float rad; + + vf[0] = dir[0]; + vf[1] = dir[1]; + vf[2] = dir[2]; + + PerpendicularVector( vr, dir ); + CrossProduct( vr, vf, vup ); + + m[0][0] = vr[0]; + m[1][0] = vr[1]; + m[2][0] = vr[2]; + + m[0][1] = vup[0]; + m[1][1] = vup[1]; + m[2][1] = vup[2]; + + m[0][2] = vf[0]; + m[1][2] = vf[1]; + m[2][2] = vf[2]; + + memcpy( im, m, sizeof( im ) ); + + im[0][1] = m[1][0]; + im[0][2] = m[2][0]; + im[1][0] = m[0][1]; + im[1][2] = m[2][1]; + im[2][0] = m[0][2]; + im[2][1] = m[1][2]; + + memset( zrot, 0, sizeof( zrot ) ); + zrot[0][0] = zrot[1][1] = zrot[2][2] = 1.0F; + + rad = DEG2RAD( degrees ); + zrot[0][0] = (float)cos( rad ); + zrot[0][1] = (float)sin( rad ); + zrot[1][0] = (float)-sin( rad ); + zrot[1][1] = (float)cos( rad ); + + MatrixMultiply( m, zrot, tmpmat ); + MatrixMultiply( tmpmat, im, rot ); + + for ( i = 0; i < 3; i++ ) + { + dst[i] = ( rot[i][0] * point[0] ) + ( rot[i][1] * point[1] ) + ( rot[i][2] * point[2] ); + } +} + +/* +=============== +RotateAroundDirection +=============== +*/ +void RotateAroundDirection( vec3_t axis[3], float yaw ) { + + // create an arbitrary axis[1] + PerpendicularVector( axis[1], axis[0] ); + + // rotate it around axis[0] by yaw + if ( yaw ) { + vec3_t temp; + + VectorCopy( axis[1], temp ); + RotatePointAroundVector( axis[1], axis[0], temp, yaw ); + } + + // cross to get axis[2] + CrossProduct( axis[0], axis[1], axis[2] ); +} + + +void vectoangles( const vec3_t value1, vec3_t angles ) { + float forward; + float yaw, pitch; + + if ( ( value1[1] == 0.0f ) && ( value1[0] == 0.0f ) ) { + yaw = 0.0f; + if ( value1[2] > 0.0f ) { + pitch = 90.0f; + } + else { + pitch = 270.0f; + } + } + else { + if ( value1[0] ) { + yaw = ( atan2 ( value1[1], value1[0] ) * 180.0f / M_PI ); + } + else if ( value1[1] > 0.0f ) { + yaw = 90.0f; + } + else { + yaw = 270.0f; + } + if ( yaw < 0.0f ) { + yaw += 360.0f; + } + + forward = sqrt ( ( value1[0] * value1[0] ) + ( value1[1] * value1[1] ) ); + pitch = ( atan2(value1[2], forward) * 180.0f / M_PI ); + if ( pitch < 0.0f ) { + pitch += 360.0f; + } + } + + angles[PITCH] = -pitch; + angles[YAW] = yaw; + angles[ROLL] = 0.0f; +} + +float vectoyaw( const vec3_t vec ) { + float yaw; + + if ( ( vec[YAW] == 0.0f ) && ( vec[PITCH] == 0.0f ) ) { + yaw = 0.0f; + } else { + if (vec[PITCH]) { + yaw = ( atan2( vec[YAW], vec[PITCH]) * 180.0f / M_PI ); + } else if (vec[YAW] > 0.0f) { + yaw = 90.0f; + } else { + yaw = 270.0f; + } + if (yaw < 0.0f) { + yaw += 360.0f; + } + } + + return yaw; +} + +void AxisClear( vec3_t axis[3] ) { + axis[0][0] = 1; + axis[0][1] = 0; + axis[0][2] = 0; + axis[1][0] = 0; + axis[1][1] = 1; + axis[1][2] = 0; + axis[2][0] = 0; + axis[2][1] = 0; + axis[2][2] = 1; +} + +void AxisCopy( const vec3_t in[3], vec3_t out[3] ) { + VectorCopy( in[0], out[0] ); + VectorCopy( in[1], out[1] ); + VectorCopy( in[2], out[2] ); +} + +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ) +{ + float d; + vec3_t n; + float inv_denom; + + inv_denom = 1.0F / DotProduct( normal, normal ); + + d = DotProduct( normal, p ) * inv_denom; + + n[0] = normal[0] * inv_denom; + n[1] = normal[1] * inv_denom; + n[2] = normal[2] * inv_denom; + + dst[0] = p[0] - ( d * n[0] ); + dst[1] = p[1] - ( d * n[1] ); + dst[2] = p[2] - ( d * n[2] ); +} + +/* +================ +MakeNormalVectors + +Given a normalized forward vector, create two +other perpendicular vectors +================ +*/ +void MakeNormalVectors (const vec3_t forward, vec3_t right, vec3_t up) +{ + float d; + + // this rotate and negate guarantees a vector + // not colinear with the original + right[1] = -forward[0]; + right[2] = forward[1]; + right[0] = forward[2]; + + d = DotProduct (right, forward); + VectorMA (right, -d, forward, right); + VectorNormalize (right); + CrossProduct (right, forward, up); +} + +void VectorRotate( const vec3_t in, const vec3_t matrix[3], vec3_t out ) +{ + out[0] = DotProduct( in, matrix[0] ); + out[1] = DotProduct( in, matrix[1] ); + out[2] = DotProduct( in, matrix[2] ); +} + +void AccumulateTransform( vec3_t dstOrigin, vec3_t dstAxes[3], + const vec3_t childOrigin, const vec3_t childAxes[3], + const vec3_t parentOrigin, const vec3_t parentAxes[3] ) +{ + vec3_t tmpVector; + + // compute final angles + MatrixMultiply( childAxes, parentAxes, dstAxes ); + + // compute final origin as parent * p + VectorRotate( childOrigin, parentAxes, tmpVector ); + VectorAdd( tmpVector, parentOrigin, dstOrigin ); +} + +void AccumulatePosition( vec3_t dstOrigin, const vec3_t childOrigin, const vec3_t parentOrigin, const vec3_t parentAxes[3] ) +{ + vec3_t tmpVector; + + // compute final origin as parent * p + VectorRotate( childOrigin, parentAxes, tmpVector ); + VectorAdd( tmpVector, parentOrigin, dstOrigin ); +} + + +//============================================================================ + +/* +** float q_rsqrt( float number ) +*/ +#if !id386 || defined LINUX +float Q_rsqrt( float number ) +{ + long i; + float x2, y; + const float threehalfs = 1.5F; + + x2 = number * 0.5F; + y = number; + i = * ( long * ) &y; // evil floating point bit level hacking + i = 0x5f3759df - ( i >> 1 ); // what is this? + y = * ( float * ) &i; + y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration +// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed + + return y; +} +#else +static const float ONE_HALF = 0.5f; +static const float THREE_HALVES = 1.5f; +__declspec(naked) float Q_rsqrt ( float f ) +{ + __asm + { + fld dword ptr [esp + 4] + fmul dword ptr [ONE_HALF] + + mov eax, [esp + 4] + mov ecx, 0x5f3759df + + shr eax, 1 + + sub ecx, eax + + mov [esp + 4], ecx + + fmul dword ptr [esp + 4] + fld dword ptr [esp + 4] + fmul dword ptr [esp + 4] + fld dword ptr [THREE_HALVES] + fmul dword ptr [esp + 4] + fxch st(2) + fmulp st(1), st + fsubp st(1), st + ret + } +} +#endif + +float Q_fabs( float f ) { + int tmp = * ( int * ) &f; + tmp &= 0x7FFFFFFF; + return * ( float * ) &tmp; +} + +//============================================================ + +/* +=============== +LerpAngle + +=============== +*/ +float LerpAngle (float from, float to, float frac) { + float a; + + if ( to - from > 180.0f ) { + to -= 360.0f; + } + if ( to - from < -180.0f ) { + to += 360.0f; + } + a = from + ( frac * (to - from) ); + + return a; +} + +/* +=============== +LerpAngleFromCurrent + +=============== +*/ +float LerpAngleFromCurrent (float from, float to, float current, float frac) { + float a; + + if ( to - current > 180.0f ) { + to -= 360.0f; + } + if ( to - current < -180.0f ) { + to += 360.0f; + } + a = from + ( frac * (to - from) ); + + return a; +} + +/* +================= +AngleSubtract + +Always returns a value from -180 to 180 +================= +*/ +float AngleSubtract( float a1, float a2 ) { + float a; + + a = a1 - a2; + while ( a > 180.0f ) { + a -= 360.0f; + } + while ( a < -180.0f ) { + a += 360.0f; + } + return a; +} + + +void AnglesSubtract( const vec3_t v1, const vec3_t v2, vec3_t v3 ) { + v3[0] = AngleSubtract( v1[0], v2[0] ); + v3[1] = AngleSubtract( v1[1], v2[1] ); + v3[2] = AngleSubtract( v1[2], v2[2] ); +} + + +float AngleMod(float a) +{ +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + if (a >= 0) + a -= 360*(int)(a/360); + else + a += 360*( 1 + (int)(-a/360) ); +#endif + +****************************************************************************/ + + a = (360.0f/65536.0f) * (float)( (int)(a*(65536.0f/360.0f)) & 65535 ); + return a; +} + + +/* +================= +AngleNormalize360 + +returns angle normalized to the range [0 <= angle < 360] +================= +*/ +float AngleNormalize360 ( float angle ) { + +// return (360.0f / 65536.0f) * (float)( (int)(angle * (65536.0f / 360.0f)) & 65535 ); + + while( angle > 360.0f ) + angle -= 360.0f; + + while( angle < 0.0f ) + angle += 360.0f; + + return( angle ); +} + + +/* +================= +AngleNormalize180 + +returns angle normalized to the range [-180 < angle <= 180] +================= +*/ +float AngleNormalize180 ( float angle ) { + angle = AngleNormalize360( angle ); + if ( angle > 180.0f ) { + angle -= 360.0f; + } + return angle; +} + + +/* +================= +AngleNormalizeArbitrary + +returns angle normalized to the range [minimumAngle < angle <= minimumAngle + 360] +================= +*/ +float AngleNormalizeArbitrary ( const float angle, const float minimumAngle ) { + float maximumAngle = minimumAngle + 360.0f; + float normalizedAngle = angle; + + + while( normalizedAngle > maximumAngle ) + normalizedAngle -= 360.0f; + + while( normalizedAngle < minimumAngle ) + normalizedAngle += 360.0f; + + return( normalizedAngle ); +} + + +/* +================= +AngleDelta + +returns the normalized delta from angle1 to angle2 +================= +*/ +float AngleDelta ( float angle1, float angle2 ) { + +// return AngleNormalize180( angle1 - angle2 ); + + float normalized1 = AngleNormalize360( angle1 ); + float normalized2 = AngleNormalize360( angle2 ); + float angularDistance = normalized1 - normalized2; + + if( angularDistance < -180 ) + angularDistance += 360; + + if( angularDistance > 180 ) + angularDistance -= 360; + + return( angularDistance ); +} + + + + +/* +================= +AnglesDelta + +returns the normalized delta from angle1 to angle2 +================= +*/ +void AnglesDelta( const vec3_t v1, const vec3_t v2, vec3_t v3 ) { + v3[0] = AngleDelta( v1[0], v2[0] ); + v3[1] = AngleDelta( v1[1], v2[1] ); + v3[2] = AngleDelta( v1[2], v2[2] ); +} + + +//============================================================ + + +/* +================= +SetPlaneSignbits +================= +*/ +void SetPlaneSignbits (cplane_t *out) { + int bits, j; + + // for fast box on planeside test + bits = 0; + for (j=0 ; j<3 ; j++) { + if (out->normal[j] < 0.0f) { + bits |= 1<signbits = bits; +} + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 + +// this is the slow, general version +int BoxOnPlaneSide2 (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + int i; + float dist1, dist2; + int sides; + vec3_t corners[2]; + + for (i=0 ; i<3 ; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = emins[i]; + corners[1][i] = emaxs[i]; + } + else + { + corners[1][i] = emins[i]; + corners[0][i] = emaxs[i]; + } + } + dist1 = DotProduct (p->normal, corners[0]) - p->dist; + dist2 = DotProduct (p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0) + sides = 1; + if (dist2 < 0) + sides |= 2; + + return sides; +} + +================== +*/ +#if !id386 || defined LINUX +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + float dist1, dist2; + int sides; + +// fast axial cases + if (p->type < 3) + { + if (p->dist <= emins[p->type]) + return 1; + if (p->dist >= emaxs[p->type]) + return 2; + return 3; + } + +// general case + switch (p->signbits) + { + case 0: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 1: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + break; + case 2: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 3: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + break; + case 4: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 5: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emaxs[2]; + break; + case 6: + dist1 = p->normal[0]*emaxs[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emins[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + case 7: + dist1 = p->normal[0]*emins[0] + p->normal[1]*emins[1] + p->normal[2]*emins[2]; + dist2 = p->normal[0]*emaxs[0] + p->normal[1]*emaxs[1] + p->normal[2]*emaxs[2]; + break; + default: + dist1 = dist2 = 0; // shut up compiler + break; + } + + sides = 0; + if (dist1 >= p->dist) + sides = 1; + if (dist2 < p->dist) + sides |= 2; + + return sides; +} +#else +#pragma warning( disable: 4035 ) + +__declspec( naked ) int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *p) +{ + static int bops_initialized; + static int Ljmptab[8]; + + __asm { + + push ebx + + cmp bops_initialized, 1 + je initialized + mov bops_initialized, 1 + + mov Ljmptab[0*4], offset Lcase0 + mov Ljmptab[1*4], offset Lcase1 + mov Ljmptab[2*4], offset Lcase2 + mov Ljmptab[3*4], offset Lcase3 + mov Ljmptab[4*4], offset Lcase4 + mov Ljmptab[5*4], offset Lcase5 + mov Ljmptab[6*4], offset Lcase6 + mov Ljmptab[7*4], offset Lcase7 + +initialized: + + mov edx,ds:dword ptr[4+12+esp] + mov ecx,ds:dword ptr[4+4+esp] + xor eax,eax + mov ebx,ds:dword ptr[4+8+esp] + mov al,ds:byte ptr[17+edx] + cmp al,8 + jge Lerror + fld ds:dword ptr[0+edx] + fld st(0) + jmp dword ptr[Ljmptab+eax*4] +Lcase0: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase1: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase2: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase3: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ebx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ecx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase4: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase5: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ebx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase6: + fmul ds:dword ptr[ebx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ecx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) + jmp LSetSides +Lcase7: + fmul ds:dword ptr[ecx] + fld ds:dword ptr[0+4+edx] + fxch st(2) + fmul ds:dword ptr[ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[4+ecx] + fld ds:dword ptr[0+8+edx] + fxch st(2) + fmul ds:dword ptr[4+ebx] + fxch st(2) + fld st(0) + fmul ds:dword ptr[8+ecx] + fxch st(5) + faddp st(3),st(0) + fmul ds:dword ptr[8+ebx] + fxch st(1) + faddp st(3),st(0) + fxch st(3) + faddp st(2),st(0) +LSetSides: + faddp st(2),st(0) + fcomp ds:dword ptr[12+edx] + xor ecx,ecx + fnstsw ax + fcomp ds:dword ptr[12+edx] + and ah,1 + xor ah,1 + add cl,ah + fnstsw ax + and ah,1 + add ah,ah + add cl,ah + pop ebx + mov eax,ecx + ret +Lerror: + int 3 + } +} +#pragma warning( default: 4035 ) +#endif + +/* +================= +RadiusFromBounds +================= +*/ +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) { + int i; + vec3_t corner; + float a, b; + + for (i=0 ; i<3 ; i++) { + a = (float)fabs( mins[i] ); + b = (float)fabs( maxs[i] ); + corner[i] = a > b ? a : b; + } + + return VectorLength (corner); +} + +#define BOUNDS_CLEAR_VALUE 99999 + +void ClearBounds( vec3_t mins, vec3_t maxs ) { + mins[0] = mins[1] = mins[2] = BOUNDS_CLEAR_VALUE; + maxs[0] = maxs[1] = maxs[2] = -BOUNDS_CLEAR_VALUE; +} + +qboolean BoundsClear( const vec3_t mins, const vec3_t maxs ) + { + if ( + ( mins[ 0 ] == BOUNDS_CLEAR_VALUE ) && + ( mins[ 1 ] == BOUNDS_CLEAR_VALUE ) && + ( mins[ 2 ] == BOUNDS_CLEAR_VALUE ) && + ( maxs[ 0 ] == -BOUNDS_CLEAR_VALUE ) && + ( maxs[ 1 ] == -BOUNDS_CLEAR_VALUE ) && + ( maxs[ 2 ] == -BOUNDS_CLEAR_VALUE ) + ) + { + return qtrue; + } + else + { + return qfalse; + } + } + +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ) { + if ( v[0] < mins[0] ) { + mins[0] = v[0]; + } + if ( v[0] > maxs[0]) { + maxs[0] = v[0]; + } + + if ( v[1] < mins[1] ) { + mins[1] = v[1]; + } + if ( v[1] > maxs[1]) { + maxs[1] = v[1]; + } + + if ( v[2] < mins[2] ) { + mins[2] = v[2]; + } + if ( v[2] > maxs[2]) { + maxs[2] = v[2]; + } +} + + +int VectorCompare( const vec3_t v1, const vec3_t v2 ) { + if ( ( v1[0] != v2[0] ) || ( v1[1] != v2[1] ) || ( v1[2] != v2[2] )) { + return qfalse; + } + + return qtrue; +} + + +vec_t VectorNormalize( vec3_t v ) { + float length, ilength; + + length = ( v[0] * v[0] ) + ( v[1] * v[1] ) + ( v[2] * v[2] ); + length = (float)sqrt (length); + + if ( length ) { + ilength = 1.0f / length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; +} + +// +// fast vector normalize routine that does not check to make sure +// that length != 0, nor does it return length +// +void VectorNormalizeFast( vec3_t v ) +{ + float ilength; + + ilength = Q_rsqrt( DotProduct( v, v ) ); + + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; +} + +vec_t VectorNormalize2( const vec3_t v, vec3_t out) { + float length, ilength; + + length = ( v[0] * v[0] ) + ( v[1] * v[1] ) + ( v[2] * v[2] ); + length = (float)sqrt (length); + + if (length) + { + ilength = 1.0f / length; + out[0] = v[0] * ilength; + out[1] = v[1] * ilength; + out[2] = v[2] * ilength; + } else { + VectorClear( out ); + } + + return length; + +} + +void _VectorMA( const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc) { + vecc[0] = veca[0] + ( scale * vecb[0] ); + vecc[1] = veca[1] + ( scale * vecb[1] ); + vecc[2] = veca[2] + ( scale * vecb[2] ); +} + + +vec_t _DotProduct( const vec3_t v1, const vec3_t v2 ) { + return ( v1[0] * v2[0] ) + ( v1[1] * v2[1] ) + ( v1[2] * v2[2] ); +} + +//intel change +#if !defined (MSVC_BUILD) && !defined( LINUX ) +//intel optimized version +__declspec(cpu_specific(pentium_4)) +void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ) { + __m128 xmm_veca, xmm_vecb, xmm_out; + + xmm_veca = _mm_load_ss(&veca[0]); + xmm_vecb = _mm_load_ss(&vecb[0]); + xmm_out = _mm_sub_ss(xmm_veca,xmm_vecb); + _mm_store_ss(&out[0],xmm_out); + + xmm_veca = _mm_load_ss(&veca[1]); + xmm_vecb = _mm_load_ss(&vecb[1]); + xmm_out = _mm_sub_ss(xmm_veca,xmm_vecb); + _mm_store_ss(&out[1],xmm_out); + + xmm_veca = _mm_load_ss(&veca[2]); + xmm_vecb = _mm_load_ss(&vecb[2]); + xmm_out = _mm_sub_ss(xmm_veca,xmm_vecb); + _mm_store_ss(&out[2],xmm_out); +} +__declspec(cpu_specific(generic)) +//original +void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ) { + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} +#else +//original code for MSVC builds +void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ) { + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} +#endif + +//intel change +#if !defined(MSVC_BUILD) && !defined (LINUX) +//intel optimized version +__declspec(cpu_specific(pentium_4)) +void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ) { + __m128 xmm_veca, xmm_vecb, xmm_out; + + xmm_veca = _mm_load_ss(&veca[0]); + xmm_vecb = _mm_load_ss(&vecb[0]); + xmm_out = _mm_add_ss(xmm_veca,xmm_vecb); + _mm_store_ss(&out[0],xmm_out); + + xmm_veca = _mm_load_ss(&veca[1]); + xmm_vecb = _mm_load_ss(&vecb[1]); + xmm_out = _mm_add_ss(xmm_veca,xmm_vecb); + _mm_store_ss(&out[1],xmm_out); + + xmm_veca = _mm_load_ss(&veca[2]); + xmm_vecb = _mm_load_ss(&vecb[2]); + xmm_out = _mm_add_ss(xmm_veca,xmm_vecb); + _mm_store_ss(&out[2],xmm_out); +} +__declspec(cpu_specific(generic)) +//original +void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ) { + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} +#else +//original code for MSVC builds +void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ) { + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} +#endif + +void _VectorCopy( const vec3_t in, vec3_t out ) { + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void _VectorScale( const vec3_t in, vec_t scale, vec3_t out ) { + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + +void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross ) { + cross[0] = ( v1[1] * v2[2] ) - ( v1[2] * v2[1] ); + cross[1] = ( v1[2] * v2[0] ) - ( v1[0] * v2[2] ); + cross[2] = ( v1[0] * v2[1] ) - ( v1[1] * v2[0] ); +} + +vec_t VectorLength( const vec3_t v ) { + return sqrt ( ( v[0] * v[0] ) + ( v[1] * v[1] ) + ( v[2] * v[2] ) ); +} + + +vec_t VectorLengthSquared( const vec3_t v ) { + return ( ( v[0] * v[0] ) + ( v[1] * v[1] ) + ( v[2] * v[2] ) ); +} + + +vec_t Distance( const vec3_t p1, const vec3_t p2 ) { + vec3_t v; + + VectorSubtract (p2, p1, v); + return VectorLength( v ); +} + +vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 ) { + vec3_t v; + + VectorSubtract (p2, p1, v); + return ( v[0] * v[0] ) + ( v[1] * v[1] ) + ( v[2] * v[2] ); +} + + +void VectorInverse( vec3_t v ){ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out ) { + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; + out[3] = in[3]*scale; +} + + +int Q_log2( int val ) { + int answer; + + answer = 0; + while ( ( val>>=1 ) != 0 ) { + answer++; + } + return answer; +} + +/* +** NormalToLatLong +** +** Lat = 0 at (1,0,0) to 360 (-1,0,0), encoded in 8-bit sine table format +** Lng = 0 at (0,0,1) to 180 (0,0,-1), encoded in 8-bit sine table format +** +** Latitude is encoded in high 8 bits +*/ +unsigned short NormalToLatLong( const vec3_t normal ) +{ + unsigned short ll; + + // check for singularities + if ( ( normal[0] == 0 ) && ( normal[1] == 0 ) ) + { + if ( normal[2] > 0.0f ) + { + ll = 0; // lat = 0, long = 0 + } + else + { + ll = 128; // lat = 0, long = 128 + } + } + else + { + unsigned short a, b; + + a = RAD2DEG( atan2( normal[1], normal[0] ) ) * (255.0f / 360.0f ); + a &= 0xff; + + b = RAD2DEG( acos( normal[2] ) ) * ( 255.0f / 360.0f ); + b &= 0xff; + + ll = ( a << 8 ) | b; + } + + return ll; +} + + +float bias(float a, float b) +{ + return (float)( pow(a, log(b) / log(0.5)) ); +} + +float gain(float a, float b) +{ + float p = (float)( log(1. - b) / log(0.5) ); + + if ( a < .001f ) + return 0.0f; + else if ( a > .999f ) + return 1.0f; + if ( a < 0.5f ) + return (float)pow( 2.0 * a, p) / 2.0f; + else + return 1.0f - (float)pow( 2.0 * (1.0 - a), p ) / 2.0f; +} + +float noise(float vec[], int len) +{ + switch (len) { + case 0: + return 0.; + case 1: + return noise1(vec[0]); + case 2: + return noise2(vec); + default: + return noise3(vec); + } +} + +float turbulence( const float *v, float freq) +{ + float t, vec[3]; + + for ( t = 0.0f ; freq >= 1.0f ; freq /= 2.0f ) { + vec[0] = freq * v[0]; + vec[1] = freq * v[1]; + vec[2] = freq * v[2]; + t += (float)fabs(noise3(vec)) / freq; + } + return t; +} + +/* noise functions over 1, 2, and 3 dimensions */ + +#define B 0x100 +#define BM 0xff + +#define N 0x1000 +//#define NP 12 /* 2^N */ +//#define NM 0xfff + +static int p[B + B + 2]; +static float g3[B + B + 2][3]; +static float g2[B + B + 2][2]; +static float g1[B + B + 2]; +static int start = 1; + +static void init(void); + +#define s_curve(t) ( t * t * (3.0f - ( 2.0f * t ) ) ) + +#define lerp(t, a, b) ( a + ( t * (b - a) ) ) + +#define setup(i,b0,b1,r0,r1)\ + t = vec[i] + N;\ + b0 = ((int)t) & BM;\ + b1 = (b0+1) & BM;\ + r0 = t - (int)t;\ + r1 = r0 - 1.0f; + +float noise1(float arg) +{ + int bx0, bx1; + float rx0, rx1, sx, t, u, v, vec[1]; + + vec[0] = arg; + if (start) { + start = 0; + init(); + } + + setup(0, bx0,bx1, rx0,rx1); + + sx = s_curve(rx0); + + u = rx0 * g1[ p[ bx0 ] ]; + v = rx1 * g1[ p[ bx1 ] ]; + + return lerp(sx, u, v); +} + +#define at2(rx,ry) ( ( rx * q[0] ) + ( ry * q[1] ) ) + +float noise2( const float vec[2] ) +{ + int bx0, bx1, by0, by1, b00, b10, b01, b11; + float rx0, rx1, ry0, ry1, *q, sx, sy, a, b, t, u, v; + register int i, j; + + if (start) { + start = 0; + init(); + } + + setup(0, bx0,bx1, rx0,rx1); + setup(1, by0,by1, ry0,ry1); + + i = p[ bx0 ]; + j = p[ bx1 ]; + + b00 = p[ i + by0 ]; + b10 = p[ j + by0 ]; + b01 = p[ i + by1 ]; + b11 = p[ j + by1 ]; + + sx = s_curve(rx0); + sy = s_curve(ry0); + + q = g2[ b00 ] ; u = at2(rx0,ry0); + q = g2[ b10 ] ; v = at2(rx1,ry0); + a = lerp(sx, u, v); + + q = g2[ b01 ] ; u = at2(rx0,ry1); + q = g2[ b11 ] ; v = at2(rx1,ry1); + b = lerp(sx, u, v); + + return lerp(sy, a, b); +} + +#define at3(rx,ry,rz) ( ( rx * q[0] ) + ( ry * q[1] ) + ( rz * q[2] ) ) + +float noise3( const float vec[3] ) +{ + int bx0, bx1, by0, by1, bz0, bz1, b00, b10, b01, b11; + float rx0, rx1, ry0, ry1, rz0, rz1, *q, sy, sz, a, b, c, d, t, u, v; + register int i, j; + + if (start) { + start = 0; + init(); + } + + setup(0, bx0,bx1, rx0,rx1); + setup(1, by0,by1, ry0,ry1); + setup(2, bz0,bz1, rz0,rz1); + + i = p[ bx0 ]; + j = p[ bx1 ]; + + b00 = p[ i + by0 ]; + b10 = p[ j + by0 ]; + b01 = p[ i + by1 ]; + b11 = p[ j + by1 ]; + + t = s_curve(rx0); + sy = s_curve(ry0); + sz = s_curve(rz0); + + q = g3[ b00 + bz0 ] ; u = at3(rx0,ry0,rz0); + q = g3[ b10 + bz0 ] ; v = at3(rx1,ry0,rz0); + a = lerp(t, u, v); + + q = g3[ b01 + bz0 ] ; u = at3(rx0,ry1,rz0); + q = g3[ b11 + bz0 ] ; v = at3(rx1,ry1,rz0); + b = lerp(t, u, v); + + c = lerp(sy, a, b); + + q = g3[ b00 + bz1 ] ; u = at3(rx0,ry0,rz1); + q = g3[ b10 + bz1 ] ; v = at3(rx1,ry0,rz1); + a = lerp(t, u, v); + + q = g3[ b01 + bz1 ] ; u = at3(rx0,ry1,rz1); + q = g3[ b11 + bz1 ] ; v = at3(rx1,ry1,rz1); + b = lerp(t, u, v); + + d = lerp(sy, a, b); + + return lerp(sz, c, d); +} + +static void normalize2(float v[2]) +{ + float s; + + s = sqrt( ( v[0] * v[0] ) + ( v[1] * v[1] ) ); + v[0] = v[0] / s; + v[1] = v[1] / s; +} + +static void normalize3(float v[3]) +{ + float s; + + s = sqrt( ( v[0] * v[0] ) + ( v[1] * v[1] ) + ( v[2] * v[2] ) ); + v[0] = v[0] / s; + v[1] = v[1] / s; + v[2] = v[2] / s; +} + +static void init(void) +{ + int i, j, k; + + for (i = 0 ; i < B ; i++) { + p[i] = i; + + g1[i] = (float)((rand() % (B + B)) - B) / B; + + for (j = 0 ; j < 2 ; j++) + g2[i][j] = (float)((rand() % (B + B)) - B) / B; + normalize2(g2[i]); + + for (j = 0 ; j < 3 ; j++) + g3[i][j] = (float)((rand() % (B + B)) - B) / B; + normalize3(g3[i]); + } + + while (--i) { + k = p[i]; + p[i] = p[j = rand() % B]; + p[j] = k; + } + + for (i = 0 ; i < B + 2 ; i++) { + p[B + i] = p[i]; + g1[B + i] = g1[i]; + for (j = 0 ; j < 2 ; j++) + g2[B + i][j] = g2[i][j]; + for (j = 0 ; j < 3 ; j++) + g3[B + i][j] = g3[i][j]; + } +} + + +/* +================ +R_ConcatRotations +================ +*/ +void R_ConcatRotations ( const float in1[3][3], const float in2[3][3], float out[3][3] ) +{ + out[0][0] = ( in1[0][0] * in2[0][0] ) + ( in1[0][1] * in2[1][0] ) + + ( in1[0][2] * in2[2][0] ); + out[0][1] = ( in1[0][0] * in2[0][1] ) + ( in1[0][1] * in2[1][1] ) + + ( in1[0][2] * in2[2][1] ); + out[0][2] = ( in1[0][0] * in2[0][2] ) + ( in1[0][1] * in2[1][2] ) + + ( in1[0][2] * in2[2][2] ); + out[1][0] = ( in1[1][0] * in2[0][0] ) + ( in1[1][1] * in2[1][0] ) + + ( in1[1][2] * in2[2][0] ); + out[1][1] = ( in1[1][0] * in2[0][1] ) + ( in1[1][1] * in2[1][1] ) + + ( in1[1][2] * in2[2][1] ); + out[1][2] = ( in1[1][0] * in2[0][2] ) + ( in1[1][1] * in2[1][2] ) + + ( in1[1][2] * in2[2][2] ); + out[2][0] = ( in1[2][0] * in2[0][0] ) + ( in1[2][1] * in2[1][0] ) + + ( in1[2][2] * in2[2][0] ); + out[2][1] = ( in1[2][0] * in2[0][1] ) + ( in1[2][1] * in2[1][1] ) + + ( in1[2][2] * in2[2][1] ); + out[2][2] = ( in1[2][0] * in2[0][2] ) + ( in1[2][1] * in2[1][2] ) + + ( in1[2][2] * in2[2][2] ); +} + + +/* +================ +R_ConcatTransforms +================ +*/ +void R_ConcatTransforms ( const float in1[3][4], const float in2[3][4], float out[3][4] ) +{ + out[0][0] = ( in1[0][0] * in2[0][0] ) + ( in1[0][1] * in2[1][0] ) + + ( in1[0][2] * in2[2][0] ); + out[0][1] = ( in1[0][0] * in2[0][1] ) + ( in1[0][1] * in2[1][1] ) + + ( in1[0][2] * in2[2][1] ); + out[0][2] = ( in1[0][0] * in2[0][2] ) + ( in1[0][1] * in2[1][2] ) + + ( in1[0][2] * in2[2][2] ); + out[0][3] = ( in1[0][0] * in2[0][3] ) + ( in1[0][1] * in2[1][3] ) + + ( in1[0][2] * in2[2][3] ) + in1[0][3]; + out[1][0] = ( in1[1][0] * in2[0][0] ) + ( in1[1][1] * in2[1][0] ) + + ( in1[1][2] * in2[2][0] ); + out[1][1] = ( in1[1][0] * in2[0][1] ) + ( in1[1][1] * in2[1][1] ) + + ( in1[1][2] * in2[2][1] ); + out[1][2] = ( in1[1][0] * in2[0][2] ) + ( in1[1][1] * in2[1][2] ) + + ( in1[1][2] * in2[2][2] ); + out[1][3] = ( in1[1][0] * in2[0][3] ) + ( in1[1][1] * in2[1][3] ) + + ( in1[1][2] * in2[2][3] ) + in1[1][3]; + out[2][0] = ( in1[2][0] * in2[0][0] ) + ( in1[2][1] * in2[1][0] ) + + ( in1[2][2] * in2[2][0] ); + out[2][1] = ( in1[2][0] * in2[0][1] ) + ( in1[2][1] * in2[1][1] ) + + ( in1[2][2] * in2[2][1] ); + out[2][2] = ( in1[2][0] * in2[0][2] ) + ( in1[2][1] * in2[1][2] ) + + ( in1[2][2] * in2[2][2] ); + out[2][3] = ( in1[2][0] * in2[0][3] ) + ( in1[2][1] * in2[1][3] ) + + ( in1[2][2] * in2[2][3] ) + in1[2][3]; +} + + +//============================================================================ + + +float anglemod(float a) +{ +/**************************************************************************** +Squirrel : #if 0 / 1 block demoted to comment + +#if 0 + if (a >= 0) + a -= 360*(int)(a/360); + else + a += 360*( 1 + (int)(-a/360) ); +#endif + +****************************************************************************/ + + a = (360.0f / 65536.0f ) * (float)( (int)(a*(65536.0f/360.0f)) & 65535 ); + return a; +} + +float angledist( float ang ) + { + float a; + + a = anglemod( ang ); + if ( a > 180.0f ) + { + a -= 360.0f; + } + + return a; + } + +// int i; +// vec3_t corners[2]; + + +// this is the slow, general version +int BoxOnPlaneSide2 ( const vec3_t emins, const vec3_t emaxs, const struct cplane_s *p ) +{ + int i; + float dist1, dist2; + int sides; + vec3_t corners[2]; + + for (i=0 ; i<3 ; i++) + { + if (p->normal[i] < 0.0f) + { + corners[0][i] = emins[i]; + corners[1][i] = emaxs[i]; + } + else + { + corners[1][i] = emins[i]; + corners[0][i] = emaxs[i]; + } + } + dist1 = DotProduct (p->normal, corners[0]) - p->dist; + dist2 = DotProduct (p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0.0f) + sides = 1; + if (dist2 < 0.0f) + sides |= 2; + + return sides; +} + +/* +================= +CalculateRotatedBounds +================= +*/ +void CalculateRotatedBounds( const vec3_t angles, vec3_t mins, vec3_t maxs ) + { + int i; + vec3_t rotmins, rotmaxs; + float trans[3][3]; + + AnglesToAxis( angles, trans ); + ClearBounds( rotmins, rotmaxs ); + for ( i = 0; i < 8; i++ ) + { + vec3_t tmp, rottemp; + + if ( i & 1 ) + tmp[0] = mins[0]; + else + tmp[0] = maxs[0]; + + if ( i & 2 ) + tmp[1] = mins[1]; + else + tmp[1] = maxs[1]; + + if ( i & 4 ) + tmp[2] = mins[2]; + else + tmp[2] = maxs[2]; + + MatrixTransformVector( tmp, trans, rottemp ); + AddPointToBounds( rottemp, rotmins, rotmaxs ); + } + VectorCopy( rotmins, mins ); + VectorCopy( rotmaxs, maxs ); + } + +/* +================= +CalculateRotatedBounds2 +================= +*/ +void CalculateRotatedBounds2( float trans[3][3], vec3_t mins, vec3_t maxs ) + { + int i; + vec3_t rotmins, rotmaxs; + + ClearBounds( rotmins, rotmaxs ); + for ( i = 0; i < 8; i++ ) + { + vec3_t tmp, rottemp; + + if ( i & 1 ) + tmp[0] = mins[0]; + else + tmp[0] = maxs[0]; + + if ( i & 2 ) + tmp[1] = mins[1]; + else + tmp[1] = maxs[1]; + + if ( i & 4 ) + tmp[2] = mins[2]; + else + tmp[2] = maxs[2]; + + MatrixTransformVector( tmp, trans, rottemp ); + AddPointToBounds( rottemp, rotmins, rotmaxs ); + } + VectorCopy( rotmins, mins ); + VectorCopy( rotmaxs, maxs ); + } + +#define BBOX_XBITS 9 +#define BBOX_YBITS 8 +#define BBOX_ZBOTTOMBITS 5 +#define BBOX_ZTOPBITS 9 + +#define BBOX_MAX_X ( 1 << BBOX_XBITS ) +#define BBOX_MAX_Y ( 1 << BBOX_YBITS ) +#define BBOX_MAX_BOTTOM_Z ( 1 << ( BBOX_ZBOTTOMBITS - 1 ) ) +#define BBOX_REALMAX_BOTTOM_Z ( 1 << BBOX_ZBOTTOMBITS ) +#define BBOX_MAX_TOP_Z ( 1 << BBOX_ZTOPBITS ) + +/* +================= +BoundingBoxToInteger +================= +*/ +int BoundingBoxToInteger( const vec3_t mins, const vec3_t maxs ) + { + int x, y, zd, zu, result; + + x = (int)maxs[ 0 ]; + if ( x < 0 ) + x = 0; + if ( x >= BBOX_MAX_X ) + x = BBOX_MAX_X - 1; + + y = (int)maxs[ 1 ]; + if ( y < 0 ) + y = 0; + if ( y >= BBOX_MAX_Y ) + y = BBOX_MAX_Y - 1; + + zd = (int)mins[ 2 ] + BBOX_MAX_BOTTOM_Z; + if ( zd < 0 ) + { + zd = 0; + } + if ( zd >= BBOX_REALMAX_BOTTOM_Z ) + { + zd = BBOX_REALMAX_BOTTOM_Z - 1; + } + + zu = (int)maxs[ 2 ]; + if ( zu < 0 ) + zu = 0; + if ( zu >= BBOX_MAX_TOP_Z ) + zu = BBOX_MAX_TOP_Z - 1; + + result = x | + ( y << BBOX_XBITS ) | + ( zd << ( BBOX_XBITS + BBOX_YBITS ) ) | + ( zu << ( BBOX_XBITS + BBOX_YBITS + BBOX_ZBOTTOMBITS ) ); + + return result; + } + +/* +================= +IntegerToBoundingBox +================= +*/ +void IntegerToBoundingBox( int num, vec3_t mins, vec3_t maxs ) + { + int x, y, zd, zu; + + x = num & ( BBOX_MAX_X - 1 ); + y = ( num >> ( BBOX_XBITS ) ) & ( BBOX_MAX_Y -1 ); + zd = ( num >> ( BBOX_XBITS + BBOX_YBITS ) ) & ( BBOX_REALMAX_BOTTOM_Z - 1 ); + zd -= BBOX_MAX_BOTTOM_Z; + zu = ( num >> ( BBOX_XBITS + BBOX_YBITS + BBOX_ZBOTTOMBITS ) ) & ( BBOX_MAX_TOP_Z - 1 ); + + mins[ 0 ] = -x; + mins[ 1 ] = -y; + mins[ 2 ] = zd; + + maxs[ 0 ] = x; + maxs[ 1 ] = y; + maxs[ 2 ] = zu; + } + +//==================================================================== + + +void MatrixTransformVector + ( + const vec3_t in, + const float mat[ 3 ][ 3 ], + vec3_t out + ) + + { + out[ 0 ] = ( in[ 0 ] * mat[ 0 ][ 0 ] ) + ( in[ 1 ] * mat[ 1 ][ 0 ] ) + ( in[ 2 ] * mat[ 2 ][ 0 ] ); + out[ 1 ] = ( in[ 0 ] * mat[ 0 ][ 1 ] ) + ( in[ 1 ] * mat[ 1 ][ 1 ] ) + ( in[ 2 ] * mat[ 2 ][ 1 ] ); + out[ 2 ] = ( in[ 0 ] * mat[ 0 ][ 2 ] ) + ( in[ 1 ] * mat[ 1 ][ 2 ] ) + ( in[ 2 ] * mat[ 2 ][ 2 ] ); + } + +void Matrix4TransformVector + ( + const vec3_t in, + const float mat[ 4 ][ 4 ], + vec3_t out + ) + + { + out[ 0 ] = ( in[ 0 ] * mat[ 0 ][ 0 ] ) + ( in[ 1 ] * mat[ 1 ][ 0 ] ) + ( in[ 2 ] * mat[ 2 ][ 0 ] ) + mat[ 3 ][ 0 ]; + out[ 1 ] = ( in[ 0 ] * mat[ 0 ][ 1 ] ) + ( in[ 1 ] * mat[ 1 ][ 1 ] ) + ( in[ 2 ] * mat[ 2 ][ 1 ] ) + mat[ 3 ][ 1 ]; + out[ 2 ] = ( in[ 0 ] * mat[ 0 ][ 2 ] ) + ( in[ 1 ] * mat[ 1 ][ 2 ] ) + ( in[ 2 ] * mat[ 2 ][ 2 ] ) + mat[ 3 ][ 2 ]; + } + +void MatrixToEulerAngles + ( + const float mat[ 3 ][ 3 ], + vec3_t ang + ) + + { + double theta; + double cp; + double sp; + + sp = mat[ 0 ][ 2 ]; + + // cap off our sin value so that we don't get any NANs + if ( sp > 1.0 ) + { + sp = 1.0; + } + if ( sp < -1.0 ) + { + sp = -1.0; + } + + theta = -asin( sp ); + cp = cos( theta ); + + if ( cp > ( 8192.0 * FLT_EPSILON ) ) + { + ang[ 0 ] = (float)( theta * 180.0 / M_PI ); + ang[ 1 ] = (float)( atan2( mat[ 0 ][ 1 ], mat[ 0 ][ 0 ] ) * 180.0 / M_PI ); + ang[ 2 ] = (float)( atan2( mat[ 1 ][ 2 ], mat[ 2 ][ 2 ] ) * 180.0 / M_PI ); + } + else + { + ang[ 0 ] = (float)( theta * 180.0 / M_PI ); + ang[ 1 ] = (float)( -atan2( mat[ 1 ][ 0 ], mat[ 1 ][ 1 ] ) * 180.0 / M_PI ); + ang[ 2 ] = 0.0f; + } + } + +void TransposeMatrix + ( + const float in[ 3 ][ 3 ], + float out[ 3 ][ 3 ] + ) + + { + out[ 0 ][ 0 ] = in[ 0 ][ 0 ]; + out[ 0 ][ 1 ] = in[ 1 ][ 0 ]; + out[ 0 ][ 2 ] = in[ 2 ][ 0 ]; + out[ 1 ][ 0 ] = in[ 0 ][ 1 ]; + out[ 1 ][ 1 ] = in[ 1 ][ 1 ]; + out[ 1 ][ 2 ] = in[ 2 ][ 1 ]; + out[ 2 ][ 0 ] = in[ 0 ][ 2 ]; + out[ 2 ][ 1 ] = in[ 1 ][ 2 ]; + out[ 2 ][ 2 ] = in[ 2 ][ 2 ]; + } + +void OrthoNormalize + ( + float mat[3][3] + ) + + { + VectorNormalize( mat[ 0 ] ); + CrossProduct( mat[ 0 ], mat[ 1 ], mat[ 2 ] ); + VectorNormalize( mat[ 2 ] ); + CrossProduct( mat[ 2 ], mat[ 0 ], mat[ 1 ] ); + VectorNormalize( mat[ 1 ] ); + } + +float NormalizeQuat + ( + float q[ 4 ] + ) + + { + float length, ilength; + + length = ( q[ 0 ] * q[ 0 ] ) + ( q[ 1 ] * q[ 1 ] ) + ( q[ 2 ] * q[ 2 ] ) + ( q[ 3 ] * q[ 3 ] ); + length = (float)sqrt( length ); + + if ( length ) + { + ilength = 1.0f / length; + q[ 0 ] *= ilength; + q[ 1 ] *= ilength; + q[ 2 ] *= ilength; + q[ 3 ] *= ilength; + } + + return length; + } + +void MatToQuat + ( + const float srcMatrix[ 3 ][ 3 ], + float destQuat[ 4 ] + ) + + { + double trace, s; + int i, j, k; + static int next[3] = {Y, Z, X}; + + trace = srcMatrix[X][X] + srcMatrix[Y][Y]+ srcMatrix[Z][Z]; + + if (trace > 0.0) + { + s = sqrt(trace + 1.0); + destQuat[W] = (float)( s * 0.5 ); + s = 0.5 / s; + + destQuat[X] = (float)( (srcMatrix[Z][Y] - srcMatrix[Y][Z]) * s ); + destQuat[Y] = (float)( (srcMatrix[X][Z] - srcMatrix[Z][X]) * s ); + destQuat[Z] = (float)( (srcMatrix[Y][X] - srcMatrix[X][Y]) * s ); + } + else + { + i = X; + if (srcMatrix[Y][Y] > srcMatrix[X][X]) + i = Y; + if (srcMatrix[Z][Z] > srcMatrix[i][i]) + i = Z; + j = next[i]; + k = next[j]; + + s = sqrt( (srcMatrix[i][i] - (srcMatrix[j][j]+srcMatrix[k][k])) + 1.0 ); + destQuat[i] = (float)( s * 0.5 ); + + s = 0.5 / s; + + destQuat[W] = (srcMatrix[k][j] - srcMatrix[j][k]) * s; + destQuat[j] = (srcMatrix[j][i] + srcMatrix[i][j]) * s; + destQuat[k] = (srcMatrix[k][i] + srcMatrix[i][k]) * s; + } + } + +void RotateAxis + ( + const float axis[ 3 ], + float angle, + float q[ 4 ] + ) + + { + float sin_a; + float inv_sin_a; + float cos_a; + float r; + + r = angle * M_PI / 360.0f; + + sin_a = sin( r ); + if ( fabs( sin_a ) > 0.00000001 ) + { + inv_sin_a = 1.0f / sin_a; + } + else + { + inv_sin_a = 0.0f; + } + cos_a = (float)cos( r ); + + q[ X ] = axis[ 0 ] * inv_sin_a; + q[ Y ] = axis[ 1 ] * inv_sin_a; + q[ Z ] = axis[ 2 ] * inv_sin_a; + q[ W ] = cos_a; + } + +void MultQuat + ( + const float q1[ 4 ], + const float q2[ 4 ], + float out[ 4 ] + ) + + { + out[ 0 ] = ( q1[X] * q2[X] ) - ( q1[Y] * q2[Y] ) - ( q1[Z] * q2[Z] ) - ( q1[W] * q2[W] ); + out[ 1 ] = ( q1[X] * q2[Y] ) + ( q1[Y] * q2[X] ) + ( q1[Z] * q2[W] ) - ( q1[W] * q2[Z] ); + out[ 2 ] = ( q1[X] * q2[Z] ) - ( q1[Y] * q2[W] ) + ( q1[Z] * q2[X] ) + ( q1[W] * q2[Y] ); + out[ 3 ] = ( q1[X] * q2[W] ) + ( q1[Y] * q2[Z] ) - ( q1[Z] * q2[Y] ) + ( q1[W] * q2[X] ); + } + +void QuatToMat + ( + const float q[ 4 ], + float m[ 3 ][ 3 ] + ) + + { + float wx, wy, wz; + float xx, yy, yz; + float xy, xz, zz; + float x2, y2, z2; + + x2 = q[ X ] + q[ X ]; + y2 = q[ Y ] + q[ Y ]; + z2 = q[ Z ] + q[ Z ]; + + xx = q[ X ] * x2; + xy = q[ X ] * y2; + xz = q[ X ] * z2; + + yy = q[ Y ] * y2; + yz = q[ Y ] * z2; + zz = q[ Z ] * z2; + + wx = q[ W ] * x2; + wy = q[ W ] * y2; + wz = q[ W ] * z2; + + m[ 0 ][ 0 ] = 1.0f - ( yy + zz ); + m[ 0 ][ 1 ] = xy - wz; + m[ 0 ][ 2 ] = xz + wy; + + m[ 1 ][ 0 ] = xy + wz; + m[ 1 ][ 1 ] = 1.0f - ( xx + zz ); + m[ 1 ][ 2 ] = yz - wx; + + m[ 2 ][ 0 ] = xz - wy; + m[ 2 ][ 1 ] = yz + wx; + m[ 2 ][ 2 ] = 1.0f - ( xx + yy ); + } + +#define DELTA 1e-6 + +void SlerpQuaternion + ( + const float from[ 4 ], + const float to[ 4 ], + float t, + float res[ 4 ] + ) + + { + float to1[ 4 ]; + double omega, cosom, sinom, scale0, scale1; + + cosom = ( from[ X ] * to[ X ] ) + ( from[ Y ] * to[ Y ] ) + ( from[ Z ] * to[ Z ] ) + ( from[ W ] * to [ W ] ); + if ( cosom < 0.0 ) + { + cosom = -cosom; + to1[ X ] = -to[ X ]; + to1[ Y ] = -to[ Y ]; + to1[ Z ] = -to[ Z ]; + to1[ W ] = -to[ W ]; + } + else if + ( + ( from[ X ] == to[ X ] ) && + ( from[ Y ] == to[ Y ] ) && + ( from[ Z ] == to[ Z ] ) && + ( from[ W ] == to[ W ] ) + ) + { + // equal case, early exit + res[ X ] = to[ X ]; + res[ Y ] = to[ Y ]; + res[ Z ] = to[ Z ]; + res[ W ] = to[ W ]; + return; + } + else + { + to1[ X ] = to[ X ]; + to1[ Y ] = to[ Y ]; + to1[ Z ] = to[ Z ]; + to1[ W ] = to[ W ]; + } + + if ( ( 1.0 - cosom ) > DELTA ) + { + omega = acos( cosom ); + sinom = sin( omega ); + scale0 = sin( ( 1.0 - t ) * omega ) / sinom; + scale1 = sin( t * omega ) / sinom; + } + else + { + scale0 = 1.0 - t; + scale1 = t; + } + + res[ X ] = scale0 * from[ X ] + scale1 * to1[ X ]; + res[ Y ] = scale0 * from[ Y ] + scale1 * to1[ Y ]; + res[ Z ] = scale0 * from[ Z ] + scale1 * to1[ Z ]; + res[ W ] = scale0 * from[ W ] + scale1 * to1[ W ]; + } + +void EulerToQuat + ( + float ang[ 3 ], + float q[ 4 ] + ) + + { + float mat[ 3 ][ 3 ]; + int *i; + + i = ( int * )ang; + if ( !i[ 0 ] && !i[ 1 ] && !i[ 2 ] ) + { + q[ 0 ] = 0; + q[ 1 ] = 0; + q[ 2 ] = 0; + q[ 3 ] = 1.0f; + } + else + { + AnglesToAxis( ang, mat ); + MatToQuat( mat, q ); + } + } + +/* +===================== +PlaneFromPoints + +Returns false if the triangle is degenrate. +The normal will point out of the clock for clockwise ordered points +===================== +*/ +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ) { + vec3_t d1, d2; + + VectorSubtract( b, a, d1 ); + VectorSubtract( c, a, d2 ); + CrossProduct( d2, d1, plane ); + if ( VectorNormalize( plane ) == 0 ) { + return qfalse; + } + + plane[3] = DotProduct( a, plane ); + return qtrue; +} + +/* +================= +PlaneTypeForNormal +================= +*/ +int PlaneTypeForNormal ( const vec3_t normal ) { + if ( normal[0] == 1.0f ) + return PLANE_X; + if ( normal[1] == 1.0f ) + return PLANE_Y; + if ( normal[2] == 1.0f ) + return PLANE_Z; + + return PLANE_NON_AXIAL; +} + + diff --git a/dlls/game/q_mathsys.c b/dlls/game/q_mathsys.c new file mode 100644 index 0000000..3ded8b9 --- /dev/null +++ b/dlls/game/q_mathsys.c @@ -0,0 +1,163 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/q_mathsys.c $ +// $Revision:: 6 $ +// $Author:: Steven $ +// $Date:: 10/10/02 1:13p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: + +#include "q_shared.h" +#include "float.h" + +// these routines will be handled as system calls +// for interpreted code to speed things up + +//#define X 0 +//#define Y 1 +//#define Z 2 +//#define W 3 +//#define QUAT_EPSILON 0.00001 + +/* +================ +MatrixMultiply +================ +*/ +void MatrixMultiply( const float in1[3][3], const float in2[3][3], float out[3][3]) { + out[0][0] = ( in1[0][0] * in2[0][0] ) + ( in1[0][1] * in2[1][0] ) + + ( in1[0][2] * in2[2][0] ); + out[0][1] = ( in1[0][0] * in2[0][1] ) + ( in1[0][1] * in2[1][1] ) + + ( in1[0][2] * in2[2][1] ); + out[0][2] = ( in1[0][0] * in2[0][2] ) + ( in1[0][1] * in2[1][2] ) + + ( in1[0][2] * in2[2][2] ); + out[1][0] = ( in1[1][0] * in2[0][0] ) + ( in1[1][1] * in2[1][0] ) + + ( in1[1][2] * in2[2][0] ); + out[1][1] = ( in1[1][0] * in2[0][1] ) + ( in1[1][1] * in2[1][1] ) + + ( in1[1][2] * in2[2][1] ); + out[1][2] = ( in1[1][0] * in2[0][2] ) + ( in1[1][1] * in2[1][2] ) + + ( in1[1][2] * in2[2][2] ); + out[2][0] = ( in1[2][0] * in2[0][0] ) + ( in1[2][1] * in2[1][0] ) + + ( in1[2][2] * in2[2][0] ); + out[2][1] = ( in1[2][0] * in2[0][1] ) + ( in1[2][1] * in2[1][1] ) + + ( in1[2][2] * in2[2][1] ); + out[2][2] = ( in1[2][0] * in2[0][2] ) + ( in1[2][1] * in2[1][2] ) + + ( in1[2][2] * in2[2][2] ); +} + + +void AnglesToAxis( const vec3_t angles, vec3_t axis[3] ) { + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + angle = angles[YAW] * ( M_PI * 2.0f / 360.0f ); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * ( M_PI * 2.0f / 360.0f ); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * ( M_PI * 2.0f / 360.0f ); + sr = sin(angle); + cr = cos(angle); + + axis[0][0] = cp * cy; + axis[0][1] = cp * sy; + axis[0][2] = -sp; + + axis[1][0] = ( ( sr * sp * cy ) + ( cr * -sy ) ); + axis[1][1] = ( ( sr * sp * sy ) + ( cr * cy ) ); + axis[1][2] = sr * cp; + + axis[2][0] = ( ( cr * sp * cy ) + ( -sr * -sy ) ); + axis[2][1] = ( ( cr * sp * sy ) + ( -sr * cy ) ); + axis[2][2] = cr * cp; +} + +void AxisToAngles( vec3_t axis[3], vec3_t angles ) { + MatrixToEulerAngles( axis, angles ); + //vectoangles(axis[0], angles); +} + +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up) { + float angle; + static float sr, sp, sy, cr, cp, cy; + // static to help MS compiler fp bugs + + angle = angles[YAW] * ( M_PI * 2.0f / 360.0f ); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * ( M_PI * 2.0f / 360.0f ); + sp = sin(angle); + cp = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + + if ( left || up ) + { + angle = angles[ROLL] * ( M_PI * 2.0f / 360.0f ); + sr = sin(angle); + cr = cos(angle); + + if (left) + { + left[0] = ( ( sr * sp * cy ) + ( cr * -sy ) ); + left[1] = ( ( sr * sp * sy ) + ( cr * cy ) ); + left[2] = sr * cp; + } + if (up) + { + up[0] = ( ( cr * sp * cy ) + ( -sr * -sy ) ); + up[1] = ( ( cr * sp * sy ) + ( -sr * cy ) ); + up[2] = cr * cp; + } + } +} + +/* +** assumes "src" is normalized +*/ +void PerpendicularVector( vec3_t dst, const vec3_t src ) +{ + int pos; + int i; + float minelem = 1.0F; + vec3_t tempvec; + + /* + ** find the smallest magnitude axially aligned vector + */ + for ( pos = 0, i = 0; i < 3; i++ ) + { + if ( fabs( src[i] ) < minelem ) + { + pos = i; + minelem = fabs( src[i] ); + } + } + tempvec[0] = tempvec[1] = tempvec[2] = 0.0F; + tempvec[pos] = 1.0F; + + /* + ** project the point onto the plane defined by src + */ + ProjectPointOnPlane( dst, tempvec, src ); + + /* + ** normalize the result + */ + VectorNormalize( dst ); +} + diff --git a/dlls/game/q_shared.c b/dlls/game/q_shared.c new file mode 100644 index 0000000..0a8ef40 --- /dev/null +++ b/dlls/game/q_shared.c @@ -0,0 +1,1508 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/q_shared.c $ +// $Revision:: 36 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// A mix of functions used by every part of the code + +#include "q_shared.h" + +#ifdef _WIN32 +#pragma optimize( "", off ) +#endif + +static char musicmoods[ mood_totalnumber ][ 16 ] = +{ + "none", + "normal", + "action", + "suspense", + "mystery", + "success", + "failure", + "surprise", + "special", + "aux1", + "aux2", + "aux3", + "aux4", + "aux5", + "aux6", + "aux7" +}; + +static char eaxmodes[ eax_totalnumber ][ 16 ] = +{ + "generic", + "paddedcell", + "room", + "bathroom", + "livingroom", + "stoneroom", + "auditorium", + "concerthall", + "cave", + "arena", + "hangar", + "carpetedhallway", + "hallway", + "stonecorridor", + "alley", + "forest", + "city", + "mountains", + "quarry", + "plain", + "parkinglot", + "sewerpipe", + "underwater", + "drugged", + "dizzy", + "psychotic" +}; + +/* +================= +MusicMood_NameToNum +================= +*/ +int MusicMood_NameToNum( const char * name ) +{ + int i; + + if ( !name ) + return -1; + + for ( i = 0; i < mood_totalnumber; i++ ) + { + if ( !strcmpi( name, musicmoods[ i ] ) ) + { + return i; + } + } + return -1; +} + +/* +================= +MusicMood_NumToName +================= +*/ +const char * MusicMood_NumToName( int num ) +{ + if ( ( num < 0 ) || ( num >= mood_totalnumber ) ) + return ""; + else + return musicmoods[ num ]; +} + +/* +================= +EAXMode_NameToNum +================= +*/ +int EAXMode_NameToNum( const char * name ) +{ + int i; + + if ( !name ) + return -1; + + for ( i = 0; i < eax_totalnumber; i++ ) + { + if ( !strcmpi( name, eaxmodes[ i ] ) ) + { + return i; + } + } + return -1; +} + +/* +================= +EAXMode_NumToName +================= +*/ +const char * EAXMode_NumToName( int num ) +{ + if ( ( num < 0 ) || ( num >= eax_totalnumber ) ) + return ""; + else + return eaxmodes[ num ]; +} + +static char playerStatNames[ STAT_LAST_STAT ][ 32 ] = +{ + "health", + "dead_yaw", + "ammo_left", + "clipammo_left", + "num_shots_left", + "max_num_shots_left", + "ammo_right", + "clipammo_right", + "num_shots_right", + "armor_level", + "maxammo_left", + "maxammo_right", + "maxclipammo_left", + "maxclipammo_right", + "ammo_type1", + "ammo_type2", + "ammo_type3", + "ammo_type4", + "last_pain", + "accumulated_pain", + "bosshealth", + "bossnameIndex", + "cinematic", + "addfade", + "letterbox", + "poweruptime", + + "weapon_generic1", + "weapon_generic2", + + "itemicon", + "itemtext", + + "votetext", + + "enemies_killed", + "teammates_killed", + "shots_fired", + "shots_hit", + "accuracy", + "mission_duration", + + "generic", + + "num_objectives", + "complete_objectives", + "failed_objectives", + "incomplete_objectives", + + "specialmovetimer", + "points", + + "secretstotal", + "secretsfound", + "itemstotal", + "itemsfound", + + "redTeamScore", + "blueTeamScore", + //"arena", + "team", + //"queue_place", + "score", + "kills", + "deaths", +// "timeleft_minutes", + "timeleft_seconds", + "won_matches", + "lost_matches", + + "mp_generic1", + "mp_generic2", + "mp_generic3", + "mp_generic4", + "mp_generic5", + "mp_generic6", + "mp_generic7", + "mp_generic8", + + "mp_spectating_entnum", + + "mp_mode_icon", + "mp_team_icon", + "mp_teamhud_icon", + "mp_otherteam_icon", + "mp_specialty_icon", + "mp_holdableitem_icon", + "mp_rune_icon", + "mp_powerup_icon", + + "mp_award_icon", + "mp_award_count", + + "mp_state" +}; + + +//---------------------------------------------------------------- +// Name: GenerateHashForName (Squirrel) +// Class: +// +// Description: Generates an unsigned 32-bit integer hash +// value for a given char* string. The resulting +// hash value is guaranteed to be in the range +// of [0,maxHash). If no maximum hash value is +// desired, pass 0 for ; this allows +// to be any 32-bit unsigned int value. +// Parameters: +// const char* name The string for which a hash will be generated +// qboolean caseSensitive If false, hash is generated from all lower-case +// unsigned int maxHash The non-inclusive maximum hash value desired +// +// Returns: unsigned int 32-bit unsigned hash value for +//---------------------------------------------------------------- +unsigned int GenerateHashForName( const char* name, qboolean caseSensitive, unsigned int maxHash ) +{ + const char* scan; + unsigned int hash = 0; + unsigned int ch; + + /// Add each individual character to the hash + for( scan = name; *scan; scan ++ ) + { + /// Get character (or its lowercase version, if we're case-insensitive) + ch = *scan; + if( !caseSensitive ) + ch = tolower( ch ); + + /// Multiply the existing hash by a magic number and add the new character + hash &= 0x07ffffff; // clear the top 5 bits to make room for multiply + hash *= 31; + hash += ch; + } + + /// Mod-clamp the hash to the range [0,maxHash) IF was specified + if( maxHash ) + hash %= maxHash; + + return( hash ); +} + + +//---------------------------------------------------------------- +// Name: PlayerStat_NameToNum +// Class: +// +// Description: Converts a player stat name to the corresponding index +// +// Parameters: const char *name - player stat name +// +// Returns: int - index of the player stat +//---------------------------------------------------------------- + +int PlayerStat_NameToNum( const char *name ) +{ + int i; + + if ( !name ) + return -1; + + for ( i = 0 ; i < STAT_LAST_STAT ; i++ ) + { + if ( strcmpi( name, playerStatNames[ i ] ) == 0 ) + { + return i; + } + } + + return -1; +} + +//---------------------------------------------------------------- +// Name: PlayerStat_NumToName +// Class: +// +// Description: Converts a player stat index to the corresponding name +// +// Parameters: int - player stat index +// +// Returns: const char * - name of the player stat +//---------------------------------------------------------------- + +const char *PlayerStat_NumToName( int num ) +{ + if ( ( num < 0 ) || ( num >= STAT_LAST_STAT ) ) + return ""; + else + return playerStatNames[ num ]; +} + +//==================================================================================== + +/* +============ +COM_SkipPath +============ +*/ +const char *COM_SkipPath (const char *pathname) +{ + const char *last; + + last = pathname; + while (*pathname) + { + if (*pathname=='/') + last = pathname+1; + pathname++; + } + return last; +} + +/* +============ +COM_ParseHex +============ +*/ +int COM_ParseHex (const char *hex) +{ + const char *str; + int num; + + num = 0; + str = hex; + + while (*str) + { + num <<= 4; + if ( ( *str >= '0' ) && ( *str <= '9' ) ) + num += *str - '0'; + else if ( ( *str >= 'a' ) && ( *str <= 'f' ) ) + num += 10 + *str - 'a'; + else if ( ( *str >= 'A' ) && ( *str <= 'F' ) ) + num += 10 + *str - 'A'; + else + Com_WPrintf("Bad hex number: %s",hex); + str++; + } + + return num; +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension (const char *in, char *out) + { + while ( *in && ( *in != '.' ) ) + *out++ = *in++; + *out = 0; + } + +/* +============ +COM_FileExtension +============ +*/ +const char *COM_FileExtension (const char *in) +{ + static char exten[8]; + int i; + + while ( *in && ( *in != '.' ) ) + in++; + if (!*in) + return ""; + in++; + for (i=0 ; i<7 && *in ; i++,in++) + exten[i] = *in; + exten[i] = 0; + return exten; +} + +/* +============ +COM_FileBase +============ +*/ +void COM_FileBase (const char *in, char *out) +{ + const char *s; + const char *s2; + + s = in + strlen(in) - 1; + + while ( ( s != in ) && ( *s != '.' ) ) + s--; + + for ( s2 = s ; ( s2 != in ) && ( *s2 != '/' ) ; s2-- ) + ; + + if (s-s2 < 2) + out[0] = 0; + else + { + s--; + strncpy (out,s2+1, s-s2); + out[s-s2] = 0; + } +} + +/* +============ +COM_FileName + +Returns the filename, without being picky like COM_FileBase +============ +*/ +void COM_FileName (const char *in, char *out) +{ + const char *start, *end, *s; + + start = NULL; + end = NULL; + + for (s = in; *s; s++) + { + if(*s == '.') + { + end = s; + } + else if((*s == '/') || (*s == '\\') || (*s == ':')) + { + start = s+1; + } + } + + if(end == NULL) + end = s; + + if(start == NULL) + start = in; + + for (s=start; s>8)&255; + + return (b1<<8) + b2; +} + +short ShortNoSwap (short l) +{ + return l; +} + + +unsigned short UnsignedShortSwap (unsigned short l) +{ + byte b1,b2; + + b1 = l&255; + b2 = (l>>8)&255; + + return (b1<<8) + b2; +} + +unsigned short UnsignedShortNoSwap (unsigned short l) +{ + return l; +} + + +int LongSwap (int l) +{ + byte b1,b2,b3,b4; + + b1 = l&255; + b2 = (l>>8)&255; + b3 = (l>>16)&255; + b4 = (l>>24)&255; + + return ((int)b1<<24) + ((int)b2<<16) + ((int)b3<<8) + b4; +} + +int LongNoSwap (int l) +{ + return l; +} + +float FloatSwap (float f) +{ + union + { + float f; + byte b[4]; + } dat1, dat2; + + + dat1.f = f; + dat2.b[0] = dat1.b[3]; + dat2.b[1] = dat1.b[2]; + dat2.b[2] = dat1.b[1]; + dat2.b[3] = dat1.b[0]; + return dat2.f; +} + +float FloatNoSwap (float f) +{ + return f; +} + +/* +================ +Swap_Init +================ +*/ +void Swap_Init (void) +{ + byte swaptest[2] = {1,0}; + + // set the byte swapping variables in a portable manner + if ( *(short *)swaptest == 1) + { + bigendian = qfalse; + _BigShort = ShortSwap; + _LittleShort = ShortNoSwap; + _BigUnsignedShort = UnsignedShortSwap; + _LittleUnsignedShort = UnsignedShortNoSwap; + _BigLong = LongSwap; + _LittleLong = LongNoSwap; + _BigFloat = FloatSwap; + _LittleFloat = FloatNoSwap; + } + else + { + bigendian = qtrue; + _BigShort = ShortNoSwap; + _LittleShort = ShortSwap; + _BigUnsignedShort = UnsignedShortNoSwap; + _LittleUnsignedShort = UnsignedShortSwap; + _BigLong = LongNoSwap; + _LittleLong = LongSwap; + _BigFloat = FloatNoSwap; + _LittleFloat = FloatSwap; + } +} + + + +/* +============ +va + +does a varargs printf into a temp buffer, so I don't need to have +varargs versions of all text functions. +FIXME: make this buffer size safe someday +============ +*/ +const char *va( const char *format, ... ) +{ + va_list argptr; + static char string[2][16384]; // in case va is called by nested functions + static int index = 0; + char *buf; + + buf = string[index & 1]; + index++; + + va_start (argptr, format); + vsprintf (buf, format,argptr); + va_end (argptr); + + return buf; +} + + +char com_token[MAX_STRING_CHARS]; + +/* +============== +COM_GetToken + +Parse a token out of a string +============== +*/ +const char *COM_GetToken(const char **data_p, qboolean crossline) +{ + unsigned int c; + int len; + const unsigned char *data; + + data = (const unsigned char*)*data_p; + len = 0; + com_token[0] = 0; + + if (!data) + { + *data_p = NULL; + return ""; + } + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == '\n' && !crossline) + { + *data_p = (const char*) data; + return ""; + } + if ( !c ) + { + *data_p = NULL; + return ""; + } + data++; + } + +// skip // comments + if ( ( c == '/' ) && ( data[1] == '/' ) ) + { + while ( *data && ( *data != '\n' ) ) + data++; + goto skipwhite; + } + +// skip /* comments + if ( ( c == '/' ) && ( data[1] == '*' ) ) + { + data++; + while (*data) + { + if ( (*(data-1)=='*') && (*data == '/') ) + break; + data++; + } + while ( *data && ( *data != '\n' ) ) + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if ( ( c == '\\' ) && ( *data == '\"' ) ) + { + if (len < MAX_STRING_CHARS) + { + com_token[len] = '\"'; + len++; + } + data++; + } + else if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = (const char*)data; + return com_token; + } + else if (len < MAX_STRING_CHARS) + { + if ( ( c == '\\' ) && ( *data == 'n' ) ) + { + com_token[len] = '\n'; + data++; + } + else + { + com_token[len] = c; + } + len++; +// com_token[len] = c; +// len++; + } + } + } + +// parse a regular word + do + { + if (len < MAX_STRING_CHARS) + { + com_token[len] = c; + len++; + } + data++; + c = *data; + } while (c>32); + + if (len == MAX_STRING_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_STRING_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = (const char*)data; + return com_token; +} + + +static int com_lines = 0; + +/* +================= +SkipRestOfLine +================= +*/ +void SkipRestOfLine ( char **data ) { + char *p; + int c; + + p = *data; + while ( (c = *p++) != 0 ) { + if ( c == '\n' ) { + com_lines++; + break; + } + } + + *data = p; +} + +/* +================= +SkipBracedSection + +The next token should be an open brace. +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +qboolean SkipBracedSection (char **program) { + char *token; + int depth; + + depth = 0; + do { + token = COM_ParseExt( program, qtrue ); + if( token[1] == 0 ) { + if( token[0] == '{' ) { + depth++; + } + else if( token[0] == '}' ) { + depth--; + } + } + } while( depth && *program ); + + return !depth; +} + +static char *SkipWhitespace( char *data, qboolean *hasNewLines ) +{ + int c; + + while ( (c = *data) <= ' ') + { + if ( !c ) + { + return NULL; + } + if ( c == '\n' ) + { + com_lines++; + *hasNewLines = qtrue; + } + data++; + } + + return data; +} + +char *COM_ParseExt( char **data_p, qboolean allowLineBreaks ) +{ + int c = 0, len; + qboolean hasNewLines = qfalse; + char *data; + + data = *data_p; + len = 0; + com_token[0] = 0; + + // make sure incoming data is valid + if ( !data ) + { + *data_p = NULL; + return com_token; + } + + while ( 1 ) + { + // skip whitespace + data = SkipWhitespace( data, &hasNewLines ); + if ( !data ) + { + *data_p = NULL; + return com_token; + } + if ( hasNewLines && !allowLineBreaks ) + { + *data_p = data; + return com_token; + } + + c = *data; + + // skip double slash comments + if ( ( c == '/' ) && ( data[1] == '/' ) ) + { + while ( *data && ( *data != '\n' ) ) + data++; + } + // skip /* */ comments + else if ( ( c == '/' ) && ( data[1] == '*' ) ) + { + while ( *data && ( ( *data != '*' ) || ( data[1] != '/' ) ) ) + { + data++; + } + if ( *data ) + { + data += 2; + } + } + else + { + break; + } + } + + // handle quoted strings + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + *data_p = ( char * ) data; + return com_token; + } + if (len < MAX_TOKEN_CHARS) + { + com_token[len] = c; + len++; + } + } + } + + // parse a regular word + do + { + if ( ( len > 0 ) && ( ( c == '{' ) || ( c == '}' ) ) ) + { + break; + } + + data++; + if (len < MAX_TOKEN_CHARS) + { + // handle '\n' correctly + if ( ( c == '\\' ) && ( *data == 'n' ) ) + { + com_token[len] = '\n'; + data++; + } + else + { + com_token[len] = c; + } + len++; + + if ( ( len == 1 ) && ( ( c == '{' ) || ( c == '}' ) ) ) + { + break; + } + + } + c = *data; + if ( c == '\n' ) + com_lines++; + } while (c>32); + + if (len == MAX_TOKEN_CHARS) + { +// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS); + len = 0; + } + com_token[len] = 0; + + *data_p = ( char * ) data; + return com_token; +} + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +int COM_GetParseLineNumber( void ) +{ + return com_lines; +} + + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +const char *COM_Parse (const char **data_p) +{ + return COM_GetToken( data_p, 1 ); +} + +/* +=============== +Com_PageInMemory + +=============== +*/ +int paged_total; + +void Com_PageInMemory ( const byte *buffer, int size ) +{ + int i; + + for (i=size-1 ; i>0 ; i-=4096) + paged_total += buffer[i]; +} + + + +/* +============================================================================ + + LIBRARY REPLACEMENT FUNCTIONS + +============================================================================ +*/ +// never goes past bounds or leaves without a terminating 0 +void Q_strcat( char *dest, int size, const char *src ) { + int l1; + + l1 = strlen( dest ); + if ( l1 >= size ) { + Com_Error( ERR_FATAL, "Q_strcat: already overflowed" ); + } + strncpy( dest + l1, src, size - 1 - l1 ); + dest[ size - 1 ] = 0; +} + + +char *Q_strlwr( char *s1 ) { + char *s; + + s = s1; + while ( *s ) { + *s = tolower(*s); + s++; + } + return s1; +} + +void Q_strncpyz( char *dest, const char *src, int destsize ) { + if ( !src ) { + Com_Error( ERR_FATAL, "Q_strncpyz: NULL src" ); + } + if ( destsize < 1 ) { + Com_Error( ERR_FATAL,"Q_strncpyz: destsize < 1" ); + } + + strncpy( dest, src, destsize-1 ); + dest[destsize-1] = 0; +} + +int Q_stricmpn (const char *s1, const char *s2, int n) { + int c1, c2; + + do { + c1 = *s1++; + c2 = *s2++; + + if (!n--) { + return 0; // strings are equal until end point + } + + if (c1 != c2) { + if ( ( c1 >= 'a' ) && ( c1 <= 'z' ) ) { + c1 -= ('a' - 'A'); + } + if ( ( c2 >= 'a' ) && ( c2 <= 'z' ) ) { + c2 -= ('a' - 'A'); + } + if (c1 < c2) { + return -1; // strings less than + } + else if ( c1 > c2 ) { + return 1; // strings greater than + } + } + } while (c1); + + return 0; // strings are equal +} + +int Q_stricmp (const char *s1, const char *s2) { + return Q_stricmpn (s1, s2, 99999); +} + +void Com_sprintf (char *dest, int size, const char *fmt, ...) +{ + char bigbuffer[0x10000]; + int len; + va_list argptr; + + va_start (argptr,fmt); + len = vsprintf (bigbuffer,fmt,argptr); + va_end (argptr); + if (len >= size) + Com_WPrintf ("Com_sprintf: overflow of %i in %i\n", len, size); + strncpy (dest, bigbuffer, size-1); +} + +void Com_BackslashToSlash( char *str ) + { + int i; + int len; + char *t; + + if ( str ) + { + t = str; + len = strlen( str ); + + for( i = 0; i < len; i++ ) + { + if ( t[ i ] == '\\' ) + { + t[ i ] = '/'; + } + } + } + } + +char *Q_CleanStr( char *string ) { + char* d; + char* s; + int c; + + s = string; + d = string; + while ((c = *s) != 0 ) { + if ( Q_IsColorString( s ) ) { + s++; + } + else if ( ( c >= 0x20 ) && ( c <= 0x7E ) ) { + *d++ = c; + } + s++; + } + *d = '\0'; + + return string; +} + +/* +===================================================================== + + INFO STRINGS + +===================================================================== +*/ + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +=============== +*/ +const char *Info_ValueForKey (const char *s, const char *key) +{ + char pkey[512]; + static char value[2][512]; // use two buffers so compares + // work without stomping on each other + static int valueindex = 0; + char *o; + + if ( !s || !key ) { + return ""; + } + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_ValueForKey: oversize infostring" ); + } + + valueindex ^= 1; + if (*s == '\\') + s++; + while (1) + { + o = pkey; + while (*s != '\\') + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + s++; + + o = value[valueindex]; + + while (*s != '\\' && *s) + { + if (!*s) + return ""; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + return value[valueindex]; + + if (!*s) + return ""; + s++; + } +} + +void Info_RemoveKey (char *s, const char *key) +{ + char *start; + char pkey[512]; + char value[512]; + char *o; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_RemoveKey: oversize infostring" ); + } + + if (strstr (key, "\\")) + { + Com_WPrintf ("Can't use a key with a \\\n"); + return; + } + + while (1) + { + start = s; + if (*s == '\\') + s++; + o = pkey; + while (*s != '\\') + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + if (!strcmp (key, pkey) ) + { + strcpy (start, s); // remove this part + return; + } + + if (!*s) + return; + } + +} + +/* +================== +Info_Validate + +Some characters are illegal in info strings because they +can mess up the server's parsing +================== +*/ +qboolean Info_Validate (const char *s) +{ + if (strstr (s, "\"")) + return qfalse; + if (strstr (s, ";")) + return qfalse; + return qtrue; +} + +void Info_SetValueForKey( char *s, const char *key, const char *value ) { + char newi[MAX_INFO_STRING]; + + if ( strlen( s ) >= MAX_INFO_STRING ) { + Com_Error( ERR_DROP, "Info_SetValueForKey: oversize infostring" ); + } + + if (strchr (key, '\\') || strchr (value, '\\')) + { + Com_WPrintf ("Can't use keys or values with a \\\n"); + return; + } + + if (strchr (key, ';') || strchr (value, ';')) + { + Com_WPrintf ("Can't use keys or values with a semicolon\n"); + return; + } + + if (strchr (key, '\"') || strchr (value, '\"')) + { + Com_WPrintf ("Can't use keys or values with a \"\n"); + return; + } + + Info_RemoveKey (s, key); + if (!value || !strlen(value)) + return; + + Com_sprintf (newi, sizeof(newi), "\\%s\\%s", key, value); + + if (strlen(newi) + strlen(s) > MAX_INFO_STRING) + { + Com_WPrintf ("Info string length exceeded\n"); + return; + } + + strcat (s, newi); +} + +/* +=================== +Info_NextPair + +Used to itterate through all the key/value pairs in an info string +=================== +*/ +void Info_NextPair( const char **head, char key[MAX_INFO_KEY], char value[MAX_INFO_VALUE] ) { + char *o; + const char *s; + + s = *head; + + if ( *s == '\\' ) { + s++; + } + key[0] = 0; + value[0] = 0; + + o = key; + while ( *s != '\\' ) { + if ( !*s ) { + *o = 0; + *head = s; + return; + } + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + while ( *s != '\\' && *s ) { + *o++ = *s++; + } + *o = 0; + + *head = s; +} + +//---------------------------------------------------------------- +// Name: ParseMapName +// Class: +// +// Description: Parses the full mapname to get the mapname, spawn position, and movie name +// +// Parameters: const char *fullname - original full mapname +// const char *mapname - parsed map name +// const char *spawnposName - parsed spawn position name +// const char *movieName - parsed movie name +// +// Returns: bool - referenced bit +//---------------------------------------------------------------- + +void ParseMapName( const char *fullName, char *mapName, char *spawnposName, char *movieName ) +{ + char fixedName[ MAX_QPATH ]; + char tempMapName[ MAX_QPATH ]; + char tempSpawnposName[ MAX_QPATH ]; + char tempMovieName[ MAX_QPATH ]; + char *spawnposNamePtr; + char *movieNamePtr; + + + // Clean up the map name + + strcpy( fixedName, fullName ); + + Com_BackslashToSlash( fixedName ); + + // Get the movie name + + movieNamePtr = strchr( fixedName, '#' ); + + if ( movieNamePtr ) + strcpy( tempMovieName, movieNamePtr ); + else + tempMovieName[ 0 ] = '/0'; + + // Get the map name + + spawnposNamePtr = strchr( fixedName, '$' ); + + if ( spawnposNamePtr ) + { + Q_strncpyz( tempMapName, fixedName, spawnposNamePtr - fixedName + 1 ); + } + else if ( movieNamePtr ) + { + Q_strncpyz( tempMapName, fixedName, movieNamePtr - fixedName + 1 ); + } + else + { + strcpy( tempMapName, fixedName ); + } + + // Get the spawn pos name + + if ( spawnposNamePtr ) + { + if ( movieNamePtr ) + { + Q_strncpyz( tempSpawnposName, spawnposNamePtr, movieNamePtr - spawnposNamePtr + 1 ); + } + else + { + strcpy( tempSpawnposName, spawnposNamePtr ); + } + } + else + { + tempSpawnposName[ 0 ] = '/0'; + } + + // Update all parms + + if ( mapName ) + strcpy( mapName, tempMapName ); + + if ( movieName ) + strcpy( movieName, tempMovieName ); + + if ( spawnposName ) + strcpy( spawnposName, tempSpawnposName ); +} diff --git a/dlls/game/q_shared.h b/dlls/game/q_shared.h new file mode 100644 index 0000000..93d2e17 --- /dev/null +++ b/dlls/game/q_shared.h @@ -0,0 +1,1922 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/q_shared.h $ +// $Revision:: 167 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:42a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// included first by ALL program modules. A user mod should never modify +// this file + +#ifndef __Q_SHARED_H__ +#define __Q_SHARED_H__ + +// q_shared.h -- included first by ALL program modules. +// A user mod should never modify this file + + +#include + +#define DEVELOPER_NAME "Ritual Entertainment" + +#define QDECL +// __cdecl // added for bot code + +#ifdef _WIN32 + +#pragma warning(disable : 4018) // signed/unsigned mismatch +//#pragma warning(disable : 4032) // formal parameter 'number' has different type when promoted +#pragma warning(disable : 4051) // type conversion, possible loss of data +#pragma warning(disable : 4057) // slightly different base types +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4115) // 'type' : named type definition in parentheses +//#pragma warning(disable : 4125) // decimal digit terminates octal escape sequence +#pragma warning(disable : 4127) // conditional expression is constant +//#pragma warning(disable : 4131) // 'function' : uses old-style declarator +#pragma warning(disable : 4136) // conversion between different floating-point types +#pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union +//#pragma warning(disable : 4214) // nonstandard extension used : bit field types other than int +//#pragma warning(disable : 4220) // varargs matches remaining parameters +//#pragma warning(disable : 4239) // nonstandard extension used, conversion from class b to class & b +#pragma warning(disable : 4244) // 'conversion' conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable : 4305) // truncation from const double to float +//#pragma warning(disable : 4310) // cast truncates constant value +#pragma warning(disable : 4512) // 'Class' : assignment operator could not be generated +#pragma warning(disable : 4514) // unreferenced inline/local function has been removed +//#pragma warning(disable : 4611) // interaction between '_setjmp' and C++ object destruction is non-portable +#pragma warning(disable : 4710) // did not inline this function +#pragma warning(disable : 4711) // selected for automatic inline expansion + + +// shut up warnings with Intel Compiler +/*ARGSUSED*/ +/*NOTREACHED*/ +/*VARARGS*/ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +//#pragma intrinsic( memset, memcpy ) + +#endif + + +// this is the define for determining if we have an asm version of a C function +#if (defined _M_IX86 || defined __i386__) && !defined C_ONLY && !defined __sun__ +#define id386 1 +#else +#define id386 0 +#endif + +//======================= WIN32 DEFINES ================================= + +#ifdef WIN32 + +#define MAC_STATIC + +// buildstring will be incorporated into the version string +#ifdef NDEBUG +#ifdef _M_IX86 +#define CPUSTRING "win-x86" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP" +#endif +#else +#ifdef _M_IX86 +#define CPUSTRING "win-x86-debug" +#elif defined _M_ALPHA +#define CPUSTRING "win-AXP-debug" +#endif +#endif + + +#define PATH_SEP '\\' + +#endif + +//======================= MAC OS X SERVER DEFINES ===================== + +#if defined(__MACH__) && defined(__APPLE__) + +#define MAC_STATIC + +#ifdef __ppc__ +#define CPUSTRING "MacOSXS-ppc" +#elif defined __i386__ +#define CPUSTRING "MacOSXS-i386" +#else +#define CPUSTRING "MacOSXS-other" +#endif + +#define PATH_SEP '/' + +#define GAME_HARD_LINKED +#define CGAME_HARD_LINKED +#define UI_HARD_LINKED +#define BOTLIB_HARD_LINKED + +#endif + +//======================= MAC DEFINES ================================= + +#ifdef __MACOS__ + +#define MAC_STATIC static + +#define CPUSTRING "MacOS-PPC" + +#define PATH_SEP ':' + +#define GAME_HARD_LINKED +#define CGAME_HARD_LINKED +#define UI_HARD_LINKED +#define BOTLIB_HARD_LINKED + +void Sys_PumpEvents( void ); + +#endif + +//======================= LINUX DEFINES ================================= + +// the mac compiler can't handle >32k of locals, so we +// just waste space and make big arrays static... +#ifdef __linux__ + +#define MAC_STATIC + +#ifdef __i386__ +#define CPUSTRING "linux-i386" +#elif defined __axp__ +#define CPUSTRING "linux-alpha" +#else +#define CPUSTRING "linux-other" +#endif + +#define PATH_SEP '/' + +#endif + +//============================================================= + +#define DATATYPE_SCHAR_MIN -128 +#define DATATYPE_SCHAR_MAX 127 + +#define DATATYPE_UCHAR_MIN 0 +#define DATATYPE_UCHAR_MAX 255 + +#define DATATYPE_SSHORT_MIN -32768 +#define DATATYPE_SSHORT_MAX 32767 + +#define DATATYPE_USHORT_MIN 0 +#define DATATYPE_USHORT_MAX 65535 + +#define DATATYPE_SINT_MIN -2147483648 +#define DATATYPE_SINT_MAX 2147483647 + +#define DATATYPE_UINT_MIN 0 +#define DATATYPE_UINT_MAX 4294967295 + +#define DATATYPE_SLONG_MIN -2147483648 +#define DATATYPE_SLONG_MAX 2147483647 + +#define DATATYPE_ULONG_MIN 0 +#define DATATYPE_ULONG_MAX 4294967295 + +//======================= C++ DEFINES ================================= + +#ifdef __cplusplus +extern "C" + { +#endif + +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#define bound(a,minval,maxval) ( ((a) > (minval)) ? ( ((a) < (maxval)) ? (a) : (maxval) ) : (minval) ) + +typedef unsigned char byte; + +#ifdef __cplusplus +typedef int qboolean; +#define qfalse (0) +#define qtrue (!qfalse) +#else +typedef enum {qfalse, qtrue} qboolean; +#endif + +typedef int qhandle_t; +typedef int sfxHandle_t; +typedef int fileHandle_t; +typedef int clipHandle_t; + + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define MAX_QINT 0x7fffffff +#define MIN_QINT (-MAX_QINT-1) + + +// angle indexes +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +// Axis vector indexes +#define AXIS_FORWARD_VECTOR 0 +#define AXIS_RIGHT_VECTOR 1 +#define AXIS_UP_VECTOR 2 + +// the game guarantees that no string from the network will ever +// exceed MAX_STRING_CHARS +#define MAX_STRING_CHARS 1024 // max length of a string passed to Cmd_TokenizeString +#define MAX_STRING_TOKENS 256 // max tokens resulting from Cmd_TokenizeString +#define MAX_TOKEN_CHARS 1024 // max length of an individual token +#define MAX_ARGUMENTS 512 // max number of arguments for commands + +#define MAX_INFO_STRING 1024 +#define MAX_INFO_KEY 1024 +#define MAX_INFO_VALUE 1024 + +#define BIG_INFO_STRING 8192 // used for system info key only +#define BIG_INFO_KEY 8192 +#define BIG_INFO_VALUE 8192 + +#define MAX_QPATH 64 // max length of a pathname +#define MAX_OSPATH 256 // max length of a filesystem pathname + +#define MAX_NAME_LENGTH 32 // max length of a client name + +#define MAX_LIGHTING_GROUPS 32 +#define MAX_STATIC_LOD_MODLES 256 + +#define MAX_EXTRA_ENTITIES_FROM_GAME 100 + +typedef enum { + CENTERPRINT_IMPORTANCE_NORMAL, + CENTERPRINT_IMPORTANCE_HIGH, + CENTERPRINT_IMPORTANCE_CRITICAL +} CenterPrintImportance; + + +// server browser sources +#define AS_LOCAL 0 +#define AS_GLOBAL 1 +#define AS_FAVORITES 2 + +// paramters for command buffer stuffing +typedef enum { + EXEC_NOW, // don't return until completed, a VM should NEVER use this, + // because some commands might cause the VM to be unloaded... + EXEC_INSERT, // insert at current position, but don't run yet + EXEC_APPEND // add to end of the command buffer (normal case) +} cbufExec_t; + + +// +// these aren't needed by any of the VMs. put in another header? +// +#define MAX_MAP_AREA_BYTES 32 // bit vector of area visibility + + +// print levels from renderer (FIXME: set up for game / cgame?) +typedef enum { + PRINT_ALL, + PRINT_DEVELOPER, // only print when "developer 1" + PRINT_DEVELOPER_2, // only print when "developer 2" + PRINT_WARNING, + PRINT_ERROR +} printParm_t; + + +// parameters to the main Error routine +typedef enum { + ERR_FATAL, // exit the entire game with a popup window + ERR_DROP, // print to console and disconnect from game + ERR_DISCONNECT, // don't kill server + ERR_NEED_CD // pop up the need-cd dialog +} errorParm_t; + +#define CIN_system 1 +#define CIN_loop 2 +#define CIN_hold 4 +#define CIN_silent 8 +#define CIN_shader 16 + +/* +============================================================== + +MATHLIB + +============================================================== +*/ + + +typedef float vec_t; +typedef vec_t vec2_t[2]; +typedef vec_t vec3_t[3]; +typedef vec_t vec4_t[4]; +typedef vec_t vec5_t[5]; + +typedef struct +{ + float radius; + float height; + vec3_t center; +}cylinder_t; + +typedef int fixed4_t; +typedef int fixed8_t; +typedef int fixed16_t; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#define NUMVERTEXNORMALS 162 +extern vec3_t bytedirs[NUMVERTEXNORMALS]; + +// all drawing is done to a 640*480 virtual screen size +// and will be automatically scaled to the real resolution +#define SCREEN_WIDTH 640 +#define SCREEN_HEIGHT 480 + +#define TINYCHAR_WIDTH (SMALLCHAR_WIDTH) +#define TINYCHAR_HEIGHT (SMALLCHAR_HEIGHT/2) + +#define SMALLCHAR_WIDTH 8 +#define SMALLCHAR_HEIGHT 16 + +#define BIGCHAR_WIDTH 16 +#define BIGCHAR_HEIGHT 16 + +#define GIANTCHAR_WIDTH 32 +#define GIANTCHAR_HEIGHT 48 + +extern vec4_t colorBlack; +extern vec4_t colorRed; +extern vec4_t colorGreen; +extern vec4_t colorBlue; +extern vec4_t colorYellow; +extern vec4_t colorMagenta; +extern vec4_t colorCyan; +extern vec4_t colorWhite; +extern vec4_t colorLtGrey; +extern vec4_t colorMdGrey; +extern vec4_t colorDkGrey; + +#define Q_COLOR_ESCAPE '^' +#define Q_IsColorString(p) ( p && ( *(p) == Q_COLOR_ESCAPE ) && ( *((p)+1) ) && ( *((p)+1) != Q_COLOR_ESCAPE ) ) + +#define COLOR_BLACK '0' +#define COLOR_RED '1' +#define COLOR_GREEN '2' +#define COLOR_YELLOW '3' +#define COLOR_BLUE '4' +#define COLOR_CYAN '5' +#define COLOR_MAGENTA '6' +#define COLOR_WHITE '7' +#define COLOR_NONE '8' +#define ColorIndex(c) ( ( (c) - '0' ) & 7 ) + +#define S_COLOR_BLACK "^0" +#define S_COLOR_RED "^1" +#define S_COLOR_GREEN "^2" +#define S_COLOR_YELLOW "^3" +#define S_COLOR_BLUE "^4" +#define S_COLOR_CYAN "^5" +#define S_COLOR_MAGENTA "^6" +#define S_COLOR_WHITE "^7" + +extern vec4_t g_color_table[8]; + +#define MAKERGB( v, r, g, b ) v[0]=r;v[1]=g;v[2]=b +#define MAKERGBA( v, r, g, b, a ) v[0]=r;v[1]=g;v[2]=b;v[3]=a + +#define DEG2RAD( a ) ( ( (a) * M_PI ) / 180.0F ) +#define RAD2DEG( a ) ( ( (a) * 180.0f ) / M_PI ) + +struct cplane_s; + +extern vec3_t vec3_origin; +extern vec3_t axisDefault[3]; + +#define nanmask (255<<23) + +#define IS_NAN(x) (((*(int *)&x)&nanmask)==nanmask) + +#ifndef __Q_FABS__ +#define __Q_FABS__ +#endif + +float Q_fabs( float f ); +float Q_rsqrt( float f ); // reciprocal square root + +#define SQRTFAST( x ) ( 1.0f / Q_rsqrt( x ) ) + +signed char ClampChar( int i ); +signed short ClampShort( int i ); + +double dEpsilon( void ); +double dIdentity( void ); +double dSign( const double number ); +double dClamp( const double value, const double min, const double max ); +double dDistance (const double value1, const double value2 ); +qboolean dCloseEnough( const double value1, const double value2, const double epsilon ); +qboolean dSmallEnough( const double value, const double epsilon ); + +float fEpsilon( void ); +float fIdentity( void ); +float fSign( const float number ); +float fClamp( const float value, const float min, const float max ); +float fDistance (const float value1, const float value2 ); +qboolean fCloseEnough( const float value1, const float value2, const float epsilon ); +qboolean fSmallEnough( const float value, const float epsilon ); + +int iSign( const int number); +int iClamp( const int value, const int min, const int max); + + +// this isn't a real cheap function to call! +int DirToByte( const vec3_t dir ); +void ByteToDir( int b, vec3_t dir ); + +#if 1 + +#define DotProduct(x,y) ( ((x)[0]*(y)[0]) + ((x)[1]*(y)[1]) + ((x)[2]*(y)[2]) ) +#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +#define VectorScale(v, s, o) ((o)[0]=(v)[0]*(s),(o)[1]=(v)[1]*(s),(o)[2]=(v)[2]*(s)) +#define VectorMA(v, s, b, o) ((o)[0]= (v)[0] + ((b)[0]*(s)), (o)[1] = (v)[1] + ((b)[1]*(s)), (o)[2] = (v)[2] + ((b)[2]*(s))) +#define Round(a) (int)(a + 0.5f) +#else + +#define DotProduct(x,y) _DotProduct(x,y) + +#ifdef MSVC_BUILD +//intel change to accomodate manual CPU dispatch. if using intel compiler, this header +//show up in q_math.c instead of here. +void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ); +void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ); +#endif + +#define VectorCopy(a,b) _VectorCopy(a,b) +#define VectorScale(v, s, o) _VectorScale(v,s,o) +#define VectorMA(v, s, b, o) _VectorMA(v,s,b,o) + +#endif + +#ifdef __LCC__ +#ifdef VectorCopy +#undef VectorCopy +// this is a little hack to get more efficient copies +typedef struct { + float v[3]; +} vec3struct_t; +#define VectorCopy(a,b) *(vec3struct_t *)b=*(vec3struct_t *)a; +#endif +#endif + +#define VectorClear(a) ((a)[0]=(a)[1]=(a)[2]=0) +#define VectorNegate(a,b) ((b)[0]=-(a)[0],(b)[1]=-(a)[1],(b)[2]=-(a)[2]) +#define VectorSet(v, x, y, z) ((v)[0]=(x), (v)[1]=(y), (v)[2]=(z)) +#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) + +#define OrientClear( orient ) \ + ( VectorSet( orient.origin, 0, 0, 0 ), \ + VectorSet( orient.axis[ 0 ], 1, 0, 0 ), \ + VectorSet( orient.axis[ 1 ], 0, 1, 0 ), \ + VectorSet( orient.axis[ 2 ], 0, 0, 1 ) ) +#define OrientCopy( a, b ) \ + ( VectorCopy( (a).origin, (b).origin ), \ + VectorCopy( (a).axis[ 0 ], (b).axis[ 0 ] ), \ + VectorCopy( (a).axis[ 1 ], (b).axis[ 1 ] ), \ + VectorCopy( (a).axis[ 2 ], (b).axis[ 2 ] ) ) + +#define QuatSet( q, x, y, z, w ) ((q)[0]=(x),(q)[1]=(y),(q)[2]=(z),(q)[3]=(w)) +#define QuatCopy( a,b ) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) + +#define uint_cast(X) ( *(unsigned int *) &(X) ) +#define int_cast(X) ( *(int *) &(X) ) +#define IsNegative(X) ( uint_cast(X) >> 31 ) +#define USES_CLAMP_ZERO const unsigned s_clamp0table[2] = { 0xFFFFFFFF, 0x00000000 } +#define ClampZero(X) ( uint_cast(X) &= s_clamp0table[IsNegative(X)] ) +#define ClampNormalFloat255Byte(dst,X) { \ + float _f_ = X; \ + unsigned mask = ~ ( (int) ( uint_cast(_f_) - 1 ) >> 31 ); \ + \ + uint_cast(_f_) += (unsigned) ( 0x04000000 - 0x437F0000 ); \ + uint_cast(_f_) &= ( (int) uint_cast(_f_) ) >> 31; \ + uint_cast(_f_) += (unsigned) 0x437F0000; \ + uint_cast(_f_) &= mask; \ + dst = (unsigned char) _f_; \ +} + +#define SetHighest(type, dst,upperbound) { \ + type _ff_=dst-upperbound;\ + ClampZero ( _ff_ );\ + dst = dst - _ff_;\ +} + +#define SetLowest(type, dst,lowerbound) { \ + type _ff_= dst - lowerbound;\ + ClampZero ( _ff_ );\ + dst = lowerbound + _ff_;\ +} + +#define SetLowestFloat(dst,l) ( dst = ( ( dst < l ) ? l : dst ) ) +#define SetHighestFloat(dst,l) ( dst = ( ( dst > l ) ? l : dst ) ) +//#define SetLowestFloat(dst,l) SetLowest ( float, dst, l ) +//#define SetHighestFloat(dst,l) SetHighest ( float, dst, l ) +#define SetLowestInt(dst,l) SetLowest ( int, dst, l ) +#define SetHighestInt(dst,l) SetHighest ( int, dst, l ) + +#ifdef __cplusplus +inline bool VectorFromString ( const char *ss, vec3_t v ) { + return sscanf ( ss, "%f %f %f", &v[0], &v[1], &v[2] ) == 3; +} +#else +#define VectorFromString(ss,v) ( sscanf ( (ss), "%f %f %f", &(v)[0], &(v)[1], &(v)[2] ) == 3 ) +#endif + +#define SnapVector(v) {v[0]=(int)v[0];v[1]=(int)v[1];v[2]=(int)v[2];} + +// just in case you do't want to use the macros +vec_t _DotProduct( const vec3_t v1, const vec3_t v2 ); +void _VectorSubtract( const vec3_t veca, const vec3_t vecb, vec3_t out ); +void _VectorAdd( const vec3_t veca, const vec3_t vecb, vec3_t out ); +void _VectorCopy( const vec3_t in, vec3_t out ); +void _VectorScale( const vec3_t in, float scale, vec3_t out ); +void _VectorMA( const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc ); + +unsigned ColorBytes3 (float r, float g, float b); +unsigned ColorBytes4 (float r, float g, float b, float a); + +float NormalizeColor( const vec3_t in, vec3_t out ); + +float RadiusFromBounds( const vec3_t mins, const vec3_t maxs ); +void ClearBounds( vec3_t mins, vec3_t maxs ); +qboolean BoundsClear( const vec3_t mins, const vec3_t maxs ); +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ); +int VectorCompare( const vec3_t v1, const vec3_t v2 ); +vec_t VectorLength( const vec3_t v ); +vec_t VectorLengthSquared( const vec3_t v ); +vec_t Distance( const vec3_t p1, const vec3_t p2 ); +vec_t DistanceSquared( const vec3_t p1, const vec3_t p2 ); +void CrossProduct( const vec3_t v1, const vec3_t v2, vec3_t cross ); +vec_t VectorNormalize (vec3_t v); // returns vector length +void VectorNormalizeFast(vec3_t v); // does NOT return vector length, uses rsqrt approximation +vec_t VectorNormalize2( const vec3_t v, vec3_t out ); +void VectorInverse (vec3_t v); +void Vector4Scale( const vec4_t in, vec_t scale, vec4_t out ); +void VectorRotate( const vec3_t in, const vec3_t matrix[3], vec3_t out ); +int Q_log2(int val); +unsigned short NormalToLatLong( const vec3_t normal ); + + +int Q_rand( int *seed ); +float Q_random( int *seed ); +float Q_crandom( int *seed ); + +#define random() ((rand () & 0x7fff) / ((float)0x7fff)) +#define crandom() (2.0f * (random() - 0.5f)) + +float randomrange( float min, float max ); +float crandomrange( float min, float max ); +float grandom( float average, float deviation ); +float erandom( float mean ); + +void AccumulateTransform( vec3_t dstLocation, vec3_t dstAxes[3], + const vec3_t childLocation, const vec3_t childAxes[3], + const vec3_t parentLocation, const vec3_t parentAxes[3] ); +void AccumulatePosition( vec3_t dstOrigin, const vec3_t childOrigin, const vec3_t parentOrigin, const vec3_t parentAxes[3] ); + +void vectoangles( const vec3_t value1, vec3_t angles); +float vectoyaw( const vec3_t vec ); + +//FIXME +// get rid of all references to AnglesToMat +#define AnglesToMat AnglesToAxis + +void AxisClear( vec3_t axis[3] ); +void AxisCopy( const vec3_t in[3], vec3_t out[3] ); + +void SetPlaneSignbits( struct cplane_s *out ); +int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct cplane_s *plane); + +float AngleMod(float a); +float LerpAngle (float from, float to, float frac); +float LerpAngleFromCurrent (float from, float to, float current, float frac); +float AngleSubtract( float a1, float a2 ); +void AnglesSubtract( const vec3_t v1, const vec3_t v2, vec3_t v3 ); + +float AngleNormalize360 ( float angle ); +float AngleNormalize180 ( float angle ); +float AngleNormalizeArbitrary ( const float angle, const float minimumAngle ); +float AngleDelta ( float angle1, float angle2 ); + +qboolean PlaneFromPoints( vec4_t plane, const vec3_t a, const vec3_t b, const vec3_t c ); +void ProjectPointOnPlane( vec3_t dst, const vec3_t p, const vec3_t normal ); +void RotatePointAroundVector( vec3_t dst, const vec3_t dir, const vec3_t point, float degrees ); +void RotateAroundDirection( vec3_t axis[3], float yaw ); +void MakeNormalVectors( const vec3_t forward, vec3_t right, vec3_t up ); + +float noise(float vec[], int len); +float noise1(float arg); +float noise2(const float arg[2]); +float noise3(const float arg[3]); +// perpendicular vector could be replaced by this + +void R_ConcatRotations ( const float in1[3][3], const float in2[3][3], float out[3][3] ); +void R_ConcatTransforms ( const float in1[3][4], const float in2[3][4], float out[3][4] ); +float anglemod(float a); +float angledist( float ang ); +int BoxOnPlaneSide2 ( const vec3_t emins, const vec3_t emaxs, const struct cplane_s *p ); +void CalculateRotatedBounds( const vec3_t angles, vec3_t mins, vec3_t maxs ); +void CalculateRotatedBounds2( float trans[3][3], vec3_t mins, vec3_t maxs ); +int BoundingBoxToInteger( const vec3_t mins, const vec3_t maxs ); +void IntegerToBoundingBox( int num, vec3_t mins, vec3_t maxs ); +void MatrixTransformVector( const vec3_t in, const float mat[ 3 ][ 3 ], vec3_t out ); +void Matrix4TransformVector( const vec3_t in, const float mat[ 4 ][ 4 ], vec3_t out ); +//void MatrixToEulerAngles( float mat[ 3 ][ 3 ], vec3_t ang ); +void TransposeMatrix( const float in[ 3 ][ 3 ], float out[ 3 ][ 3 ] ); +void OrthoNormalize( float mat[3][3] ); +float NormalizeQuat( float q[ 4 ] ); +//void MatToQuat( float srcMatrix[ 3 ][ 3 ], float destQuat[ 4 ] ); +void RotateAxis( const float axis[ 3 ], float angle, float q[ 4 ] ); +void MultQuat( const float q1[ 4 ], const float q2[ 4 ], float out[ 4 ] ); +//void QuatToMat( float q[ 4 ], float m[ 3 ][ 3 ] ); +void SlerpQuaternion( const float from[ 4 ], const float to[ 4 ], float t, float res[ 4 ] ); +//void EulerToQuat( float ang[ 3 ], float q[ 4 ] ); +int PlaneTypeForNormal ( const vec3_t normal ); + + + +//============================================= + +float Com_Clamp( float min, float max, float value ); + +void COM_FilePath (const char *in, char *out); +void COM_FileBase (const char *in, char *out); +void COM_FileName (const char *in, char *out); + +const char *COM_SkipPath( const char *pathname ); +void COM_StripExtension( const char *in, char *out ); +void COM_DefaultExtension( char *path, int maxSize, const char *extension ); +void Com_BackslashToSlash( char *str ); + +void COM_BeginParseSession( void ); +int COM_GetCurrentParseLine( void ); +const char *COM_Parse( const char **data_p ); +char *COM_ParseExt( char **data_p, qboolean allowLineBreak ); +const char *COM_GetToken(const char **data_p, qboolean crossline); +int COM_GetParseLineNumber(void); + +// data is an in/out parm, returns a parsed out token + +void COM_MatchToken( char**buf_p, char *match ); + +qboolean SkipBracedSection (char **program); +void SkipRestOfLine ( char **data ); + +void Parse1DMatrix (char **buf_p, int x, float *m); +void Parse2DMatrix (char **buf_p, int y, int x, float *m); +void Parse3DMatrix (char **buf_p, int z, int y, int x, float *m); + +void Com_sprintf (char *dest, int size, const char *fmt, ...); + + +// mode parm for FS_FOpenFile +typedef enum { + FS_READ, + FS_WRITE, + FS_APPEND, + FS_APPEND_SYNC +} fsMode_t; + +typedef enum { + FS_SEEK_CUR, + FS_SEEK_END, + FS_SEEK_SET +} fsOrigin_t; + +//============================================= + +int Q_isprint( int c ); +int Q_islower( int c ); +int Q_isupper( int c ); +int Q_isalpha( int c ); + +// portable case insensitive compare +int Q_stricmp (const char *s1, const char *s2); +int Q_strncmp (const char *s1, const char *s2, int n); +int Q_stricmpn (const char *s1, const char *s2, int n); +char *Q_strlwr( char *s1 ); +char *Q_strupr( char *s1 ); +char *Q_strrchr( const char* string, int c ); + +// buffer size safe library replacements +void Q_strncpyz( char *dest, const char *src, int destsize ); +void Q_strcat( char *dest, int size, const char *src ); +char *Q_CleanStr( char *string ); + +//============================================= + +extern qboolean bigendian; + +short BigShort(short l); +short LittleShort(short l); +int BigLong (int l); +int LittleLong (int l); +float BigFloat (float l); +float LittleFloat (float l); +unsigned short LittleUnsignedShort(unsigned short l); +unsigned short BigUnsignedShort(unsigned short l); + +void Swap_Init (void); +const char *va(const char *format, ...); + +//============================================= + +int MusicMood_NameToNum( const char * name ); +const char * MusicMood_NumToName( int num ); +int EAXMode_NameToNum( const char * name ); +const char * EAXMode_NumToName( int num ); + +unsigned int GenerateHashForName( const char* name, qboolean caseSensitive, unsigned int maxHash ); + +int PlayerStat_NameToNum( const char * name ); +const char * PlayerStat_NumToName( int num ); + +// +// key / value info strings +// +const char *Info_ValueForKey( const char *s, const char *key ); +void Info_RemoveKey( char *s, const char *key ); +void Info_SetValueForKey( char *s, const char *key, const char *value ); +qboolean Info_Validate( const char *s ); +void Info_NextPair( const char **s, char key[MAX_INFO_KEY], char value[MAX_INFO_VALUE] ); + +void ParseMapName( const char *fullName, char *mapName, char *spawnposName, char *movieName ); + +// this is only here so the functions in q_shared.c and bg_*.c can link +void Com_Error( int level, const char *error, ... ); +void Com_Printf( const char *msg, ... ); +void Com_WPrintf( const char *msg, ... ); + +void Com_WidgetPrintf ( const char *widgetName, const char *fmt, ... ); + +/* +========================================================== + +CVARS (console variables) + +Many variables can be used for cheating purposes, so when +cheats is zero, force all unspecified variables to their +default values. +========================================================== +*/ + +#define CVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc + // used for system variables, not for player + // specific configurations +#define CVAR_USERINFO 2 // sent to server on connect or change +#define CVAR_SERVERINFO 4 // sent in response to front end requests +#define CVAR_SYSTEMINFO 8 // these cvars will be duplicated on all clients +#define CVAR_INIT 16 // don't allow change from console at all, + // but can be set from the command line +#define CVAR_LATCH 32 // will only change when C code next does + // a Cvar_Get(), so it can't be changed + // without proper initialization. modified + // will be set, even though the value hasn't + // changed yet +#define CVAR_ROM 64 // display only, cannot be set by user at all +#define CVAR_USER_CREATED 128 // created by a set command +#define CVAR_TEMP 256 // can be set even when cheats are disabled, but is not archived +#define CVAR_CHEAT 512 // can not be changed if cheats are disabled +#define CVAR_NORESTART 1024 // do not clear when a cvar_restart is issued +#define CVAR_RESETSTRING 2048 // force the cvar's reset string to be set +#define CVAR_SOUND_LATCH 4096 // specifically for sound will only change +#define CVAR_UNDEFINED 8192 + // when C code next does a Cvar_Get(), so it + // can't be changed without proper initialization. + // modified will be set, even though the value hasn't + // changed yet + +#define CVAR_UNDEFINED_STRING "UNDEFINED" + +// nothing outside the Cvar_*() functions should modify these fields! +typedef struct cvar_s { + char *name; + char *string; + char *resetString; // cvar_restart will reset to this value + char *latchedString; // for CVAR_LATCH vars + int flags; + qboolean modified; // set each time the cvar is changed + int modificationCount; // incremented each time the cvar is changed + float value; // atof( string ) + int integer; // atoi( string ) + struct cvar_s *next; + qboolean defaultSet; // set when setd creates the default value +} cvar_t; + +#define MAX_CVAR_VALUE_STRING 256 + +typedef int cvarHandle_t; + +// the modules that run in the virtual machine can't access the cvar_t directly, +// so they must ask for structured updates +typedef struct { + cvarHandle_t handle; + int modificationCount; + float value; + int integer; + char string[MAX_CVAR_VALUE_STRING]; +} vmCvar_t; + +/* +============================================================== + +COLLISION DETECTION + +============================================================== +*/ + +#include "surfaceflags.h" // shared with the q3map utility + +// plane types are used to speed some tests +// 0-2 are axial planes +#define PLANE_X 0 +#define PLANE_Y 1 +#define PLANE_Z 2 +#define PLANE_NON_AXIAL 3 + + +// plane_t structure +// !!! if this is changed, it must be changed in asm code too !!! +typedef struct cplane_s { + vec3_t normal; + float dist; + byte type; // for fast side tests: 0,1,2 = axial, 3 = nonaxial + byte signbits; // signx + (signy<<1) + (signz<<2), used as lookup during collision + byte pad[2]; +} cplane_t; + +typedef struct + { + qboolean valid; + int surface; + int bone; + vec3_t position; + vec3_t normal; + float damage_multiplier; + } tikimdl_intersection_t; + +// a trace is returned when a box is swept through the world +typedef struct { + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact, transformed to world space + int surfaceFlags; // surface hit + int contents; // contents on other side of surface hit + int entityNum; // entity the contacted surface is a part of + struct gentity_s *ent; // Pointer to entity hit + tikimdl_intersection_t intersect; // set if the trace hit a specific polygon +} trace_t; + +typedef struct { + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + const float *mins; + const float *maxs; // size of the moving object + const float *start; + vec3_t end; + trace_t trace; + int passEntityNum; + int contentmask; + qboolean cylinder; + qboolean fulltrace; +} moveclip_t; + +// trace->entityNum can also be 0 to (MAX_GENTITIES-1) +// or ENTITYNUM_NONE, ENTITYNUM_WORLD + + +// markfragments are returned by CM_MarkFragments() +typedef struct { + int firstPoint; + int numPoints; + + vec3_t normal; + float dist; +} markFragment_t; + +typedef struct { + vec3_t origin; + vec3_t axis[3]; +} orientation_t; + +typedef struct { + int time; // real time of day + int serverTime; // time in the game + int totalGameTime; // total time playing the game + char comment[ MAX_QPATH ]; // user comment + char mapName[ MAX_QPATH ]; // name of map + char saveName[ MAX_QPATH ]; // local name of savegame +} savegamestruct_t; + +//===================================================================== + +// in order from highest priority to lowest +// if none of the catchers are active, bound key strings will be executed +#define KEYCATCH_CONSOLE 1 +#define KEYCATCH_UI 2 +#define KEYCATCH_MESSAGE 4 +#define KEYCATCH_HUD 8 + + +// sound channels +// channel 0 never willingly overrides +// other channels will allways override a playing sound on that channel +typedef enum { + CHAN_AUTO, + CHAN_LOCAL, + CHAN_WEAPON, + CHAN_VOICE, + CHAN_ITEM, + CHAN_BODY, + CHAN_DIALOG, + CHAN_DIALOG_SECONDARY, + CHAN_WEAPONIDLE, + CHAN_MENU, + CHAN_CINEMATIC, + CHAN_MUSIC, + CHAN_TAUNT, + CHAN_MENU2, + CHAN_COMBAT1 = 20, + CHAN_COMBAT2 = 21, + CHAN_COMBAT3 = 22, + CHAN_COMBAT4 = 23 +} soundChannel_t; + +#define DEFAULT_MIN_DIST -1.0f +#define DEFAULT_VOL -1.0f + +#define CONTEXT_WIDE_MIN_DIST 1024 +#define LEVEL_WIDE_MIN_DIST_CUTOFF 10000 +#define LEVEL_WIDE_MIN_DIST 1000000 // full volume the entire level +#define LEVEL_WIDE_STRING "levelwide" + +#define SOUND_SYNCH 0x1 +#define SOUND_SYNCH_FADE 0x2 +#define SOUND_RANDOM_PITCH_20 0x4 +#define SOUND_RANDOM_PITCH_40 0x8 +#define SOUND_LOCAL_DIALOG 0x10 + +typedef enum + { + mood_none, + mood_normal, + mood_action, + mood_suspense, + mood_mystery, + mood_success, + mood_failure, + mood_surprise, + mood_special, + mood_aux1, + mood_aux2, + mood_aux3, + mood_aux4, + mood_aux5, + mood_aux6, + mood_aux7, + mood_totalnumber + } music_mood_t; + +typedef enum + { + eax_generic, + eax_paddedcell, + eax_room, + eax_bathroom, + eax_livingroom, + eax_stoneroom, + eax_auditorium, + eax_concerthall, + eax_cave, + eax_arena, + eax_hangar, + eax_carpetedhallway, + eax_hallway, + eax_stonecorridor, + eax_alley, + eax_forest, + eax_city, + eax_mountains, + eax_quarry, + eax_plain, + eax_parkinglot, + eax_sewerpipe, + eax_underwater, + eax_drugged, + eax_dizzy, + eax_psychotic, + eax_totalnumber + } eax_mode_t; + +#define LIP_SYNC_HZ 20.0f + +typedef enum { + MORPH_CHAN_NONE, + MORPH_CHAN_MOUTH, + MORPH_CHAN_BROW, + MORPH_CHAN_LEFT_BROW, + MORPH_CHAN_RIGHT_BROW, + MORPH_CHAN_EYES, + MORPH_CHAN_LEFT_LID, + MORPH_CHAN_RIGHT_LID +} morphChannel_t; + +/* #define MAX_EXPRESSION_NAME_LENGTH 32 + +typedef struct +{ + int morph_index; + float percent; +} dtikimorphtarget_t; + +typedef struct +{ + char alias[ MAX_EXPRESSION_NAME_LENGTH ]; + int num_morph_targets; + int ofs_morph_targets; +} dtikiexpression_t; */ + +/* +======================================================================== + + ELEMENTS COMMUNICATED ACROSS THE NET + +======================================================================== +*/ + +#define ANGLE2SHORT(x) ((int)((x)*65536/360) & 65535) +#define SHORT2ANGLE(x) ((x)*(360.0/65536)) + +#define SNAPFLAG_RATE_DELAYED 1 +#define SNAPFLAG_NOT_ACTIVE 2 // snapshot used during connection and for zombies +#define SNAPFLAG_SERVERCOUNT 4 // toggled every map_restart so transitions can be detected + +// +// per-level limits +// +#define MAX_CLIENTS 128 // absolute limit +#define MAX_LOCATIONS 64 + +#define GENTITYNUM_BITS 10 // don't need to send any more +#define MAX_GENTITIES (1<serverTime of last executed command + int pm_type; + int bobCycle; // for view bobbing and footstep generation + int pm_flags; // ducked, jump_held, etc + int pm_time; + int pm_runtime; // used to keep track of how long player has been running + /* jhefty/jwaters -- another strafe-jump fix + int pm_landtime; // used to record land time, to prevent strafe jumping proper-like + */ + vec3_t origin; + vec3_t velocity; + int gravity; + int speed; + int jumpvelocity; + int crouchjumpvelocity; + qboolean crouchjumpset; + int pm_stopspeed; + int pm_airaccelerate; + int pm_wateraccelerate; + int pm_friction; + int pm_waterfriction; + int pm_accelerate; + + int pm_defaultviewheight; + + int delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters + //lean amount + float leanDelta; + + int groundEntityNum;// ENTITYNUM_NONE = in air + qboolean walking; + qboolean jumped; + qboolean groundPlane; + qboolean instantJump; + qboolean strafeJumpingAllowed; + int feetfalling; + vec3_t falldir; + trace_t groundTrace; + + int clientNum; // ranges from 0 to MAX_CLIENTS-1 + + vec3_t viewangles; // for fixed views + int viewheight; + + int stats[MAX_STATS]; + int activeItems[MAX_ACTIVE_ITEMS]; + int inventory_name_index[MAX_INVENTORY]; + int inventory_weapon_ammo_index[MAX_INVENTORY]; + int inventory_weapon_required_ammo[MAX_INVENTORY]; + int ammo_in_clip[MAX_INVENTORY]; + int ammo_name_index[MAX_AMMO]; + int ammo_amount[MAX_AMMOCOUNT]; + int max_ammo_amount[MAX_AMMOCOUNT]; + + int current_music_mood; + int fallback_music_mood; + float music_volume; + float music_volume_fade_time; + qboolean allowMusicDucking; + + int reverb_type; + float reverb_level; + + float blend[4]; // rgba full screen effect + float fov; // fov of the player + + vec3_t camera_origin; // origin for camera view + vec3_t cameraFocalPoint; + qboolean useCameraFocalPoint; + vec3_t camera_angles; // angles for camera view + float camera_time; // time to switch between camera and normal view + + vec3_t camera_offset; // angular offset for camera + vec3_t damage_angles; // these angles are added directly to the view, without lerping + int camera_flags; // third-person camera flags + vec3_t vehicleoffset; + qboolean in_vehicle; + + int dialogEntnum; + int dialogSoundIndex; + int dialogTextSoundIndex; + + int objectiveNameIndex; + unsigned int objectiveStates; + unsigned int informationStates; + + unsigned int missionStatus; + + unsigned int viewMode; + + // not communicated over the net at all + int ping; // server to game info for scoreboard +} playerState_t; + +typedef enum { + WEATHER_NONE, + WEATHER_RAIN, + WEATHER_RAIN_PLAIN, + WEATHER_SNOW +} weather_t; + +#define DYNAMIC_LIGHT_NO_DEFAULT 255 +#define DYNAMIC_LIGHT_MAX_INTENSITY 254 + +typedef struct worldState_s { + byte dynamic_light_intensities[ MAX_LIGHTING_GROUPS ]; + + byte dynamic_light_default_intensities[ MAX_LIGHTING_GROUPS ]; + + vec3_t wind_direction; + float wind_intensity; + + weather_t weather_type; + int weather_intensity; + + float time_scale; +} worldState_t; + +#define MAX_SERVER_SOUNDS 32 +#define MAX_SERVER_SOUNDS_BITS 6 + +typedef struct +{ + vec3_t origin; + int entity_number; + int channel; + short sound_index; + float volume; + float min_dist; + qboolean stop_flag; + float pitch_modifier; +} server_sound_t; + +//==================================================================== + + +// +// usercmd_t->button bits, many of which are generated by the client system, +// so they aren't game-only definitions +// +#define BUTTON_ATTACKRIGHT_BITINDEX 0 +#define BUTTON_ATTACKLEFT_BITINDEX 1 +//#define BUTTON_SNEAK_BITINDEX 7 +#define BUTTON_RELOAD_BITINDEX 2 // weapon reload key +#define BUTTON_RUN_BITINDEX 3 +#define BUTTON_HOLSTERWEAPON_BITINDEX 4 +#define BUTTON_USE_BITINDEX 5 +#define BUTTON_TALK_BITINDEX 6 // displays talk balloon and disables actions +#define BUTTON_ANY_BITINDEX 7 // any key whatsoever +#define BUTTON_DROP_RUNE_BITINDEX 8 +#define BUTTON_TRANSFER_ENERGY_BITINDEX 8 + +// This should always be at least 1 larger than the highest above +// Also if this number needs to be larger the buttons var in usercmd_t might need to be larger +#define USERCMD_BUTTON_MAX 16 + +#define BUTTON_ATTACKRIGHT ( 1 << BUTTON_ATTACKRIGHT_BITINDEX ) +#define BUTTON_ATTACKLEFT ( 1 << BUTTON_ATTACKLEFT_BITINDEX ) +//#define BUTTON_SNEAK ( 1 << BUTTON_SNEAK_BITINDEX ) +#define BUTTON_RUN ( 1 << BUTTON_RUN_BITINDEX ) +#define BUTTON_HOLSTERWEAPON ( 1 << BUTTON_HOLSTERWEAPON_BITINDEX ) +#define BUTTON_USE ( 1 << BUTTON_USE_BITINDEX ) +#define BUTTON_TALK ( 1 << BUTTON_TALK_BITINDEX ) // displays talk balloon and disables actions +#define BUTTON_RELOAD ( 1 << BUTTON_RELOAD_BITINDEX ) // weapon reload key +#define BUTTON_ANY ( 1 << BUTTON_ANY_BITINDEX ) // any key whatsoever +#define BUTTON_DROP_RUNE ( 1 << BUTTON_DROP_RUNE_BITINDEX ) +#define BUTTON_TRANSFER_ENERGY ( 1 << BUTTON_TRANSFER_ENERGY_BITINDEX ) + + + +// usercmd_t is sent to the server each client frame +typedef struct usercmd_s { + int serverTime; + byte msec; + short buttons; + byte weapon; + short angles[3]; + short deltaAngles[3]; //the absolute number of angles the player has moved. + short choffset[2]; + vec3_t realvieworigin; + vec3_t realviewangles; + qboolean thirdperson; + signed char forwardmove, rightmove, upmove, lean; + +} usercmd_t; + +//=================================================================== + +// +// Animation flags +// +#define MDL_ANIM_DELTA_DRIVEN ( 1 << 0 ) +#define MDL_ANIM_DEFAULT_ANGLES ( 1 << 3 ) +#define MDL_ANIM_NO_TIMECHECK ( 1 << 4 ) + +// if entityState->solid == SOLID_BMODEL, modelindex is an inline model number +#define SOLID_BMODEL 0xffffff + +#define RF_THIRD_PERSON (1<<0) // don't draw through eyes, only mirrors (player bodies, chat sprites) +#define RF_FIRST_PERSON (1<<1) // only draw through eyes (view weapon, damage blood blob) +#define RF_DEPTHHACK (1<<2) // hack the z-depth so that view weapons do not clip into walls +#define RF_VIEWLENSFLARE (1<<3) // View dependent lensflare +#define RF_FRAMELERP (1<<4) // interpolate between current and next state +#define RF_BEAM (1<<5) // draw a beam between origin and origin2 +#define RF_SHADOW_FROM_BIP01 (1<<6) +#define RF_DONTDRAW (1<<7) // don't draw this entity but send it over +#define RF_LENSFLARE (1<<8) // add a lens flare to this +#define RF_EXTRALIGHT (1<<9) // use good lighting on this entity +#define RF_DETAIL (1<<10) // Culls a model based on the distance away from you +#define RF_SHADOW (1<<11) // whether or not to draw a shadow +#define RF_PORTALSURFACE (1<<12) // don't draw, but use to set portal views +#define RF_SKYORIGIN (1<<13) // don't draw, but use to set sky portal origin and coordinate system +#define RF_SKYENTITY (1<<14) // this entity is only visible through a skyportal +#define RF_LIGHTOFFSET (1<<15) // this entity has a light offset +#define RF_CUSTOMSHADERPASS (1<<16) // draw the custom shader on top of the base geometry +#define RF_MINLIGHT (1<<17) // allways have some light (viewmodel, some items) +#define RF_FULLBRIGHT (1<<18) // allways have full lighting +#define RF_LIGHTING_ORIGIN (1<<19) // use refEntity->lightingOrigin instead of refEntity->origin + // for lighting. This allows entities to sink into the floor + // with their origin going solid, and allows all parts of a + // player to get the same lighting +#define RF_SHADOW_PLANE (1<<20) // use refEntity->shadowPlane +#define RF_WRAP_FRAMES (1<<21) // mod the model frames by the maxframes to allow continuous + // animation without needing to know the frame count +#define RF_PORTALENTITY (1<<22) // this entity should only be drawn from a portal +#define RF_DUALENTITY (1<<23) // this entity is drawn both in the portal and outside it. +#define RF_ADDITIVE_DLIGHT (1<<24) // this entity has an additive dynamic light +#define RF_LIGHTSTYLE_DLIGHT (1<<25) // this entity has a dynamic light that uses a light style +#define RF_SHADOW_PRECISE (1<<26) // this entity can have a precise shadow applied to it +#define RF_INVISIBLE (1<<27) // This entity is invisible, and only negative lights will light it up +#define RF_WEAPONMODEL (1<<28) // This entity is a weapon model which is attached to players +#define RF_FORCE_ALPHA (1<<29) // Force alpha on model +#define RF_FORCE_ALPHA_EFFECTS (1<<30) // Force alpha on effects +#define RF_CHILDREN_DONT_INHERIT_ALPHA (1<<31) + +// +// use this mask when propagating renderfx from one entity to another +// +#define RF_FLAGS_NOT_INHERITED ( RF_LENSFLARE | RF_VIEWLENSFLARE | RF_BEAM | RF_EXTRALIGHT | RF_SKYORIGIN | RF_SHADOW | RF_SHADOW_PRECISE | RF_SHADOW_PLANE | RF_LIGHTOFFSET | RF_CHILDREN_DONT_INHERIT_ALPHA ) + +// +// the following flag is used by the server and is also defined in bg_public.h +// +#define PMF_CAMERA_VIEW ( 1<<8 ) // use camera view instead of ps view + +#define BEAM_LIGHTNING_EFFECT (1<<0) +#define BEAM_USEMODEL (1<<1) +#define BEAM_PERSIST_EFFECT (1<<2) +#define BEAM_SPHERE_EFFECT (1<<3) +#define BEAM_RANDOM_DELAY (1<<4) +#define BEAM_TOGGLE (1<<5) +#define BEAM_RANDOM_TOGGLEDELAY (1<<6) +#define BEAM_WAVE_EFFECT (1<<7) +#define BEAM_USE_NOISE (1<<8) +#define BEAM_PARENT (1<<9) +#define BEAM_TILESHADER (1<<10) +#define BEAM_OFFSET_ENDPOINTS (1<<11) +#define BEAM_WAVE_EFFECT2 (1<<12) +#define BEAM_FULLWAVE_EFFECT (1<<13) + + +typedef enum { + TR_STATIONARY, + TR_INTERPOLATE, // non-parametric, but interpolate between snapshots + TR_LINEAR, + TR_LINEAR_STOP, + TR_SINE, // value = base + sin( time / duration ) * delta + TR_GRAVITY, + TR_LERP // Lerp between current origin and last origin +} trType_t; + +typedef struct { + trType_t trType; + int trTime; + int trDuration; // if non 0, trTime + trDuration = stop time + vec3_t trBase; + vec3_t trDelta; // velocity, etc +} trajectory_t; + +#define MAX_MODEL_SURFACES 32 // this needs to be the same in qfiles.h for TIKI_MAX_SURFACES + +#define MDL_SURFACE_SKINOFFSET_BIT0 ( 1 << 0 ) +#define MDL_SURFACE_SKINOFFSET_BIT1 ( 1 << 1 ) +#define MDL_SURFACE_NODRAW ( 1 << 2 ) +#define MDL_SURFACE_SURFACETYPE_BIT0 ( 1 << 3 ) +#define MDL_SURFACE_SURFACETYPE_BIT1 ( 1 << 4 ) +#define MDL_SURFACE_SURFACETYPE_BIT2 ( 1 << 5 ) +#define MDL_SURFACE_CROSSFADE_SKINS ( 1 << 6 ) +#define MDL_SURFACE_SKIN_NO_DAMAGE ( 1 << 7 ) + +#define CROUCH_HEIGHT 36 +#define CROUCH_EYE_HEIGHT 30 +#define STAND_HEIGHT 72 +#define STAND_EYE_HEIGHT 66 + +#define NUM_BONE_CONTROLLERS 5 + +#define NUM_MORPH_CONTROLLERS 10 + +#define NUM_EFFECTS_ANIMS 4 + +typedef struct { + int index; + float percent; +} morph_controller_t; + +// entityState_t is the information conveyed from the server +// in an update message about entities that the client will +// need to render in some way +// Different eTypes may use the information in different ways +// The messages are delta compressed, so it doesn't really matter if +// the structure size is fairly large + +typedef struct entityState_s { + int number; // entity index + int instanceNumber; + int eType; // entityType_t + int eFlags; + + trajectory_t pos; // for calculating position + trajectory_t apos; // for calculating angles + + vec3_t netorigin; // these are the ones actually sent over + vec3_t origin; + vec3_t origin2; + + vec3_t netangles; // these are the ones actually sent over + vec3_t angles; + + vec3_t viewangles ; // player's view angles. + + unsigned int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24) + + int loopSound; // constantly loop this sound + float loopSoundVolume; + float loopSoundMinDist; + + int parent; // if this entity is attached, this is non-zero + int tag_num; // if attached, the tag number it is attached to on the parent + qboolean attach_use_angles; + vec3_t attach_offset; + vec3_t attach_angles_offset; + + int modelindex; + int viewmodelindex; + int skinNum; + int customShader; + int customEmitter; + float animationRate; + int anim; + int frame; + int crossblend_time; // in milliseconds so it can be transmitted as a short + + int torso_anim; + int torso_frame; + int torso_crossblend_time; // in milliseconds so it can be transmitted as a short + + int bone_tag[ NUM_BONE_CONTROLLERS ]; + vec3_t bone_angles[ NUM_BONE_CONTROLLERS ]; + vec4_t bone_quat[ NUM_BONE_CONTROLLERS ]; // not sent over + + morph_controller_t morph_controllers[ NUM_MORPH_CONTROLLERS ]; + + byte surfaces[MAX_MODEL_SURFACES]; + + int clientNum; // 0 to (MAX_CLIENTS - 1), for players and corpses + + int groundEntityNum; // -1 = in air + int solid; // for client side prediction, trap_linkentity sets this properly + + float scale; + float alpha; + + int renderfx; + + unsigned int affectingViewModes; + + int archeTypeIndex; + int missionObjective; + + int infoIcon; + + // This enables additional effects to be combined from other anims + int effectsAnims[ NUM_EFFECTS_ANIMS ]; + + int bindparent; // not sent over + float quat[4]; // not sent over + float mat[3][3]; // not sent over +} entityState_t; + +typedef enum { + CA_UNINITIALIZED, + CA_DISCONNECTED, // not talking to a server + CA_AUTHORIZING, // not used any more, was checking cd key + CA_CONNECTING, // sending request packets to the server + CA_CHALLENGING, // sending challenge packets to the server + CA_CONNECTED, // netchan_t established, getting gamestate + CA_LOADING, // only during cg.CG_GameStateReceived, never during main loop + CA_PRIMED, // got gamestate, waiting for first frame + CA_ACTIVE, // game views should be displayed + CA_CINEMATIC // playing a cinematic or a static pic, not connected to a server +} connstate_t; + +/* +======================================================================== + +SYSTEM MATH + +======================================================================== +*/ + +void MatrixMultiply( const float in1[3][3], const float in2[3][3], float out[3][3] ); +void AnglesToAxis( const vec3_t angles, vec3_t axis[3] ); +void AxisToAngles( vec3_t axis[3], vec3_t angles ); +void AngleVectors( const vec3_t angles, vec3_t forward, vec3_t left, vec3_t up); +void PerpendicularVector( vec3_t dst, const vec3_t src ); +void EulerToQuat( float ang[ 3 ], float q[ 4 ] ); +void MatToQuat( const float srcMatrix[ 3 ][ 3 ], float destQuat[ 4 ] ); +void QuatToMat( const float q[ 4 ], float m[ 3 ][ 3 ] ); +void MatrixToEulerAngles( const float mat[ 3 ][ 3 ], vec3_t ang ); + + +/* +======================================================================== + +TIKI + +======================================================================== +*/ + +#define TIKI_CMD_MAX_CMDS 256 +#define TIKI_CMD_MAX_ARGS 12 + +typedef struct + { + int num_args; + char *args[TIKI_CMD_MAX_ARGS]; + } tiki_singlecmd_t; + +typedef struct + { + int num_cmds; + tiki_singlecmd_t cmds[ TIKI_CMD_MAX_CMDS ]; + } tiki_cmd_t; + +typedef struct + { + vec3_t start; + vec3_t end; + vec3_t color; + float alpha; + float width; + unsigned short factor; + unsigned short pattern; + } debugline_t; + +// Added for FAKK2 +#define FLOAT_TO_INT( x, fracbits ) ( ( x ) * ( 1 << ( fracbits ) ) ) + +#define FLOAT_TO_PKT( x, dest, wholebits, fracbits ) \ + { \ + if ( ( x ) >= ( 1 << ( wholebits ) ) ) \ + { \ + ( dest ) = FLOAT_TO_INT( ( 1 << ( wholebits ) ), ( fracbits ) ) - 1; \ + } \ + else if ( ( x ) < 0 ) \ + { \ + ( dest ) = 0; \ + } \ + else \ + { \ + ( dest ) = FLOAT_TO_INT( ( x ), ( fracbits ) ); \ + } \ + } + +#define SIGNED_FLOAT_TO_PKT( x, dest, wholebits, fracbits ) \ + { \ + float temp_x; \ + temp_x = ( x ) + ( 1 << ( wholebits ) ); \ + if ( temp_x >= ( 1 << ( ( wholebits ) + 1 ) ) ) \ + ( dest ) = FLOAT_TO_INT( ( 1 << ( ( wholebits ) + 1 ) ), ( fracbits ) ) - 1; \ + else if ( temp_x < 0 ) \ + (dest) = 0; \ + else \ + ( dest ) = FLOAT_TO_INT( temp_x, ( fracbits ) ); \ + } + +#define INT_TO_FLOAT( x, wholebits, fracbits ) ( ( float )( ( ( float )( x ) ) / ( float )( 1 << ( fracbits ) ) - ( float )( 1 << ( wholebits ) ) ) ) +#define UINT_TO_FLOAT( x, fracbits ) ( ( float )( ( ( float )( x ) ) / ( float )( 1 << ( fracbits ) ) ) ) + +#define TRANSLATION_TO_PKT( x, dest ) FLOAT_TO_PKT( ( x ), ( dest ), 4, 11 ) +#define PKT_TO_TRANSLATION( x ) UINT_TO_FLOAT( ( x ), 11 ) + +#define OFFSET_TO_PKT( x, dest ) FLOAT_TO_PKT( ( x ), ( dest ), 1, 14 ) +#define PKT_TO_OFFSET( x ) UINT_TO_FLOAT( ( x ), 14 ) + +#define ROTATE_TO_PKT( x, dest ) FLOAT_TO_PKT( ( x ), ( dest ), 9, 6 ) +#define PKT_TO_ROTATE( x ) UINT_TO_FLOAT( ( x ), 6 ) + +#define BASE_TO_PKT( x, dest ) SIGNED_FLOAT_TO_PKT( ( x ), ( dest ), 3, 4 ) +#define PKT_TO_BASE( x ) INT_TO_FLOAT( ( x ), 3, 4 ) + +#define AMPLITUDE_TO_PKT( x, dest ) FLOAT_TO_PKT( ( x ), ( dest ), 4, 4 ) +#define PKT_TO_AMPLITUDE( x ) UINT_TO_FLOAT( ( x ), 4 ) + +#define PHASE_TO_PKT( x, dest ) SIGNED_FLOAT_TO_PKT( ( x ), ( dest ), 3, 4 ) +#define PKT_TO_PHASE( x ) INT_TO_FLOAT( ( x ), 3, 4 ) + +#define FREQUENCY_TO_PKT( x, dest ) FLOAT_TO_PKT( ( x ), ( dest ), 4, 4 ) +#define PKT_TO_FREQUENCY( x ) UINT_TO_FLOAT( ( x ), 4 ) + +#define BEAM_PARM_TO_PKT( x, dest ) FLOAT_TO_PKT( ( x ), ( dest ), 4, 4 ) +#define PKT_TO_BEAM_PARM( x ) UINT_TO_FLOAT( ( x ), 4 ) + +// cinematic states +typedef enum { + FMV_IDLE, + FMV_PLAY, // play + FMV_EOF, // all other conditions, i.e. stop/EOF/abort + FMV_ID_BLT, + FMV_ID_IDLE, + FMV_LOOPED, + FMV_ID_WAIT +} e_status; + +//CDKey defines +#define CDKEY_LEN 16 +#define CDCHKSUM_LEN 2 + + +#ifdef __cplusplus +} +#endif + +#endif // __Q_SHARED_H__ diff --git a/dlls/game/queue.h b/dlls/game/queue.h new file mode 100644 index 0000000..01d02c2 --- /dev/null +++ b/dlls/game/queue.h @@ -0,0 +1,268 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/queue.h $ +// $Revision:: 4 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Generic Queue object +// + +#ifndef __QUEUE_H__ +#define __QUEUE_H__ + +#include "class.h" + +class QueueNode : public Class + { + public: + void *data; + QueueNode *next; + + QueueNode(); + }; + +inline QueueNode::QueueNode() + { + data = NULL; + next = NULL; + } + +class Queue : public Class + { + private: + QueueNode *head; + QueueNode *tail; + int count; + + public: + Queue(); + ~Queue(); + void Clear( void ); + qboolean Empty( void ); + void Enqueue( void *data ); + void *Dequeue( void ); + void Remove( const void *data ); + qboolean Inqueue( const void *data ); + int IndexOfObject( const void *data ); + int GetCount(); + }; + +inline int Queue::GetCount + ( + ) + { + return count; + } + +inline qboolean Queue::Empty + ( + void + ) + + { + if ( head == NULL ) + { + assert( !tail ); + return true; + } + + assert( tail ); + return false; + } + +inline void Queue::Enqueue + ( + void *data + ) + + { + QueueNode *tmp; + + tmp = new QueueNode; + if ( !tmp ) + { + assert( NULL ); + gi.Error( ERR_DROP, "Queue::Enqueue : Out of memory" ); + } + + tmp->data = data; + + assert( !tmp->next ); + if ( !head ) + { + assert( !tail ); + head = tmp; + } + else + { + assert( tail ); + tail->next = tmp; + } + tail = tmp; + count++; + } + +inline void *Queue::Dequeue + ( + void + ) + + { + void *ptr; + QueueNode *node; + + if ( !head ) + { + assert( !tail ); + return NULL; + } + + node = head; + ptr = node->data; + + head = node->next; + if ( head == NULL ) + { + assert( tail == node ); + tail = NULL; + } + + delete node; + + count--; + return ptr; + } + +inline void Queue::Clear + ( + void + ) + + { + while( !Empty() ) + { + Dequeue(); + } + count = 0; + } + +inline Queue::Queue() + { + head = NULL; + tail = NULL; + count = 0; + } + +inline Queue::~Queue() + { + Clear(); + } + +inline void Queue::Remove + ( + const void *data + ) + + { + QueueNode *node; + QueueNode *prev; + + if ( !head ) + { + assert( !tail ); + + gi.WDPrintf( "Queue::Remove : Data not found in queue\n" ); + return; + } + + for( prev = NULL, node = head; node != NULL; prev = node, node = node->next ) + { + if ( node->data == data ) + { + break; + } + } + + if ( !node ) + { + gi.WDPrintf( "Queue::Remove : Data not found in queue\n" ); + } + else + { + if ( !prev ) + { + // at head + assert( node == head ); + head = node->next; + if ( head == NULL ) + { + assert( tail == node ); + tail = NULL; + } + } + else + { + prev->next = node->next; + if ( prev->next == NULL ) + { + // at tail + assert( tail == node ); + tail = prev; + } + } + + delete node; + count--; + } + } + +inline qboolean Queue::Inqueue + ( + const void *data + ) + + { + QueueNode *node; + + for( node = head; node != NULL; node = node->next ) + { + if ( node->data == data ) + { + return true; + } + } + + return false; + } + +inline qboolean Queue::IndexOfObject + ( + const void *data + ) + + { + int count=1; + QueueNode *node; + + for( node = head; node != NULL; node = node->next ) + { + if ( node->data == data ) + { + return count; + } + count++; + } + + return 0; + } + +#endif /* queue.h */ diff --git a/dlls/game/rangedCombatWithWeapon.cpp b/dlls/game/rangedCombatWithWeapon.cpp new file mode 100644 index 0000000..92c4dac --- /dev/null +++ b/dlls/game/rangedCombatWithWeapon.cpp @@ -0,0 +1,712 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/rangedCombatWithWeapon.cpp $ +// $Revision:: 16 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// +// ANIMATIONS: +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "rangedCombatWithWeapon.hpp" + +extern Event EV_Actor_UseWeapon; +extern Event EV_PostureChanged_Completed; + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, RangedCombatWithWeapon, NULL ) + { + { &EV_Behavior_Args, &RangedCombatWithWeapon::SetArgs }, + { &EV_Behavior_AnimDone, &RangedCombatWithWeapon::AnimDone }, + { &EV_PostureChanged_Completed, &RangedCombatWithWeapon::PostureDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: RangedCombatWithWeapon() +// Class: RangedCombatWithWeapon +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +RangedCombatWithWeapon::RangedCombatWithWeapon() +{ + _approachDist = 384.00f; + _retreatDist = 256.00f; + _strafeChance = 0.85f; + _postureChangeChance = 0.15f; +} + +//-------------------------------------------------------------- +// Name: ~RangedCombatWithWeapon() +// Class: RangedCombatWithWeapon +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +RangedCombatWithWeapon::~RangedCombatWithWeapon() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: RangedCombatWithWeapon +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::SetArgs ( Event *ev) +{ + int argCount = ev->NumArgs(); + + _aimAnim = ev->GetString( 1 ); + _fireAnim = ev->GetString( 2 ); + + if ( argCount > 2 ) + _approachDist = ev->GetFloat( 3 ); + + if ( argCount > 3 ) + _retreatDist = ev->GetFloat( 4 ); + + if ( argCount > 4 ) + _strafeChance = ev->GetFloat( 5 ); + + if ( argCount > 5 ) + _postureChangeChance = ev->GetFloat( 6 ); + +} + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: RangedCombatWithWeapon +// +// Description: Handler for Animation Done +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::AnimDone( Event *ev ) +{ + switch ( _state ) + { + case RCWW_CORRIDOR_COMBAT: + _corridorCombat.AnimDone( ev ); + break; + + case RCWW_GENERAL_COMBAT: + _generalCombat.AnimDone( ev ); + break; + + case RCWW_COVER_COMBAT: + _coverCombat.AnimDone( ev ); + break; + + case RCWW_CHANGE_WEAPON: + _selectBestWeapon.AnimDone( ev ); + break; + } +} + +void RangedCombatWithWeapon::PostureDone( Event *ev ) +{ + switch ( _state ) + { + case RCWW_CORRIDOR_COMBAT: + _corridorCombat.PostureDone( ev ); + break; + + case RCWW_GENERAL_COMBAT: + _generalCombat.PostureDone( ev ); + break; + } +} +//-------------------------------------------------------------- +// Name: Begin() +// Class: RangedCombatWithWeapon +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::Begin( Actor &self ) +{ + init( self ); + updateEnemy(); + transitionToState( RCWW_CHANGE_WEAPON ); +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: RangedCombatWithWeapon +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t RangedCombatWithWeapon::Evaluate ( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case RCWW_EVALUATE_OPTIONS: + //--------------------------------------------------------------------- + //First Check if we need to change weapons + if ( checkShouldCheckWeapon() ) + { + transitionToState( RCWW_CHANGE_WEAPON ); + return BEHAVIOR_EVALUATING; + } + + //Check if we should do corridor combat + if ( checkShouldDoCorridorCombat() ) + { + transitionToState( RCWW_CORRIDOR_COMBAT ); + return BEHAVIOR_EVALUATING; + } + + if ( checkShouldDoCoverCombat() ) + { + transitionToState( RCWW_COVER_COMBAT ); + return BEHAVIOR_EVALUATING; + } + + //None of the above, so we need to do general combat + _recheckTime = level.time + G_Random() + 3.0f; + transitionToState( RCWW_GENERAL_COMBAT ); + + break; + + //--------------------------------------------------------------------- + case RCWW_CHANGE_WEAPON: + //--------------------------------------------------------------------- + stateResult = evaluateStateChangeWeapon(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( RCWW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( RCWW_EVALUATE_OPTIONS ); + + break; + + //--------------------------------------------------------------------- + case RCWW_GENERAL_COMBAT: + //--------------------------------------------------------------------- + //Check if we need to change weapons + if ( checkShouldCheckWeapon() ) + { + transitionToState( RCWW_CHANGE_WEAPON ); + return BEHAVIOR_EVALUATING; + } + + if ( level.time >= _recheckTime ) + { + transitionToState( RCWW_EVALUATE_OPTIONS ); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateStateGeneralCombat(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( RCWW_FAILED ); + break; + + //--------------------------------------------------------------------- + case RCWW_CORRIDOR_COMBAT: + //--------------------------------------------------------------------- + //First Check if we need to change weapons + if ( checkShouldCheckWeapon() ) + { + transitionToState( RCWW_CHANGE_WEAPON ); + return BEHAVIOR_EVALUATING; + } + + stateResult = evaluateStateCorridorCombat(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( RCWW_GENERAL_COMBAT ); + break; + + //--------------------------------------------------------------------- + case RCWW_COVER_COMBAT: + //--------------------------------------------------------------------- + //First Check if we need to changing weapons during cover combat + //is a pain. I'm taking it out for now. + + + stateResult = evaluateStateCoverCombat(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( RCWW_GENERAL_COMBAT ); + break; + + //--------------------------------------------------------------------- + case RCWW_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + break; + } + + return BEHAVIOR_EVALUATING; +} + + +//-------------------------------------------------------------- +// Name: End() +// Class: RangedCombatWithWeapon +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::End ( Actor &self ) +{ + _generalCombat.End( self ); + _corridorCombat.End( self ); + _coverCombat.End( self ); + self.postureController->setPostureState( "STAND" , "STAND" ); +} + + + +//-------------------------------------------------------------- +// Name: init() +// Class: RangedCombatWithWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::init(Actor &self) +{ + _self = &self; + _nextSelectWeaponTime = 0.0f; + _recheckTime = -1.0f; +} + +//-------------------------------------------------------------- +// Name: think() +// Class: RangedCombatWithWeapon +// +// Description: Think Function Called Every Frame +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::think() +{ +} + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: RangedCombatWithWeapon +// +// Description: Updates our Current Enemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::updateEnemy() +{ + _currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !_currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + _currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !_currentEnemy ) + { + SetFailureReason( "RangedCombatWithWeapon::updateEnemy -- No Enemy" ); + transitionToState( RCWW_FAILED ); + } + + } +} + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: RangedCombatWithWeapon +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::transitionToState( RangedCombatWithWeapon_t state ) +{ + switch ( state ) + { + case RCWW_EVALUATE_OPTIONS: + setupStateEvaluateOptions(); + setInternalState( state , "RCWW_EVALUATE_OPTIONS" ); + break; + + case RCWW_CHANGE_WEAPON: + setupStateChangeWeapon(); + setInternalState( state , "RCWW_CHANGE_WEAPON" ); + break; + + case RCWW_GENERAL_COMBAT: + setupStateGeneralCombat(); + setInternalState( state , "RCWW_GENERAL_COMBAT" ); + break; + + case RCWW_CORRIDOR_COMBAT: + setupStateCorridorCombat(); + setInternalState( state , "RCWW_CORRIDOR_COMBAT" ); + break; + + case RCWW_COVER_COMBAT: + setupStateCoverCombat(); + setInternalState( state , "RCWW_COVER_COMBAT" ); + break; + + case RCWW_FAILED: + setInternalState( state , "RCWW_FAILED" ); + break; + } +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: RangedCombatWithWeapon +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::setInternalState( RangedCombatWithWeapon_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: setupStateChangeWeapon() +// Class: RangedCombatWithWeapon +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::setupStateChangeWeapon() +{ + _nextSelectWeaponTime = level.time + G_Random() + 5.0f; + _selectBestWeapon.SetCurrentEnemy( _currentEnemy ); + _selectBestWeapon.Begin( *_self ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateChangeWeapon() +// Class: RangedCombatWithWeapon +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t RangedCombatWithWeapon::evaluateStateChangeWeapon() +{ + return _selectBestWeapon.Evaluate( *_self ); +} + +//-------------------------------------------------------------- +// Name: failureStateChangeWeapon() +// Class: RangedCombatWithWeapon +// +// Description: Failure Handler for State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::failureStateChangeWeapon() +{ +} + +void RangedCombatWithWeapon::setupStateEvaluateOptions() +{ +} + +BehaviorReturnCode_t RangedCombatWithWeapon::evaluateStateEvaluateOptions() +{ + return BEHAVIOR_EVALUATING; +} + +void RangedCombatWithWeapon::failureStateEvaluateOptions() +{ +} + +//-------------------------------------------------------------- +// Name: setupStateGeneralCombat() +// Class: RangedCombatWithWeapon +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::setupStateGeneralCombat() +{ + _generalCombat.SetTorsoAnim( _aimAnim ); + _generalCombat.SetFireAnim( _fireAnim ); + _generalCombat.SetApproachDist( _approachDist ); + _generalCombat.SetRetreatDist ( _retreatDist ); + _generalCombat.SetStrafeChance ( _strafeChance ); + _generalCombat.SetPostureChangeChance ( _postureChangeChance ); + _generalCombat.SetFireTimeMin( 2.0f ); + _generalCombat.SetFireTimeMax( 2.5f ); + _generalCombat.SetPauseTimeMin( 0.5f ); + _generalCombat.SetPauseTimeMax( 0.75f ); + + _generalCombat.Begin( *_self ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateGeneralCombat() +// Class: RangedCombatWithWeapon +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t RangedCombatWithWeapon::evaluateStateGeneralCombat() +{ + return _generalCombat.Evaluate( *_self ); + +} + +//-------------------------------------------------------------- +// Name: failureStateGeneralCombat() +// Class: RangedCombatWithWeapon +// +// Description: Failure Handler for State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::failureStateGeneralCombat() +{ +} + + +//-------------------------------------------------------------- +// Name: setupStateCorridorCombat() +// Class: RangedCombatWithWeapon +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::setupStateCorridorCombat() +{ + _corridorCombat.SetMovementAnim( "walk_rifle" ); + _corridorCombat.SetTorsoAnim( _aimAnim ); + _corridorCombat.SetFireAnim( _fireAnim ); + _corridorCombat.SetPreFireAnim( "" ); + _corridorCombat.SetPostFireAnim( "" ); + _corridorCombat.SetPostureChangeChance( .65 ); + _corridorCombat.SetMaxDistance( 128.0f ); + _corridorCombat.SetRetreatDistance( 96.0f ); + _corridorCombat.SetThreatDistance( 160.0f ); + _corridorCombat.SetFireTimeMin( 2.0f ); + _corridorCombat.SetFireTimeMax( 2.5f ); + _corridorCombat.SetPauseTimeMin( 0.5f ); + _corridorCombat.SetPauseTimeMax( 0.75f ); + + _corridorCombat.Begin( *_self ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateCorridorCombat() +// Class: RangedCombatWithWeapon +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t RangedCombatWithWeapon::evaluateStateCorridorCombat() +{ + return _corridorCombat.Evaluate( *_self ); + +} + +//-------------------------------------------------------------- +// Name: failureStateCorridorCombat() +// Class: RangedCombatWithWeapon +// +// Description: Failure Handler for State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::failureStateCorridorCombat() +{ +} + +//-------------------------------------------------------------- +// Name: setupStateCoverCombat() +// Class: RangedCombatWithWeapon +// +// Description: Sets up state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void RangedCombatWithWeapon::setupStateCoverCombat() +{ + _coverCombat.SetMovementAnim( "run" ); + _coverCombat.SetTorsoAnim( _aimAnim ); + _coverCombat.SetFireAnim( _fireAnim ); + _coverCombat.SetMaxDistance( 1024.0f ); + _coverCombat.SetFireTimeMin( 2.0f ); + _coverCombat.SetFireTimeMax( 2.5f ); + _coverCombat.SetPauseTimeMin( 0.5f ); + _coverCombat.SetPauseTimeMax( 0.75f ); + _coverCombat.Begin( *_self ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateCoverCombat() +// Class: RangedComb +// +// Description: +// +// Parameters: +// +// Returns: +//-------------------------------------------------------------- +BehaviorReturnCode_t RangedCombatWithWeapon::evaluateStateCoverCombat() +{ + return _coverCombat.Evaluate( *_self ); +} + +void RangedCombatWithWeapon::failureStateCoverCombat() +{ +} + +//-------------------------------------------------------------- +// Name: checkShouldDoCorridorCombat() +// Class: RangedCombatWithWeapon +// +// Description: Checks if the actor should do corridor combat +// +// Parameters: None +// +// Returns: true or false; +//-------------------------------------------------------------- +bool RangedCombatWithWeapon::checkShouldDoCorridorCombat() +{ + if ( CorridorCombatWithRangedWeapon::CanExecute( 1024.0f , *_self ) ) + return true; + + return false; +} + +//-------------------------------------------------------------- +// Name: checkShouldDoGeneralCombat() +// Class: RangedCombatWithWeapon +// +// Description: Checks if the actor should do general combat +// +// Parameters: None +// +// Returns: true or false +//-------------------------------------------------------------- +bool RangedCombatWithWeapon::checkShouldDoGeneralCombat() +{ + // We're pretty much ALWAYS able to do general combat... + // That's what its for + return true; +} + +//-------------------------------------------------------------- +// Name: checkShouldCheckWeapon() +// Class: RangedCombatWithWeapon +// +// Description: Checks if the actor should do SelectBestWeapon +// +// Parameters: None +// +// Returns: True or False +//-------------------------------------------------------------- +bool RangedCombatWithWeapon::checkShouldCheckWeapon() +{ + if ( (level.time > _nextSelectWeaponTime) && SelectBestWeapon::CanExecute( *_self ) ) + { + if ( _self->checkHaveBestWeapon() ) + { + _nextSelectWeaponTime = level.time + G_Random() + 5.0f; + return false; + } + + return true; + } + + return false; +} + +bool RangedCombatWithWeapon::checkShouldDoCoverCombat() +{ + return CoverCombatWithRangedWeapon::CanExecute( *_self , 1024.0f ); + +} + diff --git a/dlls/game/rangedCombatWithWeapon.hpp b/dlls/game/rangedCombatWithWeapon.hpp new file mode 100644 index 0000000..56ab4fc --- /dev/null +++ b/dlls/game/rangedCombatWithWeapon.hpp @@ -0,0 +1,191 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/rangedCombatWithWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: Bschofield $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// GeneralCombatWithMeleeWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class GeneralCombatWithRangedWeapon; + +#ifndef __RANGEDCOMBAT_WITH_WEAPON_HPP__ +#define __RANGEDCOMBAT_WITH_WEAPON_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" +#include "generalCombatWithRangedWeapon.hpp" +#include "corridorCombatWithRangedWeapon.hpp" +#include "coverCombatWithRangedWeapon.hpp" +#include "selectBestWeapon.hpp" + + +//------------------------- CLASS ------------------------------ +// +// Name: RangedCombatWithWeapon +// Base Class: Behavior +// +// Description: Template that can be copied and pasted to +// generate new behavior +// +// Method of Use: State machine or another behavior +//-------------------------------------------------------------- + +class RangedCombatWithWeapon : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + RCWW_EVALUATE_OPTIONS, + RCWW_CHANGE_WEAPON, + RCWW_GENERAL_COMBAT, + RCWW_CORRIDOR_COMBAT, + RCWW_COVER_COMBAT, + RCWW_FAILED, + } RangedCombatWithWeapon_t; + + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _aimAnim; // --Animation used to aim the weapon + str _fireAnim; // --The animation used to fire the weapon + float _approachDist; + float _retreatDist; + float _strafeChance; + float _postureChangeChance; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( RangedCombatWithWeapon_t state ); + void setInternalState ( RangedCombatWithWeapon_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + + bool checkShouldDoCorridorCombat(); + bool checkShouldDoGeneralCombat(); + bool checkShouldDoCoverCombat(); + bool checkShouldCheckWeapon(); + + void setupStateEvaluateOptions(); + BehaviorReturnCode_t evaluateStateEvaluateOptions(); + void failureStateEvaluateOptions(); + + void setupStateChangeWeapon(); + BehaviorReturnCode_t evaluateStateChangeWeapon(); + void failureStateChangeWeapon(); + + void setupStateGeneralCombat(); + BehaviorReturnCode_t evaluateStateGeneralCombat(); + void failureStateGeneralCombat(); + + void setupStateCoverCombat(); + BehaviorReturnCode_t evaluateStateCoverCombat(); + void failureStateCoverCombat(); + + void setupStateCorridorCombat(); + BehaviorReturnCode_t evaluateStateCorridorCombat(); + void failureStateCorridorCombat(); + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( RangedCombatWithWeapon ); + + RangedCombatWithWeapon(); + ~RangedCombatWithWeapon(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void PostureDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + virtual void Archive ( Archiver &arc ); + + // Mutators + void SetFireAnim ( const str &anim ); + + + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + // Component Behaviors + CloseInOnEnemy _gotoEnemy; + GeneralCombatWithRangedWeapon _generalCombat; + CorridorCombatWithRangedWeapon _corridorCombat; + CoverCombatWithRangedWeapon _coverCombat; + SelectBestWeapon _selectBestWeapon; + + + // Member Vars + int _state; //-- Maintains our Behavior's current State + Actor* _self; + EntityPtr _currentEnemy; + float _nextSelectWeaponTime; + float _recheckTime; + // Constants + + }; + + +inline void RangedCombatWithWeapon::Archive( Archiver &arc ) + { + Behavior::Archive( arc ); + + // Archive Parameters + arc.ArchiveString ( &_aimAnim ); + arc.ArchiveString ( &_fireAnim ); + arc.ArchiveFloat ( &_approachDist ); + arc.ArchiveFloat ( &_retreatDist ); + arc.ArchiveFloat ( &_strafeChance ); + arc.ArchiveFloat ( &_postureChangeChance ); + + // Archive Components + arc.ArchiveObject ( &_gotoEnemy ); + arc.ArchiveObject ( &_generalCombat ); + arc.ArchiveObject ( &_corridorCombat ); + arc.ArchiveObject ( &_coverCombat ); + arc.ArchiveObject ( &_selectBestWeapon ); + + // Archive Member Variables + arc.ArchiveInteger ( &_state ); + arc.ArchiveObjectPointer ( ( Class ** )&_self ); + arc.ArchiveSafePointer( &_currentEnemy ); + arc.ArchiveFloat ( &_nextSelectWeaponTime ); + arc.ArchiveFloat ( &_recheckTime ); + } + +inline void RangedCombatWithWeapon::SetFireAnim( const str &anim ) + { + _fireAnim = anim; + } + +#endif /* __RANGEDCOMBAT_WITH_WEAPON_HPP__ */ diff --git a/dlls/game/rotateToEntity.cpp b/dlls/game/rotateToEntity.cpp new file mode 100644 index 0000000..7c1a1b9 --- /dev/null +++ b/dlls/game/rotateToEntity.cpp @@ -0,0 +1,240 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/rotateToEntity.cpp $ +// $Revision:: 12 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// RotateToEntity Behavior Implementation. +// -- Rotates the actor to face an entity. If no entity or entityType is specified, then it +// will rotate the actor to face its currentEnemy +// +// PARAMETERS: +// Optional turnspeed +// Optional entityType to turn to, either "enemy" (default) or "player" +// +// ANIMATIONS: +// Rotation Animation : Optional +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "rotateToEntity.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, RotateToEntity, NULL ) + { + { &EV_Behavior_Args, &RotateToEntity::SetArgs }, + { NULL, NULL } + }; + + + +//-------------------------------------------------------------- +// Name: RotateToEntity() +// Class: RotateToEntity +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +RotateToEntity::RotateToEntity() +{ + _anim = ""; + _turnspeed = 10.0f; + _ent = NULL; + _entityType = "enemy"; +} + + +//-------------------------------------------------------------- +// Name: ~RotateToEntity() +// Class: RotateToEntity +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +RotateToEntity::~RotateToEntity() +{ +} + +//-------------------------------------------------------------- +// +// Name: SetArgs() +// Class: RotateToEntity +// +// Description: Sets Variables based on arguments inside the event +// +// Parameters: Event *ev -- Event containing the string +// +// Returns: None +// +//-------------------------------------------------------------- +void RotateToEntity::SetArgs( Event *ev ) + { + // The Parmeters we're catching now are for turn speed + // and Animation ( both of which are optional ). We do not + // catch an entity here, because, I don't think there's a way + // to pass in an entity from the state machine. However, other + // behaviors CAN set the entity, so the functionality is here + // We also now take an entityType to turn to ("player" or "enemy") + + int parmCount= ev->NumArgs(); + + if ( parmCount > 0 ) // Grab the turnSpeed + _turnspeed = ev->GetFloat( 1 ); + + if ( parmCount > 1 ) // Grab the animation + _anim = ev->GetString( 2 ); + + if ( parmCount > 2 ) // Grab the type of entity to find + _entityType = ev->GetString( 3 ); + + } + + + +//-------------------------------------------------------------- +// +// Name: Begin() +// Class: RotateToEntity +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void RotateToEntity::Begin( Actor &self ) + { + + // Parameters should be set BEFORE we call begin, either through + // the SetArgs() or explicitly set through accessors + if ( !_ent ) + { + if ( _entityType == "enemy" ) + { + self.enemyManager->FindHighestHateEnemy(); + _ent = self.enemyManager->GetCurrentEnemy(); + } + else + { + Entity *player = (Entity*)GetPlayer(0); + + if ( player && !(player->flags & FL_NOTARGET) ) + { + _ent = player; + } + } + } + + // Set our Animation if Appropriate + if ( _anim.length() && self.animate->HasAnim( _anim.c_str() ) ) + self.SetAnim( _anim ); + + // Set up our turnSpeed + _oldTurnSpeed = self.movementSubsystem->getTurnSpeed(); + self.movementSubsystem->setTurnSpeed( _turnspeed ); + + } + + +//-------------------------------------------------------------- +// +// Name: Evaluate() +// Class: RotateToEntity +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: BehaviorReturnCode_t +// +//-------------------------------------------------------------- +BehaviorReturnCode_t RotateToEntity::Evaluate( Actor &self ) + { + Vector dir; + Vector selfToEntity; + + float yawDiff; + + if ( !_ent ) + { + SetFailureReason( "RotateToEntity: Has no Entity to Rotate To" ); + //self.SetAnim( "idle" ); + return BEHAVIOR_FAILED; + } + + // + // Previously, we were just steering towards _ent->centroid. This worked, + // however it also has the unfortunate side-effect of pitching the actor + // if the entity it is rotating to is above it. This, of course, looks + // like poo... So, for an interim fix, we're going to set our target + // destination's z value to the actor's centroid z value, this, should + // prevent the pitch problem + // + Vector entityPos; + entityPos = _ent->centroid; + entityPos.z = self.centroid.z; + + if ( self.IsEntityAlive( _ent ) ) + { + dir = self.movementSubsystem->getMoveDir(); + self.movementSubsystem->Accelerate( + self.movementSubsystem->SteerTowardsPoint( entityPos, vec_zero, dir, 1.0f) + ); + } + + // + // Here, again, I replaced the _ent->centroid with the z-value modified entityPos + // + selfToEntity = entityPos - self.centroid; + selfToEntity = selfToEntity.toAngles(); + + yawDiff = selfToEntity[YAW] - self.angles[YAW]; + + yawDiff = AngleNormalize180(yawDiff); + if ( yawDiff > -1.5 && yawDiff < 1.5 ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; + + + } + + + +//-------------------------------------------------------------- +// +// Name: End() +// Class: RotateToEntity +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +// +//-------------------------------------------------------------- +void RotateToEntity::End(Actor &self) +{ + self.movementSubsystem->setTurnSpeed( _oldTurnSpeed ); +} + diff --git a/dlls/game/rotateToEntity.hpp b/dlls/game/rotateToEntity.hpp new file mode 100644 index 0000000..e545d9a --- /dev/null +++ b/dlls/game/rotateToEntity.hpp @@ -0,0 +1,123 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/rotateToEntity.h $ +// $Revision:: 169 $ +// $Author:: Bschofield $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// RotateToEntity Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class RotateToEntity; + +#ifndef __ROTATE_TO_ENTITY_H__ +#define __ROTATE_TO_ENTITY_H__ + +#include "behavior.h" + +//------------------------- CLASS ------------------------------ +// +// Name: RotateToEntity +// Base Class: Behavior +// +// Description: Rotates the actor to face the passed in entity +// +// Method of Use: Should be aggregated by other behaviors +//-------------------------------------------------------------- +class RotateToEntity : public Behavior + { + + //------------------------------------ + // Parameters + //------------------------------------ + private: + float _turnspeed; + EntityPtr _ent; + str _anim; + str _entityType; + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( RotateToEntity ); + + RotateToEntity(); + ~RotateToEntity(); + + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + void SetAnim ( const str& animName ); + void SetTurnSpeed ( float turnSpeed ); + void SetEntity ( Entity *entity ); + void SetEntityType( const str &ent ); + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + float _oldTurnSpeed; + + + }; + +inline void RotateToEntity::SetTurnSpeed( float turnSpeed ) + { + _turnspeed = turnSpeed; + } + + +inline void RotateToEntity::SetEntity( Entity *entity ) + { + if ( entity ) + _ent = entity; + } + +inline void RotateToEntity::SetAnim( const str &animName ) + { + _anim = animName; + } + +inline void RotateToEntity::SetEntityType( const str &ent ) + { + if ( ent == "player" || ent == "enemy" ) + _entityType = ent; + } + +inline void RotateToEntity::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + //------------------------------------- + // Archive Parameters + //------------------------------------- + arc.ArchiveFloat ( &_turnspeed ); + arc.ArchiveSafePointer ( &_ent ); + arc.ArchiveString ( &_anim ); + arc.ArchiveString( &_entityType ); + + //------------------------------------- + // Archive Member Variables + //------------------------------------- + arc.ArchiveFloat ( &_oldTurnSpeed ); +} + + +#endif /* __ROTATE_TO_ENTITY_H__ */ diff --git a/dlls/game/script.cpp b/dlls/game/script.cpp new file mode 100644 index 0000000..2735ac6 --- /dev/null +++ b/dlls/game/script.cpp @@ -0,0 +1,1104 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/script.cpp $ +// $Revision:: 18 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:11a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// C++ implementaion of tokenizing text interpretation. Class accepts filename +// to load or pointer to preloaded text data. Standard tokenizing operations +// such as skip white-space, get string, get integer, get float, get token, +// and skip line are implemented. +// +// Note: all '//', '#', and ';' are treated as comments. Probably should +// make this behaviour toggleable. +// + +#include "_pch_cpp.h" +#include "script.h" +#include + +#define TOKENCOMMENT (';') +#define TOKENCOMMENT2 ('#') +#define TOKENEOL ('\n') +//#define TOKENNULL ('\0') +#define TOKENSPACE (' ') +#define TOKENSPECIAL ('$') + +CLASS_DECLARATION( Class, Script, NULL ) +{ + { NULL, NULL } +}; + +Script::~Script() +{ + Close(); +} + +Script::Script(const char* filename /*= 0*/) +{ + buffer = NULL; + script_p = NULL; + end_p = NULL; + line = 0; + length = 0; + releaseBuffer = false; + tokenready = false; + memset( token, 0, sizeof( token ) ); + + if ( filename != 0 ) + LoadFile(filename); +} + +void Script::Close( void ) +{ + if ( releaseBuffer && buffer ) + { + gi.Free( ( void * )buffer ); + } + + buffer = NULL; + script_p = NULL; + end_p = NULL; + line = 0; + releaseBuffer = false; + tokenready = false; + memset( token, 0, sizeof( token ) ); + + //Loop Through the macro container and delete (del33t -hehe) them all + for( int i = 1; i <= macrolist.NumObjects(); i++) + { + if (macrolist.ObjectAt( i ) ) + { + delete macrolist.ObjectAt( i ); + macrolist.ObjectAt( i ) = 0; + } + } +} + +/* +============== += += Filename += +============== +*/ + +const char *Script::Filename( void ) +{ + return filename.c_str(); +} + +/* +============== += += GetLineNumber += +============== +*/ + +int Script::GetLineNumber( void ) +{ + return line; +} + +/* +============== += += Reset += +============== +*/ + +void Script::Reset( void ) +{ + script_p = buffer; + line = 1; + tokenready = false; +} + +/* +============== += += MarkPosition += +============== +*/ + +void Script::MarkPosition( scriptmarker_t *mark ) +{ + assert( mark ); + + mark->tokenready = tokenready; + mark->offset = script_p - buffer; + mark->line = line; + strcpy( mark->token, token ); +} + +/* +============== += += RestorePosition += +============== +*/ + +void Script::RestorePosition( const scriptmarker_t *mark ) +{ + assert( mark ); + + tokenready = mark->tokenready; + script_p = buffer + mark->offset; + line = mark->line; + strcpy( token, mark->token ); + + assert( script_p <= end_p ); + if ( script_p > end_p ) + { + script_p = end_p; + } +} + +/* +============== += += SkipToEOL += +============== +*/ + +qboolean Script::SkipToEOL( void ) +{ + if ( script_p >= end_p ) + { + return true; + } + + while( *script_p != TOKENEOL ) + { + if ( script_p >= end_p ) + { + return true; + } + script_p++; + } + return false; +} + +/* +============== += += CheckOverflow += +============== +*/ + +void Script::CheckOverflow( void ) +{ + if ( script_p >= end_p ) + { + gi.Error( ERR_DROP, "End of token file reached prematurely reading %s\n", filename.c_str() ); + } +} + +/* +============== += += SkipWhiteSpace += +============== +*/ + +void Script::SkipWhiteSpace( qboolean crossline ) +{ + // + // skip space + // + CheckOverflow(); + + while( *script_p <= TOKENSPACE ) + { + if ( *script_p++ == TOKENEOL ) + { + if ( !crossline ) + { + gi.Error( ERR_DROP, "Line %i is incomplete in file %s\n", line, filename.c_str() ); + } + + line++; + } + CheckOverflow(); + } +} + +qboolean Script::AtComment( void ) +{ + if ( script_p >= end_p ) + { + return false; + } + + if ( *script_p == TOKENCOMMENT ) + { + return true; + } + + if ( *script_p == TOKENCOMMENT2 ) + { + return true; + } + + // Two or more character comment specifiers + if ( ( script_p + 1 ) >= end_p ) + { + return false; + } + + if ( ( *script_p == '/' ) && ( *( script_p + 1 ) == '/' ) ) + { + return true; + } + + return false; +} + +/* +============== += += SkipNonToken += +============== +*/ + +void Script::SkipNonToken( qboolean crossline ) +{ + // + // skip space and comments + // + SkipWhiteSpace( crossline ); + while( AtComment() ) + { + SkipToEOL(); + SkipWhiteSpace( crossline ); + } +} + +/* +============================================================================= += += Token section += +============================================================================= +*/ + +/* +============== += += TokenAvailable += +============== +*/ + +qboolean Script::TokenAvailable( qboolean crossline ) +{ + if ( script_p >= end_p ) + { + return false; + } + + while ( 1 ) + { + while ( *script_p <= TOKENSPACE ) + { + if ( *script_p == TOKENEOL ) + { + if ( !crossline ) + { + return( false ); + } + line++; + } + + script_p++; + if ( script_p >= end_p ) + { + return false; + } + } + + if ( AtComment() ) + { + qboolean done; + + done = SkipToEOL(); + if ( done ) + { + return false; + } + } + else + { + break; + } + } + + return true; +} + +/* +============== += += CommentAvailable += +============== +*/ + +qboolean Script::CommentAvailable( qboolean crossline ) +{ + const char *searchptr; + + searchptr = script_p; + + if ( searchptr >= end_p ) + { + return false; + } + + while ( *searchptr <= TOKENSPACE ) + { + if ( ( *searchptr == TOKENEOL ) && ( !crossline ) ) + { + return false; + } + searchptr++; + if ( searchptr >= end_p ) + { + return false; + } + } + + return true; +} + +/* +============== += += UnGet += += Signals that the current token was not used, and should be reported += for the next GetToken. Note that + +GetToken (true); +UnGetToken (); +GetToken (false); + += could cross a line boundary. += +============== +*/ + +void Script::UnGetToken( void ) +{ + tokenready = true; +} + +/* +============== += += Get += +============== +*/ +qboolean Script::AtString( qboolean crossline ) +{ + // + // skip space + // + SkipNonToken( crossline ); + + return ( *script_p == '"' ); +} + +qboolean Script::AtOpenParen( qboolean crossline ) +{ + // + // skip space + // + SkipNonToken( crossline ); + + return ( *script_p == '(' ); +} + +qboolean Script::AtCloseParen( qboolean crossline ) +{ + // + // skip space + // + SkipNonToken( crossline ); + + return ( *script_p == ')' ); +} + +/* +============== += += Get += +============== +*/ + +const char *Script::GetToken( qboolean crossline ) +{ + + str token_p = token; + qboolean is_Macro = false; + + // is a token already waiting? + if ( tokenready ) + { + tokenready = false; + return token; + } + + is_Macro = isMacro(); + + token_p = GrabNextToken(crossline); + + if ( is_Macro && ( token_p != "$include" ) ) + { + + //Check to see if we need to add any definitions + while ( ( token_p == "$define" ) || ( token_p == "$Define" ) ) + { + AddMacroDefinition(crossline); + is_Macro = isMacro(); + //if ( !is_Macro ) + // return ""; + token_p = GrabNextToken(crossline); + } + + //Check to see if we need return any defines strings + if( is_Macro && ( token_p != "$include" ) && ( token_p[token_p.length() - 1] == '$' ) ) + { + return GetMacroString(token_p); + } + + } + + return token; +} + +/* +============== += += GrabNextToken += +============== +*/ +const char *Script::GrabNextToken( qboolean crossline ) +{ + char *token_p; + + // + // skip space + // + SkipNonToken( crossline ); + + // + // copy token + // + if ( *script_p == '"' ) + { + return GetString( crossline ); + } + + token_p = token; + while( ( *script_p > TOKENSPACE ) && !AtComment() ) + { + if ( ( *script_p == '\\' ) && ( script_p < ( end_p - 1 ) ) ) + { + script_p++; + switch( *script_p ) + { + case 'n' : *token_p++ = '\n'; break; + case 'r' : *token_p++ = '\n'; break; + case '\'' : *token_p++ = '\''; break; + case '\"' : *token_p++ = '\"'; break; + case '\\' : *token_p++ = '\\'; break; + default: *token_p++ = *script_p; break; + } + script_p++; + } + else + { + *token_p++ = *script_p++; + } + + if ( token_p == &token[ MAXTOKEN ] ) + { + gi.Error( ERR_DROP, "Token too large on line %i in file %s\n", line, filename.c_str() ); + } + + if ( script_p == end_p ) + { + break; + } + } + + + *token_p = 0; + + return token; +} + +/* +============== += += AddMacroDefinition += +============== +*/ +void Script::AddMacroDefinition( qboolean crossline ) +{ + macro *theMacro; + + //Create a new macro structure. This new macro will be deleted in the script close() + theMacro = new macro; + + //Grab the macro name + theMacro->macroName = '$'; + theMacro->macroName.append(GrabNextToken(crossline)); + theMacro->macroName.append('$'); //<-- Adding closing ($) to keep formatting consistant + + //Grab the macro string + str tmpstr; + tmpstr = GrabNextToken(crossline); + //Check to see if we need return any defines strings + if( ( tmpstr != "$include" ) && ( tmpstr[tmpstr.length() - 1] == '$' ) ) + theMacro->macroText = GetMacroString(tmpstr); + else + theMacro->macroText = tmpstr; + + macrolist.AddObject( theMacro ); + +} + +/* +============== += += GetMacroString += +============== +*/ +const char *Script::GetMacroString( const char *theMacroName ) +{ + + macro *theMacro =0; //Initialize this puppy + + for( int i = 1; i <= macrolist.NumObjects(); i++) + { + theMacro = macrolist.ObjectAt( i ); + + if(!theMacro->macroName.cmp(theMacro->macroName.c_str(), theMacroName)) + { + const char *text = theMacro->macroText.c_str(); + + // If our define value is another define... + if( text[0] == '$' ) + return EvaluateMacroString(text); + else + return text; + } + + } + + char tmpstr[255], *sptr = tmpstr; + strcpy(tmpstr, theMacroName); + tmpstr[strlen(tmpstr)-1] = 0; + sptr++; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( gpm->isDefined(sptr) ) + return gpm->getDefine(sptr).c_str(); + + // We didn't find what we were looking for + gi.Error( ERR_DROP, "No Macro Text found for %s in file %s\n", theMacroName, filename.c_str() ); + return 0; + +} + +//================================================================ +// Name: AddMacro +// Class: Script +// +// Description: Adds a macro to the definitions list. +// +// Parameters: const char *name -- Name of the macro +// const char *value -- Value +// +// Returns: None +// +//================================================================ +void Script::AddMacro(const char *name, const char *value) +{ + +} + +/* +============== += += EvaluateMacroString += +============== +*/ +char *Script::EvaluateMacroString( const char *theMacroString ) +{ + static char evalText[255]; + char buffer[255], *bufferptr = buffer, oper = '+', newoper = '+'; + bool haveoper = false; + int i; + float value = 0.0f, val = 0.0f; + memset(buffer, 0, 255); + + for ( i=0;i<=strlen(theMacroString);i++ ) + { + if ( theMacroString[i] == '+' ) { haveoper = true; newoper = '+'; } + if ( theMacroString[i] == '-' ) { haveoper = true; newoper = '-'; } + if ( theMacroString[i] == '*' ) { haveoper = true; newoper = '*'; } + if ( theMacroString[i] == '/' ) { haveoper = true; newoper = '/'; } + if ( theMacroString[i] == 0 ) haveoper = true; + + if ( haveoper ) + { + if ( buffer[0] == '$' ) + val = atof(GetMacroString(buffer)); + else + val = atof(buffer); + + value = EvaluateMacroMath(value, val, oper); + oper = newoper; + + // Reset everything + haveoper = false; + memset(buffer, 0, 255); + bufferptr = buffer; + continue; + } + + *bufferptr = theMacroString[i]; + bufferptr++; + } + + sprintf(evalText,"%f",value); + return evalText; +} + +/* +============== += += EvaluateMacroMath += +============== +*/ +float Script::EvaluateMacroMath(float value, float newval, char oper) +{ + switch ( oper ) + { + case '+' : value += newval; break; + case '-' : value -= newval; break; + case '*' : value *= newval; break; + case '/' : value /= newval; break; + } + + return value; +} + +/* +============== += += isMacro += +============== +*/ +qboolean Script::isMacro( void ) +{ + if ( !TokenAvailable( true ) ) + return false; + + SkipNonToken( true ); + if ( *script_p == TOKENSPECIAL ) + { + return true; + } + + return false; +} + +/* +============== += += GetLine += +============== +*/ + +const char *Script::GetLine( qboolean crossline ) +{ + const char *start; + int size; + + // is a token already waiting? + if ( tokenready ) + { + tokenready = false; + return token; + } + + // + // skip space + // + SkipNonToken( crossline ); + + // + // copy token + // + start = script_p; + SkipToEOL(); + size = script_p - start; + if ( size < ( MAXTOKEN - 1 ) ) + { + memcpy( token, start, size ); + token[ size ] = '\0'; + } + else + { + gi.Error( ERR_DROP, "Token too large on line %i in file %s\n", line, filename.c_str() ); + } + + return token; +} + +/* +============== += += GetRaw += +============== +*/ + +const char *Script::GetRaw( void ) +{ + const char *start; + int size; + + // + // skip white space + // + SkipWhiteSpace( true ); + + // + // copy token + // + start = script_p; + SkipToEOL(); + size = script_p - start; + if ( size < ( MAXTOKEN - 1 ) ) + { + memset( token, 0, sizeof( token ) ); + memcpy( token, start, size ); + } + else + { + gi.Error( ERR_DROP, "Token too large on line %i in file %s\n", line, filename.c_str() ); + } + + return token; +} + +/* +============== += += GetString += +============== +*/ + +const char *Script::GetString( qboolean crossline ) +{ + int startline; + char *token_p; + + // is a token already waiting? + if ( tokenready ) + { + tokenready = false; + return token; + } + + // + // skip space + // + SkipNonToken( crossline ); + + if ( *script_p != '"' ) + { + gi.Error( ERR_DROP, "Expecting string on line %i in file %s\n", line, filename.c_str() ); + } + + script_p++; + + startline = line; + token_p = token; + while( *script_p != '"' ) + { + if ( *script_p == TOKENEOL ) + { + gi.Error( ERR_DROP, "Line %i is incomplete while reading string in file %s\n", line, filename.c_str() ); + } + + if ( ( *script_p == '\\' ) && ( script_p < ( end_p - 1 ) ) ) + { + script_p++; + switch( *script_p ) + { + case 'n' : *token_p++ = '\n'; break; + case 'r' : *token_p++ = '\n'; break; + case '\'' : *token_p++ = '\''; break; + case '\"' : *token_p++ = '\"'; break; + case '\\' : *token_p++ = '\\'; break; + default: *token_p++ = *script_p; break; + } + script_p++; + } + else + { + *token_p++ = *script_p++; + } + + if ( script_p >= end_p ) + { + gi.Error( ERR_DROP, "End of token file reached prematurely while reading string on\n" + "line %d in file %s\n", startline, filename.c_str() ); + } + + if ( token_p == &token[ MAXTOKEN ] ) + { + gi.Error( ERR_DROP, "String too large on line %i in file %s\n", line, filename.c_str() ); + } + } + + *token_p = 0; + + // skip last quote + script_p++; + + return token; +} + +/* +============== += += GetSpecific += +============== +*/ + +qboolean Script::GetSpecific( const char *string ) +{ + do + { + if ( !TokenAvailable( true ) ) + { + return false; + } + GetToken( true ); + } + while( strcmp( token, string ) ); + + return true; +} + +//=============================================================== +// Name: GetBoolean +// Class: Script +// +// Description: Retrieves the next boolean value in the token +// stream. If the next token is either "true" +// or "1", then it returns true. Otherwise, it +// returns false. +// +// Parameters: qboolean -- determines if token parsing can cross newlines +// +// Returns: qboolean -- true if next token was "true" (or "1") +// +//=============================================================== +qboolean Script::GetBoolean( qboolean crossline ) +{ + GetToken( crossline ); + if ( stricmp( token, "true" ) == 0 ) + return qtrue ; + else if ( stricmp( token, "1" ) == 0 ) + return qtrue ; + return qfalse ; +} + +/* +============== += += GetInteger += +============== +*/ + +int Script::GetInteger( qboolean crossline ) +{ + GetToken( crossline ); + return atoi( token ); +} + +/* +============== += += GetDouble += +============== +*/ + +double Script::GetDouble( qboolean crossline ) +{ + GetToken( crossline ); + return atof( token ); +} + +/* +============== += += GetFloat += +============== +*/ + +float Script::GetFloat( qboolean crossline ) +{ + return ( float )GetDouble( crossline ); +} + +/* +============== += += GetVector += +============== +*/ + +Vector Script::GetVector( qboolean crossline ) +{ + float x = GetFloat( crossline ); + float y = GetFloat( crossline ); + float z = GetFloat( crossline ); + return Vector( x, y, z ); +} + +/* +=================== += += LinesInFile += +=================== +*/ +int Script::LinesInFile( void ) +{ + qboolean temp_tokenready; + const char *temp_script_p; + int temp_line; + char temp_token[ MAXTOKEN ]; + int numentries; + + temp_tokenready = tokenready; + temp_script_p = script_p; + temp_line = line; + strcpy( temp_token, token ); + + numentries = 0; + + Reset(); + while( TokenAvailable( true ) ) + { + GetLine( true ); + numentries++; + } + + tokenready = temp_tokenready; + script_p = temp_script_p; + line = temp_line; + strcpy( token, temp_token ); + + return numentries; +} + +/* +============== += += Parse += +============== +*/ + +void Script::Parse( const char *data, int length, const char *name ) +{ + Close(); + + buffer = data; + Reset(); + end_p = script_p + length; + this->length = length; + filename = name; +} + +/* +============== += += Load += +============== +*/ + +void Script::LoadFile( const char *name ) +{ + int length; + byte *buffer; + byte *tempbuf; + const char *const_buffer; + + Close(); + + length = gi.FS_ReadFile( name, ( void ** )&tempbuf, true ); + if ( length < 0 ) + { + error( "LoadFile", "Couldn't load %s\n", name ); + } + // create our own space + buffer = ( byte * )gi.Malloc( length ); + // copy the file over to our space + memcpy( buffer, tempbuf, length ); + // free the file + gi.FS_FreeFile( tempbuf ); + + const_buffer = ( char * )buffer; + + Parse( const_buffer, length, name ); + releaseBuffer = true; +} + +const char *Script::Token( void ) +{ + return token; +} diff --git a/dlls/game/script.h b/dlls/game/script.h new file mode 100644 index 0000000..7c8d1fe --- /dev/null +++ b/dlls/game/script.h @@ -0,0 +1,165 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/script.h $ +// $Revision:: 8 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// C++ implementaion of tokenizing text interpretation. Class accepts filename +// to load or pointer to preloaded text data. Standard tokenizing operations +// such as skip white-space, get string, get integer, get float, get token, +// and skip line are implemented. +// +// Note: all '//', '#', and ';' are treated as comments. Probably should +// make this behaviour toggleable. +// + +#ifndef __SCRIPT_H__ +#define __SCRIPT_H__ + +#include "class.h" +#include "vector.h" +#include "str.h" + +#define MAXTOKEN 256 + +typedef struct + { + qboolean tokenready; + int offset; + int line; + char token[ MAXTOKEN ]; + } scriptmarker_t; + +typedef struct + { + //const char *macroName; + //const char *macroText; + str macroName; + str macroText; + } macro; + +class Script : public Class + { + protected: + qboolean tokenready; + + str filename; + const char *script_p; + const char *end_p; + Container macrolist; + + int line; + char token[ MAXTOKEN ]; + + qboolean releaseBuffer; + + qboolean AtComment( void ); + void CheckOverflow( void ); + + public: + const char *buffer; + int length; + + CLASS_PROTOTYPE( Script ); + + ~Script(); + Script(const char* filename = 0); + void Close( void ); + const char *Filename( void ); + int GetLineNumber( void ); + void Reset( void ); + void MarkPosition( scriptmarker_t *mark ); + void RestorePosition( const scriptmarker_t *mark ); + qboolean SkipToEOL( void ); + void SkipWhiteSpace( qboolean crossline ); + void SkipNonToken( qboolean crossline ); + qboolean TokenAvailable( qboolean crossline ); + qboolean CommentAvailable( qboolean crossline ); + void UnGetToken( void ); + qboolean AtString( qboolean crossline ); + qboolean AtOpenParen( qboolean crossline ); + qboolean AtCloseParen( qboolean crossline ); + qboolean AtComma( qboolean crossline ); + const char *GetToken( qboolean crossline ); + const char *GetLine( qboolean crossline ); + const char *GetRaw( void ); + const char *GetString( qboolean crossline ); + qboolean GetSpecific( const char *string ); + qboolean GetBoolean( qboolean crossline ); + int GetInteger( qboolean crossline ); + double GetDouble( qboolean crossline ); + float GetFloat( qboolean crossline ); + Vector GetVector( qboolean crossline ); + int LinesInFile( void ); + void Parse( const char *data, int length, const char *name ); + void LoadFile( const char *name ); + const char *Token( void ); + virtual void Archive( Archiver &arc ); + void AddMacroDefinition( qboolean crossline ); + const char *GetMacroString( const char *theMacroName ); + char *EvaluateMacroString( const char *theMacroString ); + float EvaluateMacroMath(float value, float newval, char oper); + const char *GetExprToken(const char *ptr, char *token); + const char *GrabNextToken( qboolean crossline ); + qboolean isMacro( void ); + + Container *GetMacroList() { return ¯olist; } + void AddMacro(const char *name, const char *value); + }; + +inline void Script::Archive + ( + Archiver &arc + ) + { + int pos; + + Class::Archive( arc ); + + arc.ArchiveBoolean( &tokenready ); + + arc.ArchiveString( &filename ); + if ( arc.Loading() ) + { + // + // load the file in + // + LoadFile( filename.c_str() ); + } + + if ( arc.Saving() ) + { + // + // save out current pointer as an offset + // + pos = script_p - buffer; + } + arc.ArchiveInteger( &pos ); + if ( arc.Loading() ) + { + // + // restore the script pointer + // + script_p = buffer + pos; + } + + //const char *end_p; + //Container macrolist; + + arc.ArchiveInteger( &line ); + arc.ArchiveRaw( token, sizeof( token ) ); + + //qboolean releaseBuffer; + + } + +#endif diff --git a/dlls/game/scriptmaster.cpp b/dlls/game/scriptmaster.cpp new file mode 100644 index 0000000..521ac42 --- /dev/null +++ b/dlls/game/scriptmaster.cpp @@ -0,0 +1,3521 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/scriptmaster.cpp $ +// $Revision:: 13 $ +// $Author:: Steven $ +// $Date:: 10/04/02 1:32p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Script masters are invisible entities that are spawned at the begining of each +// map. They simple parse the script and send commands to the specified objects +// at the apropriate time. Using a combination of simple commands, very complex +// scripted events can occur. +// + +#include "_pch_cpp.h" +#include "class.h" +#include "scriptmaster.h" +#include "container.h" +#include "scriptvariable.h" +#include "misc.h" +#include "specialfx.h" +#include "worldspawn.h" +#include "program.h" +#include "interpreter.h" +#include "globalcmd.h" + + +Program program; + +ScriptVariableList gameVars; +ScriptVariableList levelVars; + +//static ScriptVariablePtr GameTime = NULL; + +/* Event EV_ProcessCommands + ( + "processCommands", + EV_DEFAULT, + NULL, + NULL, + "Not used." + ); +Event EV_Script_NewOrders + ( + "newOrders", + EV_DEFAULT, + NULL, + NULL, + "Inform script that it is about to get new orders." + ); +Event EV_ScriptThread_Execute + ( + "execute", + EV_DEFAULT, + NULL, + NULL, + "Execute the thread." + ); +Event EV_ScriptThread_Callback + ( + "script_callback", + EV_DEFAULT, + "ese", + "slave name other", + "Script callback." + ); +Event EV_ScriptThread_ThreadCallback + ( + "thread_callback", + EV_DEFAULT, + NULL, + NULL, + "Thread callback." + ); +Event EV_ScriptThread_VariableCallback + ( + "variable_callback", + EV_DEFAULT, + NULL, + NULL, + "Variable callback." + ); +Event EV_ScriptThread_DeathCallback + ( + "death_callback", + EV_DEFAULT, + NULL, + NULL, + "Death callback." + ); +Event EV_ScriptThread_CreateThread + ( + "thread", + EV_DEFAULT, + "s", + "label", + "Creates a thread starting at label." + ); +Event EV_ScriptThread_TerminateThread + ( + "terminate", + EV_DEFAULT, + "i", + "thread_number", + "Terminates the specified thread or the current one if none specified." + ); +Event EV_ScriptThread_ControlObject + ( + "control", + EV_DEFAULT, + "e", + "object", + "Not used." + ); +Event EV_ScriptThread_Goto + ( + "goto", + EV_DEFAULT, + "s", + "label", + "Goes to the specified label." + ); +Event EV_ScriptThread_Pause + ( + "pause", + EV_DEFAULT, + NULL, + NULL, + "Pauses the thread." + ); +Event EV_ScriptThread_Wait + ( + "wait", + EV_DEFAULT, + "f", + "wait_time", + "Wait for the specified amount of time." + ); +Event EV_ScriptThread_WaitFor + ( + "waitFor", + EV_DEFAULT, + "e", + "ent", + "Wait for the specified entity." + ); +Event EV_ScriptThread_WaitForThread + ( + "waitForThread", + EV_DEFAULT, + "i", + "thread_number", + "Wait for the specified thread." + ); +Event EV_ScriptThread_WaitForVariable + ( + "waitForVariable", + EV_DEFAULT, + "s", + "variable_name", + "Wait for the specified variable." + ); +Event EV_ScriptThread_WaitForDeath + ( + "waitForDeath", + EV_DEFAULT, + "s", + "name", + "Wait for death." + ); +Event EV_ScriptThread_WaitForSound + ( + "waitForSound", + EV_DEFAULT, + "sf", + "sound_name delay", + "Wait for end of the named sound plus an optional delay." + ); +Event EV_ScriptThread_WaitForDialog + ( + "waitForDialog", + EV_DEFAULT, + "eF", + "actor additional_delay", + "Wait for end of the dialog for the specified actor.\n" + "If additional_delay is specified, than the actor will\n" + "wait the dialog length plus the delay." + ); +Event EV_ScriptThread_WaitForPlayer + ( + "waitForPlayer", + EV_DEFAULT, + NULL, + NULL, + "Wait for player to be ready." + ); +Event EV_ScriptThread_End + ( + "end", + EV_DEFAULT, + NULL, + NULL, + "End the thread." + ); +Event EV_ScriptThread_Print + ( + "print", + EV_DEFAULT, + "s", + "string", + "Prints a string." + ); +Event EV_ScriptThread_PrintInt + ( + "printint", + EV_DEFAULT, + "i", + "integer", + "Print integer." + ); +Event EV_ScriptThread_PrintFloat + ( + "printfloat", + EV_DEFAULT, + "f", + "float", + "Prints a float."); +Event EV_ScriptThread_PrintVector + ( + "printvector", + EV_DEFAULT, + "v", + "vector", + "Prints a vector." + ); +Event EV_ScriptThread_NewLine + ( + "newline", + EV_DEFAULT, + NULL, + NULL, + "Prints a newline." + ); +Event EV_ScriptThread_Assert + ( + "assert", + EV_DEFAULT, + "f", + "value", + "Assert if value is 0." + ); +Event EV_ScriptThread_Break + ( + "break", + EV_DEFAULT, + NULL, + NULL, + "Asserts so that we can break back into the debugger from script." + ); +Event EV_ScriptThread_Clear + ( + "clear", + EV_DEFAULT, + "s", + "var_group", + "Clears the specified var group." + ); +Event EV_ScriptThread_Trigger + ( + "trigger", + EV_DEFAULT, + "s", + "name", + "Trigger the specified target or entity." + ); +Event EV_ScriptThread_Spawn + ( + "spawn", + EV_CHEAT, + "sSSSSSSSS", + "entityname keyname1 value1 keyname2 value2 keyname3 value3 keyname4 value4", + "Spawn the specified entity." + ); +Event EV_ScriptThread_Map + ( + "map", + EV_DEFAULT, + "s", + "map_name", + "Starts the specified map." + ); +Event EV_ScriptThread_SetCvar + ( + "setcvar", + EV_DEFAULT, + "ss", + "cvar_name value", + "Sets the value of the specified cvar." + ); +Event EV_ScriptThread_CueCamera + ( + "cuecamera", + EV_DEFAULT, + "eF", + "entity switchTime", + "Cue the camera. If switchTime is specified, then the camera\n" + "will switch over that length of time." + ); +Event EV_ScriptThread_CuePlayer + ( + "cueplayer", + EV_DEFAULT, + "F", + "switchTime", + "Go back to the normal camera. If switchTime is specified,\n" + "then the camera will switch over that length of time." + ); +Event EV_ScriptThread_FreezePlayer + ( + "freezeplayer", + EV_DEFAULT, + NULL, + NULL, + "Freeze the player." + ); +Event EV_ScriptThread_ReleasePlayer + ( + "releaseplayer", + EV_DEFAULT, + NULL, + NULL, + "Release the player." + ); +Event EV_ScriptThread_FakePlayer + ( + "fakeplayer", + EV_DEFAULT, + NULL, + NULL, + "Will create a fake version of the player, hide the real one and\n" + "call the fake one 'fakeplayer'." + ); +Event EV_ScriptThread_RemoveFakePlayer + ( + "removefakeplayer", + EV_DEFAULT, + NULL, + NULL, + "Will delete the fake version of the player, un-hide the real one and\n" + "return to the normal game" + ); + +Event EV_ScriptThread_KillEnt + ( + "killent", + EV_CHEAT, + "i", + "ent_num", + "Kill the specified entity." + ); +Event EV_ScriptThread_KillClass + ( + "killclass", + EV_CHEAT, + "sI", + "class_name except", + "Kills everything in the specified class except for the specified entity (optional)." + ); +Event EV_ScriptThread_RemoveEnt + ( + "removeent", + EV_CHEAT, + "i", + "ent_num", + "Removes the specified entity." + ); +Event EV_ScriptThread_RemoveClass + ( + "removeclass", + EV_CHEAT, + "sI", + "class_name except", + "Removes everything in the specified class except for the specified entity (optional)." + ); + +// client/server flow control + +Event EV_ScriptThread_ServerOnly + ( + "server", + EV_DEFAULT, + "SSSSSS", + "arg1 arg2 arg3 arg4 arg5 arg6", + "Server only command." + ); +Event EV_ScriptThread_ClientOnly + ( + "client", + EV_DEFAULT, + "SSSSSS", + "arg1 arg2 arg3 arg4 arg5 arg6", + "Client only command." + ); +Event EV_ScriptThread_StuffCommand + ( + "stuffcmd", + EV_DEFAULT, + "SSSSSS", + "arg1 arg2 arg3 arg4 arg5 arg6", + "Server only command." + ); + +// +// world stuff +// +Event EV_ScriptThread_RegisterAlias + ( + "alias", + EV_DEFAULT, + "ssSSSS", + "alias_name real_name arg1 arg2 arg3 arg4", + "Sets up an alias." + ); +Event EV_ScriptThread_RegisterAliasAndCache + ( + "aliascache", + EV_DEFAULT, + "ssSSSS", + "alias_name real_name arg1 arg2 arg3 arg4", + "Sets up an alias and caches the resourse." + ); +Event EV_ScriptThread_SetCinematic + ( + "cinematic", + EV_DEFAULT, + NULL, + NULL, + "Turns on cinematic." + ); +Event EV_ScriptThread_SetNonCinematic + ( + "noncinematic", + EV_DEFAULT, + NULL, + NULL, + "Turns off cinematic." + ); +Event EV_ScriptThread_SetAllAIOff + ( + "all_ai_off", + EV_DEFAULT, + NULL, + NULL, + "Turns all AI off." + ); +Event EV_ScriptThread_SetAllAIOn + ( + "all_ai_on", + EV_DEFAULT, + NULL, + NULL, + "Turns all AI back on." + ); +Event EV_ScriptThread_SetSkipThread + ( + "skipthread", + EV_DEFAULT, + "s", + "thread_name", + "Set the thread to skip." + ); + +// Precache specific +Event EV_ScriptThread_Precache_Cache + ( + "cache", + EV_DEFAULT, + "s", + "resource_name", + "Cache the specified resource." + ); +// fades for movies +Event EV_ScriptThread_FadeIn + ( + "fadein", + EV_DEFAULT, + "fffffI", + "time red green blue alpha mode", + "Sets up fadein in values." + ); +Event EV_ScriptThread_FadeOut + ( + "fadeout", + EV_DEFAULT, + "fffffI", + "time red green blue alpha mode", + "Sets up fadeout values." + ); +Event EV_ScriptThread_FadeSound + ( + "fadesound", + EV_DEFAULT, + "f", + "time", + "fades the sound out over the given time." + ); +Event EV_ScriptThread_CameraCommand + ( + "cam", + EV_DEFAULT, + "sSSSSSS", + "command arg1 arg2 arg3 arg4 arg5 arg6", + "Processes a camera command." + ); + +// music command +Event EV_ScriptThread_MusicEvent + ( + "music", + EV_DEFAULT, + "sS", + "current fallback", + "Sets the current and fallback (optional) music moods." + ); +Event EV_ScriptThread_ForceMusicEvent + ( + "forcemusic", + EV_DEFAULT, + "sS", + "current fallback", + "Forces the current and fallback (optional) music moods." + ); +Event EV_ScriptThread_MusicVolumeEvent + ( + "musicvolume", + EV_DEFAULT, + "ff", + "volume fade_time", + "Sets the volume and fade time of the music." + ); +Event EV_ScriptThread_RestoreMusicVolumeEvent + ( + "restoremusicvolume", + EV_DEFAULT, + "f", + "fade_time", + "Restores the music volume to its previous value." + ); +Event EV_ScriptThread_SoundtrackEvent + ( + "soundtrack", + EV_DEFAULT, + "s", + "soundtrack_name", + "Changes the soundtrack." + ); +Event EV_ScriptThread_RestoreSoundtrackEvent + ( + "restoresoundtrack", + EV_DEFAULT, + NULL, + NULL, + "Restores the soundtrack to the previous one." + ); +Event EV_ScriptThread_ClearFade + ( + "clearfade", + EV_DEFAULT, + NULL, + NULL, + "Clear the fade from the screen" + ); +Event EV_ScriptThread_Letterbox + ( + "letterbox", + EV_DEFAULT, + "f", + "time", + "Puts the game in letterbox mode." + ); +Event EV_ScriptThread_ClearLetterbox + ( + "clearletterbox", + EV_DEFAULT, + "f", + "time", + "Clears letterbox mode." + ); +Event EV_ScriptThread_SetDialogScript + ( + "setdialogscript" , + EV_DEFAULT, + "s", + "dialog_script", + "Set the script to be used when dialog:: is used" + ); + +Event EV_ScriptThread_SetLightStyle + ( + "setlightstyle" , + EV_DEFAULT, + "is", + "lightstyleindex lightstyledata", + "Set up the lightstyle with lightstyleindex to the specified data" + ); + +Event EV_ScriptThread_KillThreadEvent + ( + "killthread", + EV_DEFAULT, + "s", + "threadName", + "kills all threads starting with the specified name" + ); + +Event EV_ScriptThread_SetThreadNameEvent + ( + "threadname", + EV_DEFAULT, + "s", + "threadName", + "sets the name of the thread" + ); + +Event EV_ScriptThread_CenterPrint + ( + "centerprint", + EV_DEFAULT, + "s", + "stuffToPrint", + "prints the included message in the middle of all player's screens" + ); + +Event EV_ScriptThread_LocationPrint + ( + "locprint", + EV_DEFAULT, + "iis", + "xoffset yoffset stuffToPrint", + "prints the included message in the specified location of all player's screens" + ); + +Event EV_ScriptThread_MissionFailed + ( + "missionfailed", + EV_DEFAULT, + NULL, + NULL, + "Makes the player fail their mission, level restarts." + ); +Event EV_ScriptThread_ArenaCommand + ( + "arena", + EV_DEFAULT, + "dsSSSSSS", + "arenanum command arg1 arg2 arg3 arg4 arg5 arg6", + "Send a command to the specified arena" + ); + + +CLASS_DECLARATION( Class, ThreadMarker, NULL ) + { + { NULL, NULL } + }; */ + +ScriptMaster Director; + +CLASS_DECLARATION( Listener, ScriptMaster, NULL ) +{ + { NULL, NULL } +}; + +ScriptMaster::ScriptMaster() +{ + threadIndex = 0; + currentThread = NULL; + player_ready = false; +} + +ScriptMaster::~ScriptMaster() +{ + /* if ( GameTime ) + { + gameVars.RemoveVariable( GameTime ); + GameTime = NULL; + } */ + CloseScript(); +} + +void ScriptMaster::CloseScript( void ) +{ + KillThreads(); + ScriptLib.CloseScripts(); + /* if ( GameTime ) + { + gameVars.RemoveVariable( GameTime ); + GameTime = NULL; + } */ +} + +void ScriptMaster::KillThreads( void ) +{ + int i; + int num; + + num = Threads.NumObjects(); + + // Clear the waitfor's from each thread before deleting + // so they don't get triggered. + for( i = num; i > 0; i-- ) + { + Threads.ObjectAt( i )->ClearWaitFor(); + } + + for( i = num; i > 0; i-- ) + { + delete Threads.ObjectAt( i ); + } + + Threads.FreeObjectList(); + + threadIndex = 0; + currentThread = NULL; +} + +qboolean ScriptMaster::NotifyOtherThreads( int num ) +{ + CThread *thread1; + CThread *thread2; + int i; + int n; + Event *ev; + + thread1 = GetThread( num ); + assert( thread1 ); + n = Threads.NumObjects(); + for( i = 1; i <= n; i++ ) + { + thread2 = Threads.ObjectAt( i ); + assert( thread2 ); + if ( thread2->WaitingOnThread() == thread1 ) + { + ev = new Event( EV_ScriptThread_ThreadCallback ); + ev->SetThread( thread1 ); + thread2->ProcessEvent( ev ); + + return true; + } + } + return false; +} + +void ScriptMaster::DeathMessage( const char *name ) +{ + CThread *thread; + Event *ev; + int i,n; + + // Look for threads that are waiting for this name + n = Threads.NumObjects(); + for( i = 1; i <= n; i++ ) + { + const char *waiting; + thread = Threads.ObjectAt( i ); + assert( thread ); + if (thread) + { + waiting = thread->WaitingOnDeath(); + if (waiting) + { + if (!strcmp(waiting, name)) + { + ev = new Event( EV_ScriptThread_DeathCallback ); + thread->ProcessEvent(ev); + } + } + } + } +} + +void ScriptMaster::PlayerSpawned( void ) +{ + CThread *thread; + int i,n; + + player_ready = true; + // Look for threads that are waiting for the player + n = Threads.NumObjects(); + for( i = 1; i <= n; i++ ) + { + thread = Threads.ObjectAt( i ); + assert( thread ); + if (thread) + { + if ( thread->WaitingOnPlayer() ) + { + thread->ClearWaitFor(); + thread->DelayedStart( 0.0f ); + } + } + } +} + +void ScriptMaster::KillThread( const str &name ) +{ + int i; + int num; + int len; + const char * ptr; + CThread *thread; + bool useWildCard; + + + useWildCard = false; + len = 0; + // see if the name uses a wild card + ptr = strchr( name.c_str(), '*' ); + if ( ptr ) + { + len = ptr - name.c_str(); + useWildCard = true; + } + + // kill only those threads whose name matches name + num = Threads.NumObjects(); + + // Clear the waitfor's from each thread before deleting + // so they don't get triggered. + for( i = num; i > 0; i-- ) + { + thread = Threads.ObjectAt( i ); + int result; + + //With the wild card used, we want to delete any matches of the thread + //name + if(useWildCard == true) + { + result = strnicmp( thread->ThreadName(), name.c_str(), len ); + } + else + { + //if the wild card is not active, then we want the exact thread + //name + result = stricmp( thread->ThreadName(), name.c_str()); + } + + //if the result is 0, then we have found a thread to delete. + if ( result == 0 ) + { + thread->ClearWaitFor(); + if ( currentThread == thread ) + { + SetCurrentThread( NULL ); + } + delete thread; + } + } +} + +qboolean ScriptMaster::KillThread( int num ) +{ + CThread *thread; + + // Must be safely reentryable so that the thread destructor can tell us that it's being deleted. + thread = GetThread( num ); + if ( thread ) + { + thread->ClearWaitFor(); + if ( currentThread == thread ) + { + SetCurrentThread( NULL ); + } + delete thread; + + return true; + } + + return false; +} + +qboolean ScriptMaster::RemoveThread( int num ) +{ + CThread *thread; + int i; + int n; + + // Must be safely reentryable so that the thread destructor can tell us that it's being deleted. + n = Threads.NumObjects(); + for( i = n; i > 0; i-- ) + { + thread = Threads.ObjectAt( i ); + assert( thread ); + if ( thread ) + { + if ( thread->ThreadNum() == num ) + { + Threads.ObjectAt( i )->ClearWaitFor(); + Threads.RemoveObjectAt( i ); + if ( currentThread == thread ) + { + SetCurrentThread( NULL ); + } + return true; + } + } + } + + return false; +} + +CThread *ScriptMaster::CurrentThread( void ) +{ + return currentThread; +} + +void ScriptMaster::SetCurrentThread( CThread *thread ) +{ + currentThread = thread; +} + +CThread *ScriptMaster::CreateThread( Program *program ) +{ + CThread *thread; + + thread = new CThread; + + thread->program = program; + Threads.AddObject( thread ); + + return thread; +} + +CThread *ScriptMaster::CreateThread( const char *label, Program *program ) +{ + CThread *thread; + int threadnum; + + thread = new CThread; + thread->program = program; + + threadnum = GetUniqueThreadNumber(); + Threads.AddObject( thread ); + + if ( !thread->Setup( threadnum, label ) ) + { + KillThread( threadnum ); + return NULL; + } + + return thread; +} + +CThread *ScriptMaster::CreateThread( void ) +{ + CThread *thread; + + thread = new CThread; + + thread->SetThreadNum( GetUniqueThreadNumber() ); + Threads.AddObject( thread ); + + return thread; +} + +CThread *ScriptMaster::CreateThread( const char *label ) +{ + CThread *thread; + int threadnum; + + thread = new CThread; + + threadnum = GetUniqueThreadNumber(); + Threads.AddObject( thread ); + + if ( !thread->Setup( threadnum, label ) ) + { + KillThread( threadnum ); + return NULL; + } + + return thread; +} + +CThread *ScriptMaster::GetThread( int num ) +{ + int i; + int n; + CThread *thread; + + n = Threads.NumObjects(); + for( i = 1; i <= n; i++ ) + { + thread = Threads.ObjectAt( i ); + assert( thread ); + if ( thread ) + { + if ( thread->ThreadNum() == num ) + { + return thread; + } + } + } + + return NULL; +} + +int ScriptMaster::GetUniqueThreadNumber( void ) +{ + do + { + threadIndex++; + if ( threadIndex == 0 ) + { + threadIndex = 1; + } + } + while( GetThread( threadIndex ) ); + + return threadIndex; +} + +/* qboolean ScriptMaster::labelExists + ( + GameScript * scr, + const char *name + ) + + { + return scr->labelExists( name ); + } + +qboolean ScriptMaster::labelExists + ( + const char *filename, + const char *name + ) + { + GameScript *scr; + + scr = ScriptLib.GetScript( filename ); + if ( scr ) + { + return scr->labelExists( name ); + } + + return false; + } + +qboolean ScriptMaster::Goto + ( + GameScript * scr, + const char *name + ) + + { + return scr->Goto( name ); + } + +bool ScriptMaster::isVarGroup + ( + const char *name + ) + + { + const char *v; + char vargroup[ 20 ]; + int len; + + assert( name ); + if ( !name ) + { + return false; + } + + v = strchr( name, '.' ); + if ( !v ) + { + return false; + } + + len = v - name; + if ( len > sizeof( vargroup ) - 1 ) + { + len = sizeof( vargroup ) - 1; + } + memset( vargroup, 0, sizeof( vargroup ) ); + strncpy( vargroup, name, len ); + + if ( !strcmp( vargroup, "game" ) || !strcmp( vargroup, "level" ) || + !strcmp( vargroup, "local" ) || !strcmp( vargroup, "parm" ) ) + { + return true; + } + + return false; + } + +ScriptVariableList *ScriptMaster::GetVarGroup + ( + const char *name + ) + + { + ScriptVariableList *vars; + const char *v; + char vargroup[ 20 ]; + int len; + + v = strchr( name, '.' ); + if ( !v ) + { + return NULL; + } + + len = v - name; + if ( len > sizeof( vargroup ) - 1 ) + { + len = sizeof( vargroup ) - 1; + } + memset( vargroup, 0, sizeof( vargroup ) ); + strncpy( vargroup, name, len ); + + vars = NULL; + if ( !strcmp( vargroup, "game" ) ) + { + vars = &gameVars; + } + else if ( !strcmp( vargroup, "level" ) ) + { + vars = &levelVars; + } + else if ( !strcmp( vargroup, "local" ) && currentThread ) + { + vars = currentThread->Vars(); + } + else if ( !strcmp( vargroup, "parm" ) ) + { + vars = &parmVars; + } + + return vars; + } + +ScriptVariable *ScriptMaster::GetExistingVariable + ( + const char *name + ) + + { + ScriptVariableList *vars; + const char *v; + + vars = GetVarGroup( name ); + if ( !vars ) + { + return NULL; + } + + v = strchr( name, '.' ); + if ( !v ) + { + return NULL; + } + + return vars->GetVariable( v + 1 ); + } + +ScriptVariable *ScriptMaster::GetVariable + ( + const char *name + ) + + { + ScriptVariable *var; + ScriptVariableList *vars; + const char *v; + + var = GetExistingVariable( name ); + if ( !var ) + { + vars = GetVarGroup( name ); + if ( !vars ) + { + return NULL; + } + + v = strchr( name, '.' ); + assert( v ); + var = vars->CreateVariable( v + 1, "" ); + } + + return var; + } */ + +/* +CLASS_DECLARATION( Listener, ScriptThread, NULL ) + { + { &EV_ScriptThread_Execute, Execute }, + { &EV_MoveDone, ObjectMoveDone }, + { &EV_ScriptThread_Callback, ScriptCallback }, + { &EV_ScriptThread_ThreadCallback, ThreadCallback }, + { &EV_ScriptThread_VariableCallback, VariableCallback }, + { &EV_ScriptThread_DeathCallback, DeathCallback }, + { &EV_ScriptThread_CreateThread, CreateThread }, + { &EV_ScriptThread_TerminateThread, TerminateThread }, + { &EV_ScriptThread_ControlObject, ControlObject }, + { &EV_ScriptThread_Goto, EventGoto }, + { &EV_ScriptThread_Pause, EventPause }, + { &EV_ScriptThread_Wait, EventWait }, + { &EV_ScriptThread_WaitFor, EventWaitFor }, + { &EV_ScriptThread_WaitForThread, EventWaitForThread }, + { &EV_ScriptThread_WaitForVariable, EventWaitForVariable }, + { &EV_ScriptThread_WaitForDeath, EventWaitForDeath }, + { &EV_ScriptThread_WaitForSound, EventWaitForSound }, + { &EV_ScriptThread_WaitForDialog, EventWaitForDialog }, + { &EV_ScriptThread_WaitForPlayer, EventWaitForPlayer }, + { &EV_ScriptThread_End, EventEnd }, + { &EV_ScriptThread_Print, Print }, + { &EV_ScriptThread_PrintInt, PrintInt }, + { &EV_ScriptThread_PrintFloat, PrintFloat }, + { &EV_ScriptThread_PrintVector, PrintVector }, + { &EV_ScriptThread_NewLine, NewLine }, + { &EV_ScriptThread_Assert, Assert }, + { &EV_ScriptThread_Break, Break }, + { &EV_ScriptThread_Clear, Clear }, + { &EV_ScriptThread_Trigger, TriggerEvent }, + { &EV_ScriptThread_ServerOnly, ServerEvent }, + { &EV_ScriptThread_ClientOnly, ClientEvent }, + { &EV_ScriptThread_StuffCommand, StuffCommand }, + { &EV_ScriptThread_Spawn, Spawn }, + { &EV_ScriptThread_Precache_Cache, CacheResourceEvent }, + { &EV_ScriptThread_RegisterAlias, RegisterAlias }, + { &EV_ScriptThread_RegisterAliasAndCache, RegisterAliasAndCache }, + { &EV_ScriptThread_Map, MapEvent }, + { &EV_ScriptThread_SetCvar, SetCvarEvent }, + { &EV_ScriptThread_CueCamera, CueCamera }, + { &EV_ScriptThread_CuePlayer, CuePlayer }, + { &EV_ScriptThread_FreezePlayer, FreezePlayer }, + { &EV_ScriptThread_ReleasePlayer, ReleasePlayer }, + { &EV_ScriptThread_FadeIn, FadeIn }, + { &EV_ScriptThread_FadeOut, FadeOut }, + { &EV_ScriptThread_FadeSound, FadeSound }, + { &EV_ScriptThread_ClearFade, ClearFade }, + { &EV_ScriptThread_Letterbox, Letterbox }, + { &EV_ScriptThread_ClearLetterbox, ClearLetterbox }, + { &EV_ScriptThread_MusicEvent, MusicEvent }, + { &EV_ScriptThread_ForceMusicEvent, ForceMusicEvent }, + { &EV_ScriptThread_MusicVolumeEvent, MusicVolumeEvent }, + { &EV_ScriptThread_RestoreMusicVolumeEvent, RestoreMusicVolumeEvent }, + { &EV_ScriptThread_SoundtrackEvent, SoundtrackEvent }, + { &EV_ScriptThread_RestoreSoundtrackEvent, RestoreSoundtrackEvent }, + { &EV_ScriptThread_CameraCommand, CameraCommand }, + { &EV_ScriptThread_SetCinematic, SetCinematic }, + { &EV_ScriptThread_SetNonCinematic, SetNonCinematic }, + { &EV_ScriptThread_SetAllAIOff, SetAllAIOff }, + { &EV_ScriptThread_SetAllAIOn, SetAllAIOn }, + { &EV_ScriptThread_SetSkipThread, SetSkipThread }, + { &EV_ScriptThread_KillEnt, KillEnt }, + { &EV_ScriptThread_RemoveEnt, RemoveEnt }, + { &EV_ScriptThread_KillClass, KillClass }, + { &EV_ScriptThread_RemoveClass, RemoveClass }, + { &EV_ScriptThread_FakePlayer, FakePlayer }, + { &EV_ScriptThread_RemoveFakePlayer, RemoveFakePlayer }, + { &EV_ScriptThread_SetDialogScript, SetDialogScript }, + { &EV_ScriptThread_SetLightStyle, SetLightStyle }, + { &EV_ScriptThread_KillThreadEvent, KillThreadEvent }, + { &EV_ScriptThread_SetThreadNameEvent, SetThreadName }, + { &EV_ScriptThread_CenterPrint, CenterPrint }, + { &EV_ScriptThread_LocationPrint, LocationPrint }, + { &EV_ScriptThread_MissionFailed, MissionFailed }, + { &EV_ScriptThread_ArenaCommand, ArenaCommand }, + { &EV_AI_RecalcPaths, PassToPathmanager }, + { &EV_AI_CalcPath, PassToPathmanager }, + { NULL, NULL } + }; + +ScriptThread::ScriptThread() + { + threadNum = 0; + ClearWaitFor(); + threadDying = false; + doneProcessing = true; + type = LEVEL_SCRIPT; + } + +ScriptThread::~ScriptThread() + { + Director.NotifyOtherThreads( threadNum ); + Director.RemoveThread( threadNum ); + } + +void ScriptThread::ClearWaitFor + ( + void + ) + + { + waitUntil = 0; + waitingFor = ""; + waitingNumObjects = 0; + waitingForThread = NULL; + waitingForVariable = ""; + waitingForDeath = ""; + waitingForPlayer = false; + } + +void ScriptThread::SetType + ( + scripttype_t newtype + ) + + { + type = newtype; + } + +scripttype_t ScriptThread::GetType + ( + void + ) + + { + return type; + } + +void ScriptThread::SetThreadNum + ( + int num + ) + + { + threadNum = num; + } + +int ScriptThread::ThreadNum + ( + void + ) + + { + return threadNum; + } + +const char *ScriptThread::ThreadName + ( + void + ) + + { + return threadName.c_str(); + } + +void ScriptThread::SetThreadName + ( + Event *ev + ) + + { + threadName = ev->GetString( 1 ); + } + +int ScriptThread::CurrentLine + ( + void + ) + + { + return linenumber; + } + +const char *ScriptThread::Filename + ( + void + ) + + { + return script.Filename(); + } + +ScriptThread *ScriptThread::WaitingOnThread + ( + void + ) + + { + return waitingForThread; + } + +const char *ScriptThread::WaitingOnVariable + ( + void + ) + + { + return waitingForVariable.c_str(); + } + +const char *ScriptThread::WaitingOnDeath + ( + void + ) + + { + return waitingForDeath.c_str(); + } + +qboolean ScriptThread::WaitingOnPlayer + ( + void + ) + + { + return waitingForPlayer; + } + +ScriptVariableList *ScriptThread::Vars + ( + void + ) + + { + return &localVars; + } + + +qboolean ScriptThread::Setup + ( + int num, + GameScript *scr, + const char *label + ) + + { + threadNum = num; + + ClearWaitFor(); + script.SetSourceScript( scr ); + if ( label && !Goto( label ) ) + { + ScriptError( "Can't create thread. Label '%s' not found", label ); + return false; + } + + if ( label ) + { + threadName = label; + } + else + { + threadName = script.Filename(); + } + + return true; + } + +qboolean ScriptThread::SetScript + ( + const char *name + ) + + { + GameScript *scr; + + scr = ScriptLib.GetScript( name ); + if ( scr ) + { + Setup( threadNum, scr, NULL ); + return true; + } + + return false; + } + +qboolean ScriptThread::Goto + ( + const char *name + ) + + { + qboolean result; + + result = ScriptLib.Goto( &script, name ); + if ( result ) + { + // Cancel pending execute events when waitUntil is set + if ( waitUntil ) + { + CancelEventsOfType( EV_ScriptThread_Execute ); + } + ClearWaitFor(); + } + return result; + } + +qboolean ScriptThread::labelExists + ( + const char *name + ) + + { + return ScriptLib.labelExists( &script, name ); + } + +void ScriptThread::Mark + ( + ThreadMarker *mark + ) + + { + assert( mark ); + + mark->linenumber = linenumber; + mark->doneProcessing = doneProcessing; + mark->waitingFor = waitingFor; + mark->waitingForThread = waitingForThread; + mark->waitingForVariable = waitingForVariable; + mark->waitingForDeath = waitingForDeath; + mark->waitingForPlayer = waitingForPlayer; + mark->waitingNumObjects = waitingNumObjects; + if ( waitUntil ) + { + // add one so that 0 is always reserved for no wait + mark->waitUntil = waitUntil - level.time + 1; + } + else + { + mark->waitUntil = 0; + } + + script.Mark( &mark->scriptmarker ); + } + +void ScriptThread::Restore + ( + ThreadMarker *mark + ) + + { + assert( mark ); + + linenumber = mark->linenumber; + doneProcessing = mark->doneProcessing; + + waitingFor = mark->waitingFor; + waitingForThread = mark->waitingForThread; + waitingForVariable = mark->waitingForVariable; + waitingForDeath = mark->waitingForDeath; + waitingForPlayer = mark->waitingForPlayer; + waitingNumObjects = mark->waitingNumObjects; + + script.Restore( &mark->scriptmarker ); + + if ( mark->waitUntil ) + { + // subtract one since we added one when we stored it. + // this way, 0 is always reserved for no wait + // Cheezy, yeah, but since I'm commenting it, it's "ok". :) + waitUntil = mark->waitUntil + level.time - 1; + Start( waitUntil - level.time ); + } + } + +TargetList *ScriptThread::GetTargetList + ( + const str &targetname + ) + { + TargetList *tlist; + int num; + int i; + + num = targets.NumObjects(); + for( i = 1; i <= num; i++ ) + { + tlist = targets.ObjectAt( i ); + if ( targetname == tlist->targetname ) + { + return tlist; + } + } + + tlist = world->GetTargetList( targetname ); + targets.AddObject( tlist ); + + return tlist; + } + +void ScriptThread::SendCommandToSlaves + ( + const char *name, + Event *ev + ) + + { + Event *sendevent; + Entity *ent; + TargetList *tlist; + int i; + int num; + + if ( name && name[ 0 ] ) + { + tlist = GetTargetList( str(name+1) ); + num = tlist->list.NumObjects(); + for ( i = 1; i <= num; i++ ) + { + ent = tlist->list.ObjectAt( i ); + + assert( ent ); + + sendevent = new Event( *ev ); + + if ( !updateList.ObjectInList( ent->entnum ) ) + { + updateList.AddObject( ent->entnum ); + + // Tell the object that we're about to send it some orders + ent->ProcessEvent( EV_Script_NewOrders ); + } + + // Send the command + ent->ProcessEvent( sendevent ); + } + if ( !num ) + { + warning( "SendCommandToSlaves", "Could not find target %s in world.\n", name ); + } + } + // + // free up the event + // + delete ev; + } + +qboolean ScriptThread::FindEvent + ( + const char *name + ) + + { + if ( !Event::Exists( name ) ) + { + ScriptError( "Unknown command '%s'\n", name ); + script.SkipToEOL(); + return false; + } + + return true; + } + +void ScriptThread::ProcessCommand + ( + int argc, + const char **argv + ) + + { + ScriptVariableList *vars; + ScriptVariable *var; + str command; + str name; + Event *event; + Entity *ent; + + if ( argc < 1 ) + { + return; + } + + name = argv[ 0 ]; + if ( argc > 1 ) + { + command = argv[ 1 ]; + } + + // Check for variable commands + vars = Director.GetVarGroup( name.c_str() ); + if ( vars ) + { + var = Director.GetVariable( name.c_str() ); + if ( var->isVariableCommand( command.c_str() ) ) + { + event = new Event( command ); + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( linenumber ); + event->AddTokens( argc - 2, &argv[ 2 ] ); + var->ProcessEvent( event ); + return; + } + + name = var->stringValue(); + if ( !name.length() ) + { + ScriptError( "Uninitialized variable '%s' used for command '%s'", var->getName(), command.c_str() ); + return; + } + else if ( name[ 0 ] != '$' && name[ 0 ] != '@' && name[ 0 ] != '%' && name[ 0 ] != '*' ) + { + ScriptError( "Invalid variable command '%s'", command.c_str() ); + return; + } + } + + // Check for object commands + if ( name[ 0 ] == '$' ) + { + if ( FindEvent( command.c_str() ) ) + { + event = new Event( command ); + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( linenumber ); + event->AddTokens( argc - 2, &argv[ 2 ] ); + SendCommandToSlaves( name.c_str(), event ); + } + return; + } + + // Check for entnum commands + if ( name[ 0 ] == '*' ) + { + if ( !IsNumeric( &name[ 1 ] ) ) + { + ScriptError( "Expecting numeric value for * command, but found '%s'\n", &name[ 1 ] ); + } + else if ( FindEvent( command.c_str() ) ) + { + ent = G_GetEntity( atoi( &name[ 1 ] ) ); + if ( ent ) + { + event = new Event( command ); + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( linenumber ); + event->AddTokens( argc - 2, &argv[ 2 ] ); + ent->ProcessEvent( event ); + } + else + { + ScriptError( "Entity not found for * command\n" ); + } + } + return; + } + + // Handle global commands + if ( FindEvent( name.c_str() ) ) + { + event = new Event( name ); + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( linenumber ); + event->AddTokens( argc - 1, &argv[ 1 ] ); + if ( !ProcessEvent( event ) ) + { + ScriptError( "Invalid global command '%s'\n", name.c_str() ); + } + } + } + +void ScriptThread::ProcessCommandFromEvent + ( + Event *ev, + int startarg + ) + + { + int argc; + int numargs; + const char *argv[ MAX_COMMANDS ]; + str args[ MAX_COMMANDS ]; + int i; + + numargs = ev->NumArgs(); + if ( numargs < startarg ) + { + ev->Error( "Expecting statement after conditional", MAX_COMMANDS ); + return; + } + + argc = numargs - startarg + 1; + + if ( argc >= MAX_COMMANDS ) + { + ev->Error( "Line exceeds %d command limit", MAX_COMMANDS ); + return; + } + + for( i = 0; i < argc; i++ ) + { + args[ i ] = ev->GetToken( startarg + i ); + argv[ i ] = args[ i ].c_str(); + } + + ProcessCommand( argc, argv ); + } + +void ScriptThread::Start + ( + float delay + ) + { + CancelEventsOfType( EV_ScriptThread_Execute ); + PostEvent( EV_ScriptThread_Execute, delay ); + } + +void ScriptThread::StartImmediately + ( + void + ) + { + CancelEventsOfType( EV_ScriptThread_Execute ); + ProcessEvent( EV_ScriptThread_Execute ); + } + +void ScriptThread::Execute + ( + Event *ev + ) + + { + int num; + ScriptThread *oldthread; + int argc; + const char *argv[ MAX_COMMANDS ]; + char args[ MAX_COMMANDS ][ MAXTOKEN ]; + ScriptVariable *var; + + if ( threadDying ) + { + return; + } + + // set the current game time + if ( !GameTime ) + { + GameTime = gameVars.CreateVariable( "time", 0 ); + } + + GameTime->setFloatValue( level.time ); + + // clear the updateList so that all objects moved this frame are notified before they receive any commands + // we have to do this here as well as in DoMove, since DoMove may not be called + updateList.ClearObjectList(); + + oldthread = Director.CurrentThread(); + Director.SetCurrentThread( this ); + + // if we're not being called from another thread, clear the parm variables + if ( !oldthread ) + { + parmVars.ClearList(); + } + + ClearWaitFor(); + + var = Director.GetVariable( "parm.previousthread" ); + if ( oldthread ) + { + var->setIntValue( oldthread->ThreadNum() ); + } + else + { + var->setIntValue( 0 ); + } + + var = Director.GetVariable( "parm.currentthread" ); + + doneProcessing = false; + + num = 0; + while( ( num++ < 10000 ) && !doneProcessing && !threadDying ) + { + // keep our thread number up to date + var->setIntValue( threadNum ); + + script.SkipNonToken( true ); + + // save the line number for errors + linenumber = script.GetLineNumber(); + + argc = 0; + while( script.TokenAvailable( false ) ) + { + if ( argc >= MAX_COMMANDS ) + { + ScriptError( "Line exceeds %d command limit", MAX_COMMANDS ); + script.SkipToEOL(); + break; + } + strcpy( args[ argc ], script.GetToken( false ) ); + argv[ argc ] = args[ argc ]; + argc++; + } + + assert( argc > 0 ); + + // Ignore labels + if ( args[ 0 ][ strlen( args[ 0 ] ) - 1 ] != ':' ) + { + ProcessCommand( argc, argv ); + } + } + + if ( !doneProcessing ) + { + gi.Error( ERR_DROP, "Command overflow. Possible infinite loop in thread '%s'.\n" + "Stopping on line %d of %s\n", threadName.c_str(), script.GetLineNumber(), script.Filename() ); + } + + Director.SetCurrentThread( oldthread ); + + // Set the thread number on exit, in case we were called by someone who wants to know our thread + var = Director.GetVariable( "parm.previousthread" ); + var->setIntValue( threadNum ); + } + +void ScriptThread::ScriptError + ( + const char *fmt, + ... + ) + + { + va_list argptr; + char text[ 1024 ]; + + va_start( argptr, fmt ); + vsprintf( text, fmt, argptr ); + va_end( argptr ); + + gi.DPrintf( "%s(%d):: %s\n", Filename(), linenumber, text ); + } */ + +//**************************************************************************************** +// +// global commands +// +//**************************************************************************************** + +/* qboolean ScriptThread::WaitingFor + ( + Entity *obj + ) + + { + assert( obj ); + + if ( waitingFor.length() ) + { + if ( ( ( waitingFor[ 0 ] == '*' ) && ( atoi( &waitingFor.c_str()[ 1 ] ) == obj->entnum ) ) || + ( waitingFor == obj->TargetName() ) ) + { + return true; + } + } + + return false; + } + +void ScriptThread::ObjectMoveDone + ( + Event *ev + ) + + { + Entity *obj; + + obj = ev->GetEntity( 1 ); + + if ( !obj ) + { + gi.DPrintf( "Object no longer exists, MoveDone invalid now\n" ); + return; + } + +#if 0 + gi.DPrintf( "Move done %d:'%s'\n", obj->entnum, obj->TargetName() ); +#endif + + if ( WaitingFor( obj ) ) + { + waitingNumObjects--; + if ( waitingNumObjects <= 0 ) + { + ClearWaitFor(); + // start right away + Start( 0 ); + } + } + } + +void ScriptThread::CreateThread + ( + Event *ev + ) + + { + ScriptThread * pThread; + + pThread = Director.CreateThread( &script, ev->GetToken( 1 ) ); + if ( pThread ) + { + // start right away + pThread->StartImmediately(); + } + } + +void ScriptThread::TerminateThread + ( + Event *ev + ) + + { + int threadnum; + ScriptThread *thread; + + threadnum = 0; + // we specified the thread to kill + if ( ev->NumArgs() > 0 ) + { + threadnum = ev->GetInteger( 1 ); + } + else + { + thread = ev->GetThread(); + if ( thread ) + { + threadnum = thread->ThreadNum(); + } + } + + if ( threadnum != 0 ) + { + thread = Director.GetThread( threadnum ); + if ( thread && ( thread->GetType() == MODEL_SCRIPT ) && ( ev->GetSource() == EV_FROM_SCRIPT ) ) + { + ev->Error( "Can't terminate an actor's thread via script." ); + return; + } + Director.KillThread( threadnum ); + } + } + +void ScriptThread::ControlObject + ( + Event *ev + ) + + { + ev->GetEntity( 1 ); + } + +void ScriptThread::EventGoto + ( + Event *ev + ) + + { + const char *label; + + label = ev->GetToken( 1 ); + if ( !Goto( label ) ) + { + ev->Error( "Label '%s' not found", label ); + } + } + +void ScriptThread::EventPause + ( + Event *ev + ) + + { + DoMove(); + + ClearWaitFor(); + + doneProcessing = true; + } + +void ScriptThread::EventWait + ( + Event *ev + ) + + { + DoMove(); + + ClearWaitFor(); + + waitUntil = ev->GetFloat( 1 ) + level.time; + + Start( ev->GetFloat( 1 ) ); + doneProcessing = true; + } + +void ScriptThread::EventWaitFor + ( + Event *ev + ) + + { + Entity *ent; + const char *objname; + const char *tname; + + ClearWaitFor(); + // + // we need to make sure we have an entity we are going to wait for + // + + ent = ev->GetEntity( 1 ); + if ( ent ) + { + doneProcessing = true; + + objname = ev->GetString( 1 ); + tname = ent->TargetName(); + if ( objname && objname[ 0 ] == '*' ) + { + if ( !IsNumeric( &objname[ 1 ] ) ) + { + ScriptError( "Expecting *-prefixed numeric value for waitFor command, but found '%s'\n", objname ); + return; + } + + waitingFor = objname; + } + else if ( !tname || !tname[ 0 ] ) + { + // Probably won't happen, but check for it anyway. + ScriptError( "Entity doesn't have a targetname.\n" ); + return; + } + else + { + TargetList *tlist; + waitingFor = tname; + // + // set the number of objects that belong to this targetname + // + tlist = GetTargetList( waitingFor ); + waitingNumObjects = tlist->list.NumObjects(); + if ( waitingNumObjects <= 0 ) + { + waitingNumObjects = 1; + ScriptError( "no objects of targetname %s found.\n", tname ); + } + else + { + Entity * tent; + int i; + // + // make sure all these objects are in the update list + // + for ( i = 1; i <= waitingNumObjects; i++ ) + { + tent = tlist->list.ObjectAt( i ); + // add the object to the update list to make sure we tell it to do a move + if ( !updateList.ObjectInList( tent->entnum ) ) + { + updateList.AddObject( tent->entnum ); + } + } + } + } + + // add the object to the update list to make sure we tell it to do a move + if ( !updateList.ObjectInList( ent->entnum ) ) + { + updateList.AddObject( ent->entnum ); + } + } + + DoMove(); + } + +void ScriptThread::EventWaitForThread + ( + Event *ev + ) + + { + ClearWaitFor(); + waitingForThread = Director.GetThread( ev->GetInteger( 1 ) ); + if ( !waitingForThread ) + { + ev->Error( "EventWaitForThread", "Thread %d not running", ev->GetInteger( 1 ) ); + return; + } + doneProcessing = true; + + DoMove(); + } + +void ScriptThread::EventWaitForDeath + ( + Event *ev + ) + + { + ClearWaitFor(); + waitingForDeath = ev->GetString(1); + if ( !waitingForDeath.length() ) + { + ev->Error( "EventWaitForDeath", "Null name" ); + return; + } + doneProcessing = true; + + DoMove(); + } + +void ScriptThread::EventWaitForVariable + ( + Event *ev + ) + + { + ClearWaitFor(); + waitingForVariable = ev->GetString(1); + + if ( !waitingForVariable.length() ) + { + ev->Error( "EventWaitForVariable", "Null variable" ); + return; + } + doneProcessing = true; + + DoMove(); + } + +void ScriptThread::EventWaitForSound + ( + Event *ev + ) + + { + str sound; + float delay; + + ClearWaitFor(); + + DoMove(); + + delay = 0; + sound = ev->GetString( 1 ); + + delay = gi.SoundLength( sound.c_str() ); + + if ( delay < 0 ) + gi.DPrintf( "Lip file not found for dialog %s\n", sound.c_str() ); + + if ( ev->NumArgs() > 1 ) + delay += ev->GetFloat( 2 ); + + Start( delay ); + doneProcessing = true; + } + +void ScriptThread::EventWaitForDialog + ( + Event *ev + ) + + { + float delay; + Actor *act; + Entity *ent; + + ClearWaitFor(); + + ent = ev->GetEntity( 1 ); + + if ( ent && ent->isSubclassOf( Actor ) ) + { + act = (Actor *)ent; + + delay = act->GetDialogRemainingTime( ); + if ( ev->NumArgs() > 1 ) + { + delay += ev->GetFloat( 2 ); + } + Start( delay ); + doneProcessing = true; + } + } + +void ScriptThread::EventWaitForPlayer + ( + Event *ev + ) + + { + doneProcessing = true; + + ClearWaitFor(); + waitingForPlayer = true; + + DoMove(); + } + +void ScriptThread::EventEnd + ( + Event *ev + ) + + { + ScriptVariable *var; + const char *text; + Entity *ent; + + ClearWaitFor(); + + // If we're a model script, we have to tell our owning entity that we're done. + if ( ( ev->GetSource() == EV_FROM_SCRIPT ) && ( type == MODEL_SCRIPT ) ) + { + doneProcessing = true; + var = Vars()->GetVariable( "self" ); + if ( !var ) + { + ev->Error( "Model script ending without local.self set" ); + } + else + { + text = var->stringValue(); + if ( ( text[ 0 ] == '*' ) && IsNumeric( &text[ 1 ] ) ) + { + ent = G_GetEntity( atoi( &text[ 1 ] ) ); + + // pass the event on to entity pointed to by self + if ( ent ) + { + Event * newevent; + + newevent = new Event( ev ); + ent->ProcessEvent( newevent ); + return; + } + } + else + { + ev->Error( "Model script ending. Invalid value in local.self '%s'", text ); + } + } + } + + Director.NotifyOtherThreads( threadNum ); + PostEvent( EV_Remove, 0 ); + doneProcessing = true; + threadDying = true; + } + +void ScriptThread::Print + ( + Event *ev + ) + + { + gi.DPrintf( "%s", ev->GetString( 1 ) ); + } + +void ScriptThread::PrintInt + ( + Event *ev + ) + + { + gi.DPrintf( "%d", ev->GetInteger( 1 ) ); + } + +void ScriptThread::PrintFloat + ( + Event *ev + ) + + { + gi.DPrintf( "%.2f", ev->GetFloat( 1 ) ); + } + +void ScriptThread::PrintVector + ( + Event *ev + ) + + { + Vector vec; + + vec = ev->GetVector( 1 ); + gi.DPrintf( "(%.2f %.2f %.2f)", vec.x, vec.y, vec.z ); + } + +void ScriptThread::NewLine + ( + Event *ev + ) + + { + gi.DPrintf( "\n" ); + } + +void ScriptThread::Assert + ( + Event *ev + ) + + { + assert( ev->GetFloat( 1 ) ); + } + +void ScriptThread::Break + ( + Event *ev + ) + + { + // Break into the debugger + assert( 0 ); + } + +void ScriptThread::Clear + ( + Event *ev + ) + + { + ScriptVariableList *vars; + + vars = Director.GetVarGroup( ev->GetToken( 1 ) ); + if ( vars ) + { + vars->ClearList(); + } + } + +void ScriptThread::ScriptCallback + ( + Event *ev + ) + + { + char name[ MAXTOKEN ]; + Entity *other; + Entity *slave; + + if ( threadDying ) + { + return; + } + + slave = ev->GetEntity( 1 ); + strcpy( name, ev->GetString( 2 ) ); + other = ev->GetEntity( 3 ); + + if ( !Goto( name ) ) + { + ev->Error( "Label '%s' not found", name ); + } + else + { + // kill any execute events (in case our last command was "wait") + ClearWaitFor(); + // start right away + StartImmediately(); + } + } + +void ScriptThread::ThreadCallback + ( + Event *ev + ) + + { + ScriptThread *thread; + + if ( threadDying ) + { + return; + } + + thread = ev->GetThread(); + + if ( thread && ( thread == waitingForThread ) ) + { + ClearWaitFor(); + StartImmediately(); + } + } + +void ScriptThread::VariableCallback + ( + Event *ev + ) + + { + ClearWaitFor(); + // start right away + Start( 0 ); + } + +void ScriptThread::DeathCallback + ( + Event *ev + ) + + { + if ( threadDying ) + { + return; + } + + ClearWaitFor(); + // start right away + Start( 0 ); + } + +void ScriptThread::DoMove + ( + void + ) + + { + int entnum; + Entity *ent; + Event *event; + int count; + int i; + + count = updateList.NumObjects(); + + for( i = 1; i <= count; i++ ) + { + entnum = ( int )updateList.ObjectAt( i ); + ent = G_GetEntity( entnum ); + if ( ent ) + { + if ( ent->ValidEvent( EV_ProcessCommands ) ) + { + event = new Event( EV_ProcessCommands ); + event->SetThread( this ); + ent->PostEvent( event, 0 ); + } + else + { + // try to remove this from the update list + if ( waitingNumObjects > 0 ) + { + waitingNumObjects--; + } + } + } + } + + updateList.ClearObjectList(); + } + +void ScriptThread::TriggerEvent + ( + Event *ev + ) + + { + const char *name; + Event *event; + Entity *ent; + TargetList *tlist; + int i; + int num; + + name = ev->GetString( 1 ); + + // Check for object commands + if ( name && name[ 0 ] == '$' ) + { + tlist = GetTargetList( str( name + 1 ) ); + num = tlist->list.NumObjects(); + for ( i = 1; i <= num; i++ ) + { + ent = tlist->list.ObjectAt( i ); + + assert( ent ); + + event = new Event( EV_Activate ); + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( linenumber ); + event->AddEntity( world ); + ent->ProcessEvent( event ); + } + } + else if ( name[ 0 ] == '*' ) // Check for entnum commands + { + if ( !IsNumeric( &name[ 1 ] ) ) + { + ScriptError( "Expecting numeric value for * command, but found '%s'\n", &name[ 1 ] ); + } + else + { + ent = G_GetEntity( atoi( &name[ 1 ] ) ); + if ( ent ) + { + event = new Event( EV_Activate ); + event->SetSource( EV_FROM_SCRIPT ); + event->SetThread( this ); + event->SetLineNumber( linenumber ); + event->AddEntity( world ); + ent->ProcessEvent( event ); + } + else + { + ScriptError( "Entity not found for * command\n" ); + } + } + return; + } + else + { + ScriptError( "Invalid entity reference '%s'.\n", name ); + } + } + +void ScriptThread::ServerEvent + ( + Event *ev + ) + + { + int i, argc; + const char *argv[ MAX_COMMANDS ]; + + argc = 0; + for ( i = 1; i <= ev->NumArgs(); i++ ) + { + argv[argc++] = ev->GetString( i ); + } + + if (argc) + { + ProcessCommand( argc, argv ); + } + } + +void ScriptThread::ClientEvent + ( + Event *ev + ) + + { + // + // do nothing + // + } + +void ScriptThread::CacheResourceEvent + ( + Event * ev + ) + + { + if ( !precache->integer ) + { + return; + } + + if ( world ) + { + CacheResource( ev->GetString( 1 ), world ); + } + else + { + CacheResource( ev->GetString( 1 ), NULL ); + } + } + +void ScriptThread::RegisterAlias + ( + Event *ev + ) + + { + char parameters[100]; + int i; + + // Get the parameters for this alias command + + parameters[0] = 0; + + for( i = 3 ; i <= ev->NumArgs() ; i++ ) + { + strcat( parameters, ev->GetString( i ) ); + strcat( parameters, " "); + } + + gi.GlobalAlias_Add( ev->GetString( 1 ), ev->GetString( 2 ), parameters ); + } + +void ScriptThread::RegisterAliasAndCache + ( + Event *ev + ) + + { + RegisterAlias(ev); + + if ( !precache->integer ) + return; + + CacheResource( ev->GetString( 2 ) ); + } + +void ScriptThread::MapEvent + ( + Event *ev + ) + + { + if ( level.mission_failed ) + return; + + G_BeginIntermission( ev->GetString( 1 ) ); + doneProcessing = true; + } + +void ScriptThread::SetCvarEvent + ( + Event *ev + ) + + { + str name; + + name = ev->GetString( 1 ); + if ( name != "" ) + { + gi.cvar_set( name.c_str(), ev->GetString( 2 ) ); + } + } + +void ScriptThread::CueCamera + ( + Event *ev + ) + + { + float switchTime; + Entity *ent; + + if ( ev->NumArgs() > 1 ) + { + switchTime = ev->GetFloat( 2 ); + } + else + { + switchTime = 0; + } + + ent = ev->GetEntity( 1 ); + if ( ent ) + { + SetCamera( ent, switchTime ); + } + else + { + ev->Error( "Camera named %s not found", ev->GetString( 1 ) ); + } + } + +void ScriptThread::CuePlayer + ( + Event *ev + ) + + { + float switchTime; + + if ( ev->NumArgs() > 0 ) + { + switchTime = ev->GetFloat( 1 ); + } + else + { + switchTime = 0; + } + + SetCamera( NULL, switchTime ); + } + +void ScriptThread::FreezePlayer + ( + Event *ev + ) + + { + level.playerfrozen = true; + } + +void ScriptThread::ReleasePlayer + ( + Event *ev + ) + + { + level.playerfrozen = false; + } + +void ScriptThread::FakePlayer + ( + Event *ev + ) + + { + qboolean holster; + Player *player; + + player = ( Player * )g_entities[ 0 ].entity; + + if ( ( ev->NumArgs() < 1 ) || ev->GetBoolean( 1 ) ) + { + holster = true; + } + else + { + holster = false; + } + + if ( player && player->edict->inuse && player->edict->client ) + { + player->FakePlayer( holster ); + } + } + +void ScriptThread::RemoveFakePlayer + ( + Event *ev + ) + + { + Player *player; + + player = ( Player * )g_entities[ 0 ].entity; + + if ( player && player->edict->inuse && player->edict->client ) + { + player->RemoveFakePlayer(); + } + } + +void ScriptThread::Spawn + ( + Event *ev + ) + + { + Entity *ent; + Entity *tent; + const char *name; + ClassDef *cls; + int n; + int i; + const char *targetname; + ScriptVariable *var; + const char *value; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "Usage: spawn entityname [keyname] [value]..." ); + return; + } + + // create a new entity + SpawnArgs args; + + name = ev->GetString( 1 ); + + if ( name ) + { + cls = getClassForID( name ); + if ( !cls ) + { + cls = getClass( name ); + } + + if ( !cls ) + { + str n; + + n = name; + if ( !strstr( n.c_str(), ".tik" ) ) + { + n += ".tik"; + } + args.setArg( "model", n.c_str() ); + } + else + { + args.setArg( "classname", name ); + } + } + + if ( ev->NumArgs() > 2 ) + { + n = ev->NumArgs(); + for( i = 2; i <= n; i += 2 ) + { + args.setArg( ev->GetString( i ), ev->GetString( i + 1 ) ); + } + } + + cls = args.getClassDef(); + if ( !cls ) + { + ev->Error( "'%s' is not a valid entity name", name ); + return; + } + + // If there is a spawntarget set, then use that entity's origin and angles + targetname = args.getArg( "spawntarget" ); + + if ( targetname ) + { + tent = G_FindTarget( NULL, targetname ); + if ( tent ) + { + args.setArg( "origin", va( "%f %f %f", tent->origin[ 0 ], tent->origin[ 1 ], tent->origin[ 2 ] ) ); + args.setArg( "angle", va( "%f", tent->angles[1] ) ); + } + else + { + ev->Error( "Can't find targetname %s", targetname ); + } + } + + // + // make sure to setup spawnflags properly + // + level.spawnflags = 0; + value = args.getArg( "spawnflags" ); + if ( value ) + { + level.spawnflags = atoi( value ); + } + + ent = args.Spawn(); + if ( ent ) + { + ent->ProcessPendingEvents(); + } + var = Director.GetVariable( "parm.lastspawn" ); + var->setIntValue( ent->entnum ); + } + +//FIXME +//Move this to someplace Level class. +static float last_fraction = 1.0f/8.0f; + +void ScriptThread::Letterbox + ( + Event *ev + ) + + { + level.m_letterbox_fraction = 1.0f/8.0f; + level.m_letterbox_time = ev->GetFloat( 1 ); + level.m_letterbox_time_start = ev->GetFloat( 1 ); + level.m_letterbox_dir = letterbox_in; + + if ( ev->NumArgs() > 1 ) + level.m_letterbox_fraction = ev->GetFloat( 2 ); + } + +void ScriptThread::ClearLetterbox + ( + Event *ev + ) + + { + level.m_letterbox_time = level.m_letterbox_time_start; + level.m_letterbox_dir = letterbox_out; + } + +void ScriptThread::SetDialogScript + ( + Event *ev + ) + { + ScriptThread * pThread; + + ScriptLib.SetDialogScript( ev->GetString( 1 ) ); + // + // precache the data in the dialog script + // + pThread = Director.CreateThread( &script, "dialog::precache" ); + if ( pThread ) + { + // start right away + pThread->Start( -1 ); + } + } + +void ScriptThread::SetLightStyle + ( + Event *ev + ) + { + lightStyles.SetLightStyle( ev->GetInteger( 1 ), ev->GetString( 2 ) ); + } + +void ScriptThread::FadeIn + ( + Event *ev + ) + { + level.m_fade_time_start = ev->GetFloat( 1 ); + level.m_fade_time = ev->GetFloat( 1 ); + level.m_fade_color[0] = ev->GetFloat( 2 ); + level.m_fade_color[1] = ev->GetFloat( 3 ); + level.m_fade_color[2] = ev->GetFloat( 4 ); + level.m_fade_alpha = ev->GetFloat( 5 ); + level.m_fade_type = fadein; + level.m_fade_style = alphablend; + + if ( ev->NumArgs() > 5 ) + { + level.m_fade_style = (fadestyle_t)ev->GetInteger( 6 ); + } + } + +void ScriptThread::ClearFade + ( + Event *ev + ) + + { + level.m_fade_time = -1; + level.m_fade_type = fadein; + } + +void ScriptThread::FadeOut + ( + Event *ev + ) + { + level.m_fade_time_start = ev->GetFloat( 1 ); + level.m_fade_time = ev->GetFloat( 1 ); + level.m_fade_color[0] = ev->GetFloat( 2 ); + level.m_fade_color[1] = ev->GetFloat( 3 ); + level.m_fade_color[2] = ev->GetFloat( 4 ); + level.m_fade_alpha = ev->GetFloat( 5 ); + level.m_fade_type = fadeout; + level.m_fade_style = alphablend; + + if ( ev->NumArgs() > 5 ) + { + level.m_fade_style = (fadestyle_t)ev->GetInteger( 6 ); + } + } + +void ScriptThread::FadeSound + ( + Event *ev + ) + { + G_FadeSound( ev->GetFloat( 1 ) ); + } + +void ScriptThread::MusicEvent + ( + Event *ev + ) + { + const char *current; + const char *fallback; + + current = NULL; + fallback = NULL; + current = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + fallback = ev->GetString( 2 ); + + ChangeMusic( current, fallback, false ); + } + +void ScriptThread::MusicVolumeEvent + ( + Event *ev + ) + { + float volume; + float fade_time; + + volume = ev->GetFloat( 1 ); + fade_time = ev->GetFloat( 2 ); + + ChangeMusicVolume( volume, fade_time ); + } + +void ScriptThread::RestoreMusicVolumeEvent + ( + Event *ev + ) + { + float fade_time; + + fade_time = ev->GetFloat( 1 ); + + RestoreMusicVolume( fade_time ); + } + +void ScriptThread::ForceMusicEvent + ( + Event *ev + ) + { + const char *current; + const char *fallback; + + current = NULL; + fallback = NULL; + current = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + fallback = ev->GetString( 2 ); + + ChangeMusic( current, fallback, true ); + } + +void ScriptThread::SoundtrackEvent + ( + Event *ev + ) + { + ChangeSoundtrack( ev->GetString( 1 ) ); + } + +void ScriptThread::RestoreSoundtrackEvent + ( + Event *ev + ) + { + RestoreSoundtrack(); + } + +void ScriptThread::SetCinematic + ( + Event *ev + ) + + { + G_StartCinematic(); + } + +void ScriptThread::SetNonCinematic + ( + Event *ev + ) + + { + G_StopCinematic(); + } + +void ScriptThread::SetAllAIOff + ( + Event *ev + ) + + { + level.ai_on = false; + } + +void ScriptThread::SetAllAIOn + ( + Event *ev + ) + + { + level.ai_on = true; + } + +void ScriptThread::SetSkipThread + ( + Event *ev + ) + + { + str label; + + label = ev->GetString( 1 ); + if ( label.length() && label.icmp( "null" ) ) + { + world->skipthread = script.Filename() + str( "::" ) + label; + } + else + { + world->skipthread = ""; + } + } + +void ScriptThread::KillThreadEvent + ( + Event *ev + ) + + { + Director.KillThread( ev->GetString( 1 ) ); + } + +void ScriptThread::PassToPathmanager + ( + Event *ev + ) + + { + PathManager.ProcessEvent( ev ); + } + +void ScriptThread::CenterPrint + ( + Event *ev + ) + + { + int j; + gentity_t *other; + + for( j = 0; j < game.maxclients; j++ ) + { + other = &g_entities[ j ]; + if ( other->inuse && other->client ) + { + gi.centerprintf( other, ev->GetString( 1 ) ); + } + } + } + +void ScriptThread::LocationPrint + ( + Event *ev + ) + + { + int j; + gentity_t *other; + int x,y; + + x = ev->GetInteger( 1 ); + y = ev->GetInteger( 2 ); + + for( j = 0; j < game.maxclients; j++ ) + { + other = &g_entities[ j ]; + if ( other->inuse && other->client ) + { + gi.locationprintf( other, x, y, ev->GetString( 3 ) ); + } + } + } + +void ScriptThread::StuffCommand + ( + Event *ev + ) + + { + gi.SendConsoleCommand( va( "%s\n", ev->GetString( 1 ) ) ); + } + +void ScriptThread::KillEnt + ( + Event * ev + ) + { + int num; + Entity *ent; + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "No args passed in" ); + return; + } + + num = ev->GetInteger( 1 ); + if ( ( num < 0 ) || ( num >= globals.max_entities ) ) + { + ev->Error( "Value out of range. Possible values range from 0 to %d.\n", globals.max_entities ); + return; + } + + ent = G_GetEntity( num ); + ent->Damage( world, world, ent->max_health + 25, vec_zero, vec_zero, vec_zero, 0, 0, 0 ); + } + +void ScriptThread::RemoveEnt + ( + Event * ev + ) + { + int num; + Entity *ent; + + if ( ev->NumArgs() != 1 ) + { + ev->Error( "No args passed in" ); + return; + } + + num = ev->GetInteger( 1 ); + if ( ( num < 0 ) || ( num >= globals.max_entities ) ) + { + ev->Error( "Value out of range. Possible values range from 0 to %d.\n", globals.max_entities ); + return; + } + + ent = G_GetEntity( num ); + ent->PostEvent( Event( EV_Remove ), 0 ); + } + +void ScriptThread::KillClass + ( + Event * ev + ) + { + int except; + str classname; + gentity_t * from; + Entity *ent; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "No args passed in" ); + return; + } + + classname = ev->GetString( 1 ); + + except = 0; + if ( ev->NumArgs() == 2 ) + { + except = ev->GetInteger( 1 ); + } + + for ( from = &g_entities[ game.maxclients ]; from < &g_entities[ globals.num_entities ]; from++ ) + { + if ( !from->inuse ) + { + continue; + } + + assert( from->entity ); + + ent = from->entity; + + if ( ent->entnum == except ) + { + continue; + } + + if ( ent->inheritsFrom( classname.c_str() ) ) + { + ent->Damage( world, world, ent->max_health + 25, vec_zero, vec_zero, vec_zero, 0, 0, 0 ); + } + } + } + +void ScriptThread::RemoveClass + ( + Event * ev + ) + { + int except; + str classname; + gentity_t * from; + Entity *ent; + + if ( ev->NumArgs() < 1 ) + { + ev->Error( "No args passed in" ); + return; + } + + classname = ev->GetString( 1 ); + + except = 0; + if ( ev->NumArgs() == 2 ) + { + except = ev->GetInteger( 1 ); + } + + for ( from = &g_entities[ game.maxclients ]; from < &g_entities[ globals.num_entities ]; from++ ) + { + if ( !from->inuse ) + { + continue; + } + + assert( from->entity ); + + ent = from->entity; + + if ( ent->entnum == except ) + continue; + + if ( ent->inheritsFrom( classname.c_str() ) ) + { + ent->PostEvent( Event( EV_Remove ), 0 ); + } + } + } + +void ScriptThread::CameraCommand + ( + Event * ev + ) + + { + Event *e; + const char *cmd; + int i; + int n; + + if ( !ev->NumArgs() ) + { + ev->Error( "Usage: cam [command] [arg 1]...[arg n]" ); + return; + } + + cmd = ev->GetString( 1 ); + if ( Event::Exists( cmd ) ) + { + e = new Event( cmd ); + e->SetSource( EV_FROM_SCRIPT ); + e->SetThread( this ); + e->SetLineNumber( linenumber ); + + n = ev->NumArgs(); + for( i = 2; i <= n; i++ ) + { + e->AddToken( ev->GetToken( i ) ); + } + + CameraMan.ProcessEvent( e ); + } + else + { + ev->Error( "Unknown camera command '%s'.\n", cmd ); + } + } + +void ScriptThread::MissionFailed + ( + Event *ev + ) + + { + G_MissionFailed(); + } + +void ScriptThread::ArenaCommand + ( + Event *ev + ) + + { + // Make sure we are in arena mode for this command + + if ( g_gametype->integer == GT_ARENA ) + { + dmManager.ArenaCommand( ev ); + } + else + { + warning( "ScriptThread::ArenaCommand", "Arena mode not set, ignoring arena command.\n" ); + } + } */ + +//////////////////////// +// +// LIGHTSTYLE REPOSITORY +// +//////////////////////// + +LightStyleClass lightStyles; + +CLASS_DECLARATION( Class, LightStyleClass, NULL ) +{ + { NULL, NULL } +}; + +void LightStyleClass::SetLightStyle( int index, const str &style ) +{ + if ( ( index < 0 ) || ( index >= MAX_LIGHTSTYLES ) ) + { + assert( 0 ); + return; + } + + styles[ index ] = style; + gi.SetLightStyle( index, style.c_str() ); +} + +void LightStyleClass::Archive( Archiver &arc ) +{ + int i; + + for( i = 0; i < MAX_LIGHTSTYLES; i++ ) + { + arc.ArchiveString( &styles[ i ] ); + if ( arc.Loading() && styles[ i ].length() ) + { + gi.SetLightStyle( i, styles[ i ].c_str() ); + } + } +} diff --git a/dlls/game/scriptmaster.h b/dlls/game/scriptmaster.h new file mode 100644 index 0000000..1839734 --- /dev/null +++ b/dlls/game/scriptmaster.h @@ -0,0 +1,429 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/scriptmaster.h $ +// $Revision:: 9 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Script masters are invisible entities that are spawned at the begining of each +// map. They simple parse the script and send commands to the specified objects +// at the apropriate time. Using a combination of simple commands, very complex +// scripted events can occur. +// + +#ifndef __SCRIPTMASTER_H__ +#define __SCRIPTMASTER_H__ + +#include "g_local.h" +#include "entity.h" +//#include "trigger.h" +#include "gamescript.h" +#include "container.h" +#include "scriptvariable.h" +//#include "worldspawn.h" +#include "program.h" +#include "globalcmd.h" + +extern Program program; + +#define MAX_COMMANDS 20 + +extern ScriptVariableList gameVars; +extern ScriptVariableList levelVars; + +/* typedef enum + { + LEVEL_SCRIPT, + MODEL_SCRIPT, + CONSOLE_SCRIPT + } scripttype_t; + +extern ScriptVariableList gameVars; +extern ScriptVariableList levelVars; +extern ScriptVariableList parmVars; + +extern Event EV_ProcessCommands; +extern Event EV_ScriptThread_Execute; +extern Event EV_ScriptThread_Callback; +extern Event EV_ScriptThread_CreateThread; +extern Event EV_ScriptThread_TerminateThread; +extern Event EV_ScriptThread_ControlObject; +extern Event EV_ScriptThread_Goto; +extern Event EV_ScriptThread_Pause; +extern Event EV_ScriptThread_Wait; +extern Event EV_ScriptThread_WaitFor; +extern Event EV_ScriptThread_WaitForThread; +extern Event EV_ScriptThread_WaitForSound; +extern Event EV_ScriptThread_End; +extern Event EV_ScriptThread_Print; +extern Event EV_ScriptThread_PrintInt; +extern Event EV_ScriptThread_PrintFloat; +extern Event EV_ScriptThread_PrintVector; +extern Event EV_ScriptThread_NewLine; +extern Event EV_ScriptThread_Clear; +extern Event EV_ScriptThread_Assert; +extern Event EV_ScriptThread_Break; +extern Event EV_ScriptThread_Clear; +extern Event EV_ScriptThread_Trigger; +extern Event EV_ScriptThread_Spawn; +extern Event EV_ScriptThread_Map; +extern Event EV_ScriptThread_SetCvar; +extern Event EV_ScriptThread_CueCamera; +extern Event EV_ScriptThread_CuePlayer; +extern Event EV_ScriptThread_FreezePlayer; +extern Event EV_ScriptThread_ReleasePlayer; +extern Event EV_ScriptThread_SetCinematic; +extern Event EV_ScriptThread_SetNonCinematic; +extern Event EV_ScriptThread_SetSkipThread; +extern Event EV_ScriptThread_MissionFailed; + +class ScriptThread; + +typedef SafePtr ThreadPtr; + +class ThreadMarker; + +class ScriptThread : public Listener + { + protected: + int threadNum; + str threadName; + + scripttype_t type; + GameScript script; + Container targets; + + int linenumber; + qboolean doneProcessing; + qboolean threadDying; + + Container updateList; + float waitUntil; + str waitingFor; + ScriptThread *waitingForThread; + str waitingForVariable; + str waitingForDeath; + qboolean waitingForPlayer; + int waitingNumObjects; + ScriptVariableList localVars; + + void ObjectMoveDone( Event *ev ); + void CreateThread( Event *ev ); + void TerminateThread( Event *ev ); + void ControlObject( Event *ev ); + void EventGoto( Event *ev ); + void EventPause( Event *ev ); + void EventWait( Event *ev ); + void EventWaitFor( Event *ev ); + void EventWaitForThread( Event *ev ); + void EventWaitForVariable( Event *ev ); + void EventWaitForDeath( Event *ev ); + void EventWaitForSound( Event *ev ); + void EventWaitForDialog( Event *ev ); + void EventWaitForPlayer( Event *ev ); + void EventEnd( Event *ev ); + void Print( Event *ev ); + void PrintInt( Event *ev ); + void PrintFloat( Event *ev ); + void PrintVector( Event *ev ); + void NewLine( Event *ev ); + void Assert( Event *ev ); + void Break( Event *ev ); + void Clear( Event *ev ); + void ScriptCallback( Event *ev ); + void ThreadCallback( Event *ev ); + void VariableCallback( Event *ev ); + void DeathCallback( Event *ev ); + void DoMove( void ); + void Execute( Event *ev ); + void TriggerEvent( Event *ev ); + void ServerEvent( Event *ev ); + void ClientEvent( Event *ev ); + void CacheResourceEvent( Event *ev ); + void RegisterAlias( Event *ev ); + void RegisterAliasAndCache( Event *ev ); + void MapEvent( Event *ev ); + void SetCvarEvent( Event *ev ); + void SetThreadName( Event *ev ); + + TargetList *GetTargetList( const str &targetname ); + + void CueCamera( Event *ev ); + void CuePlayer( Event *ev ); + void FreezePlayer( Event *ev ); + void ReleasePlayer( Event *ev ); + void Spawn( Event *ev ); + void FadeIn( Event *ev ); + void FadeOut( Event *ev ); + void FadeSound( Event *ev ); + void ClearFade( Event *ev ); + void Letterbox( Event *ev ); + void ClearLetterbox( Event *ev ); + void MusicEvent( Event *ev ); + void ForceMusicEvent( Event *ev ); + void MusicVolumeEvent( Event *ev ); + void RestoreMusicVolumeEvent( Event *ev ); + void SoundtrackEvent( Event *ev ); + void RestoreSoundtrackEvent( Event *ev ); + void ScriptError( const char *fmt, ... ); + void SetCinematic( Event *ev ); + void SetNonCinematic( Event *ev ); + void SetAllAIOff( Event *ev ); + void SetAllAIOn( Event *ev ); + void SetSkipThread( Event *ev ); + void PassToPathmanager( Event *ev ); + void StuffCommand( Event *ev ); + void KillEnt( Event *ev ); + void RemoveEnt( Event *ev ); + void KillClass( Event *ev ); + void RemoveClass( Event *ev ); + void CameraCommand( Event *ev ); + void FakePlayer( Event *ev ); + void RemoveFakePlayer( Event *ev ); + void SetDialogScript( Event *ev ); + void SetLightStyle( Event *ev ); + void KillThreadEvent( Event *ev ); + void CenterPrint( Event *ev ); + void LocationPrint( Event *ev ); + void MissionFailed( Event *ev ); + void ArenaCommand( Event *ev ); + + public: + CLASS_PROTOTYPE( ScriptThread ); + + ScriptThread(); + ~ScriptThread(); + void ClearWaitFor( void ); + void SetType( scripttype_t newtype ); + scripttype_t GetType( void ); + void SetThreadNum( int num ); + int ThreadNum( void ); + const char *ThreadName( void ); + int CurrentLine( void ); + const char *Filename( void ); + qboolean WaitingFor( Entity *obj ); + ScriptThread *WaitingOnThread( void ); + const char *WaitingOnVariable( void ); + const char *WaitingOnDeath( void ); + qboolean WaitingOnPlayer( void ); + ScriptVariableList *Vars( void ); + qboolean Setup( int num, GameScript *scr, const char *label ); + qboolean SetScript( const char *name ); + qboolean Goto( const char *name ); + qboolean labelExists( const char *name ); + void Start( float delay ); + void StartImmediately( void ); + + void Mark( ThreadMarker *mark ); + void Restore( ThreadMarker *mark ); + + void SendCommandToSlaves( const char *name, Event *ev ); + qboolean FindEvent( const char *name ); + void ProcessCommand( int argc, const char **argv ); + void ProcessCommandFromEvent( Event *ev, int startarg ); + virtual void Archive( Archiver &arc ); + }; + +inline void ScriptThread::Archive + ( + Archiver &arc + ) + + { + Listener::Archive( arc ); + + arc.ArchiveInteger( &threadNum ); + arc.ArchiveString( &threadName ); + + ArchiveEnum( type, scripttype_t ); + + arc.ArchiveObject( &script ); + + // targets + // don't need to save out targets + if ( arc.Loading() ) + { + targets.ClearObjectList(); + } + + arc.ArchiveInteger( &linenumber ); + arc.ArchiveBoolean( &doneProcessing ); + arc.ArchiveBoolean( &threadDying ); + + // updateList + // don't need to save out updatelist + if ( arc.Loading() ) + { + updateList.ClearObjectList(); + } + + arc.ArchiveFloat( &waitUntil ); + arc.ArchiveString( &waitingFor ); + arc.ArchiveObjectPointer( ( Class ** )&waitingForThread ); + arc.ArchiveString( &waitingForVariable ); + arc.ArchiveString( &waitingForDeath ); + arc.ArchiveBoolean( &waitingForPlayer ); + arc.ArchiveInteger( &waitingNumObjects ); + arc.ArchiveObject( &localVars ); + } + +class ThreadMarker : public Class + { + public: + CLASS_PROTOTYPE( ThreadMarker ); + + int linenumber; + qboolean doneProcessing; + float waitUntil; + str waitingFor; + ScriptThread *waitingForThread; + str waitingForVariable; + str waitingForDeath; + qboolean waitingForPlayer; + int waitingNumObjects; + GameScriptMarker scriptmarker; + virtual void Archive( Archiver &arc ); + }; + +inline void ThreadMarker::Archive + ( + Archiver &arc + ) + { + Class::Archive( arc ); + + arc.ArchiveInteger( &linenumber ); + arc.ArchiveBoolean( &doneProcessing ); + arc.ArchiveFloat( &waitUntil ); + arc.ArchiveString( &waitingFor ); + arc.ArchiveObjectPointer( ( Class ** )&waitingForThread ); + arc.ArchiveString( &waitingForVariable ); + arc.ArchiveString( &waitingForDeath ); + arc.ArchiveBoolean( &waitingForPlayer ); + arc.ArchiveInteger( &waitingNumObjects ); + arc.ArchiveObject( &scriptmarker ); + } */ + + +class ScriptMaster : public Listener + { + protected: + CThread *currentThread; + Container Threads; + int threadIndex; + qboolean player_ready; + + public: + CLASS_PROTOTYPE( ScriptMaster ); + + ScriptMaster(); + ~ScriptMaster(); + void CloseScript( void ); + qboolean NotifyOtherThreads( int num ); + void KillThreads( void ); + void KillThread( const str &name ); + qboolean KillThread( int num ); + qboolean RemoveThread( int num ); + + CThread *CurrentThread( void ); + void SetCurrentThread( CThread *thread ); + CThread *CreateThread( const char *label ); + CThread *CreateThread( const char *label , Program *program ); + + CThread *CreateThread( void ); + CThread *CreateThread( Program *program ); + + CThread *GetThread( int num ); + + bool isVarGroup( const char *name ); + void DeathMessage( const char *name ); + void PlayerSpawned( void ); + qboolean PlayerReady( void ); + void PlayerNotReady( void ); + qboolean Goto( GameScript * scr, const char *name ); + qboolean labelExists( GameScript * scr, const char *name ); + int GetUniqueThreadNumber( void ); + void FindLabels( void ); + virtual void Archive( Archiver &arc ); + + }; + +inline void ScriptMaster::Archive + ( + Archiver &arc + ) + { + CThread * ptr; + int i, num; + + Listener::Archive( arc ); + + arc.ArchiveObjectPointer( ( Class ** )¤tThread ); + + if ( arc.Saving() ) + num = Threads.NumObjects(); + else + Threads.FreeObjectList(); + + arc.ArchiveInteger( &num ); + + for ( i = 1; i <= num; i++ ) + { + if ( arc.Loading() ) + { + ptr = new CThread; + Threads.AddObject( ptr ); + } + else + { + ptr = Threads.ObjectAt( i ); + } + arc.ArchiveObject( ptr ); + } + + arc.ArchiveInteger( &threadIndex ); + arc.ArchiveBoolean( &player_ready ); + } + +inline qboolean ScriptMaster::PlayerReady + ( + void + ) + { + return player_ready; + } + +inline void ScriptMaster::PlayerNotReady + ( + void + ) + { + player_ready = false; + } + +extern ScriptMaster Director; + +class LightStyleClass : public Class + { + private: + CLASS_PROTOTYPE( LightStyleClass ); + + str styles[ MAX_LIGHTSTYLES ]; + + public: + + void SetLightStyle( int index, const str &style ); + void Archive( Archiver &arc ); + }; + +extern LightStyleClass lightStyles; + +#endif /* scriptmaster.h */ diff --git a/dlls/game/scriptslave.cpp b/dlls/game/scriptslave.cpp new file mode 100644 index 0000000..de85e57 --- /dev/null +++ b/dlls/game/scriptslave.cpp @@ -0,0 +1,2701 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/scriptslave.cpp $ +// $Revision:: 64 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// + +// +// DESCRIPTION: +// Standard scripted objects. Controlled by ScriptThread. These objects +// are bmodel objects created in the editor and controlled by an external +// text based script. Commands are interpretted on by one and executed +// upon a signal from the script master. The base script object can +// perform several different relative and specific rotations and translations +// and can cause other parts of the script to be executed when touched, damaged, +// touched, or used. +// + +#include "_pch_cpp.h" +#include "class.h" +#include "mover.h" +#include "scriptmaster.h" +#include "scriptslave.h" +#include "sentient.h" +#include "item.h" +#include "gibs.h" +#include "explosion.h" +#include "equipment.h" +#include + +/*****************************************************************************/ +/*QUAKED script_object (0 0.5 1) ? NOT_SOLID + +******************************************************************************/ + +Event EV_ScriptSlave_DoMove +( + "processCommands", + EV_CODEONLY, + NULL, + NULL, + "Move the script slave." +); +Event EV_ScriptSlave_NewOrders +( + "newOrders", + EV_CODEONLY, + NULL, + NULL, + "Inform script that it is about to get new orders." +); +Event EV_ScriptSlave_Angles +( + "angles", + EV_SCRIPTONLY, + "v", + "angles", + "Sets the angles." +); +Event EV_ScriptSlave_Trigger +( + "trigger", + EV_SCRIPTONLY, + "s", + "entname", + "Trigger entities target." +); +Event EV_ScriptSlave_Next +( + "next", + EV_DEFAULT, + NULL, + NULL, + "Goto the next waypoint." +); +Event EV_ScriptSlave_JumpTo +( + "jumpto", + EV_SCRIPTONLY, + "sFF", + "vector_or_entity token token", + "Jump to specified vector or entity." +); +Event EV_ScriptSlave_MoveTo +( + "moveto", + EV_SCRIPTONLY, + "e", + "entity_to_move_to", + "Move to the specified entity." +); +Event EV_ScriptSlave_MoveToPosition +( + "movetopos", + EV_SCRIPTONLY, + "v", + "position", + "Move to the specified position." +); +Event EV_ScriptSlave_Speed +( + "speed", + EV_DEFAULT, + "f", + "speed", + "Sets the speed." +); +Event EV_ScriptSlave_Time +( + "time", + EV_SCRIPTONLY, + "f", + "travel_time", + "Sets the travel time." +); +Event EV_ScriptSlave_MoveUp +( + "moveUp", + EV_SCRIPTONLY, + "f", + "dist", + "Move the position up." +); +Event EV_ScriptSlave_MoveDown +( + "moveDown", + EV_SCRIPTONLY, + "f", + "dist", + "Move the position down." +); +Event EV_ScriptSlave_MoveNorth +( + "moveNorth", + EV_SCRIPTONLY, + "f", + "dist", + "Move the position north." +); +Event EV_ScriptSlave_MoveSouth +( + "moveSouth", + EV_SCRIPTONLY, + "f", + "dist", + "Move the position south." +); +Event EV_ScriptSlave_MoveEast +( + "moveEast", + EV_SCRIPTONLY, + "f", + "dist", + "Move the position east." +); +Event EV_ScriptSlave_MoveWest +( + "moveWest", + EV_SCRIPTONLY, + "f", + "dist", + "Move the position west." +); +Event EV_ScriptSlave_MoveForward +( + "moveForward", + EV_SCRIPTONLY, + "f", + "dist", + "Move the position forward." +); +Event EV_ScriptSlave_MoveBackward +( + "moveBackward", + EV_SCRIPTONLY, + "f", + "dist", + "Move the position backward." +); +Event EV_ScriptSlave_MoveLeft +( + "moveLeft", + EV_SCRIPTONLY, + "f", + "dist", + "Move the position left." +); +Event EV_ScriptSlave_MoveRight +( + "moveRight", + EV_SCRIPTONLY, + "f", + "dist", + "Move the position right." +); +Event EV_ScriptSlave_RotateXDownTo +( + "rotateXdownto", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the x down to angle." +); +Event EV_ScriptSlave_RotateYDownTo +( + "rotateYdownto", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the y down to angle." +); +Event EV_ScriptSlave_RotateZDownTo +( + "rotateZdownto", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the z down to angle." +); +Event EV_ScriptSlave_RotateAxisDownTo +( + "rotateaxisdownto", + EV_SCRIPTONLY, + "if", + "axis angle", + "Rotate the specified axis down to angle." +); +Event EV_ScriptSlave_RotateXUpTo +( + "rotateXupto", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the x up to angle." +); +Event EV_ScriptSlave_RotateYUpTo +( + "rotateYupto", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the y up to angle." +); +Event EV_ScriptSlave_RotateZUpTo +( + "rotateZupto", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the z up to angle." +); +Event EV_ScriptSlave_RotateAxisUpTo +( + "rotateaxisupto", + EV_SCRIPTONLY, + "if", + "axis angle", + "Rotate the specified axis up to angle." +); +Event EV_ScriptSlave_RotateXDown +( + "rotateXdown", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the x down by the specified amount." +); +Event EV_ScriptSlave_RotateYDown +( + "rotateYdown", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the y down by the specified amount." +); +Event EV_ScriptSlave_RotateZDown +( + "rotateZdown", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the z down by the specified amount." +); +Event EV_ScriptSlave_RotateAxisDown +( + "rotateaxisdown", + EV_SCRIPTONLY, + "if", + "axis angle", + "Rotate the specified axis down by the specified amount." +); +Event EV_ScriptSlave_RotateXUp +( + "rotateXup", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the x up by the specified amount." +); +Event EV_ScriptSlave_RotateYUp +( + "rotateYup", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the y up by the specified amount." +); +Event EV_ScriptSlave_RotateZUp +( + "rotateZup", + EV_SCRIPTONLY, + "f", + "angle", + "Rotate the z up by the specified amount." +); +Event EV_ScriptSlave_RotateAxisUp +( + "rotateaxisup", + EV_SCRIPTONLY, + "if", + "axis angle", + "Rotate the specified axis up by the specified amount." +); +Event EV_ScriptSlave_RotateX +( + "rotateX", + EV_SCRIPTONLY, + "f", + "avelocity", + "Rotate about the x axis at the specified angular velocity." +); +Event EV_ScriptSlave_RotateY +( + "rotateY", + EV_SCRIPTONLY, + "f", + "avelocity", + "Rotate about the y axis at the specified angular velocity." +); +Event EV_ScriptSlave_RotateZ +( + "rotateZ", + EV_SCRIPTONLY, + "f", + "avelocity", + "Rotate about the z axis at the specified angular velocity." +); +Event EV_ScriptSlave_RotateAxis +( + "rotateaxis", + EV_SCRIPTONLY, + "if", + "axis avelocity", + "Rotate about the specified axis at the specified angular velocity." +); +Event EV_ScriptSlave_RotateDownTo +( + "rotatedownto", + EV_SCRIPTONLY, + "v", + "direction", + "Rotate down to the specified direction." +); +Event EV_ScriptSlave_RotateUpTo +( + "rotateupto", + EV_SCRIPTONLY, + "v", + "direction", + "Rotate up to the specified direction." +); +Event EV_ScriptSlave_RotateTo +( + "rotateto", + EV_SCRIPTONLY, + "v", + "direction", + "Rotate to the specified direction." +); +Event EV_ScriptSlave_OnTouch +( + "ontouch", + EV_SCRIPTONLY, + "s", + "label", + "Sets what label to jump to and process script at when touched." +); +Event EV_ScriptSlave_NoTouch +( + "notouch", + EV_SCRIPTONLY, + NULL, + NULL, + "Removes the ontouch thread." +); +Event EV_ScriptSlave_OnUse +( + "onuse", + EV_SCRIPTONLY, + "s", + "label", + "Sets what label to jump to and process script at when used." +); +Event EV_ScriptSlave_NoUse +( + "nouse", + EV_SCRIPTONLY, + NULL, + NULL, + "Removes the onuse thread." +); +Event EV_ScriptSlave_OnBlock +( + "onblock", + EV_SCRIPTONLY, + "s", + "label", + "Sets what label to jump to and process script at when blocked." +); +Event EV_ScriptSlave_NoBlock +( + "noblock", + EV_SCRIPTONLY, + NULL, + NULL, + "Removes the onblock thread." +); +Event EV_ScriptSlave_OnTrigger +( + "ontrigger", + EV_SCRIPTONLY, + "s", + "label", + "Sets what label to jump to and process script at when triggered." +); +Event EV_ScriptSlave_NoTrigger +( + "notrigger", + EV_SCRIPTONLY, + NULL, + NULL, + "Removes the ontrigger thread." +); +Event EV_ScriptSlave_OnDamage +( + "ondamage", + EV_SCRIPTONLY, + "s", + "label", + "Sets what label to jump to and process script at when damaged." +); +Event EV_ScriptSlave_NoDamage +( + "nodamage", + EV_DEFAULT, + NULL, + NULL, + "Removes the ondamage thread." +); +Event EV_ScriptSlave_SetMeansOfDeath +( + "setmeansofdeath", + EV_SCRIPTONLY, + "s", + "means_of_death", + "Set the damage means of death." +); +Event EV_ScriptSlave_SetDamageSpawn +( + "dmg", + EV_SCRIPTONLY, + "f", + "damage", + "Set the damage." +); +Event EV_ScriptSlave_FollowPath +( + "followpath", + EV_SCRIPTONLY, + "eSSSSSS", + "path arg1 arg2 arg3 arg4 arg5 arg6", + "Makes the script slave follow the specified path. The allowable arguments are ignoreangles,\n" + "normalangles, loop, and a number specifying the start time." +); +Event EV_ScriptSlave_EndPath +( + "endpath", + EV_SCRIPTONLY, + NULL, + NULL, + "Stop following the path" +); +Event EV_ScriptSlave_MoveDone +( + "scriptslave_movedone", + EV_CODEONLY, + NULL, + NULL, + "Called when the script slave is doen moving" +); +Event EV_ScriptSlave_FollowingPath +( + "scriptslave_followingpath", + EV_CODEONLY, + NULL, + NULL, + "Called every frame to actually follow the path" +); +Event EV_ScriptSlave_Explode +( + "scriptSlave_explode", + EV_SCRIPTONLY, + "f", + "damage", + "Creates an explosion at the script slave's position" +); +Event EV_ScriptSlave_NotShootable +( + "notshootable", + EV_SCRIPTONLY, + NULL, + NULL, + "Makes the script slave not shootable" +); +Event EV_ScriptSlave_OpenAreaPortal +( + "openportal", + EV_SCRIPTONLY, + NULL, + NULL, + "Open the area portal enclosed in this object" +); +Event EV_ScriptSlave_CloseAreaPortal +( + "closeportal", + EV_SCRIPTONLY, + NULL, + NULL, + "Close the area portal enclosed in this object" +); +Event EV_ScriptSlave_PhysicsOn +( + "physics_on", + EV_SCRIPTONLY, + NULL, + NULL, + "Turn physics on this script object on" +); +Event EV_ScriptSlave_PhysicsOff +( + "physics_off", + EV_SCRIPTONLY, + NULL, + NULL, + "Turn physics off this script object on" +); +Event EV_ScriptSlave_PhysicsVelocity +( + "physics_velocity", + EV_SCRIPTONLY, + "v", + "impulseVector", + "Add a physical impulse to an object when it is being physically simulated" +); +Event EV_ScriptSlave_StopEvent +( + "stopspline", + EV_SCRIPTONLY, + NULL, + NULL, + "stops an scriptobject from moving on a spline" +); +Event EV_ScriptSlave_ContinueEvent +( + "continuespline", + EV_SCRIPTONLY, + NULL, + NULL, + "makes a script object continue on a spline" +); + +// 1ST PLAYABLE HACK +Event EV_ScriptSlave_Hack_SetTriggerParms +( + "setobjectparms", + EV_DEFAULT, + "ff", + "force_field_number trigger_number", + "HACK HACK HACK HACK HACK HACK HACK" +); +Event EV_ScriptSlave_Hack_GetForceFieldNumber +( + "getforcefieldnumber", + EV_DEFAULT, + "@f", + "number", + "HACK HACK HACK HACK HACK HACK HACK" +); +Event EV_ScriptSlave_Hack_GetTriggerNumber +( + "gettriggernumber", + EV_DEFAULT, + "@f", + "number", + "HACK HACK HACK HACK HACK HACK HACK" +); +Event EV_ScriptSlave_Hack_GetScanner +( + "getscanner", + EV_DEFAULT, + "@e", + "scanner", + "HACK HACK HACK HACK HACK HACK HACK" +); + +Event EV_ScriptSlave_HandlesDamage +( + "handlesdamage", + EV_DEFAULT, + "b", + "damage_flag", + "sets the handlesdamage flag on the script slave" +); +Event EV_ScriptSlave_DamageEffect +( + "damageEffect", + EV_DEFAULT, + "s", + "damageEffectModel", + "Sets the damage effect model name." +); + +Event EV_ScriptSlave_BloodModel +( + "setBloodModel", + EV_DEFAULT, + "s", + "bloodmodel", + "Sets the blood model" +); + +Event EV_ScriptSlave_AddRequiredDamageMOD +( + "addrequireddamagemod", + EV_DEFAULT, + "s", + "MOD_String", + "Adds the required MOD for damage to be applied" +); + +Event EV_ScriptSlave_SetCanBeAttackedByOtherScriptObjects +( + "allowAttackFromOtherScriptObjects", + EV_DEFAULT, + "b", + "allow_flag", + "Sets the _canBeAttackedByOtherScriptSlaves variable" +); + +CLASS_DECLARATION( Trigger, ScriptSlave, "script_object" ) +{ + { &EV_Bind, &ScriptSlave::BindEvent }, + { &EV_Unbind, &ScriptSlave::EventUnbind }, + { &EV_ScriptSlave_DoMove, &ScriptSlave::DoMove }, + { &EV_ScriptSlave_NewOrders, &ScriptSlave::NewOrders }, + { &EV_ScriptSlave_Angles, &ScriptSlave::SetAnglesEvent }, + { &EV_SetAngle, &ScriptSlave::SetAngleEvent }, + { &EV_Model, &ScriptSlave::SetModelEvent }, + { &EV_ScriptSlave_Trigger, &ScriptSlave::TriggerEvent }, + { &EV_ScriptSlave_Next, &ScriptSlave::GotoNextWaypoint }, + { &EV_ScriptSlave_JumpTo, &ScriptSlave::JumpTo }, + { &EV_ScriptSlave_MoveTo, &ScriptSlave::MoveToEvent }, + { &EV_ScriptSlave_MoveToPosition, &ScriptSlave::MoveToPositionEvent }, + { &EV_ScriptSlave_Speed, &ScriptSlave::SetSpeed }, + { &EV_ScriptSlave_Time, &ScriptSlave::SetTime }, + { &EV_ScriptSlave_MoveUp, &ScriptSlave::MoveUp }, + { &EV_ScriptSlave_MoveDown, &ScriptSlave::MoveDown }, + { &EV_ScriptSlave_MoveNorth, &ScriptSlave::MoveNorth }, + { &EV_ScriptSlave_MoveSouth, &ScriptSlave::MoveSouth }, + { &EV_ScriptSlave_MoveEast, &ScriptSlave::MoveEast }, + { &EV_ScriptSlave_MoveWest, &ScriptSlave::MoveWest }, + { &EV_ScriptSlave_MoveForward, &ScriptSlave::MoveForward }, + { &EV_ScriptSlave_MoveBackward, &ScriptSlave::MoveBackward }, + { &EV_ScriptSlave_MoveLeft, &ScriptSlave::MoveLeft }, + { &EV_ScriptSlave_MoveRight, &ScriptSlave::MoveRight }, + { &EV_ScriptSlave_RotateXDownTo, &ScriptSlave::RotateXdownto }, + { &EV_ScriptSlave_RotateYDownTo, &ScriptSlave::RotateYdownto }, + { &EV_ScriptSlave_RotateZDownTo, &ScriptSlave::RotateZdownto }, + { &EV_ScriptSlave_RotateXUpTo, &ScriptSlave::RotateXupto }, + { &EV_ScriptSlave_RotateYUpTo, &ScriptSlave::RotateYupto }, + { &EV_ScriptSlave_RotateZUpTo, &ScriptSlave::RotateZupto }, + { &EV_ScriptSlave_RotateXDown, &ScriptSlave::RotateXdown }, + { &EV_ScriptSlave_RotateYDown, &ScriptSlave::RotateYdown }, + { &EV_ScriptSlave_RotateZDown, &ScriptSlave::RotateZdown }, + { &EV_ScriptSlave_RotateXUp, &ScriptSlave::RotateXup }, + { &EV_ScriptSlave_RotateYUp, &ScriptSlave::RotateYup }, + { &EV_ScriptSlave_RotateZUp, &ScriptSlave::RotateZup }, + { &EV_ScriptSlave_RotateX, &ScriptSlave::RotateX }, + { &EV_ScriptSlave_RotateY, &ScriptSlave::RotateY }, + { &EV_ScriptSlave_RotateZ, &ScriptSlave::RotateZ }, + { &EV_ScriptSlave_RotateAxisDownTo, &ScriptSlave::RotateAxisdownto }, + { &EV_ScriptSlave_RotateAxisUpTo, &ScriptSlave::RotateAxisupto }, + { &EV_ScriptSlave_RotateAxisDown, &ScriptSlave::RotateAxisdown }, + { &EV_ScriptSlave_RotateAxisUp, &ScriptSlave::RotateAxisup }, + { &EV_ScriptSlave_RotateAxis, &ScriptSlave::RotateZ }, + { &EV_ScriptSlave_OnTouch, &ScriptSlave::OnTouch }, + { &EV_ScriptSlave_NoTouch, &ScriptSlave::NoTouch }, + { &EV_ScriptSlave_OnUse, &ScriptSlave::OnUse }, + { &EV_ScriptSlave_NoUse, &ScriptSlave::NoUse }, + { &EV_ScriptSlave_OnBlock, &ScriptSlave::OnBlock }, + { &EV_ScriptSlave_NoBlock, &ScriptSlave::NoBlock }, + { &EV_ScriptSlave_OnTrigger, &ScriptSlave::OnTrigger }, + { &EV_ScriptSlave_NoTrigger, &ScriptSlave::NoTrigger }, + { &EV_ScriptSlave_OnDamage, &ScriptSlave::OnDamage }, + { &EV_ScriptSlave_NoDamage, &ScriptSlave::NoDamage }, + { &EV_ScriptSlave_SetMeansOfDeath, &ScriptSlave::SetMeansOfDeath }, + { &EV_ScriptSlave_SetDamageSpawn, &ScriptSlave::SetDamage }, + { &EV_ScriptSlave_FollowPath, &ScriptSlave::FollowPath }, + { &EV_ScriptSlave_EndPath, &ScriptSlave::EndPath }, + { &EV_ScriptSlave_FollowingPath, &ScriptSlave::FollowingPath }, + { &EV_Touch, &ScriptSlave::TouchFunc }, + { &EV_Blocked, &ScriptSlave::BlockFunc }, + { &EV_Activate, &ScriptSlave::TriggerFunc }, + { &EV_Use, &ScriptSlave::UseFunc }, + { &EV_ScriptSlave_MoveDone, &ScriptSlave::MoveEnd }, + { &EV_Damage, &ScriptSlave::DamageFunc }, + { &EV_ScriptSlave_RotateDownTo, &ScriptSlave::Rotatedownto }, + { &EV_ScriptSlave_RotateUpTo, &ScriptSlave::Rotateupto }, + { &EV_ScriptSlave_RotateTo, &ScriptSlave::Rotateto }, + { &EV_ScriptSlave_Explode, &ScriptSlave::Explode }, + { &EV_ScriptSlave_NotShootable, &ScriptSlave::NotShootable }, + { &EV_ScriptSlave_OpenAreaPortal, &ScriptSlave::OpenPortal }, + { &EV_ScriptSlave_CloseAreaPortal, &ScriptSlave::ClosePortal }, + { &EV_ScriptSlave_PhysicsOn, &ScriptSlave::PhysicsOn }, + { &EV_ScriptSlave_PhysicsOff, &ScriptSlave::PhysicsOff }, + { &EV_ScriptSlave_PhysicsVelocity, &ScriptSlave::PhysicsVelocity }, + { &EV_ScriptSlave_StopEvent, &ScriptSlave::StopEvent }, + { &EV_ScriptSlave_ContinueEvent, &ScriptSlave::ContinueEvent }, + { &EV_SetGameplayDamage, &ScriptSlave::setDamage }, + { &EV_ScriptSlave_HandlesDamage, &ScriptSlave::HandlesDamage }, + { &EV_ScriptSlave_DamageEffect, &ScriptSlave::setDamageEffect }, + { &EV_ScriptSlave_BloodModel, &ScriptSlave::setBloodModel }, + { &EV_ScriptSlave_AddRequiredDamageMOD, &ScriptSlave::addRequiredDamageMOD }, + { &EV_ScriptSlave_SetCanBeAttackedByOtherScriptObjects, &ScriptSlave::setCanBeAttackedByOtherSlaves }, + + //1ST PLAYABLE HACK STUFF + { &EV_ScriptSlave_Hack_SetTriggerParms, &ScriptSlave::Hack_AddParms }, + { &EV_ScriptSlave_Hack_GetForceFieldNumber, &ScriptSlave::Hack_GetForceFieldNumber }, + { &EV_ScriptSlave_Hack_GetTriggerNumber, &ScriptSlave::Hack_GetTriggerNumber }, + { &EV_ScriptSlave_Hack_GetScanner, &ScriptSlave::Hack_GetScanner }, + + { NULL, NULL } +}; + +ScriptSlave::ScriptSlave() +{ + mover = new Mover( this ); + + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + // this is a normal entity + edict->s.eType = ET_GENERAL; + + speed = 100; + takedamage = DAMAGE_YES; + waypoint = NULL; + SetNewAngles( localangles ); + SetNewPosition( GetLocalOrigin() ); + traveltime = 0; + commandswaiting = false; + movethread = NULL; + touchthread = NULL; + blockthread = NULL; + damagethread = NULL; + triggerthread = NULL; + usethread = NULL; + splinePath = NULL; + splineangles = false; + attack_finished = 0; + thinking = true; + + dmg = 2; + dmg_means_of_death = MOD_CRUSH; + + setMoveType( MOVETYPE_PUSH ); + setSolidType( SOLID_NOT ); + + //1ST PLAYABLE HACK + _forcefieldNumber = -1.0f; + _triggerNumber = -1.0f; + _scanner = NULL; + + _portalOpen = false; + _handlesDamage = false; + + _nextNeedToUseTime = 0.0f; + _canBeAttackedByOtherScriptSlaves = true; + + if ( spawnflags & 1 ) + { + PostEvent( EV_BecomeNonSolid, EV_POSTSPAWN ); + } +} + +ScriptSlave::~ScriptSlave() +{ + if ( splinePath ) + { + delete splinePath; + splinePath = NULL; + } +} + +//-------------------------------------------------------------- +// +// Name: setDamage +// Class: ScriptSlave +// +// Description: This function acts as a filter to the real function. +// It gets data from the database, and then passes it +// along to the original event. This is here as an attempt +// to sway people into using the database standard instead of +// hardcoded numbers. +// +// Parameters: Event *ev +// str -- The value keyword from the database (low, medium, high, etc). +// +// Returns: None +// +//-------------------------------------------------------------- +void ScriptSlave::setDamage( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasFormula("OffensiveDamage") ) + return; + + str damagestr = ev->GetString( 1 ); + float damagemod = 1.0f; + if ( gpm->getDefine(damagestr) != "" ) + damagemod = (float)atof(gpm->getDefine(damagestr)); + GameplayFormulaData fd(this, 0, 0, ""); + float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod); + Event *newev = new Event(EV_ScriptSlave_SetDamageSpawn); + newev->AddFloat(finaldamage); + ProcessEvent(newev); +} + +void ScriptSlave::setOrigin( const Vector &point ) +{ + Trigger::setOrigin( point ); + SetNewPosition( GetLocalOrigin() ); +} + +void ScriptSlave::setOrigin( void ) +{ + Trigger::setOrigin(); + SetNewPosition( GetLocalOrigin() ); +} + +void ScriptSlave::NewOrders( Event *ev ) +{ + // make sure position and angles are current + SetNewAngles( localangles ); + SetNewPosition( GetLocalOrigin() ); +} + +void ScriptSlave::BindEvent( Event *ev ) +{ + Entity *ent; + + ent = ev->GetEntity( 1 ); + if ( ent ) + { + bind( ent ); + } + + // make sure position and angles are current + SetNewAngles( localangles ); + SetNewPosition( GetLocalOrigin() ); +} + +void ScriptSlave::EventUnbind( Event *ev ) +{ + unbind(); + + // make sure position and angles are current + SetNewAngles( localangles ); + SetNewPosition( GetLocalOrigin() ); +} + +void ScriptSlave::DoMove( Event *ev ) +{ + float dist; + CThread *thread; + Event *event; + + thread = ev->GetThread(); + assert( thread ); + if ( thread && thread->WaitingFor( this ) ) + { + if ( movethread && ( movethread != thread ) ) + { + // warn the user + ev->Error( "Overriding previous move commands for '%s'\n", TargetName() ); + + // Yeah, we're not REALLY done, but we tell our old thread + // that we are so that it doesn't wait forever + event = new Event( EV_MoveDone ); + event->AddEntity( this ); + movethread->ProcessEvent( event ); + } + + movethread = thread; + } + if ( commandswaiting ) + { + if ( splinePath ) + { + moving = true; + PostEvent( EV_ScriptSlave_FollowingPath, 0.0f ); + } + else + { + float t = traveltime; + if ( t == 0.0f ) + { + dist = Vector( GetNewPosition() - GetLocalOrigin() ).length(); + t = dist / speed; + } + moving = true; + mover->LinearInterpolate( GetNewPosition(), GetNewAngles(), t, EV_ScriptSlave_MoveDone ); + } + commandswaiting = false; + } + else if ( movethread && ( movethread == thread ) && !moving ) + { + // No commands, so tell the master that we're done + PostEvent( EV_ScriptSlave_MoveDone, 0.0f ); + } +} + + + +void ScriptSlave::MoveEnd( Event *ev ) +{ + Event *event; + + moving = false; + commandswaiting = false; + SetNewAngles( localangles ); + SetNewPosition( GetLocalOrigin() ); + + if ( movethread ) + { + event = new Event( EV_MoveDone ); + event->AddEntity( this ); + movethread->ProcessEvent( event ); + movethread = NULL; + } +} + +void ScriptSlave::SetAnglesEvent( Event *ev ) +{ + commandswaiting = true; + setAngles( ev->GetVector( 1 ) ); + SetNewAngles( localangles ); +} + +void ScriptSlave::SetAngleEvent( Event *ev ) +{ + float angle; + + angle = ev->GetFloat( 1 ); + if ( angle == -1.0f ) + { + ForwardDir = Vector( 0.0f, 0.0f, 90.0f ); + } + else if ( angle == -2.0f ) + { + ForwardDir = Vector( 0.0f, 0.0f, -90.0f ); + } + else + { + ForwardDir = Vector( 0.0f, angle, 0.0f ); + } +} + +void ScriptSlave::SetModelEvent( Event *ev ) +{ + const char *m; + + m = ev->GetString( 1 ); + + setModel( m ); + showModel(); + + if ( !edict->s.modelindex ) + { + hideModel(); + setSolidType( SOLID_NOT ); + } + else if ( !m || strstr( m, ".tik" ) ) + { + setSolidType( SOLID_BBOX ); + } + else if ( strstr( m, ".spr" ) ) + { + setSolidType( SOLID_NOT ); + } + else + { + setSolidType( SOLID_BSP ); + } +} + +void ScriptSlave::TriggerEvent( Event *ev ) +{ + Entity *ent; + Event *e; + str name; + + name = ev->GetString(1); + ent = G_FindTarget(NULL, name); + + if ( ent ) + { + SetTarget( ent->TargetName() ); + + e = new Event( EV_Trigger_ActivateTargets ); + //fixme + //get "other" + e->AddEntity( world ); + ProcessEvent( e ); + } + else + { + gi.WDPrintf( "Invalid entity reference '%s'.\n", name.c_str() ); + } +} + +void ScriptSlave::GotoNextWaypoint( Event *ev ) +{ + commandswaiting = true; + + if ( !waypoint ) + { + ev->Error( "%s is currently not at a waypoint", TargetName() ); + return; + } + + waypoint = ( Waypoint * )G_FindTarget( NULL, waypoint->Target() ); + if ( !waypoint ) + { + ev->Error( "%s could not find waypoint %s", TargetName(), waypoint->Target() ); + return; + } + else + { + SetNewPosition( waypoint->origin ); + } +} + +void ScriptSlave::JumpTo( Event *ev ) +{ + Entity *part; + + // + // see if it is a vector + // + if ( ev->IsVectorAt( 1 ) ) + { + SetNewPosition( ev->GetVector( 1 ) ); + if ( bind_info && bind_info->bindmaster ) + { + SetLocalOrigin( bind_info->bindmaster->getLocalVector( GetNewPosition() - bind_info->bindmaster->origin ) ); + } + else + { + SetLocalOrigin( GetNewPosition() ); + } + + part = this; + + while( part ) + { + part->setOrigin(); + part->origin.copyTo( part->edict->s.origin2 ); + part->edict->s.renderfx |= RF_FRAMELERP; + + if ( part->bind_info ) + part = part->bind_info->teamchain; + else + part = NULL; + } + } + else + { + waypoint = ( Waypoint * )ev->GetEntity( 1 ); + if ( waypoint ) + { + SetNewPosition( waypoint->GetLocalOrigin() ); + if ( bind_info && bind_info->bindmaster ) + { + SetLocalOrigin( bind_info->bindmaster->getLocalVector( GetNewPosition() - bind_info->bindmaster->origin ) ); + } + else + { + SetLocalOrigin( GetNewPosition() ); + } + + part = this; + + while( part ) + { + part->setOrigin(); + part->origin.copyTo( part->edict->s.origin2 ); + part->edict->s.renderfx |= RF_FRAMELERP; + + if ( part->bind_info ) + part = part->bind_info->teamchain; + else + part = NULL; + } + } + } +} + +void ScriptSlave::MoveToEvent( Event *ev ) +{ + commandswaiting = true; + + waypoint = ( Waypoint * )ev->GetEntity( 1 ); + + if ( waypoint ) + { + SetNewPosition( waypoint->origin ); + } +} + +void ScriptSlave::MoveToPositionEvent( Event *ev ) +{ + commandswaiting = true; + + SetNewPosition( ev->GetVector( 1 ) ); +} + +void ScriptSlave::SetSpeed( Event *ev ) +{ + speed = ev->GetFloat( 1 ); + traveltime = 0; +} + +void ScriptSlave::SetTime( Event *ev ) +{ + traveltime = ev->GetFloat( 1 ); +} + +// Relative move commands + +void ScriptSlave::MoveUp( Event *ev ) +{ + commandswaiting = true; + SetNewPosition( Vector( GetNewPosition().x, GetNewPosition().y, GetNewPosition().z + ev->GetFloat( 1 ) ) ); +} + +void ScriptSlave::MoveDown( Event *ev ) +{ + commandswaiting = true; + SetNewPosition( Vector( GetNewPosition().x, GetNewPosition().y, GetNewPosition().z - ev->GetFloat( 1 ) ) ); +} + +void ScriptSlave::MoveNorth( Event *ev ) +{ + commandswaiting = true; + SetNewPosition( Vector( GetNewPosition().x, GetNewPosition().y + ev->GetFloat( 1 ), GetNewPosition().z ) ); +} + +void ScriptSlave::MoveSouth( Event *ev ) +{ + commandswaiting = true; + SetNewPosition( Vector( GetNewPosition().x, GetNewPosition().y - ev->GetFloat( 1 ), GetNewPosition().z ) ); +} + +void ScriptSlave::MoveEast( Event *ev ) +{ + commandswaiting = true; + SetNewPosition( Vector( GetNewPosition().x + ev->GetFloat( 1 ), GetNewPosition().y, GetNewPosition().z ) ); +} + +void ScriptSlave::MoveWest( Event *ev ) +{ + commandswaiting = true; + SetNewPosition( Vector( GetNewPosition().x - ev->GetFloat( 1 ), GetNewPosition().y, GetNewPosition().z ) ); +} + +void ScriptSlave::MoveForward( Event *ev ) +{ + Vector v; + Vector t; + + commandswaiting = true; + + t = GetNewAngles() + ForwardDir; + t.AngleVectors( &v, NULL, NULL ); + + SetNewPosition( GetNewPosition() + v * ev->GetFloat( 1 ) ); +} + +void ScriptSlave::MoveBackward( Event *ev ) +{ + Vector v; + Vector t; + + commandswaiting = true; + + t = GetNewAngles() + ForwardDir; + t.AngleVectors( &v, NULL, NULL ); + + SetNewPosition( GetNewPosition() - v * ev->GetFloat( 1 ) ); +} + +void ScriptSlave::MoveLeft( Event *ev ) +{ + Vector v; + Vector t; + + commandswaiting = true; + + t = GetNewAngles() + ForwardDir; + t.AngleVectors( NULL, &v, NULL ); + + SetNewPosition( GetNewPosition() + v * ev->GetFloat( 1 ) ); +} + +void ScriptSlave::MoveRight( Event *ev ) +{ + Vector t; + Vector v; + + commandswaiting = true; + + t = GetNewAngles() + ForwardDir; + t.AngleVectors( NULL, &v, NULL ); + + SetNewPosition( GetNewPosition() - v * ev->GetFloat( 1 ) ); +} + +// exact rotate commands + +void ScriptSlave::RotateXdownto( Event *ev ) +{ + commandswaiting = true; + + Vector newAngles( GetNewAngles() ); + newAngles[ 0 ] = ev->GetFloat( 1 ); + if ( newAngles[ 0 ] < localangles[ 0 ] ) + { + newAngles[ 0 ] -= 360.0f; + } + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateYdownto( Event *ev ) +{ + commandswaiting = true; + + Vector newAngles( GetNewAngles() ); + newAngles[ 1 ] = ev->GetFloat( 1 ); + if ( newAngles[ 1 ] < localangles[ 1 ] ) + { + newAngles[ 1 ] -= 360.0f; + } + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateZdownto( Event *ev ) +{ + commandswaiting = true; + + Vector newAngles( GetNewAngles() ); + newAngles[ 2 ] = ev->GetFloat( 1 ); + if ( newAngles[ 2 ] < localangles[ 2 ] ) + { + newAngles[ 2 ] -= 360.0f; + } + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateAxisdownto( Event *ev ) +{ + int axis; + commandswaiting = true; + + axis = ev->GetInteger( 1 ); + + Vector newAngles( GetNewAngles() ); + newAngles[ axis ] = ev->GetFloat( 1 ); + if ( newAngles[ axis ] < localangles[ axis ] ) + { + newAngles[ axis ] -= 360.0f; + } + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateXupto( Event *ev ) +{ + commandswaiting = true; + Vector newAngles( GetNewAngles() ); + newAngles[ 0 ] = ev->GetFloat( 1 ); + if ( newAngles[ 0 ] < localangles[ 0 ] ) + { + newAngles[ 0 ] += 360.0f; + } + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateYupto( Event *ev ) +{ + commandswaiting = true; + + Vector newAngles( GetNewAngles() ); + newAngles[ 1 ] = ev->GetFloat( 1 ); + if ( newAngles[ 1 ] < localangles[ 1 ] ) + { + newAngles[ 1 ] += 360.0f; + } + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateZupto( Event *ev ) +{ + commandswaiting = true; + + Vector newAngles( GetNewAngles() ); + newAngles[ 2 ] = ev->GetFloat( 1 ); + if ( newAngles[ 2 ] < localangles[ 2 ] ) + { + newAngles[ 2 ] += 360.0f; + } + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateAxisupto( Event *ev ) +{ + int axis; + commandswaiting = true; + + axis = ev->GetInteger( 1 ); + Vector newAngles( GetNewAngles() ); + newAngles[ axis ] = ev->GetFloat( 1 ); + if ( newAngles[ axis ] < localangles[ axis ] ) + { + newAngles[ axis ] += 360.0f; + } + SetNewAngles( newAngles ); +} + +// full vector rotation + +void ScriptSlave::Rotatedownto( Event *ev ) +{ + Vector ang; + commandswaiting = true; + + ang = ev->GetVector( 1 ); + + Vector newAngles( GetNewAngles() ); + newAngles[ 0 ] = ang[ 0 ]; + if ( newAngles[ 0 ] > localangles[ 0 ] ) + { + newAngles[ 0 ] -= 360.0f; + } + newAngles[ 1 ] = ang[ 1 ]; + if ( newAngles[ 1 ] > localangles[ 1 ] ) + { + newAngles[ 1 ] -= 360.0f; + } + newAngles[ 2 ] = ang[ 2 ]; + if ( newAngles[ 2 ] > localangles[ 2 ] ) + { + newAngles[ 2 ] -= 360.0f; + } + SetNewAngles( newAngles ); +} + +void ScriptSlave::Rotateupto( Event *ev ) +{ + Vector ang; + commandswaiting = true; + + ang = ev->GetVector( 1 ); + + Vector newAngles( GetNewAngles() ); + newAngles[ 0 ] = ang[ 0 ]; + if ( newAngles[ 0 ] < localangles[ 0 ] ) + { + newAngles[ 0 ] += 360.0f; + } + newAngles[ 1 ] = ang[ 1 ]; + if ( newAngles[ 1 ] < localangles[ 1 ] ) + { + newAngles[ 1 ] += 360.0f; + } + newAngles[ 2 ] = ang[ 2 ]; + if ( newAngles[ 2 ] < localangles[ 2 ] ) + { + newAngles[ 2 ] += 360.0f; + } + SetNewAngles( newAngles ); +} + +void ScriptSlave::Rotateto( Event *ev ) +{ + commandswaiting = true; + + Vector ang = ev->GetVector( 1 ); + + SetNewAngles( ang ); +} + +// Relative rotate commands + +void ScriptSlave::RotateXdown( Event *ev ) +{ + commandswaiting = true; + Vector newAngles( GetNewAngles() ); + newAngles[ 0 ] = localangles[ 0 ] - ev->GetFloat( 1 ); + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateYdown( Event *ev ) +{ + commandswaiting = true; + Vector newAngles( GetNewAngles() ); + newAngles[ 1 ] = localangles[ 1 ] - ev->GetFloat( 1 ); + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateZdown( Event *ev ) +{ + commandswaiting = true; + Vector newAngles( GetNewAngles() ); + newAngles[ 2 ] = localangles[ 2 ] - ev->GetFloat( 1 ); + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateAxisdown( Event *ev ) +{ + int axis; + commandswaiting = true; + + axis = ev->GetInteger( 1 ); + Vector newAngles( GetNewAngles() ); + newAngles[ axis ] = localangles[ axis ] - ev->GetFloat( 1 ); + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateXup( Event *ev ) +{ + commandswaiting = true; + Vector newAngles( GetNewAngles() ); + newAngles[ 0 ] = localangles[ 0 ] + ev->GetFloat( 1 ); + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateYup( Event *ev ) +{ + commandswaiting = true; + Vector newAngles( GetNewAngles() ); + newAngles[ 1 ] = localangles[ 1 ] + ev->GetFloat( 1 ); + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateZup( Event *ev ) +{ + commandswaiting = true; + Vector newAngles( GetNewAngles() ); + newAngles[ 2 ] = localangles[ 2 ] + ev->GetFloat( 1 ); + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateAxisup( Event *ev ) +{ + int axis; + commandswaiting = true; + + axis = ev->GetInteger( 1 ); + Vector newAngles( GetNewAngles() ); + newAngles[ axis ] = localangles[ axis ] + ev->GetFloat( 1 ); + SetNewAngles( newAngles ); +} + +void ScriptSlave::RotateX( Event *ev ) +{ + avelocity[ 0 ] = ev->GetFloat( 1 ); +} + +void ScriptSlave::RotateY( Event *ev ) +{ + avelocity[ 1 ] = ev->GetFloat( 1 ); +} + +void ScriptSlave::RotateZ( Event *ev ) +{ + avelocity[ 2 ] = ev->GetFloat( 1 ); +} + +void ScriptSlave::RotateAxis( Event *ev ) +{ + int axis; + + axis = ev->GetInteger( 1 ); + avelocity[ axis ] = ev->GetFloat( 2 ); +} + + +void ScriptSlave::OnTouch( Event *ev ) +{ + const char *jumpto; + + touchlabel = ""; + + jumpto = ev->GetString( 1 ); + touchthread = ev->GetThread(); + + assert( jumpto && touchthread ); + if ( touchthread && !touchthread->labelExists( jumpto ) ) + { + ev->Error( "Label '%s' not found", jumpto ); + return; + } + + // if it isn't solid than we need to change it to a trigger + if ( getSolidType() == SOLID_NOT ) + { + setSolidType( SOLID_TRIGGER ); + } + + touchlabel = jumpto; +} + +void ScriptSlave::NoTouch( Event *ev ) +{ + touchlabel = ""; + // if it is a trigger than it wasn't solid, so restore that condition + if ( getSolidType() == SOLID_TRIGGER ) + { + setSolidType( SOLID_NOT ); + } +} + +void ScriptSlave::TouchFunc( Event *ev ) +{ + Event *e; + Entity *other; + + if ( touchlabel.length() ) + { + // since we use a SafePtr, the thread pointer will be NULL if the thread has ended + // so we should just clear our label and continue + if ( !touchthread ) + { + touchlabel = ""; + return; + } + + other = ev->GetEntity( 1 ); + + setActivatingEntity( other ); + + e = new Event( EV_ScriptThread_Callback ); + e->AddEntity( this ); + e->AddString( touchlabel ); + e->AddEntity( other ); + touchthread->ProcessEvent( e ); + } +} + +void ScriptSlave::OnBlock( Event *ev ) +{ + const char *jumpto; + + blocklabel = ""; + + jumpto = ev->GetString( 1 ); + blockthread = ev->GetThread(); + + assert( jumpto && blockthread ); + if ( blockthread && !blockthread->labelExists( jumpto ) ) + { + ev->Error( "Label '%s' not found", jumpto ); + return; + } + + blocklabel = jumpto; +} + +void ScriptSlave::NoBlock( Event *ev ) +{ + blocklabel = ""; +} + +void ScriptSlave::BlockFunc( Event *ev ) +{ + Event *e; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( level.time >= attack_finished ) + { + attack_finished = level.time + ( float )0.5; + if ( dmg != 0 ) + { + other->Damage( this, this, dmg, origin, vec_zero, vec_zero, 0, 0, dmg_means_of_death ); + } + } + + if ( blocklabel.length() ) + { + // since we use a SafePtr, the thread pointer will be NULL if the thread has ended + // so we should just clear our label and continue + if ( !blockthread ) + { + blocklabel = ""; + return; + } + + setActivatingEntity( other ); + + e = new Event( EV_ScriptThread_Callback ); + e->AddEntity( this ); + e->AddString( blocklabel ); + e->AddEntity( other ); + blockthread->ProcessEvent( e ); + } +} + +void ScriptSlave::OnTrigger( Event *ev ) +{ + const char *jumpto; + + triggerlabel = ""; + + jumpto = ev->GetString( 1 ); + triggerthread = ev->GetThread(); + + assert( jumpto && triggerthread ); + + if ( triggerthread && !triggerthread->labelExists( jumpto ) ) + { + ev->Error( "Label '%s' not found", jumpto ); + return; + } + + triggerlabel = jumpto; +} + +void ScriptSlave::NoTrigger( Event *ev ) +{ + triggerlabel = ""; +} + +void ScriptSlave::TriggerFunc( Event *ev ) +{ + Event *e; + Entity *other; + + if ( triggerlabel.length() ) + { + // since we use a SafePtr, the thread pointer will be NULL if the thread has ended + // so we should just clear our label and continue + if ( !triggerthread ) + { + triggerlabel = ""; + return; + } + + other = ev->GetEntity( 1 ); + + setActivatingEntity( other ); + + e = new Event( EV_ScriptThread_Callback ); + e->AddEntity( this ); + e->AddString( triggerlabel ); + e->AddEntity( other ); + + triggerthread->ProcessEvent( e ); + } +} + +void ScriptSlave::OnUse( Event *ev ) +{ + const char *jumpto; + + uselabel = ""; + + jumpto = ev->GetString( 1 ); + usethread = ev->GetThread(); + + assert( jumpto && usethread ); + + if ( usethread && !usethread->labelExists( jumpto ) ) + { + ev->Error( "Label '%s' not found", jumpto ); + return; + } + + uselabel = jumpto; +} + +void ScriptSlave::NoUse( Event *ev ) +{ + uselabel = ""; +} + +void ScriptSlave::UseFunc( Event *ev ) +{ + Event *e; + Entity *other; + + other = ev->GetEntity( 1 ); + + // See if object == key + + if ( other->isSubclassOf( Equipment ) ) + { + Equipment *equipment = (Equipment *)other; + + if ( equipment->getTypeName() != key ) + return; + } + else if ( key.length() ) + { + if ( !other->isSubclassOf( Sentient ) || !( ( (Sentient *)other )->HasItem( key.c_str() ) ) ) + { + Item *item; + ClassDef *cls; + + cls = getClass( key.c_str() ); + if ( !cls ) + { + gi.WDPrintf( "No item named '%s'\n", key.c_str() ); + return; + } + item = ( Item * )cls->newInstance(); + item->CancelEventsOfType( EV_Item_DropToFloor ); + item->CancelEventsOfType( EV_Remove ); + item->ProcessPendingEvents(); + gi.centerprintf ( other->edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$ItemNeeded$$%s", item->getName().c_str() ); + delete item; + return; + } + else if ( other->isSubclassOf( Sentient ) ) + { + Item *item = ( (Sentient *)other )->FindItem( key.c_str() ); + + if ( !item ) + return; + + if ( item->isSubclassOf( Equipment ) ) + { + if ( _nextNeedToUseTime < level.time ) + { + gi.centerprintf ( other->edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$NeedToUse$$ %s", key.c_str() ); + + if ( other->isSubclassOf( Player ) ) + { + Player *player = (Player *)other; + + player->loadUseItem( key ); + } + + _nextNeedToUseTime = level.time + 1.0f; + } + + return; + } + } + } + + if ( uselabel.length() ) + { + //ScriptVariableList *vars; + + // since we use a SafePtr, the thread pointer will be NULL if the thread has ended + // so we should just clear our label and continue + if ( !usethread ) + { + uselabel = ""; + return; + } + + setActivatingEntity( other ); + + e = new Event( EV_ScriptThread_Callback ); + e->AddEntity( this ); + e->AddString( uselabel ); + e->AddEntity( other ); + + /* vars = usethread->Vars(); + vars->SetVariable( "other", other ); + if ( key.length() ) + { + vars->SetVariable( "key", key.c_str() ); + } */ + usethread->ProcessEvent( e ); + } +} + +void ScriptSlave::OnDamage( Event *ev ) +{ + const char *jumpto; + + damagelabel = ""; + + jumpto = ev->GetString( 1 ); + damagethread = ev->GetThread(); + + assert( jumpto && damagethread ); + + if ( damagethread && !damagethread->labelExists( jumpto ) ) + { + ev->Error( "Label '%s' not found", jumpto ); + return; + } + + damagelabel = jumpto; +} + +void ScriptSlave::NoDamage( Event *ev ) +{ + damagelabel = ""; +} + +void ScriptSlave::DamageFunc( Event *ev ) +{ + Event *e; + //Entity *inflictor; + Entity *attacker; + //int damage; + //Vector position; + //Vector direction; + //ScriptVariableList *vars; + + attacker = ev->GetEntity( 3 ); + + if ( !_canBeAttackedByOtherScriptSlaves ) + { + if ( attacker->isSubclassOf(ScriptSlave) ) + return; + } + + if ( _handlesDamage ) + { + Event *newDamageEvent = new Event (ev); + Entity::DamageEvent(newDamageEvent); + } + + if ( damagelabel.length() ) + { + // since we use a SafePtr, the thread pointer will be NULL if the thread has ended + // so we should just clear our label and continue + if ( !damagethread ) + { + damagelabel = ""; + return; + } + + if( _requiredMODlist.NumObjects() != 0 ) + { + bool modMatches = false; + for( int i = 1; !modMatches && i <= _requiredMODlist.NumObjects(); i++ ) + { + str& modname = _requiredMODlist.ObjectAt( i ); + int modID = MOD_NameToNum( modname ); + int damageMOD = ev->GetInteger( 9 ); + + if ( modID == damageMOD ) + modMatches = true; + } + + if( !modMatches ) + return; + } + + + setActivatingEntity( attacker ); + + e = new Event( EV_ScriptThread_Callback ); + e->AddEntity( this ); + e->AddString( damagelabel ); + e->AddEntity( attacker ); + + /* damage = ev->GetInteger( 1 ); + inflictor = ev->GetEntity( 2 ); + position = ev->GetVector( 4 ); + direction = ev->GetVector( 5 ); */ + + /* vars = damagethread->Vars(); + vars->SetVariable( "damage", damage ); + vars->SetVariable( "inflictor", inflictor ); + vars->SetVariable( "attacker", attacker ); + vars->SetVariable( "position", position ); + vars->SetVariable( "direction", direction ); */ + damagethread->ProcessEvent( e ); + } + + if ( _bloodModel.length() ) + { + SpawnEffect( _bloodModel , ev->GetVector( 4 ) , ev->GetVector( 6 ) , 1.0f ); + } + + if ( _damageEffect.length() ) + { + //SpawnEffect( "models/fx/fx-impactburn-sniperrifle.tik", ev->GetVector( 4 ), ev->GetVector( 6 ), 1.0f ); + SpawnEffect( "models/fx/fx-electricitymesh-impactpoint.tik", ev->GetVector( 4 ), ev->GetVector( 6 ), 1.0f ); + } +} + +void ScriptSlave::SetDamage( Event *ev ) +{ + dmg = ev->GetFloat( 1 ); +} + +void ScriptSlave::SetMeansOfDeath( Event *ev ) +{ + dmg_means_of_death = MOD_NameToNum( ev->GetString( 1 ) ); +} + +void ScriptSlave::CreatePath( SplinePath *path, splinetype_t type ) +{ + SplinePath* node; + SplinePath* nextNode; + + if ( !splinePath ) + { + splinePath = new BSpline; + } + + splinePath->Clear(); + splinePath->SetType( type ); + + node = path; + while( node != NULL ) + { + splinePath->AppendControlPoint( node->origin, node->angles, node->speed ); + + // get the next node and check it before using it + nextNode = node->GetNext(); + if( nextNode == node ) + { + gi.Error( ERR_DROP, "info_splinepath '%s' targets itself\n", node->targetname.c_str() ); + } + + // don't loop + if ( nextNode == path ) + { + break; + } + + // move to the next node + node = nextNode; + } +} + +void ScriptSlave::FollowPath( Event *ev ) +{ + int i, argnum; + Entity * ent; + const char * token; + SplinePath *path; + qboolean clamp; + float starttime; + + + ent = ev->GetEntity( 1 ); + argnum = 2; + starttime = -2; + clamp = true; + ignoreangles = false; + splineangles = true; + for ( i = argnum; i <= ev->NumArgs() ; i++ ) + { + token = ev->GetString( i ); + if (!strcmpi( token, "ignoreangles")) + { + ignoreangles = true; + } + else if (!strcmpi( token, "normalangles")) + { + splineangles = false; + } + else if (!strcmpi (token, "loop")) + { + clamp = false; + } + else if ( IsNumeric( token ) ) + { + starttime = (float)atof( token ); + } + else + { + ev->Error( "Unknown followpath command %s.", token ); + } + } + if ( ent && ent->isSubclassOf( SplinePath ) ) + { + commandswaiting = true; + path = ( SplinePath * )ent; + if ( clamp ) + CreatePath( path, SPLINE_CLAMP ); + else + CreatePath( path, SPLINE_LOOP ); + + currentNode = path; + + if ( currentNode->thread != "" ) + { + if ( !ExecuteThread( currentNode->thread , true , this) ) + { + gi.Error( ERR_DROP, "Scriptslave could not start thread '%s' from info_splinepath '%s'\n", + currentNode->thread.c_str(), currentNode->targetname.c_str() ); + } + } + + if ( currentNode->triggertarget != "" ) + { + Entity *ent; + Event *event; + + ent = NULL; + do + { + ent = G_FindTarget( ent, currentNode->triggertarget.c_str() ); + if ( !ent ) + { + break; + } + event = new Event( EV_Activate ); + event->AddEntity( this ); + ent->PostEvent( event, 0.0f ); + } + while ( 1 ); + } + + splineTime = starttime; + lastTime = (int)(splineTime + 2.0f); + newTime = (int)(splineTime + 2.0f); + CancelEventsOfType( EV_ScriptSlave_FollowingPath ); + if ( !ignoreangles ) + { + avelocity = vec_zero; + } + velocity = vec_zero; + } +} + +void ScriptSlave::EndPath( Event *ev ) +{ + if ( !splinePath ) + return; + + delete splinePath; + splinePath = NULL; + velocity = vec_zero; + if ( !ignoreangles ) + { + avelocity = vec_zero; + } +} + +void ScriptSlave::FollowingPath( Event *ev ) +{ + Vector pos; + Vector orient; + float speed_multiplier; + + if ( !splinePath ) + return; + + if ( !thinking ) + { + velocity = vec_zero; + if ( !ignoreangles ) + { + avelocity = vec_zero; + } + + return; + //PostEvent( EV_ScriptSlave_FollowingPath, level.frametime ); + } + + if ( ( splinePath->GetType() == SPLINE_CLAMP ) && ( splineTime > ( splinePath->EndPoint() - 2.0f ) ) ) + { + delete splinePath; + splinePath = NULL; + velocity = vec_zero; + if ( !ignoreangles ) + { + avelocity = vec_zero; + } + moving = false; + ProcessEvent( EV_ScriptSlave_MoveDone ); + return; + } + + if ( ( lastTime != newTime ) && currentNode ) + { + if ( newTime > 1 ) + { + + if ( currentNode->thread != "" ) + { + if ( !ExecuteThread( currentNode->thread ,true , this) ) + { + gi.Error( ERR_DROP, "Scriptslave could not start thread '%s' from info_splinepath '%s'\n", + currentNode->thread.c_str(), currentNode->targetname.c_str() ); + } + } + + if ( currentNode->triggertarget != "" ) + { + Entity *ent; + Event *event; + + ent = NULL; + do + { + ent = G_FindTarget( ent, currentNode->triggertarget.c_str() ); + if ( !ent ) + { + break; + } + event = new Event( EV_Activate ); + event->AddEntity( this ); + ent->PostEvent( event, 0.0f ); + } + while ( 1 ); + } + + } + + currentNode = currentNode->GetNext(); + } + + lastTime = newTime; + + speed_multiplier = splinePath->Eval( splineTime, pos, orient ); + + splineTime += level.frametime * speed_multiplier; + + velocity = ( pos - origin ) * ( 1.0f / level.frametime ); + if ( !ignoreangles ) + { + if ( splineangles ) + { + avelocity = ( orient - angles ) * ( 1.0f / level.frametime ); + } + else + { + float len; + + len = velocity.length(); + if ( len > 0.05f ) + { + Vector ang; + Vector dir; + float aroll; + + aroll = avelocity[ ROLL ]; + dir = velocity * ( 1.0f / len ); + ang = dir.toAngles(); + avelocity = ( ang - angles ) * ( 1.0f / level.frametime ); + avelocity[ ROLL ] = aroll; + } + else + avelocity = vec_zero; + } + } + + newTime = splineTime + 2.0f; + + if ( newTime < 0 ) + { + newTime = 0; + } + + PostEvent( EV_ScriptSlave_FollowingPath, level.frametime ); + +} + +void ScriptSlave::Explode( Event *ev ) +{ + float damage; + + if ( ev->NumArgs() ) + { + damage = ev->GetFloat( 1 ); + } + else + { + damage = 120.0f; + } + + CreateExplosion( origin, damage, this, this, this ); +} + +void ScriptSlave::NotShootable( Event *ev ) +{ + setContents( 0 ); +} + +void ScriptSlave::OpenPortal( Event *ev ) +{ + if ( !_portalOpen ) + { + gi.AdjustAreaPortalState( this->edict, true ); + _portalOpen = true; + } +} + +void ScriptSlave::ClosePortal( Event *ev ) +{ + if ( _portalOpen ) + { + gi.AdjustAreaPortalState( this->edict, false ); + _portalOpen = false; + } +} + +void ScriptSlave::PhysicsOn( Event *ev ) +{ + commandswaiting = false; + setMoveType( MOVETYPE_BOUNCE ); + setSolidType( SOLID_BBOX ); + velocity = Vector(0, 0, 1); + edict->clipmask = MASK_SOLID|CONTENTS_BODY; +} + +void ScriptSlave::PhysicsOff( Event *ev ) +{ + Event * event; + + commandswaiting = false; + setMoveType( MOVETYPE_PUSH ); + edict->clipmask = 0; + // become solid again + event = new Event( EV_Model ); + event->AddString( model ); + PostEvent( event, 0.0f ); +} + +void ScriptSlave::PhysicsVelocity( Event *ev ) +{ + velocity += ev->GetVector( 1 ); +} + +void ScriptSlave::StopEvent( Event *ev ) +{ + Stop(); +} + +void ScriptSlave::Stop( void ) +{ + thinking = false; +} + +void ScriptSlave::ContinueEvent( Event *ev ) +{ + Continue(); +} + +void ScriptSlave::Continue( void ) +{ + thinking = true; + PostEvent( EV_ScriptSlave_FollowingPath, level.frametime ); +} + +//----------------------------------------------------- +// +// Name: Think +// Class: ScriptSlave +// +// Description: Processes the updates for the Script Slave. +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void ScriptSlave::Think() +{ + if(flags & FL_TOUCH_TRIGGERS) + { + G_TouchTriggers(this); + } +} + +/*****************************************************************************/ +/*QUAKED script_model (0 0.5 1) (0 0 0) (0 0 0) NOT_SOLID LIT_STATIC + +******************************************************************************/ + +Event EV_ScriptModel_SetAnim +( + "anim", + EV_DEFAULT, + "s", + "anim_name", + "Sets the script model's animation" +); +Event EV_ScriptModel_SetAnimDriven +( + "animdriven", + EV_DEFAULT, + "B", + "boolean", + "Sets the script model to be anim driven" +); +Event EV_ScriptModel_AnimOnce +( + "animonce", + EV_SCRIPTONLY, + "s", + "anim_name", + "Sets the script model's animation but only plays it once" +); + +CLASS_DECLARATION( ScriptSlave, ScriptModel, "script_model" ) +{ + { &EV_Gib, &ScriptModel::GibEvent }, + { &EV_SetAngle, &ScriptModel::SetAngleEvent }, + { &EV_ScriptModel_SetAnim, &ScriptModel::SetAnimEvent }, + { &EV_ScriptModel_SetAnimDriven, &ScriptModel::SetAnimDrivenEvent }, + { &EV_ScriptModel_AnimOnce, &ScriptModel::AnimOnceEvent }, + { &EV_Model, &ScriptModel::SetModelEvent }, + + { NULL, NULL }, +}; + +ScriptModel::ScriptModel() +{ + // this is a tiki model + edict->s.eType = ET_MODELANIM; + animationDriven = false; + + animate = new Animate( this ); +} + +void ScriptModel::Think( void ) +{ + total_delta = vec_zero; + ScriptSlave::Think(); + + if(animationDriven) + { + localangles = InterceptTargetXY( GetNewPosition(), Vector::Identity(), velocity.lengthXY() ); + SetNewAngles( localangles ); + setAngles( localangles ); + + // Set velocity based on current delta move + Vector forward; + Vector left; + Vector up; + localangles.AngleVectors( &forward, &left, &up ); + + total_delta /= level.frametime; + velocity = ( forward * total_delta[0] ) + ( left * total_delta[1] ) + ( up * total_delta[2] ); + } +} + +void ScriptModel::SetModelEvent( Event *ev ) +{ + char modelname[256] ; + char *tmpPtr ; + + strcpy(modelname, ev->GetString( 1 ) ); + tmpPtr = strstr(modelname, "*"); + + if (tmpPtr) + { + ev->SetString( 1 , tmpPtr ); + } + + ScriptSlave::SetModelEvent( ev ); + setSolidType( SOLID_BBOX ); + + if ( ( gi.IsModel( edict->s.modelindex ) ) && !mins.length() && !maxs.length() ) + { + gi.CalculateBounds( edict->s.modelindex, edict->s.scale, mins, maxs ); + } +} + +void ScriptModel::SetAnimEvent( Event *ev ) +{ + const char * const animname = ev->GetString( 1 ); + if ( animname && strlen( animname ) && gi.IsModel( edict->s.modelindex ) ) + { + int animnum = gi.Anim_NumForName( edict->s.modelindex, animname ); + + if ( animnum >= 0 ) + { + animate->NewAnim( animnum ); + } + } +} + +void ScriptModel::SetAnimDrivenEvent( Event *ev ) +{ + if ( (ev->NumArgs() > 0) && ( ev->GetBoolean( 1 ) == false ) ) + { + animationDriven = false; + } + else + { + animationDriven = true; + turnThinkOn(); + } +} + +void ScriptModel::AnimOnceEvent( Event *ev ) +{ + const char * animname; + + animname = ev->GetString( 1 ); + if ( animname && strlen( animname ) && gi.IsModel( edict->s.modelindex ) ) + { + animate->RandomAnimate( animname, EV_StopAnimating ); + } +} + +void ScriptModel::SetAngleEvent( Event *ev ) +{ + float angle; + + angle = ev->GetFloat( 1 ); + if ( angle == -1.0f ) + { + ForwardDir = Vector( 0.0f, 0.0f, 90.0f ); + localangles = Vector( -90.0f, 0.0f, 0.0f ); + } + else if ( angle == -2.0f ) + { + ForwardDir = Vector( 0.0f, 0.0f, -90.0f ); + localangles = Vector( 90.0f, 0.0f, 0.0f ); + } + else + { + ForwardDir = Vector( 0.0f, angle, 0.0f ); + localangles = Vector( 0.0f, angle, 0.0f ); + } + + setAngles( localangles ); +} + +void ScriptModel::GibEvent( Event *ev ) +{ + int num,power; + float scale; + + setSolidType( SOLID_NOT ); + hideModel(); + + if ( !com_blood->integer ) + { + PostEvent( EV_Remove, 0.0f ); + return; + } + + num = ev->GetInteger( 1 ); + power = ev->GetInteger( 2 ); + scale = ev->GetFloat( 3 ); + + power = -power; + + if ( ev->NumArgs() > 3 ) + { + CreateGibs( this, power, scale, num, ev->GetString( 4 ) ); + } + else + { + CreateGibs( this, power, scale, num ); + } + + PostEvent( EV_Remove, 0.0f ); +} + +/*****************************************************************************/ +/*QUAKED script_origin (1.0 0 0) (-8 -8 -8) (8 8 8) + +Used as an alternate origin for objects. Bind the object to the script_origin +in order to simulate changing that object's origin. +******************************************************************************/ + +CLASS_DECLARATION( ScriptSlave, ScriptOrigin, "script_origin" ) +{ + { &EV_Model, &ScriptOrigin::SetModelEvent }, + + { NULL, NULL } +}; + +ScriptOrigin::ScriptOrigin() +{ + setContents( 0 ); + setSolidType( SOLID_NOT ); +} + +/*****************************************************************************/ +/*QUAKED script_skyorigin (1.0 0 0) ? + +Used to specify the origin of a portal sky +******************************************************************************/ + +Event EV_ScriptSkyOrigin_SetBasePosition +( + "baseposition", + EV_SCRIPTONLY, + "v", + "base_position", + "Sets the base position of the sky origin." +); +Event EV_ScriptSkyOrigin_SetPlayerBasePosition +( + "playerbaseposition", + EV_SCRIPTONLY, + "v", + "base_position", + "Sets the base position for the player for the sky origin." +); +Event EV_ScriptSkyOrigin_SetTranslationMultiplier +( + "translationmult", + EV_SCRIPTONLY, + "f", + "translation_multiplier", + "Sets the translation multiplier for the sky origin." +); +Event EV_ScriptSkyOrigin_SetMaxDistance +( + "maxtranslationdist", + EV_SCRIPTONLY, + "f", + "max_translation_distance", + "Sets the maximum distance the sky origin will translate." +); + +CLASS_DECLARATION( ScriptSlave, ScriptSkyOrigin, "script_skyorigin" ) +{ + { &EV_ScriptSkyOrigin_SetBasePosition, &ScriptSkyOrigin::SetBasePosition }, + { &EV_ScriptSkyOrigin_SetPlayerBasePosition, &ScriptSkyOrigin::SetPlayerBasePosition }, + { &EV_ScriptSkyOrigin_SetTranslationMultiplier, &ScriptSkyOrigin::SetTranslationMultiplier }, + { &EV_ScriptSkyOrigin_SetMaxDistance, &ScriptSkyOrigin::SetMaxDistance }, + + { NULL, NULL } +}; + +ScriptSkyOrigin::ScriptSkyOrigin() +{ + edict->s.renderfx |= RF_SKYORIGIN; + setContents( 0 ); + setSolidType( SOLID_NOT ); + turnThinkOn(); + + use_base_position = false; + use_player_base_position = false; + translation_multiplier = 0; + max_distance = 0; +} + +void ScriptSkyOrigin::Think( void ) +{ + Vector delta; + Entity *player; + Vector new_origin; + + new_origin = origin; + + if ( use_base_position ) + { + if ( translation_multiplier ) + { + // Get player + + player = g_entities[ 0 ].entity; + + if ( !use_player_base_position ) + { + // Get current player position + + player_base_position = player->origin; + + use_player_base_position = true; + } + + // Calculate the new origin + + delta = player->origin - player_base_position; + delta *= translation_multiplier; + + if ( max_distance && ( delta.length() > max_distance ) ) + { + delta.normalize(); + delta *= max_distance; + } + + new_origin = base_position + delta; + } + else + { + new_origin = base_position; + } + } + + setOrigin( new_origin ); +} + +void ScriptSkyOrigin::SetBasePosition( Event *ev ) +{ + use_base_position = true; + base_position = ev->GetVector( 1 ); + use_player_base_position = false; +} + +void ScriptSkyOrigin::SetPlayerBasePosition( Event *ev ) +{ + use_player_base_position = true; + player_base_position = ev->GetVector( 1 ); +} + +void ScriptSkyOrigin::SetTranslationMultiplier( Event *ev ) +{ + translation_multiplier = ev->GetFloat( 1 ); +} + +void ScriptSkyOrigin::SetMaxDistance( Event *ev ) +{ + max_distance = ev->GetFloat( 1 ); +} + + +//1ST PLAYABLE HACK STUFF +void ScriptSlave::Hack_AddParms( Event *ev ) +{ + _forcefieldNumber = ev->GetFloat( 1 ); + _triggerNumber = ev->GetFloat( 2 ); +} + +void ScriptSlave::Hack_GetForceFieldNumber( Event *ev ) +{ + ev->ReturnFloat( _forcefieldNumber ); +} + +void ScriptSlave::Hack_GetScanner( Event *ev ) +{ + ev->ReturnEntity( _scanner ); +} + +void ScriptSlave::Hack_GetTriggerNumber( Event *ev ) +{ + ev->ReturnFloat( _triggerNumber ); +} + +void ScriptSlave::HandlesDamage( Event *ev ) +{ + _handlesDamage = ev->GetBoolean( 1 ); +} + +void ScriptSlave::setDamageEffect( Event *ev ) +{ + _damageEffect = ev->GetString( 1 ); +} + +void ScriptSlave::setBloodModel( Event *ev ) +{ + _bloodModel = ev->GetString( 1 ); +} + +void ScriptSlave::addRequiredDamageMOD( Event *ev ) +{ + str modname( ev->GetString( 1 ) ); + assert( modname.length() > 0 ); + _requiredMODlist.AddObject( modname ); + +} + +void ScriptSlave::setCanBeAttackedByOtherSlaves(Event *ev) +{ + _canBeAttackedByOtherScriptSlaves = ev->GetBoolean( 1 ); +} diff --git a/dlls/game/scriptslave.h b/dlls/game/scriptslave.h new file mode 100644 index 0000000..9b5f48f --- /dev/null +++ b/dlls/game/scriptslave.h @@ -0,0 +1,394 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/scriptslave.h $ +// $Revision:: 29 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Standard scripted objects. Controlled by scriptmaster. These objects +// are bmodel objects created in the editor and controlled by an external +// text based script. Commands are interpretted on by one and executed +// upon a signal from the script master. The base script object can +// perform several different relative and specific rotations and translations +// and can cause other parts of the script to be executed when touched, damaged, +// touched, or used. +// + +#ifndef __SCRIPTSLAVE_H__ +#define __SCRIPTSLAVE_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "mover.h" +#include "script.h" +#include "scriptmaster.h" +#include "misc.h" +#include "bspline.h" + +extern Event EV_ScriptSlave_NewOrders; + +class ScriptSlave : public Trigger + { + private: + Vector _newAngles; + Vector _newPosition; + bool _handlesDamage; + + protected: + ThreadPtr touchthread; + ThreadPtr blockthread; + ThreadPtr triggerthread; + ThreadPtr usethread; + ThreadPtr damagethread; + ThreadPtr movethread; + + str touchlabel; + str uselabel; + str blocklabel; + str triggerlabel; + str damagelabel; + + float attack_finished; + float dmg; + int dmg_means_of_death; + qboolean thinking; + + float _forcefieldNumber; + float _triggerNumber; + EntityPtr _scanner; + + bool _portalOpen; + + str _damageEffect; + str _bloodModel; + Container _requiredMODlist; + + float _nextNeedToUseTime; + + bool _canBeAttackedByOtherScriptSlaves; + + + public: + + + + qboolean commandswaiting; + Vector TotalRotation; + Vector ForwardDir; + float speed; + Waypoint *waypoint; + float traveltime; + BSpline *splinePath; + float splineTime; + + SplinePathPtr currentNode; + int lastTime; + int newTime; + + qboolean splineangles; + qboolean ignoreangles; + qboolean moving; // is the script object currently moving? + + CLASS_PROTOTYPE( ScriptSlave ); + + ScriptSlave(); + ~ScriptSlave(); + + void StopEvent( Event *ev ); + void Stop( void ); + void ContinueEvent( Event *ev ); + void Continue( void ); + void Think(void); + + const Vector & GetNewAngles( void ) const { return _newAngles; } + void SetNewAngles( const Vector &newAngles ){ _newAngles = newAngles; } + const Vector & GetNewPosition( void ) const { return _newPosition; } + void SetNewPosition( const Vector &newPosition ) { _newPosition = newPosition; } + virtual void setOrigin( const Vector &point ); + virtual void setOrigin( void ); + void NewOrders( Event *ev ); + void BindEvent( Event *ev ); + void EventUnbind( Event *ev ); + void DoMove( Event *ev ); + void MoveEnd( Event *ev ); + void SetAnglesEvent( Event *ev ); + void SetAngleEvent( Event *ev ); + void SetModelEvent( Event *ev ); + void TriggerEvent( Event *ev ); + void GotoNextWaypoint( Event *ev ); + void JumpTo( Event *ev ); + void MoveToEvent( Event *ev ); + void MoveToPositionEvent( Event *ev ); + void SetSpeed( Event *ev ); + void SetTime( Event *ev ); + void MoveUp( Event *ev ); + void MoveDown( Event *ev ); + void MoveNorth( Event *ev ); + void MoveSouth( Event *ev ); + void MoveEast( Event *ev ); + void MoveWest( Event *ev ); + void MoveForward( Event *ev ); + void MoveBackward( Event *ev ); + void MoveLeft( Event *ev ); + void MoveRight( Event *ev ); + void RotateXdownto( Event *ev ); + void RotateYdownto( Event *ev ); + void RotateZdownto( Event *ev ); + void RotateAxisdownto( Event *ev ); + void RotateXupto( Event *ev ); + void RotateYupto( Event *ev ); + void RotateZupto( Event *ev ); + void RotateAxisupto( Event *ev ); + void Rotateupto( Event *ev ); + void Rotatedownto( Event *ev ); + void Rotateto( Event *ev ); + void RotateXdown( Event *ev ); + void RotateYdown( Event *ev ); + void RotateZdown( Event *ev ); + void RotateAxisdown( Event *ev ); + void RotateXup( Event *ev ); + void RotateYup( Event *ev ); + void RotateZup( Event *ev ); + void RotateAxisup( Event *ev ); + void RotateX( Event *ev ); + void RotateY( Event *ev ); + void RotateZ( Event *ev ); + void RotateAxis( Event *ev ); + void OnTouch( Event *ev ); + void NoTouch( Event *ev ); + void TouchFunc( Event *ev ); + void OnBlock( Event *ev ); + void NoBlock( Event *ev ); + void BlockFunc( Event *ev ); + void OnTrigger( Event *ev ); + void NoTrigger( Event *ev ); + void TriggerFunc( Event *ev ); + void OnUse( Event *ev ); + void NoUse( Event *ev ); + void UseFunc( Event *ev ); + void OnDamage( Event *ev ); + void NoDamage( Event *ev ); + void DamageFunc( Event *ev ); + void SetDamage( Event *ev ); + void SetMeansOfDeath( Event *ev ); + void FollowPath( Event *ev ); + void EndPath( Event *ev ); + void FollowingPath( Event *ev ); + void CreatePath( SplinePath *path, splinetype_t type ); + void Explode( Event *ev ); + void NotShootable( Event *ev ); + void OpenPortal( Event *ev ); + void ClosePortal( Event *ev ); + void PhysicsOn( Event *ev ); + void PhysicsOff( Event *ev ); + void PhysicsVelocity( Event *ev ); + void setDamage( Event *ev ); + void HandlesDamage( Event *ev ); + void setDamageEffect( Event *ev ); + void setBloodModel( Event *ev ); + void addRequiredDamageMOD( Event *ev ); + void setCanBeAttackedByOtherSlaves( Event *ev ); + + + // 1ST PLAYABLE HACK + void Hack_AddParms( Event *ev ); + void Hack_GetForceFieldNumber( Event *ev ); + void Hack_GetTriggerNumber( Event *ev ); + void Hack_GetScanner( Event *ev ); + + virtual void Archive( Archiver &arc ); + }; + +inline void ScriptSlave::Archive( Archiver &arc ) +{ + int tempInt; + + Trigger::Archive( arc ); + + arc.ArchiveVector( &_newAngles ); + arc.ArchiveVector( &_newPosition ); + arc.ArchiveBool( &_handlesDamage ); + + arc.ArchiveSafePointer( &touchthread ); + arc.ArchiveSafePointer( &blockthread ); + arc.ArchiveSafePointer( &triggerthread ); + arc.ArchiveSafePointer( &usethread ); + arc.ArchiveSafePointer( &damagethread ); + arc.ArchiveSafePointer( &movethread ); + + arc.ArchiveString( &touchlabel ); + arc.ArchiveString( &uselabel ); + arc.ArchiveString( &blocklabel ); + arc.ArchiveString( &triggerlabel ); + arc.ArchiveString( &damagelabel ); + + arc.ArchiveFloat( &attack_finished ); + arc.ArchiveFloat( &dmg ); + arc.ArchiveInteger( &dmg_means_of_death ); + arc.ArchiveBoolean( &thinking ); + + arc.ArchiveFloat( &_forcefieldNumber ); + arc.ArchiveFloat( &_triggerNumber ); + arc.ArchiveSafePointer( &_scanner ); + + arc.ArchiveBool( &_portalOpen ); + + arc.ArchiveString( &_damageEffect ); + arc.ArchiveString( &_bloodModel ); + + + if( arc.Saving() ) + { + int count = _requiredMODlist.NumObjects(); + arc.ArchiveInteger( &count ); + for( int i = 1; i <= _requiredMODlist.NumObjects(); i++ ) + arc.ArchiveString( &_requiredMODlist.ObjectAt( i ) ); + } + else + { + assert( _requiredMODlist.NumObjects() == 0 ); + + int count; + arc.ArchiveInteger( &count ); + + str s; + for( int i = 0; i < count; i++ ) + { + arc.ArchiveString( &s ); + _requiredMODlist.AddObject( s ); + } + } + + + arc.ArchiveFloat( &_nextNeedToUseTime ); + + arc.ArchiveBoolean( &commandswaiting ); + arc.ArchiveVector( &TotalRotation ); + + arc.ArchiveVector( &ForwardDir ); + arc.ArchiveFloat( &speed ); + arc.ArchiveObjectPointer( ( Class ** )&waypoint ); + arc.ArchiveFloat( &traveltime ); + + if ( arc.Saving() ) + { + // if it exists, archive it, otherwise place a special NULL ptr tag + if ( splinePath ) + { + tempInt = ARCHIVE_POINTER_VALID; + } + else + { + tempInt = ARCHIVE_POINTER_NULL; + } + arc.ArchiveInteger( &tempInt ); + if ( tempInt == ARCHIVE_POINTER_VALID ) + { + splinePath->Archive( arc ); + } + } + else + { + arc.ArchiveInteger( &tempInt ); + if ( tempInt == ARCHIVE_POINTER_VALID ) + { + splinePath = new BSpline; + splinePath->Archive( arc ); + } + else + { + splinePath = NULL; + } + } + arc.ArchiveFloat( &splineTime ); + + arc.ArchiveSafePointer( ¤tNode ); + arc.ArchiveInteger( &lastTime ); + arc.ArchiveInteger( &newTime ); + + arc.ArchiveBoolean( &splineangles ); + arc.ArchiveBoolean( &ignoreangles ); + arc.ArchiveBoolean( &moving ); + arc.ArchiveBool( &_canBeAttackedByOtherScriptSlaves ); + +} + + +class ScriptModel : public ScriptSlave +{ + private: + void GibEvent(Event *ev); + qboolean animationDriven; + + public: + CLASS_PROTOTYPE( ScriptModel ); + + ScriptModel(); + virtual void Think( void ); + void SetAngleEvent( Event *ev ); + void SetModelEvent( Event *ev ); + void SetAnimEvent( Event *ev ); + void SetAnimDrivenEvent( Event *ev ); + void AnimOnceEvent( Event *ev ); + virtual void Archive( Archiver &arc ); +}; + +inline void ScriptModel::Archive(Archiver& arc) +{ + ScriptSlave::Archive( arc ); + + arc.ArchiveBoolean( &animationDriven ); +} + + +class ScriptOrigin : public ScriptSlave +{ + public: + CLASS_PROTOTYPE( ScriptOrigin ); + ScriptOrigin(); +}; + +class ScriptSkyOrigin : public ScriptSlave + { + public: + qboolean use_base_position; + Vector base_position; + qboolean use_player_base_position; + Vector player_base_position; + float translation_multiplier; + float max_distance; + + + CLASS_PROTOTYPE( ScriptSkyOrigin ); + + ScriptSkyOrigin(); + void Think( void ); + void SetBasePosition( Event *ev ); + void SetPlayerBasePosition( Event *ev ); + void SetTranslationMultiplier( Event *ev ); + void SetMaxDistance( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void ScriptSkyOrigin::Archive( Archiver &arc ) +{ + ScriptSlave::Archive( arc ); + + arc.ArchiveBoolean( &use_base_position ); + arc.ArchiveVector( &base_position ); + arc.ArchiveBoolean( &use_player_base_position ); + arc.ArchiveVector( &player_base_position ); + arc.ArchiveFloat( &translation_multiplier ); + arc.ArchiveFloat( &max_distance ); +} + +#endif /* scriptslave.h */ diff --git a/dlls/game/scriptvariable.cpp b/dlls/game/scriptvariable.cpp new file mode 100644 index 0000000..c9901e4 --- /dev/null +++ b/dlls/game/scriptvariable.cpp @@ -0,0 +1,1210 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/scriptvariable.cpp $ +// $Revision:: 9 $ +// $Author:: Steven $ +// $Date:: 10/03/02 9:51a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Dynamic variable list for scripts. +// + +#include "_pch_cpp.h" +#include "class.h" +#include "scriptvariable.h" +#include "scriptmaster.h" + +//#define VAR_FLOAT_EPSILON 0.0001 +/* +Event EV_Var_Append + ( + "append", + EV_SCRIPTONLY, + "s", + "string_to_append", + "Appends the specified string to the current string." + ); +Event EV_Var_AppendInt + ( + "appendint", + EV_SCRIPTONLY, + "i", + "integer_to_append", + "Appends the specified integer to the current string." + ); +Event EV_Var_AppendFloat + ( + "appendfloat", + EV_SCRIPTONLY, + "f", + "float_to_append", + "Appends the specified float to the current string." + ); +Event EV_Var_String + ( + "string", + EV_SCRIPTONLY, + "s", + "new_string", + "Sets the value of the string to the specified one." + ); +Event EV_Var_RandomFloat + ( + "randomfloat", + EV_SCRIPTONLY, + "f", + "max_float", + "Sets the float to a random number." + ); +Event EV_Var_RandomInteger + ( + "randomint", + EV_SCRIPTONLY, + "i", + "max_int", + "Sets the integer to a random number." + ); +Event EV_Var_TargetOf + ( + "targetof", + EV_SCRIPTONLY, + "e", + "ent", + "Sets string to the specified entities target." + ); +Event EV_Var_Equals + ( + "=", + EV_SCRIPTONLY, + "f", + "num", + "Sets the float to the specified number." + ); +Event EV_Var_PlusEquals + ( + "+=", + EV_SCRIPTONLY, + "f", + "num", + "Adds the specified number to the current float." + ); +Event EV_Var_MinusEquals + ( + "-=", + EV_SCRIPTONLY, + "f", + "num", + "Subtract the specified number to the current float." + ); +Event EV_Var_TimesEquals + ( + "*=", + EV_SCRIPTONLY, + "f", + "num", + "Multiplies the current float by the specified number." + ); +Event EV_Var_DivideEquals + ( + "/=", + EV_SCRIPTONLY, + "f", + "num", + "Divides the current float by the specified number." + ); +Event EV_Var_Div + ( + "div", + EV_SCRIPTONLY, + "i", + "num", + "Divides the current float by the specified number (integer)." + ); +Event EV_Var_Mod + ( + "mod", + EV_SCRIPTONLY, + "i", + "num", + "Gets the mod of the current int by the specified number." + ); +Event EV_Var_IfEqual + ( + "ifequal", + EV_SCRIPTONLY, + "fSSSSSS", + "num command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the variable equals the specified number." + ); +Event EV_Var_IfNotEqual + ( + "ifnotequal", + EV_SCRIPTONLY, + "fSSSSSS", + "num command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the variable doesn't equal the specified number." + ); +Event EV_Var_IfGreater + ( + "ifgreater", + EV_SCRIPTONLY, + "fSSSSSS", + "num command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the variable is greater than the specified number." + ); +Event EV_Var_IfGreaterEqual + ( + "ifgreaterequal", + EV_SCRIPTONLY, + "fSSSSSS", + "num command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the variable is\n" + "greater than or equal to the specified number." + ); +Event EV_Var_IfLess + ( + "ifless", + EV_SCRIPTONLY, + "fSSSSSS", + "num command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the variable is less than the specified number." + ); +Event EV_Var_IfLessEqual + ( + "iflessequal", + EV_SCRIPTONLY, + "fSSSSSS", + "num command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the variable is less than or equal to the specified number." + ); +Event EV_Var_IfStrEqual + ( + "ifstrequal", + EV_SCRIPTONLY, + "sSSSSSS", + "test_string command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the variable equals the specified string." + ); +Event EV_Var_IfStrNotEqual + ( + "ifstrnotequal", + EV_SCRIPTONLY, + "sSSSSSS", + "test_string command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the variable doesn't equal the specified string." + ); +Event EV_Var_IfThreadActive + ( + "ifthreadactive", + EV_SCRIPTONLY, + "SSSSSS", + "command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the thread\n" + "specified by the current integer value is active." + ); +Event EV_Var_IfThreadNotActive + ( + "ifthreadnotactive", + EV_SCRIPTONLY, + "SSSSSS", + "command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the thread specified\n" + "by the current integer value is not active." + ); +Event EV_Var_GetCvar + ( + "getcvar", + EV_SCRIPTONLY, + "s", + "cvar_name", + "Sets the variable to the value of the cvar." + ); +Event EV_Var_Vector + ( + "vector", + EV_SCRIPTONLY, + "v", + "new_vector", + "Sets the variable to the specified vector. New vector can also be a targetname,\n" + "the origin of that entity will be used as the vector." + ); +Event EV_Var_VectorIfEqual + ( + "vector_ifequal", + EV_SCRIPTONLY, + "vSSSSSS", + "test_vector command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the variable is equal to the specified vector." + ); +Event EV_Var_VectorIfNotEqual + ( + "vector_ifnotequal", + EV_SCRIPTONLY, + "vSSSSSS", + "test_vector command arg1 arg2 arg3 arg4 arg5", + "Processes the specified command if the variable is not equal to the specified vector." + ); +Event EV_Var_VectorAdd + ( + "vector_add", + EV_SCRIPTONLY, + "v", + "new_vector", + "Adds the specified vector to the variable." + ); +Event EV_Var_VectorSubtract + ( + "vector_subtract", + EV_SCRIPTONLY, + "v", + "new_vector", + "Subtracts the specified vector from the variable." + ); +Event EV_Var_VectorScale + ( + "vector_scale", + EV_SCRIPTONLY, + "f", + "scale", + "Scales the variable (vector) by the scale value." + ); +Event EV_Var_VectorDotProduct + ( + "vector_dot", + EV_SCRIPTONLY, + "vv", + "vector1 vector2", + "Sets the variable to the dot product of the specified vectors." + ); +Event EV_Var_VectorCrossProduct + ( + "vector_cross", + EV_SCRIPTONLY, + "vv", + "vector1 vector2", + "Sets the variable to the cross product of the specified vectors." + ); +Event EV_Var_VectorNormalize + ( + "vector_normalize", + EV_SCRIPTONLY, + NULL, + NULL, + "Normalize the variable (vector)." + ); +Event EV_Var_VectorLength + ( + "vector_length", + EV_SCRIPTONLY, + "v", + "new_vector", + "Sets the variable (float) to the length of the specified vector." + ); +Event EV_Var_VectorGetX + ( + "vector_x", + EV_SCRIPTONLY, + "v", + "new_vector", + "Sets the variable (float) to the x value of the specified vector." + ); +Event EV_Var_VectorGetY + ( + "vector_y", + EV_SCRIPTONLY, + "v", + "new_vector", + "Sets the variable (float) to the y value of the specified vector." + ); +Event EV_Var_VectorGetZ + ( + "vector_z", + EV_SCRIPTONLY, + "v", + "new_vector", + "Sets the variable (float) to the z value of the specified vector." + ); +Event EV_Var_VectorSetX + ( + "vector_setx", + EV_SCRIPTONLY, + "f", + "x_coord", + "Sets the x value of the variable (vector)." + ); +Event EV_Var_VectorSetY + ( + "vector_sety", + EV_SCRIPTONLY, + "f", + "y_coord", + "Sets the y value of the variable (vector)." + ); +Event EV_Var_VectorSetZ + ( + "vector_setz", + EV_SCRIPTONLY, + "f", + "z_coord", + "Sets the z value of the variable (vector)." + ); +Event EV_Var_AnglesToForward + ( + "angles_toforward", + EV_SCRIPTONLY, + "v", + "angles", + "Sets the variable (vector) to the forward vector of the specified angles." + ); +Event EV_Var_AnglesToLeft + ( + "angles_toleft", + EV_SCRIPTONLY, + "v", + "angles", + "Sets the variable (vector) to the left vector of the specified angles." + ); +Event EV_Var_AnglesToUp + ( + "angles_toup", + EV_SCRIPTONLY, + "v", + "angles", + "Sets the variable (vector) to the up vector of the specified angles." + ); +*/ +/* static void VarProcessCommand + ( + Event *ev, + int arg + ) + + { + ScriptThread *thread; + + thread = ev->GetThread(); + if ( thread ) + { + thread->ProcessCommandFromEvent( ev, arg ); + } + } */ + +CLASS_DECLARATION( Listener, ScriptVariable, NULL ) +{ +/* + { &EV_Var_Append, Var_Append }, + { &EV_Var_AppendInt, Var_AppendInt }, + { &EV_Var_AppendFloat, Var_AppendFloat }, + { &EV_Var_String, Var_String }, + { &EV_Var_RandomFloat, Var_RandomFloat }, + { &EV_Var_RandomInteger, Var_RandomInteger }, + { &EV_Var_TargetOf, Var_TargetOf }, + { &EV_Var_Equals, Var_Equals }, + { &EV_Var_PlusEquals, Var_PlusEquals }, + { &EV_Var_MinusEquals, Var_MinusEquals }, + { &EV_Var_TimesEquals, Var_TimesEquals }, + { &EV_Var_DivideEquals, Var_DivideEquals }, + { &EV_Var_Div, Var_Div }, + { &EV_Var_Mod, Var_Mod }, + { &EV_Var_IfEqual, Var_IfEqual }, + { &EV_Var_IfNotEqual, Var_IfNotEqual }, + { &EV_Var_IfGreater, Var_IfGreater }, + { &EV_Var_IfGreaterEqual, Var_IfGreaterEqual }, + { &EV_Var_IfLess, Var_IfLess }, + { &EV_Var_IfLessEqual, Var_IfLessEqual }, + { &EV_Var_IfStrEqual, Var_IfStrEqual }, + { &EV_Var_IfStrNotEqual, Var_IfStrNotEqual }, + { &EV_Var_IfThreadActive, Var_IfThreadActive }, + { &EV_Var_IfThreadNotActive, Var_IfThreadNotActive }, + { &EV_Var_GetCvar, Var_GetCvar }, + { &EV_Var_Vector, Var_Vector }, + { &EV_Var_VectorAdd, Var_Vector_Add }, + { &EV_Var_VectorSubtract, Var_Vector_Subtract }, + { &EV_Var_VectorScale, Var_Vector_Scale }, + { &EV_Var_VectorNormalize, Var_Vector_Normalize }, + { &EV_Var_VectorGetX, Var_Vector_GetX }, + { &EV_Var_VectorGetY, Var_Vector_GetY }, + { &EV_Var_VectorGetZ, Var_Vector_GetZ }, + { &EV_Var_VectorSetX, Var_Vector_SetX }, + { &EV_Var_VectorSetY, Var_Vector_SetY }, + { &EV_Var_VectorSetZ, Var_Vector_SetZ }, + { &EV_Var_VectorIfEqual, Var_Vector_IfEqual }, + { &EV_Var_VectorIfNotEqual, Var_Vector_IfNotEqual }, + { &EV_Var_VectorDotProduct, Var_Vector_DotProduct }, + { &EV_Var_VectorCrossProduct, Var_Vector_CrossProduct }, + { &EV_Var_VectorLength, Var_Vector_Length }, + { &EV_Var_AnglesToForward, Var_Angles_ToForward }, + { &EV_Var_AnglesToLeft, Var_Angles_ToLeft }, + { &EV_Var_AnglesToUp, Var_Angles_ToUp }, + */ + { NULL, NULL } +}; + +ScriptVariable::ScriptVariable() +{ + value = 0; +} + +qboolean ScriptVariable::isVariableCommand( const char *name ) +{ + int i; + + for( i = 0; ScriptVariable::Responses[ i ].event != NULL; i++ ) + { + if ( ScriptVariable::Responses[ i ].event->getName() == name ) + { + return true; + } + } + + return false; +} + +void ScriptVariable::setName( const char *newname ) +{ + name = newname; +} + +const char *ScriptVariable::getName( void ) +{ + return name.c_str(); +} + +const char *ScriptVariable::stringValue( void ) +{ + return string.c_str(); +} + +void ScriptVariable::setString( const char *value ) +{ + string = value; +} + +void ScriptVariable::setStringValue( const char *newvalue ) +{ + setString( newvalue ); + value = (float)atof( string.c_str() ); +} + +int ScriptVariable::intValue( void ) +{ + return ( int )value; +} + +void ScriptVariable::setIntValue( int newvalue ) +{ + char text[ 128 ]; + + value = ( float )newvalue; + sprintf( text, "%d", newvalue ); + setString( text ); +} + +float ScriptVariable::floatValue( void ) +{ + return value; +} + +void ScriptVariable::setFloatValue( float newvalue ) +{ + char text[ 128 ]; + + value = newvalue; + sprintf( text, "%f", newvalue ); + setString( text ); +} + +void ScriptVariable::setVectorValue( const Vector &newvector ) +{ + char text[ 128 ]; + + vec = newvector; + sprintf( text, "(%f %f %f)", newvector.x, newvector.y, newvector.z ); + setString( text ); +} + +Vector ScriptVariable::vectorValue( void ) +{ + return vec; +} + +CLASS_DECLARATION( Class, ScriptVariableList, NULL ) +{ + { NULL, NULL } +}; + +ScriptVariableList::ScriptVariableList() +{ +} + +ScriptVariableList::~ScriptVariableList() +{ + ClearList(); +} + +void ScriptVariableList::ClearList( void ) +{ + int i; + int num; + ScriptVariable *var; + + num = NumVariables(); + for( i = num; i > 0; i-- ) + { + var = GetVariable( i ); + RemoveVariable( var ); + delete var; + } + + list.FreeObjectList(); +} + +void ScriptVariableList::AddVariable( ScriptVariable *var ) +{ + if ( VariableExists( var->getName() ) ) + { + warning( "AddVariable", "Variable %s already exists.\n", var->getName() ); + return; + } + + list.AddObject( var ); +} + +ScriptVariable *ScriptVariableList::CreateVariable( const char *name, float value ) +{ + ScriptVariable *var; + + if ( VariableExists( name ) ) + { + warning( "AddVariable", "Variable %s already exists.\n", name ); + return GetVariable( name ); + } + + var = new ScriptVariable; + var->setName( name ); + var->setFloatValue( value ); + list.AddObject( var ); + + return var; +} + +ScriptVariable *ScriptVariableList::CreateVariable( const char *name, int value ) +{ + ScriptVariable *var; + + if ( VariableExists( name ) ) + { + warning( "AddVariable", "Variable %s already exists.\n", name ); + return GetVariable( name ); + } + + var = new ScriptVariable; + var->setName( name ); + var->setIntValue( value ); + list.AddObject( var ); + + return var; +} + +ScriptVariable *ScriptVariableList::CreateVariable( const char *name, const char *text ) +{ + ScriptVariable *var; + + if ( VariableExists( name ) ) + { + warning( "AddVariable", "Variable %s already exists.\n", name ); + return GetVariable( name ); + } + + var = new ScriptVariable; + var->setName( name ); + var->setStringValue( text ); + list.AddObject( var ); + + return var; +} + +ScriptVariable *ScriptVariableList::CreateVariable( const char *name, const Entity *ent ) +{ + ScriptVariable *var; + + assert( ent ); + + if ( VariableExists( name ) ) + { + warning( "AddVariable", "Variable %s already exists.\n", name ); + return GetVariable( name ); + } + + var = new ScriptVariable; + var->setName( name ); + list.AddObject( var ); + + if ( !ent ) + { + warning( "SetVariable", "Null entity\n" ); + + // use the world + var->setStringValue( "*0" ); + } + else + { + var->setStringValue( va( "*%d", ent->entnum ) ); + } + + return var; +} + +ScriptVariable *ScriptVariableList::CreateVariable( const char *name, const Vector &vec ) +{ + ScriptVariable *var; + + if ( VariableExists( name ) ) + { + warning( "AddVariable", "Variable %s already exists.\n", name ); + return GetVariable( name ); + } + + var = new ScriptVariable; + var->setName( name ); + var->setStringValue( va( "(%f %f %f)", vec.x, vec.y, vec.z) ); + list.AddObject( var ); + + return var; +} + +void ScriptVariableList::RemoveVariable( ScriptVariable *var ) +{ + if ( !list.ObjectInList( var ) ) + { + warning( "RemoveVariable", "Variable %s does not exist.\n", var->getName() ); + return; + } + + list.RemoveObject( var ); +} + +void ScriptVariableList::RemoveVariable( const char *name ) +{ + ScriptVariable *var; + + var = GetVariable( name ); + if ( !var ) + { + warning( "RemoveVariable", "Variable %s does not exist.\n", name ); + return; + } + + RemoveVariable( var ); +} + +qboolean ScriptVariableList::VariableExists( const char *name ) +{ + int i; + int num; + ScriptVariable *var; + + num = list.NumObjects(); + for( i = 1; i <= num; i++ ) + { + var = list.ObjectAt( i ); + if ( !strcmp( var->getName(), name ) ) + { + return true; + } + } + + return false; +} + +ScriptVariable *ScriptVariableList::GetVariable( const char *name ) +{ + int i; + int num; + ScriptVariable *var; + + num = list.NumObjects(); + for( i = 1; i <= num; i++ ) + { + var = list.ObjectAt( i ); + if ( !strcmp( var->getName(), name ) ) + { + return var; + } + } + + return NULL; +} + +int ScriptVariableList::NumVariables( void ) +{ + return list.NumObjects(); +} + +ScriptVariable *ScriptVariableList::GetVariable( int num ) +{ + return list.ObjectAt( num ); +} + +ScriptVariable *ScriptVariableList::SetVariable( const char *name, float value ) +{ + ScriptVariable *var; + + var = GetVariable( name ); + if ( !var ) + { + var = new ScriptVariable; + var->setName( name ); + list.AddObject( var ); + } + + var->setFloatValue( value ); + + return var; +} + +ScriptVariable *ScriptVariableList::SetVariable( const char *name, int value ) +{ + ScriptVariable *var; + + var = GetVariable( name ); + if ( !var ) + { + var = new ScriptVariable; + var->setName( name ); + list.AddObject( var ); + } + + var->setIntValue( value ); + + return var; +} + +ScriptVariable *ScriptVariableList::SetVariable( const char *name, const char *text ) +{ + ScriptVariable *var; + + var = GetVariable( name ); + if ( !var ) + { + var = new ScriptVariable; + var->setName( name ); + list.AddObject( var ); + } + + var->setStringValue( text ); + + return var; +} + +ScriptVariable *ScriptVariableList::SetVariable( const char *name, const Entity *ent ) +{ + ScriptVariable *var; + + var = GetVariable( name ); + if ( !var ) + { + var = new ScriptVariable; + var->setName( name ); + list.AddObject( var ); + } + + if ( !ent ) + { + // use the world + var->setStringValue( "*0" ); + } + else + { + var->setStringValue( va( "*%d", ent->entnum ) ); + } + + return var; +} + +ScriptVariable *ScriptVariableList::SetVariable( const char *name, const Vector &vec ) +{ + ScriptVariable *var; + + var = GetVariable( name ); + if ( !var ) + { + var = new ScriptVariable; + var->setName( name ); + list.AddObject( var ); + } + + var->setStringValue( va( "(%f %f %f)", vec.x, vec.y, vec.z) ); + var->setVectorValue ( vec ); + return var; +} + +//**************************************************************************************** +// +// Variable commands +// +//**************************************************************************************** + +void ScriptVariable::Var_Append( Event *ev ) +{ + str newstring; + + newstring = string + ev->GetString( 1 ); + setStringValue( newstring.c_str() ); +} + +void ScriptVariable::Var_AppendInt( Event *ev ) +{ + str newstring; + + newstring = string + va( "%d", ev->GetInteger( 1 ) ); + setStringValue( newstring.c_str() ); +} + +void ScriptVariable::Var_AppendFloat( Event *ev ) +{ + str newstring; + + newstring = string + va( "%f", ev->GetFloat( 1 ) ); + setStringValue( newstring.c_str() ); +} + +void ScriptVariable::Var_String( Event *ev ) +{ + setStringValue( ev->GetString( 1 ) ); +} + +void ScriptVariable::Var_RandomFloat( Event *ev ) +{ + setFloatValue( G_Random( ev->GetFloat( 1 ) ) ); +} + +void ScriptVariable::Var_RandomInteger( Event *ev ) +{ + setIntValue( (int)G_Random( ev->GetInteger( 1 ) ) ); +} + +void ScriptVariable::Var_Equals( Event *ev ) +{ + setFloatValue( ev->GetFloat( 1 ) ); +} + +void ScriptVariable::Var_PlusEquals( Event *ev ) +{ + setFloatValue( floatValue() + ev->GetFloat( 1 ) ); +} + +void ScriptVariable::Var_MinusEquals( Event *ev ) +{ + setFloatValue( floatValue() - ev->GetFloat( 1 ) ); +} + +void ScriptVariable::Var_TimesEquals( Event *ev ) +{ + setFloatValue( floatValue() * ev->GetFloat( 1 ) ); +} + +void ScriptVariable::Var_DivideEquals( Event *ev ) +{ + setFloatValue( floatValue() / ev->GetFloat( 1 ) ); +} + +void ScriptVariable::Var_Div( Event *ev ) +{ + int temp_int; + + temp_int = ev->GetInteger( 1 ); + + if ( temp_int == 0 ) + { + gi.WDPrintf( "Script divide by zero, variable %s\n", name.c_str() ); + return; + } + + setIntValue( (int)( floatValue() / temp_int ) ); +} + +void ScriptVariable::Var_Mod( Event *ev ) +{ + int temp_int; + + temp_int = ev->GetInteger( 1 ); + + if ( temp_int == 0 ) + { + gi.WDPrintf( "Script mod by zero, variable %s\n", name.c_str() ); + return; + } + + setIntValue( intValue() % temp_int ); +} + +/* void ScriptVariable::Var_IfEqual( Event *ev ) +{ + if ( fabs( floatValue() - ev->GetFloat( 1 ) ) < VAR_FLOAT_EPSILON ) + { + VarProcessCommand( ev, 2 ); + } +} + +void ScriptVariable::Var_IfNotEqual( Event *ev ) +{ +if ( fabs( floatValue() - ev->GetFloat( 1 ) ) >= VAR_FLOAT_EPSILON ) + { + VarProcessCommand( ev, 2 ); + } +} + +void ScriptVariable::Var_IfGreater( Event *ev ) +{ +if ( floatValue() > ev->GetFloat( 1 ) ) + { + VarProcessCommand( ev, 2 ); + } +} + +void ScriptVariable::Var_IfGreaterEqual( Event *ev ) +{ +if ( floatValue() >= ev->GetFloat( 1 ) ) + { + VarProcessCommand( ev, 2 ); + } +} + +void ScriptVariable::Var_IfLess( Event *ev ) +{ +if ( floatValue() < ev->GetFloat( 1 ) ) + { + VarProcessCommand( ev, 2 ); + } +} + +void ScriptVariable::Var_IfLessEqual( Event *ev ) +{ +if ( floatValue() <= ev->GetFloat( 1 ) ) + { + VarProcessCommand( ev, 2 ); + } +} + +void ScriptVariable::Var_IfStrEqual( Event *ev ) +{ +if ( stricmp( stringValue(), ev->GetString( 1 ) ) == 0 ) + { + VarProcessCommand( ev, 2 ); + } +} + +void ScriptVariable::Var_IfStrNotEqual( Event *ev ) +{ +if ( stricmp( stringValue(), ev->GetString( 1 ) ) != 0 ) + { + VarProcessCommand( ev, 2 ); + } +} + +void ScriptVariable::Var_IfThreadActive( Event *ev ) +{ + ScriptThread *thread; + + thread = Director.GetThread( intValue() ); + if ( thread ) + { + VarProcessCommand( ev, 1 ); + } +} + +void ScriptVariable::Var_IfThreadNotActive( Event *ev ) +{ + ScriptThread *thread; + + thread = Director.GetThread( intValue() ); + if ( !thread ) + { + VarProcessCommand( ev, 1 ); + } +} +*/ + +void ScriptVariable::Var_GetCvar( Event *ev ) +{ + cvar_t *var; + + var = gi.cvar( ev->GetString( 1 ), "", 0 ); + setStringValue( var->string ); +} + +void ScriptVariable::Var_Vector( Event *ev ) +{ + setVectorValue( ev->GetVector( 1 ) ); +} + +void ScriptVariable::Var_Vector_Add( Event *ev ) +{ + Vector tempvec; + + tempvec = vectorValue(); + tempvec += ev->GetVector( 1 ); + setVectorValue( tempvec ); +} + +void ScriptVariable::Var_Vector_Subtract( Event *ev ) +{ + Vector tempvec; + + tempvec = vectorValue(); + tempvec -= ev->GetVector( 1 ); + setVectorValue( tempvec ); +} + +void ScriptVariable::Var_Vector_Scale( Event *ev ) +{ + Vector tempvec; + + tempvec = vectorValue(); + tempvec = tempvec * ev->GetFloat( 1 ); + setVectorValue( tempvec ); +} + +void ScriptVariable::Var_Vector_Normalize( Event *ev ) +{ + Vector vec( vectorValue() ); + vec.normalize(); + setVectorValue( vec ); +} + +void ScriptVariable::Var_Vector_GetX( Event *ev ) +{ + setFloatValue( ev->GetVector( 1 ).x ); +} + +void ScriptVariable::Var_Vector_GetY( Event *ev ) +{ + setFloatValue( ev->GetVector( 1 ).y ); +} + +void ScriptVariable::Var_Vector_GetZ( Event *ev ) +{ + setFloatValue( ev->GetVector( 1 ).z ); +} + +void ScriptVariable::Var_Vector_SetX( Event *ev ) +{ + Vector tempvec; + + tempvec = vectorValue(); + tempvec.x = ev->GetFloat( 1 ); + setVectorValue( tempvec ); +} + +void ScriptVariable::Var_Vector_SetY( Event *ev ) +{ + Vector tempvec; + + tempvec = vectorValue(); + tempvec.y = ev->GetFloat( 1 ); + setVectorValue( tempvec ); +} + +void ScriptVariable::Var_Vector_SetZ( Event *ev ) +{ + Vector tempvec; + + tempvec = vectorValue(); + tempvec.z = ev->GetFloat( 1 ); + setVectorValue( tempvec ); +} + +/* +void ScriptVariable::Var_Vector_IfEqual( Event *ev ) +{ + if ( vectorValue().FuzzyEqual( ev->GetVector( 1 ), VAR_FLOAT_EPSILON ) ) + { + VarProcessCommand( ev, 2 ); + } +} + +void ScriptVariable::Var_Vector_IfNotEqual( Event *ev ) +{ + if ( !vectorValue().FuzzyEqual( ev->GetVector( 1 ), VAR_FLOAT_EPSILON ) ) + { + VarProcessCommand( ev, 2 ); + } +} +*/ + +void ScriptVariable::Var_Vector_DotProduct( Event *ev ) +{ + Vector vec1, vec2; + + vec1 = ev->GetVector( 1 ); + vec2 = ev->GetVector( 2 ); + setFloatValue( vec1 * vec2 ); +} + +void ScriptVariable::Var_Vector_Length( Event *ev ) +{ + setFloatValue( ev->GetVector( 1 ).length() ); +} + +void ScriptVariable::Var_Vector_CrossProduct( Event *ev ) +{ + Vector vec1, vec2, cross; + + vec1 = ev->GetVector( 1 ); + vec2 = ev->GetVector( 2 ); + cross.CrossProduct( vec1, vec2 ); + setVectorValue( cross ); +} + +void ScriptVariable::Var_Angles_ToForward( Event *ev ) +{ + Vector forward; + + ev->GetVector( 1 ).AngleVectors( &forward, NULL, NULL ); + setVectorValue( forward ); +} + +void ScriptVariable::Var_Angles_ToLeft( Event *ev ) +{ + Vector left; + + ev->GetVector( 1 ).AngleVectors( NULL, &left, NULL ); + setVectorValue( left ); +} + +void ScriptVariable::Var_Angles_ToUp( Event *ev ) +{ + Vector up; + + ev->GetVector( 1 ).AngleVectors( NULL, NULL, &up ); + setVectorValue( up ); +} + +void ScriptVariable::Var_TargetOf( Event *ev ) +{ + Entity * ent; + const char *target; + + ent = ev->GetEntity( 1 ); + if ( ent ) + { + target = ent->Target(); + } + else + { + return; + } + + if ( target ) + { + setStringValue( va( "$%s", target ) ); + } +} diff --git a/dlls/game/scriptvariable.h b/dlls/game/scriptvariable.h new file mode 100644 index 0000000..d40237c --- /dev/null +++ b/dlls/game/scriptvariable.h @@ -0,0 +1,233 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/scriptvariable.h $ +// $Revision:: 7 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Dynamic variable list for scripts. +// + +#ifndef __SCRIPTVARIABLE_H__ +#define __SCRIPTVARIABLE_H__ + +#include "g_local.h" +#include "container.h" +#include "listener.h" + +/* +extern Event EV_Var_Append; +extern Event EV_Var_AppendInt; +extern Event EV_Var_AppendFloat; +extern Event EV_Var_String; +extern Event EV_Var_TargetOf; +extern Event EV_Var_Equals; +extern Event EV_Var_PlusEquals; +extern Event EV_Var_MinusEquals; +extern Event EV_Var_TimesEquals; +extern Event EV_Var_DivideEquals; +extern Event EV_Var_IfEqual; +extern Event EV_Var_IfNotEqual; +extern Event EV_Var_IfGreater; +extern Event EV_Var_IfGreaterEqual; +extern Event EV_Var_IfLess; +extern Event EV_Var_IfLessEqual; +extern Event EV_Var_IfStrEqual; +extern Event EV_Var_IfStrNotEqual; +extern Event EV_Var_IfThreadActive; +extern Event EV_Var_IfThreadNotActive; +extern Event EV_Var_GetCvar; +extern Event EV_Var_Vector; +extern Event EV_Var_VectorIfEqual; +extern Event EV_Var_VectorIfNotEqual; +extern Event EV_Var_VectorAdd; +extern Event EV_Var_VectorSubtract; +extern Event EV_Var_VectorScale; +extern Event EV_Var_VectorNormalize; +extern Event EV_Var_VectorGetX; +extern Event EV_Var_VectorGetY; +extern Event EV_Var_VectorGetZ; +extern Event EV_Var_VectorSetX; +extern Event EV_Var_VectorSetY; +extern Event EV_Var_VectorSetZ; +extern Event EV_Var_VectorDotProduct; +extern Event EV_Var_VectorCrossProduct; +extern Event EV_Var_VectorLength; +extern Event EV_Var_AnglesToForward; +extern Event EV_Var_AnglesToRight; +extern Event EV_Var_AnglesToUp; +*/ + +class ScriptVariable : public Listener + { + private: + str name; + float value; + str string; + Vector vec; + + void setString( const char *newvalue ); + + public: + CLASS_PROTOTYPE( ScriptVariable ); + + ScriptVariable(); + + qboolean isVariableCommand( const char *name ); + + void setName( const char *newname ); + const char *getName( void ); + + const char *stringValue( void ); + void setStringValue( const char *newvalue ); + + int intValue( void ); + void setIntValue( int newvalue ); + + float floatValue( void ); + void setFloatValue( float newvalue ); + + void setVectorValue( const Vector &newvector ); + Vector vectorValue( void ); + + void Var_Append( Event *ev ); + void Var_AppendInt( Event *ev ); + void Var_AppendFloat( Event *ev ); + void Var_String( Event *ev ); + void Var_RandomFloat(Event *ev ); + void Var_RandomInteger(Event *ev ); + void Var_Equals( Event *ev ); + void Var_PlusEquals( Event *ev ); + void Var_MinusEquals( Event *ev ); + void Var_TimesEquals( Event *ev ); + void Var_DivideEquals( Event *ev ); + void Var_Div( Event *ev ); + void Var_Mod( Event *ev ); + /* void Var_IfEqual( Event *ev ); + void Var_IfNotEqual( Event *ev ); + void Var_IfGreater( Event *ev ); + void Var_IfGreaterEqual( Event *ev ); + void Var_IfLess( Event *ev ); + void Var_IfLessEqual( Event *ev ); + void Var_IfStrEqual( Event *ev ); + void Var_IfStrNotEqual( Event *ev ); + void Var_IfThreadActive( Event *ev ); + void Var_IfThreadNotActive( Event *ev ); */ + void Var_GetCvar( Event *ev ); + void Var_Vector( Event *ev ); + void Var_Vector_Add( Event *ev ); + void Var_Vector_Subtract( Event *ev ); + void Var_Vector_Scale( Event *ev ); + void Var_Vector_Normalize( Event *ev ); + void Var_Vector_GetX( Event *ev ); + void Var_Vector_GetY( Event *ev ); + void Var_Vector_GetZ( Event *ev ); + void Var_Vector_SetX( Event *ev ); + void Var_Vector_SetY( Event *ev ); + void Var_Vector_SetZ( Event *ev ); + /* void Var_Vector_IfEqual( Event *ev ); + void Var_Vector_IfNotEqual( Event *ev ); */ + void Var_Vector_DotProduct( Event *ev ); + void Var_Vector_CrossProduct( Event *ev ); + void Var_Vector_Length( Event *ev ); + void Var_Angles_ToForward( Event *ev ); + void Var_Angles_ToLeft( Event *ev ); + void Var_Angles_ToUp( Event *ev ); + void Var_TargetOf( Event * ev ); + + virtual void Archive( Archiver &arc ); + }; + +inline void ScriptVariable::Archive + ( + Archiver &arc + ) + + { + Listener::Archive( arc ); + + arc.ArchiveString( &name ); + arc.ArchiveFloat( &value ); + arc.ArchiveString( &string ); + arc.ArchiveVector( &vec ); + } + +class ScriptVariableList : public Class + { + private: + Container list; + + public: + CLASS_PROTOTYPE( ScriptVariableList ); + + ScriptVariableList(); + ~ScriptVariableList(); + + void ClearList( void ); + void AddVariable( ScriptVariable *var ); + ScriptVariable *CreateVariable( const char *name, float value ); + ScriptVariable *CreateVariable( const char *name, int value ); + ScriptVariable *CreateVariable( const char *name, const char *text ); + ScriptVariable *CreateVariable( const char *name, const Entity *ent ); + ScriptVariable *CreateVariable( const char *name, const Vector &vec ); + void RemoveVariable( ScriptVariable *var ); + void RemoveVariable( const char *name ); + qboolean VariableExists( const char *name ); + ScriptVariable *GetVariable( const char *name ); + int NumVariables( void ); + ScriptVariable *GetVariable( int num ); + ScriptVariable *SetVariable( const char *name, float value ); + ScriptVariable *SetVariable( const char *name, int value ); + ScriptVariable *SetVariable( const char *name, const char *text ); + ScriptVariable *SetVariable( const char *name, const Entity *ent ); + ScriptVariable *SetVariable( const char *name, const Vector &vec ); + virtual void Archive( Archiver &arc ); + }; + +inline void ScriptVariableList::Archive + ( + Archiver &arc + ) + + { + int i; + int num; + ScriptVariable *var; + + Class::Archive( arc ); + + if ( arc.Saving() ) + { + num = NumVariables(); + } + else + { + ClearList(); + } + arc.ArchiveInteger( &num ); + for( i = 1; i <= num; i++ ) + { + if ( arc.Saving() ) + { + var = GetVariable( i ); + } + else + { + var = new ScriptVariable; + AddVariable( var ); + } + arc.ArchiveObject( var ); + } + } + +typedef SafePtr ScriptVariablePtr; + +#endif /* scriptvariable.h */ diff --git a/dlls/game/selectBestWeapon.cpp b/dlls/game/selectBestWeapon.cpp new file mode 100644 index 0000000..f7b4ac8 --- /dev/null +++ b/dlls/game/selectBestWeapon.cpp @@ -0,0 +1,538 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/selectBestWeapon.cpp $ +// $Revision:: 12 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// SelectBestWeapon Implementation +// +// PARAMETERS: +// +// ANIMATIONS: +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "selectBestWeapon.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, SelectBestWeapon, NULL ) + { + { &EV_Behavior_Args, &SelectBestWeapon::SetArgs }, + { &EV_Behavior_AnimDone, &SelectBestWeapon::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SelectBestWeapon() +// Class: SelectBestWeapon +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +SelectBestWeapon::SelectBestWeapon() +{ + _self = NULL; +} + +//-------------------------------------------------------------- +// Name: SelectBestWeapon() +// Class: SelectBestWeapon +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +SelectBestWeapon::~SelectBestWeapon() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: SelectBestWeapon +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::SetArgs( Event *ev ) +{ +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: SelectBestWeapon +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::AnimDone( Event *ev ) +{ + _animDone = true; +} + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: SelectBestWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::Begin( Actor &self ) + { + init( self ); + } + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: SelectBestWeapon +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t SelectBestWeapon::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case SBW_SELECT_WEAPON: + //--------------------------------------------------------------------- + stateResult = evaluateStateSelectWeapon(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( SBW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + if ( _self->combatSubsystem->UsingWeaponNamed( _bestWeaponName ) ) + transitionToState( SBW_SUCCESS ); + else + transitionToState( SBW_PUT_AWAY_CURRENT_WEAPON ); + + } + + break; + + //--------------------------------------------------------------------- + case SBW_PUT_AWAY_CURRENT_WEAPON: + //--------------------------------------------------------------------- + stateResult = evaluateStatePutAwayCurrentWeapon(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( SBW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( SBW_PULL_OUT_NEW_WEAPON ); + + break; + + //--------------------------------------------------------------------- + case SBW_PULL_OUT_NEW_WEAPON: + //--------------------------------------------------------------------- + stateResult = evaluateStatePullOutNewWeapon(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( SBW_FAILED ); + + //if ( stateResult == BEHAVIOR_SUCCESS ) + // transitionToState( SBW_READY_NEW_WEAPON ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( SBW_SUCCESS ); + + break; + + //--------------------------------------------------------------------- + case SBW_READY_NEW_WEAPON: + //--------------------------------------------------------------------- + stateResult = evaluateStateReadyNewWeapon(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( SBW_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( SBW_SUCCESS ); + + break; + + //--------------------------------------------------------------------- + case SBW_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + break; + + //--------------------------------------------------------------------- + case SBW_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + break; + } + + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: SelectBestWeapon +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::End(Actor &self) +{ +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: SelectBestWeapon +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::transitionToState( selectBestWeaponStates_t state ) +{ + + switch ( state ) + { + case SBW_SELECT_WEAPON: + setupStateSelectWeapon(); + setInternalState( state , "SBW_SELECT_WEAPON" ); + break; + + case SBW_PUT_AWAY_CURRENT_WEAPON: + setupStatePutAwayCurrentWeapon(); + setInternalState( state , "SBW_PUT_AWAY_CURRENT_WEAPON" ); + break; + + case SBW_PULL_OUT_NEW_WEAPON: + setupStatePullOutNewWeapon(); + setInternalState( state , "SWB_PULL_OUT_NEW_WEAPON" ); + break; + + case SBW_READY_NEW_WEAPON: + setupStateReadyNewWeapon(); + setInternalState( state , "SBW_READY_NEW_WEAPON" ); + break; + + case SBW_SUCCESS: + setInternalState( state , "SBW_SUCCESS" ); + break; + + case SBW_FAILED: + setInternalState( state , "SBW_FAILED" ); + break; + } + +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: SelectBestWeapon +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::setInternalState( selectBestWeaponStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: SelectBestWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::init( Actor &self ) +{ + _self = &self; + _animDone = false; + transitionToState( SBW_SELECT_WEAPON ); + _currentWeaponName = self.combatSubsystem->GetActiveWeaponName(); + +} + +//-------------------------------------------------------------- +// Name: think() +// Class: SelectBestWeapon +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::think() +{ + if ( !_bestWeapon ) + { + failureStateSelectWeapon( "No Best Weapon" ); + transitionToState( SBW_SUCCESS ); + } +} + +//-------------------------------------------------------------- +// Name: setupStateSelectWeapon() +// Class: SelectBestWeapon +// +// Description: Sets up the state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::setupStateSelectWeapon() +{ + if ( !_currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + _currentEnemy = _self->enemyManager->GetCurrentEnemy(); + } +} + +//-------------------------------------------------------------- +// Name: evaluateStateSelectWeapon() +// Class: SelectBestWeapon +// +// Description: Evaluates the State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t SelectBestWeapon::evaluateStateSelectWeapon() +{ + str bestWeaponName; + float powerRating; + str currentPostureState; + + + //First Check if our current weapon is useless + powerRating = _self->combatSubsystem->GetActiveWeaponPowerRating( _currentEnemy ); + if ( powerRating < .05 ) + { + _self->InContext( "weaponuseless" , 0 ); + //Put Weapon Useless Context Here + } + + + _bestWeapon = _self->combatSubsystem->GetBestAvailableWeapon( _currentEnemy ); + + if ( !_bestWeapon ) + { + failureStateSelectWeapon( "No Best Weapon" ); + return BEHAVIOR_SUCCESS; + } + + _bestWeaponName = _bestWeapon->getName(); + + + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateSelectWeapon() +// Class: SelectBestWeapon +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::failureStateSelectWeapon(const str& failureReason) +{ +} + + +//-------------------------------------------------------------- +// Name: setupStatePutAwayCurrentWeapon() +// Class: SelectBestWeapon +// +// Description: Sets up the state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::setupStatePutAwayCurrentWeapon() +{ + str anim; + anim = _currentWeaponName + "_putaway"; + _self->SetAnim( anim , EV_Actor_NotifyBehavior, torso ); + _animDone = false; +} + +//-------------------------------------------------------------- +// Name: evaluateStatePutAwayCurrentWeapon() +// Class: SelectBestWeapon +// +// Description: Evaluates the State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t SelectBestWeapon::evaluateStatePutAwayCurrentWeapon() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStatePutAwayCurrentWeapon() +// Class: SelectBestWeapon +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::failureStatePutAwayCurrentWeapon(const str& failureReason) +{ +} + +//-------------------------------------------------------------- +// Name: setupStatePullOutNewWeapon() +// Class: SelectBestWeapon +// +// Description: Sets up the state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::setupStatePullOutNewWeapon() +{ + str anim; + anim = _bestWeaponName + "_draw"; + _self->SetAnim( anim , EV_Actor_NotifyBehavior , torso ); + _animDone = false; +} + +//-------------------------------------------------------------- +// Name: evaluateStatePullOutNewWeapon() +// Class: SelectBestWeapon +// +// Description: Evaluates the State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t SelectBestWeapon::evaluateStatePullOutNewWeapon() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStatePullOutNewWeapon() +// Class: SelectBestWeapon +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void SelectBestWeapon::failureStatePullOutNewWeapon(const str& failureReason) +{ +} + +void SelectBestWeapon::setupStateReadyNewWeapon() +{ + str anim; + anim = _bestWeaponName + "_idle"; + _self->SetAnim( anim , EV_Actor_NotifyBehavior , torso ); + _animDone = false; +} + +BehaviorReturnCode_t SelectBestWeapon::evaluateStateReadyNewWeapon() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +void SelectBestWeapon::failureStateReadyNewWeapon( const str &failureReason ) +{ +} + +bool SelectBestWeapon::CanExecute( Actor &self ) +{ + str currentPosture; + currentPosture = self.postureController->getCurrentPostureName(); + + if ( currentPosture == "STAND" ) + return true; + + return false; +} + diff --git a/dlls/game/selectBestWeapon.hpp b/dlls/game/selectBestWeapon.hpp new file mode 100644 index 0000000..62bacdb --- /dev/null +++ b/dlls/game/selectBestWeapon.hpp @@ -0,0 +1,161 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/coverCombatWithRangedWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CoverCombatWithRangedWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class SelectBestWeapon; + +#ifndef __SELECT_BEST_WEAPON_HPP__ +#define __SELECT_BEST_WEAPON_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: SelectBestWeapon +// Base Class: Behavior +// +// Description: Selects and "Equips" the "best" weapon in the +// Actor's inventory +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class SelectBestWeapon : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + SBW_SELECT_WEAPON, + SBW_PUT_AWAY_CURRENT_WEAPON, + SBW_PULL_OUT_NEW_WEAPON, + SBW_READY_NEW_WEAPON, + SBW_SUCCESS, + SBW_FAILED + } selectBestWeaponStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + EntityPtr _currentEnemy; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( selectBestWeaponStates_t state ); + void setInternalState ( selectBestWeaponStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + + void setupStateSelectWeapon (); + BehaviorReturnCode_t evaluateStateSelectWeapon(); + void failureStateSelectWeapon( const str& failureReason ); + + void setupStatePutAwayCurrentWeapon(); + BehaviorReturnCode_t evaluateStatePutAwayCurrentWeapon(); + void failureStatePutAwayCurrentWeapon(const str& failureReason); + + void setupStatePullOutNewWeapon(); + BehaviorReturnCode_t evaluateStatePullOutNewWeapon(); + void failureStatePullOutNewWeapon(const str& failureReason); + + void setupStateReadyNewWeapon(); + BehaviorReturnCode_t evaluateStateReadyNewWeapon(); + void failureStateReadyNewWeapon( const str& failureReason ); + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( SelectBestWeapon ); + + SelectBestWeapon(); + ~SelectBestWeapon(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void PostureDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + void SetCurrentEnemy ( Entity* enemy ); + + static bool CanExecute( Actor &self ); + virtual void Archive ( Archiver &arc ); + + + + //------------------------------------- + // Components + //------------------------------------- + private: + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + selectBestWeaponStates_t _state; + Actor *_self; + WeaponPtr _bestWeapon; + str _currentWeaponName; + str _bestWeaponName; + bool _animDone; + + }; + +inline void SelectBestWeapon::SetCurrentEnemy( Entity *enemy ) +{ + _currentEnemy = enemy; +} + +inline void SelectBestWeapon::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveSafePointer( &_currentEnemy ); + + // + // Archive Components + // + + // + // Archive Member Variables + // + ArchiveEnum ( _state, selectBestWeaponStates_t ); + arc.ArchiveObjectPointer( ( Class ** )&_self ); + arc.ArchiveSafePointer ( &_bestWeapon ); + arc.ArchiveString ( &_currentWeaponName ); + arc.ArchiveString ( &_bestWeaponName ); + arc.ArchiveBool ( &_animDone ); +} + + +#endif /* __SELECT_BEST_WEAPON_HPP__ */ diff --git a/dlls/game/sentient.cpp b/dlls/game/sentient.cpp new file mode 100644 index 0000000..1ce02d3 --- /dev/null +++ b/dlls/game/sentient.cpp @@ -0,0 +1,5119 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/sentient.cpp $ +// $Revision:: 197 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class of entity that can carry other entities, and use weapons. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "sentient.h" +#include "weapon.h" +#include "WeaponDualWield.h" +#include "weaputils.h" +#include "scriptmaster.h" +#include "ammo.h" +#include "armor.h" +#include "misc.h" +#include "inventoryitem.h" +#include "player.h" +#include "actor.h" +#include "mp_manager.hpp" +#include + +#include "decals.h" + +Event EV_Sentient_BeginAttack +( + "beginattack", + EV_DEFAULT, + NULL, + NULL, + "Called before attack animation begins" +); +Event EV_Sentient_EndAttack +( + "endattack", + EV_DEFAULT, + NULL, + NULL, + "Called after attack animation ends" +); +Event EV_Sentient_Attack +( + "fire", + EV_DEFAULT, + "SS", + "hand mode", + "Fires the weapon in the specified hand. With the specified mode (primary, alternate)" +); +Event EV_Sentient_StopFire +( + "stopfire", + EV_TIKIONLY, + "s", + "hand", + "Stops the firing of the weapon in the specified hand." +); +Event EV_Sentient_StartChargeFire +( + "startcharge", + EV_DEFAULT, + "s", + "firemode", + "Draws back the bow string" +); +Event EV_Sentient_ReleaseAttack +( + "releasefire", + EV_TIKIONLY, + "f", + "fireholdtime", + "Releases the attack in the time specified." +); +Event EV_Sentient_GiveWeapon +( + "weapon", + EV_DEFAULT, + "s", + "weapon_modelname", + "Gives the sentient the weapon specified." +); +Event EV_Sentient_Take +( + "take", + EV_DEFAULT, + "s", + "item_name", + "Takes away the specified item from the sentient." +); +Event EV_Sentient_GiveAmmo +( + "ammo", + EV_DEFAULT, + "siI", + "type amount max_amount", + "Gives the sentient some ammo." +); +Event EV_Sentient_GiveAmmoOverTime +( + "giveAmmoOverTime", + EV_DEFAULT, + "sif", + "type amount time", + "Gives the sentient some ammo over the specified period of time." +); +Event EV_Sentient_GiveArmor +( + "armor", + EV_DEFAULT, + "sFB", + "type amount pickedup", + "Gives the sentient some armor." +); +Event EV_Sentient_GiveItem +( + "item", + EV_DEFAULT, + "si", + "type amount", + "Gives the sentient the specified amount of the specified item." +); +Event EV_Sentient_GiveTargetname +( + "give", + EV_CHEAT | EV_TIKIONLY | EV_SCRIPTONLY, + "s", + "name", + "Gives the sentient the targeted item." +); +Event EV_Sentient_GiveHealth +( + "health", + EV_CHEAT | EV_TIKIONLY, + "f", + "health", + "Gives the sentient the specified amount health." +); +Event EV_Sentient_SetBloodModel +( + "bloodmodel", + EV_TIKIONLY, + "s", + "bloodModel", + "set the model to be used when showing blood" +); +Event EV_Sentient_TurnOffShadow +( + "noshadow", + EV_TIKIONLY, + NULL, + NULL, + "Turns off the shadow for this sentient." +); +Event EV_Sentient_TurnOnShadow +( + "shadow", + EV_TIKIONLY, + NULL, + NULL, + "Turns on the shadow for this sentient." +); +Event EV_Sentient_AddImmunity +( + "immune", + EV_DEFAULT, + "sSSSSS", + "immune_string1 immune_string2 immune_string3 immune_string4 immune_string5 immune_string6", + "Adds to the immunity list for this sentient." +); +Event EV_Sentient_AddResistance +( + "resistance", + EV_DEFAULT, + "si", + "resistance_string resistance_amount", + "Adds to the resistance list for this sentient." +); +Event EV_Sentient_RemoveImmunity +( + "removeimmune", + EV_DEFAULT, + "sSSSSS", + "immune_string1 immune_string2 immune_string3 immune_string4 immune_string5 immune_string6", + "Removes from the immunity list for this sentient." +); +Event EV_Sentient_RemoveResistance +( + "removeresistance", + EV_DEFAULT, + "s", + "resistance_string", + "Removes from the resistance list for this sentient." +); +Event EV_Sentient_UpdateOffsetColor +( + "updateoffsetcolor", + EV_CODEONLY, + NULL, + NULL, + "Updates the offset color." +); +Event EV_Sentient_JumpXY +( + "jumpxy", + EV_DEFAULT, + "fff", + "forwardmove sidemove speed", + "Makes the sentient jump." +); +Event EV_Sentient_MeleeAttackStart +( + "meleeattackstart", + EV_TIKIONLY, + "S", + "hand", + "Is the start of the sentient's melee attack." +); +Event EV_Sentient_MeleeAttackEnd +( + "meleeattackend", + EV_TIKIONLY, + "S", + "hand", + "Is the end of the sentient's melee attack." +); +Event EV_Sentient_RangedAttackStart +( + "rangedattackstart", + EV_TIKIONLY, + NULL, + NULL, + "Is the start of the sentient's hitscan attack." +); +Event EV_Sentient_RangedAttackEnd +( + "rangedattackend", + EV_TIKIONLY, + NULL, + NULL, + "Is the end of the sentient's hitscan attack." +); +Event EV_Sentient_BlockStart +( + "blockstart", + EV_TIKIONLY, + NULL, + NULL, + "Is the start of the sentient's block." +); +Event EV_Sentient_BlockEnd +( + "blockend", + EV_TIKIONLY, + NULL, + NULL, + "Is the end of the sentient's block." +); +Event EV_Sentient_StunStart +( + "stunstart", + EV_CODEONLY, + NULL, + NULL, + "Is the start of the sentient's stun." +); +Event EV_Sentient_StunEnd +( + "stunend", + EV_CODEONLY, + NULL, + NULL, + "Is the end of the sentient's stun." +); +Event EV_Sentient_SetMouthAngle +( + "mouthangle", + EV_DEFAULT, + "f", + "mouth_angle", + "Sets the mouth angle of the sentient." +); +Event EV_Sentient_SetMaxMouthAngle +( + "maxmouthangle", + EV_TIKIONLY, + "f", + "max_mouth_angle", + "Sets the max mouth angle." +); +Event EV_Sentient_OnFire +( + "onfire", + EV_CODEONLY, + NULL, + NULL, + "Called every frame when the sentient is on fire." +); +Event EV_Sentient_StopOnFire +( + "stoponfire", + EV_CODEONLY, + NULL, + NULL, + "Stops the sentient from being on fire." +); +Event EV_Sentient_SpawnBloodyGibs +( + "spawnbloodygibs", + EV_DEFAULT, + "IF", + "number_of_gibs scale", + "Spawns some bloody generic gibs." +); +Event EV_Sentient_SetMaxGibs +( + "maxgibs", + EV_TIKIONLY, + "i", + "max_number_of_gibs", + "Sets the maximum amount of generic gibs this sentient will spawn when hit." +); +Event EV_Sentient_CheckAnimations +( + "checkanims", + EV_CONSOLE, + NULL, + NULL, + "Check the animations in the .tik file versus the statefile" +); +Event EV_Sentient_SetStateFile +( + "setstatefile", + EV_DEFAULT, + "s", + "state_file", + "Change the state file associated with this character" +); + +Event EV_Sentient_AddHealth +( + "addhealth", + EV_SCRIPTONLY, + "fF", + "health_to_add maxhealth", + "Adds health to the sentient." +); +Event EV_Sentient_SetViewMode +( + "setviewmode", + EV_SCRIPTONLY, + "sB", + "viewModeName override", + "Puts this sentient into the specified view mode.\n" + "Override defaults to true." +); +Event EV_Sentient_GetActiveWeaponName +( + "getActiveWeaponName", + EV_SCRIPTONLY, + "@sS", + "weaponName hand", + "Gets the name of the weapon in the specified hand (left, right, or dual).\n" + "If no hand is specified it will return the first it finds" +); +Event EV_Sentient_CatchOnFire +( + "catchonfire", + EV_SCRIPTONLY, + NULL, + NULL, + "Catches the actor on fire." +); +Event EV_Sentient_AddMeleeAttacker +( + "addmeleeattacker", + EV_CODEONLY, + "e", + "attack_ent", + "Adds the entity to the melee attacker list." +); +Event EV_Sentient_SwipeOn +( + "swipeon", + EV_TIKIONLY, + "s", + "hand", + "Turn on the sword swiping for the weapon in the specified hand" +); +Event EV_Sentient_SwipeOff +( + "swipeoff", + EV_TIKIONLY, + "s", + "hand", + "Turn off the sword swiping for the weapon in the specified hand" +); +Event EV_Sentient_WeaponAnim +( + "weaponanimon", + EV_TIKIONLY, + "ss", + "animname hand", + "Put the weapon in hand in the animation specified" +); +Event EV_Sentient_HealOverTime +( + "healovertime", + EV_DEFAULT, + "ffff", + "add_immediately add_at_interval interval max_percentage", + "Will add specified amount of health immediatly, then add more at each specified time interval\n" + "until health reaches the maxPercentage" +); +Event EV_Sentient_HealAtInterval +( + "healatinterval", + EV_CODEONLY, + "fff", + "percentageToAdd interval maxPercentage", + "Will add the specified amount to health, then, if necessary , will generate a new event of its\n" + "own type to continue the regen process" +); +Event EV_Sentient_GroupMemberInjured +( + "groupmemberinjured", + EV_DEFAULT, + "b", + "injured", + "Informs us that a group member is injured or not" +); +Event EV_Sentient_SetCriticalHealthPercentage +( + "setcriticalhealthpercentage", + EV_DEFAULT, + "f", + "percentage", + "Sets the percentage of health that qualifies as critical -- Note, values must be entered as\n" + "floating point numbers... .10 for example is 10 percent" +); +Event EV_Sentient_HeadWatchAllowed +( + "headwatchallowed", + EV_DEFAULT, + "B", + "flag", + "Sets whether to headwatch or not, default is true." +); +Event EV_Sentient_SetDamageThreshold +( + "setdamagethreshold", + EV_DEFAULT, + "ff", + "maxDamage duration", + "Sets up the damage threshold" +); +Event EV_Sentient_DisplayFireEffect +( + "displayfireeffect", + EV_DEFAULT, + "B", + "flag", + "Sets whether or not this sentient displays the fire effect when on fire." +); +Event EV_Sentient_ClearDamageThreshold +( + "cleardamagethreshold", + EV_DEFAULT, + NULL, + NULL, + "Clears out and resets the damage threshold" +); +Event EV_Sentient_DropItem +( + "dropitem", + EV_DEFAULT, + "s", + "itemName", + "Drops the item by the specified name." +); +Event EV_Sentient_SetArmorActiveStatus +( + "setmyarmorstatus", + EV_DEFAULT, + "b", + "flag", + "Sets The Active Status on Armor" +); + +Event EV_Sentient_SetArmorMultiplier +( + "setmyarmormultiplier", + EV_DEFAULT, + "f", + "multiplier", + "Sets the mulitplier of the armor" +); + +Event EV_Sentient_AddToMyArmor +( + "addtomyarmor", + EV_DEFAULT, + "f", + "amountToAdd", + "Adds the amount to the current Armor" +); + +Event EV_Sentient_SetMyArmorAmount +( + "setmyarmoramount", + EV_DEFAULT, + "f", + "amount", + "Sets the amount of armor to the specifed number" +); + +Event EV_Sentient_FreeInventory +( + "freeInventory", + EV_DEFAULT, + NULL, + NULL, + "Frees the sentient's inventory" +); + +Event EV_Sentient_ArmorCommand +( + "armorcommand", + EV_CODEONLY, + "sSSSSSSS", + "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8", + "Pass the args to the active armor" +); + +Event EV_Sentient_SetHateModifier +( + "sethatemodifier", + EV_DEFAULT, + "f", + "hate_modifier", + "Sets the hate modifier" +); + +Event EV_Sentient_CacheStateMachineAnims +( + "cacheStateMachineAnims", + EV_CACHE, + "s", + "stateMachineName", + "Caches all of the anims needed by the statemachine" +); + + +CLASS_DECLARATION( Entity, Sentient, NULL ) +{ + { &EV_Sentient_BeginAttack, &Sentient::BeginAttack }, + { &EV_Sentient_EndAttack, &Sentient::EndAttack }, + { &EV_Sentient_Attack, &Sentient::FireWeapon }, + { &EV_Sentient_StopFire, &Sentient::StopFireWeapon }, + { &EV_Sentient_StartChargeFire, &Sentient::StartChargeFire }, + { &EV_Sentient_ReleaseAttack, &Sentient::ReleaseFireWeapon }, + { &EV_Sentient_GiveAmmo, &Sentient::EventGiveAmmo }, + { &EV_Sentient_GiveAmmoOverTime, &Sentient::giveAmmoOverTime }, + { &EV_Sentient_GiveWeapon, &Sentient::EventGiveItem }, + { &EV_Sentient_GiveArmor, &Sentient::EventGiveArmor }, + { &EV_Sentient_GiveItem, &Sentient::EventGiveItem }, + { &EV_Sentient_GiveHealth, &Sentient::EventGiveHealth }, + { &EV_Sentient_Take, &Sentient::EventTake }, + { &EV_Sentient_SetBloodModel, &Sentient::SetBloodModel }, + { &EV_Sentient_GiveTargetname, &Sentient::EventGiveTargetname }, + { &EV_Damage, &Sentient::ArmorDamage }, + { &EV_Sentient_TurnOffShadow, &Sentient::TurnOffShadow }, + { &EV_Sentient_TurnOnShadow, &Sentient::TurnOnShadow }, + { &EV_Sentient_AddImmunity, &Sentient::AddImmunity }, + { &EV_Sentient_RemoveImmunity, &Sentient::RemoveImmunity }, + { &EV_Sentient_AddResistance, &Sentient::AddResistance }, + { &EV_Sentient_RemoveResistance, &Sentient::RemoveResistance }, + { &EV_Sentient_UpdateOffsetColor, &Sentient::UpdateOffsetColor }, + { &EV_Sentient_JumpXY, &Sentient::JumpXY }, + { &EV_Sentient_MeleeAttackStart, &Sentient::MeleeAttackStart }, + { &EV_Sentient_MeleeAttackEnd, &Sentient::MeleeAttackEnd }, + { &EV_Sentient_RangedAttackStart, &Sentient::RangedAttackStart }, + { &EV_Sentient_RangedAttackEnd, &Sentient::RangedAttackEnd }, + { &EV_Sentient_BlockStart, &Sentient::BlockStart }, + { &EV_Sentient_BlockEnd, &Sentient::BlockEnd }, + { &EV_Sentient_StunStart, &Sentient::StunStart }, + { &EV_Sentient_StunEnd, &Sentient::StunEnd }, + { &EV_Sentient_SetMaxMouthAngle, &Sentient::SetMaxMouthAngle }, + { &EV_Sentient_OnFire, &Sentient::OnFire }, + { &EV_Sentient_StopOnFire, &Sentient::StopOnFire }, + { &EV_Sentient_SpawnBloodyGibs, &Sentient::SpawnBloodyGibs }, + { &EV_Sentient_SetMaxGibs, &Sentient::SetMaxGibs }, + { &EV_Sentient_CheckAnimations, &Sentient::CheckAnimations }, + { &EV_Sentient_SetStateFile, &Sentient::SetStateFile }, + { &EV_Sentient_AddHealth, &Sentient::AddHealth }, + { &EV_Sentient_SetViewMode, &Sentient::setViewMode }, + { &EV_Sentient_GetActiveWeaponName, &Sentient::getActiveWeaponName }, + { &EV_Sentient_CatchOnFire, &Sentient::CatchOnFire }, + { &EV_Sentient_SwipeOn, &Sentient::SwipeOn }, + { &EV_Sentient_SwipeOff, &Sentient::SwipeOff }, + { &EV_Sentient_HealOverTime, &Sentient::AddHealthOverTime }, + { &EV_Sentient_HealAtInterval, &Sentient::AddHealthAtInterval }, + { &EV_Sentient_SetCriticalHealthPercentage, &Sentient::SetCriticalHealthPercentage }, + { &EV_Sentient_HeadWatchAllowed, &Sentient::HeadWatchAllowed }, + { &EV_Sentient_WeaponAnim, &Sentient::SetWeaponAnim }, + { &EV_Sentient_SetDamageThreshold, &Sentient::SetDamageThreshold }, + { &EV_Sentient_DisplayFireEffect, &Sentient::DisplayFireEffect }, + { &EV_Sentient_ClearDamageThreshold, &Sentient::ClearDamageThreshold }, + { &EV_Sentient_DropItem, &Sentient::DropItemEvent }, + { &EV_Sentient_SetArmorActiveStatus, &Sentient::SetArmorActiveStatus }, + { &EV_Sentient_SetArmorMultiplier, &Sentient::SetArmorMultiplier }, + { &EV_Sentient_AddToMyArmor, &Sentient::AddToMyArmor }, + { &EV_Sentient_SetMyArmorAmount, &Sentient::SetMyArmorAmount }, + { &EV_Sentient_ArmorCommand, &Sentient::ArmorEvent }, + { &EV_Sentient_SetHateModifier, &Sentient::SetHateModifier }, + + { &EV_Sentient_FreeInventory, &Sentient::FreeInventory }, + { &EV_Sentient_CacheStateMachineAnims, &Sentient::cacheStateMachineAnims }, + { NULL, NULL } +}; + +Container SentientList; + +Sentient::Sentient() +{ + animate = new Animate( this ); + SentientList.AddObject( ( Sentient * )this ); + + setContents( CONTENTS_BODY ); + inventory.ClearObjectList(); + + newWeapon = NULL; + currentBaseArmor = NULL; + eyeposition = Vector(0, 0, 64); + shotsFiredThisVolley = 0; + //firing_frame = -1; + //firing_anim = -1; + knock_start_time = 0; + // do better lighting on all sentients + edict->s.renderfx |= RF_EXTRALIGHT; + edict->s.renderfx |= RF_SHADOW; + // sentients have precise shadows + edict->s.renderfx |= RF_SHADOW_PRECISE; + + in_melee_attack = false; + in_ranged_attack = false; + in_block = false; + in_stun = false; + attack_blocked = false; + max_mouth_angle = 10; + // touch triggers by default + flags |= FL_TOUCH_TRIGGERS; + on_fire = false; + max_gibs = 0; + next_bleed_time = 0; + + last_surface_hit = -1; + last_bone_hit = -1; + _canSendInjuredEvent = true; + _headWatchAllowed = true; + _hateModifier = 1.0; + + addAffectingViewModes( gi.GetViewModeClassMask( "sentient" ) ); + SetCriticalHealthPercentage( .5 ); + + //Initial Max Value for _damageThreshold is set to -1 so that + //we won't do a lot of processing unless we specifically set + //our max value + _damageThreshold.maxDamage = -1.0f; + _displayFireEffect = true; + + on_fire_tagnums[ 0 ] = -1; + on_fire_tagnums[ 1 ] = -1; + on_fire_tagnums[ 2 ] = -1; +} + +Sentient::~Sentient() +{ + Sentient *sentient; + + for ( int i = 1; i <= resistances.NumObjects(); i++ ) + { + if (resistances.ObjectAt( i ) ) + { + delete resistances.ObjectAt( i ); + resistances.ObjectAt( i ) = 0; + } + } + + sentient = this; + + SentientList.RemoveObject( sentient ); + FreeInventory(); +} + + +// HACK HACK HACK +void Sentient::UpdateOffsetColor( Event *ev ) +{ + G_SetConstantLight( &edict->s.constantLight, &offset_color[ 0 ], &offset_color[ 1 ], &offset_color[ 2 ], NULL ); + offset_color -= offset_delta; + offset_time -= FRAMETIME; + if ( offset_time > 0.0f ) + { + PostEvent( EV_Sentient_UpdateOffsetColor, FRAMETIME ); + } + else + { + CancelEventsOfType( EV_Sentient_UpdateOffsetColor ); + edict->s.renderfx &= ~RF_LIGHTOFFSET; + offset_color[ 0 ] = offset_color[ 1 ] = offset_color[ 2 ] = 0; + G_SetConstantLight( &edict->s.constantLight, &offset_color[ 0 ], &offset_color[ 1 ], &offset_color[ 2 ], NULL ); + } +} + +void Sentient::SetOffsetColor( float r, float g, float b, float time ) +{ + // kill all pending events + CancelEventsOfType( EV_Sentient_UpdateOffsetColor ); + + offset_color[ 0 ] = r; + offset_color[ 1 ] = g; + offset_color[ 2 ] = b; + + G_SetConstantLight( &edict->s.constantLight, &offset_color[ 0 ], &offset_color[ 1 ], &offset_color[ 2 ], NULL ); + + // delta is a little less so we don't go below zero + offset_delta = offset_color * ( FRAMETIME / ( time + ( 0.5f * FRAMETIME ) ) ); + offset_time = time; + + edict->s.renderfx |= RF_LIGHTOFFSET; + + PostEvent( EV_Sentient_UpdateOffsetColor, FRAMETIME ); +} + +Vector Sentient::EyePosition( void ) +{ + int tagNum; + + tagNum = gi.Tag_NumForName( edict->s.modelindex, "tag_eyes" ); + + if ( tagNum >= 0 ) + { + Vector tag_pos; + + GetTag( tagNum, &tag_pos ); + + return tag_pos; + } + else + { + return origin + eyeposition; + } +} + +Vector Sentient::GunPosition( void ) +{ + return origin; +} + +void Sentient::EventGiveHealth( Event *ev ) +{ + health = ev->GetFloat( 1 ); +} + +void Sentient::AddHealth( Event *ev ) +{ + float health_to_add; + float maxhealth; + + + health_to_add = ev->GetFloat( 1 ); + + if ( ev->NumArgs() > 1 ) + maxhealth = ev->GetFloat( 2 ); + else + maxhealth = max_health; + + AddHealth( health_to_add, maxhealth ); +} + +void Sentient::AddHealth( float healthToAdd, float maxHealth ) +{ + float tempMaxHealth; + float newHealth; + + if ( maxHealth ) + tempMaxHealth = maxHealth; + else + tempMaxHealth = max_health; + + newHealth = health + healthToAdd; + + // See if the health is already above the max (don't do anything if it is) + + if ( health >= tempMaxHealth ) + return; + + if ( newHealth > tempMaxHealth ) + SetHealth(tempMaxHealth); + else + SetHealth(newHealth); + +} + +void Sentient::SetBloodModel( Event *ev ) +{ + str name; + str cache_name; + str models_dir = "models/"; + + if ( ev->NumArgs() < 1 ) + return; + + blood_model = ev->GetString( 1 ); + cache_name = models_dir + blood_model; + CacheResource( cache_name.c_str(), this ); + + name = GetBloodSpurtName(); + if ( name.length() ) + { + cache_name = models_dir + name; + CacheResource( cache_name.c_str(), this ); + } + + name = GetBloodSplatName(); + if ( name.length() ) + CacheResource( name.c_str(), this ); + + name = GetGibName(); + if ( name.length() ) + { + cache_name = models_dir + name; + CacheResource( cache_name.c_str(), this ); + } +} + +void Sentient::StartChargeFire( Event *ev ) +{ + Weapon * activeWeapon; + firemode_t mode=FIRE_MODE1; + weaponhand_t hand=WEAPON_RIGHT; + + + hand = WeaponHandNameToNum( "dualhand" ); + + if ( ev->NumArgs() > 0 ) + { + mode = WeaponModeNameToNum( ev->GetString( 1 ) ); + + if ( mode < 0 ) + return; + } + + + if ( hand > MAX_ACTIVE_WEAPONS ) + { + warning( "Sentient::StartChargeFire", "Weapon hand number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", hand, MAX_ACTIVE_WEAPONS ); + return; + } + + // start charging the active weapon + activeWeapon = activeWeaponList[ (int)hand ]; + + // Save off firing animation and frame + /*firing_anim = ev->GetAnimationNumber(); + firing_frame = ev->GetAnimationFrame();*/ + + knock_start_time = level.time; + + /* if ( ( activeWeapon ) && activeWeapon->ReadyToFire( mode ) ) + { + knock_start_time = level.time; + + if ( mode == FIRE_MODE1 ) + activeWeapon->SetAnim( "charge", EV_Weapon_DrawBowStrain); + else if ( mode == FIRE_MODE2 ) + activeWeapon->SetAnim( "alternatecharge", EV_Weapon_AltDrawBowStrain ); + } */ +} + + +void Sentient::BeginAttack( Event *ev ) +{ + if ( !inheritsFrom( "Player" ) ) + { + return; + } + Weapon *weapon = GetActiveWeapon( WEAPON_RIGHT ); + if ( !weapon ) + { + weapon = GetActiveWeapon( WEAPON_LEFT ); + } + + if ( !weapon ) + { + return; + } + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + + float animationRate = 1.0f; + if (gpm->hasProperty( weapon->getArchetype(), "animationRate" ) ) + { + animationRate = gpm->getFloatValue( weapon->getArchetype(), "animationRate" ); + } + + animate->SetAnimationRate( animationRate ); +} + +void Sentient::EndAttack( Event *ev ) +{ + animate->RestoreAnimationRate(); +} + +void Sentient::FireWeapon( Event *ev ) +{ + Weapon *activeWeapon; + firemode_t mode=FIRE_MODE1; + int number=0; + str modestring, side; + + if ( ev->NumArgs() > 0 ) + { + side = ev->GetString( 1 ); + + if ( !stricmp( side, "righthand" ) ) + number = WEAPON_RIGHT; + else if ( !stricmp( side, "lefthand" ) ) + number = WEAPON_LEFT; + else if ( !stricmp( side, "dualhand" ) ) + { + number = WEAPON_DUAL; + + if ( ev->NumArgs() == 2 ) + { + modestring = ev->GetString( 2 ); + mode = WeaponModeNameToNum(modestring); + //if ( !modestring.icmp( "primary" ) ) + // mode = FIRE_MODE1; + //else if ( !modestring.icmp( "alternate" ) ) + // mode = FIRE_MODE2; + //else + // warning( "Sentient::FireWeapon", "Invalid fire mode %s\n", modestring ); + } + } + else + number = atoi( side.c_str() ); + } + + if ( ( number > MAX_ACTIVE_WEAPONS ) || ( number < 0 ) ) + { + warning( "Sentient::FireWeapon", "Weapon number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", number, MAX_ACTIVE_WEAPONS ); + return; + } + + // Save off firing animation and frame + //firing_anim = ev->GetAnimationNumber(); + //firing_frame = ev->GetAnimationFrame(); + + activeWeapon = activeWeaponList[ number ]; + + if ( ( activeWeapon ) && activeWeapon->ReadyToFire( mode ) ) + { + activeWeapon->Fire( mode ); + } + else + { + if ( !activeWeapon ) + gi.WDPrintf( "No active weapon in slot #: \"%i\"\n", number ); + } +} + +void Sentient::StopFireWeapon( Event *ev ) +{ + Weapon *activeWeapon; + int number=0; + str side; + + if ( ev->NumArgs() > 0 ) + { + side = ev->GetString( 1 ); + + if ( !stricmp( side, "righthand" ) ) + number = WEAPON_RIGHT; + else if ( !stricmp( side, "lefthand" ) ) + number = WEAPON_LEFT; + else if ( !stricmp( side, "dualhand" ) ) + number = WEAPON_DUAL; + else + number = atoi( side.c_str() ); + } + + if ( ( number > MAX_ACTIVE_WEAPONS ) || ( number < 0 ) ) + { + warning( "Sentient::StopFireWeapon", "Weapon number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", number, MAX_ACTIVE_WEAPONS ); + return; + } + + activeWeapon = activeWeaponList[ number ]; + + if ( activeWeapon ) + { + if ( activeWeapon->animate->HasAnim("fire_stop") ) + activeWeapon->SetAnim("fire_stop"); + else + activeWeapon->ForceIdle(); + } + else + { + gi.WDPrintf( "No active weapon in slot #: \"%i\"\n", number ); + } +} + +void Sentient::ReleaseFireWeapon( Event *ev ) +{ + Weapon *activeWeapon; + float charge_time=0; + firemode_t mode=FIRE_MODE1; + int number=0; + str modestring, side; + + charge_time = level.time - knock_start_time; + + // Reset the down timer + knock_start_time = 0; + + if ( ev->NumArgs() > 0 ) + { + side = ev->GetString( 1 ); + + if ( !stricmp( side, "righthand" ) ) + number = WEAPON_RIGHT; + else if ( !stricmp( side, "lefthand" ) ) + number = WEAPON_LEFT; + else if ( !stricmp( side, "dualhand" ) ) + { + number = WEAPON_DUAL; + if ( ev->NumArgs() == 2 ) + { + modestring = ev->GetString( 2 ); + mode = WeaponModeNameToNum(modestring); + //if ( !modestring.icmp( "primary" ) ) + // mode = FIRE_MODE1; + //else if ( !modestring.icmp( "alternate" ) ) + // mode = FIRE_MODE2; + //else + // warning( "Sentient::FireWeapon", "Invalid fire mode %s\n", modestring ); + } + } + else + number = atoi( side.c_str() ); + } + + if ( ( number > MAX_ACTIVE_WEAPONS ) || ( number < 0 ) ) + { + warning( "Sentient::FireWeapon", "Weapon number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", number, MAX_ACTIVE_WEAPONS ); + return; + } + + // Save off firing animation and frame + //firing_anim = ev->GetAnimationNumber(); + //firing_frame = ev->GetAnimationFrame(); + + activeWeapon = activeWeaponList[ number ]; + + if ( activeWeapon ) + { + activeWeapon->ReleaseFire( mode, charge_time ); + } +} + +void Sentient::AddItem( const Item *object ) +{ + inventory.AddObject( object->entnum ); +} + +void Sentient::RemoveItem( Item *object ) +{ + int i; + + inventory.RemoveObject( object->entnum ); + + if ( object->isSubclassOf( Weapon ) ) + DeactivateWeapon( (Weapon *)object ); + + for ( i=0; iisSubclassOf( Armor ) ) + { + return item; + } + } + + return NULL; +} + +//-------------------------------------------------------------- +// +// Name: FindItemByExternalName +// Class: Sentient +// +// Description: Finds the item by its name +// +// Parameters: const char *itemname -- Name to find +// Item *current (default, 0) -- Item to start searching from +// +// Returns: Item * or NULL +// +//-------------------------------------------------------------- +Item *Sentient::FindItemByExternalName( const char *itemname, Item *current ) +{ + int num, i; + Item *item; + bool seeking; + + if ( current ) + seeking = true; + else + seeking = false; + + num = inventory.NumObjects(); + for( i = 1; i <= num; i++ ) + { + item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + assert( item ); + if ( item == current ) + { + seeking = false; + continue; // Start compares with the next item + } + + if ( seeking ) + continue; + + if ( !Q_stricmp( item->getName(), itemname ) ) + return item; + } + + return NULL; +} + + +//-------------------------------------------------------------- +// +// Name: FindItemByModelname +// Class: Sentient +// +// Description: Finds the item by its name +// +// Parameters: const char *mdl -- Model name to find +// Item *current (default, 0) -- Item to start searching from +// +// Returns: Item * or NULL +// +//-------------------------------------------------------------- +Item *Sentient::FindItemByModelname( const char *mdl, Item *current ) +{ + int i, num; + bool seeking; + Item *item; + str tmpmdl; + + if ( current ) + seeking = true; + else + seeking = false; + + if ( strnicmp( "models/", mdl, 7 ) ) + tmpmdl = "models/"; + + tmpmdl += mdl; + + num = inventory.NumObjects(); + for( i = 1; i <= num; i++ ) + { + item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + assert( item ); + if ( item == current ) + { + seeking = false; + continue; // Start compares with the next item + } + + if ( seeking ) + continue; + + if ( !Q_stricmp( item->model, tmpmdl ) ) + return item; + } + + return NULL; +} + + +//-------------------------------------------------------------- +// +// Name: FindItemByClassName +// Class: Sentient +// +// Description: Finds the item by its name +// +// Parameters: const char *classname -- Classname to find +// Item *current (default, 0) -- Item to start searching from +// +// Returns: Item * or NULL +// +//-------------------------------------------------------------- +Item *Sentient::FindItemByClassName( const char *classname, Item *current ) +{ + int num, i; + Item *item; + bool seeking; + + if ( current ) + seeking = true; + else + seeking = false; + + num = inventory.NumObjects(); + for( i = 1; i <= num; i++ ) + { + item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + assert( item ); + + if ( item == current ) + { + seeking = false; + continue; // Start compares with the next item + } + + if ( seeking ) + continue; + + if ( !Q_stricmp( item->edict->entname, classname ) ) + return item; + } + + return NULL; +} + + +//-------------------------------------------------------------- +// +// Name: FindItem +// Class: Sentient +// +// Description: Finds the item by its name +// +// Parameters: const char *classname -- Classname to find +// Item *current (default, 0) -- Item to start searching from +// +// Returns: Item * or NULL +// +//-------------------------------------------------------------- +Item *Sentient::FindItem( const char *itemname, Item *current ) +{ + Item *item = NULL; + + if ( !itemname ) + return NULL; + + item = FindItemByExternalName( itemname, current ); + if ( !item ) + { + item = FindItemByModelname( itemname, current ); + if ( !item ) + { + item = FindItemByClassName( itemname, current ); + } + } + + return item; +} + +void Sentient::AttachAllActiveWeapons( void ) +{ + int i; + + for ( i=0; iAttachToOwner( (weaponhand_t )i ); + } +} + +void Sentient::DetachAllActiveWeapons( void ) +{ + int i; + + for ( i=0; iDetachFromOwner(); + } +} + +void Sentient::FreeInventory( Event *ev ) +{ + FreeInventory(); +} + +void Sentient::FreeInventory( void ) +{ + int num; + int i; + Item *item; + Ammo *ammo; + + // Detach all Weapons + DetachAllActiveWeapons(); + + // Delete all inventory items ( this includes weapons ) + + num = inventory.NumObjects(); + for( i = num; i > 0; i-- ) + { + item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + delete item; + } + inventory.ClearObjectList(); + + // Remove all ammo + num = ammo_inventory.NumObjects(); + for( i = num; i > 0; i-- ) + { + ammo = ( Ammo * )ammo_inventory.ObjectAt( i ); + delete ammo; + } + + ammo_inventory.ClearObjectList(); + + for( i = 0 ; i < MAX_ACTIVE_WEAPONS ; i++ ) + { + activeWeaponList[i] = NULL; + } + +} + + +qboolean Sentient::HasItem( const char *itemname ) +{ + return ( FindItem( itemname ) != NULL ); +} + +int Sentient::NumWeapons( void ) +{ + int num; + int i; + Item *item; + int numweaps; + + numweaps = 0; + + num = inventory.NumObjects(); + for( i = 1; i <= num; i++ ) + { + item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + if ( checkInheritance( &Weapon::ClassInfo, item->getClassname() ) ) + { + numweaps++; + } + } + + return numweaps; +} + +bool Sentient::ChangeWeapon( Weapon *weapon, weaponhand_t hand ) +{ + if ( ( hand > MAX_ACTIVE_WEAPONS ) ) + { + warning( "Sentient::ChangeWeapon", "Weapon hand number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", hand, MAX_ACTIVE_WEAPONS ); + return false; + } + + // Check if weapon is already active in the slot + if ( weapon == activeWeaponList[hand] ) + return false; + + ActivateWeapon( weapon, hand ); + return true; +} + +void Sentient::DeactivateWeapon( weaponhand_t hand ) +{ + int i; + + if ( !activeWeaponList[hand] ) + { + warning( "Sentient::DeactivateWeapon", "Tried to deactivate a non-active weapon in hand %d\n", hand ); + return; + } + + activeWeaponList[hand]->AttachToHolster( hand ); + activeWeaponList[hand]->SetPutAway( false ); + activeWeaponList[hand]->animate->RandomAnimate( "putaway" ); + + // Check the player's inventory and detach any weapons that are already attached to that spot + for ( i=1; i<=inventory.NumObjects(); i++ ) + { + Item *item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + + if ( item->isSubclassOf( Weapon ) ) + { + Weapon *weap = ( Weapon * )item; + + if ( + ( weap != activeWeaponList[hand] ) && + ( !str::cmp( weap->GetCurrentAttachToTag(), activeWeaponList[hand]->GetCurrentAttachToTag() ) ) + ) + { + weap->DetachFromOwner(); + } + } + } + activeWeaponList[hand] = NULL; +} + +void Sentient::DeactivateWeapon( Weapon *weapon ) +{ + int i; + + for ( i=0; iDetachFromOwner(); + activeWeaponList[i]->SetPutAway( false ); + activeWeaponList[i] = NULL; + } + } +} + +void Sentient::ActivateWeapon( Weapon *weapon, weaponhand_t hand ) +{ + int i; + + if ( weapon->isSubclassOf(WeaponDualWield) ) + { + WeaponDualWield *dw = (WeaponDualWield*)weapon; + activeWeaponList[WEAPON_LEFT] = dw->getLeftWeapon(); + activeWeaponList[WEAPON_RIGHT] = dw->getRightWeapon(); + } + else + activeWeaponList[hand] = weapon; + + str holsterTag; + + switch( hand ) + { + case WEAPON_LEFT: + holsterTag = weapon->GetLeftHolsterTag(); + break; + case WEAPON_RIGHT: + holsterTag = weapon->GetRightHolsterTag(); + break; + case WEAPON_DUAL: + holsterTag = weapon->GetDualHolsterTag(); + break; + default: + holsterTag = ""; + break; + } + + // Check the player's inventory and detach any weapons that are currently attached to that tag. + for ( i=1; i<=inventory.NumObjects(); i++ ) + { + Item *item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + + if ( item->isSubclassOf( Weapon ) ) + { + Weapon *weap = ( Weapon * )item; + + if ( + ( !str::cmp( holsterTag, weap->GetCurrentAttachToTag() ) ) + ) + { + weap->DetachFromOwner(); + } + } + } + weapon->AttachToOwner( hand ); + + weapon->playAnim( "raise", true ); +} + +Weapon *Sentient::BestWeapon( Weapon *ignore ) +{ + Item *next; + int n; + int j; + int bestrank; + Weapon *bestweapon; + + n = inventory.NumObjects(); + + // Search forewards until we find a weapon + bestweapon = NULL; + bestrank = -999999; + for( j = 1; j <= n; j++ ) + { + next = ( Item * )G_GetEntity( inventory.ObjectAt( j ) ); + + assert( next ); + + if ( ( next != ignore ) && next->isSubclassOf( Weapon ) && ( ( ( Weapon * )next )->GetRank() > bestrank ) && + ( ( ( Weapon * )next )->HasAmmo( FIRE_MODE1 ) ) ) + { + bestweapon = ( Weapon * )next; + bestrank = bestweapon->GetRank(); + } + } + + return bestweapon; +} + +Weapon *Sentient::NextWeapon( Weapon *weapon ) +{ + Item *item; + int i; + int n; + int weaponorder; + Weapon *choice; + int choiceorder; + Weapon *bestchoice; + int bestorder; + Weapon *worstchoice; + int worstorder; + + if ( !inventory.ObjectInList( weapon->entnum ) ) + { + error( "NextWeapon", "Weapon not in list" ); + } + + weaponorder = weapon->GetOrder(); + bestchoice = weapon; + bestorder = 65535; + worstchoice = weapon; + worstorder = weaponorder; + + n = inventory.NumObjects(); + for( i = 1; i <= n; i++ ) + { + item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + + assert( item ); + + if ( item->isSubclassOf( Weapon ) ) + { + choice = ( Weapon * )item; + if ( !choice->HasAmmo( FIRE_MODE1 ) || !choice->AutoChange() ) + { + continue; + } + + choiceorder = choice->GetOrder(); + if ( ( choiceorder > weaponorder ) && ( choiceorder < bestorder ) ) + { + bestorder = choiceorder; + bestchoice = choice; + } + + if ( choiceorder < worstorder ) + { + worstorder = choiceorder; + worstchoice = choice; + } + } + } + + if ( bestchoice == weapon ) + { + return worstchoice; + } + + return bestchoice; +} + +Weapon *Sentient::PreviousWeapon( Weapon *weapon ) +{ + Item *item; + int i; + int n; + int weaponorder; + Weapon *choice; + int choiceorder; + Weapon *bestchoice; + int bestorder; + Weapon *worstchoice; + int worstorder; + + if ( !inventory.ObjectInList( weapon->entnum ) ) + { + error( "PreviousWeapon", "Weapon not in list" ); + } + + weaponorder = weapon->GetOrder(); + bestchoice = weapon; + bestorder = -65535; + worstchoice = weapon; + worstorder = weaponorder; + + n = inventory.NumObjects(); + for( i = 1; i <= n; i++ ) + { + item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + + assert( item ); + + if ( item->isSubclassOf( Weapon ) ) + { + choice = ( Weapon * )item; + if ( !choice->HasAmmo( FIRE_MODE1 ) || !choice->AutoChange() ) + { + continue; + } + + choiceorder = choice->GetOrder(); + if ( ( choiceorder < weaponorder ) && ( choiceorder > bestorder ) ) + { + bestorder = choiceorder; + bestchoice = choice; + } + + if ( choiceorder > worstorder ) + { + worstorder = choiceorder; + worstchoice = choice; + } + } + } + + if ( bestchoice == weapon ) + { + return worstchoice; + } + + return bestchoice; +} + +Weapon *Sentient::GetActiveWeapon( weaponhand_t hand ) +{ + if ( ( hand > MAX_ACTIVE_WEAPONS ) || ( hand < 0 ) ) + { + warning( "Sentient::GetActiveWeapon", "Weapon hand number \"%d\" is out of bounds of 0 to MAX_ACTIVE_WEAPONS:%d\n", hand, MAX_ACTIVE_WEAPONS ); + return NULL; + } + else if ( hand == WEAPON_ANY ) + { + if ( activeWeaponList[ WEAPON_LEFT ] ) + return activeWeaponList[ WEAPON_LEFT ]; + else if ( activeWeaponList[ WEAPON_RIGHT ] ) + return activeWeaponList[ WEAPON_RIGHT ]; + else if ( activeWeaponList[ WEAPON_DUAL ] ) + return activeWeaponList[ WEAPON_DUAL ]; + else + return NULL; + } + else + { + return activeWeaponList[hand]; + } +} + +qboolean Sentient::IsActiveWeapon( const Weapon *weapon ) +{ + int i; + + for( i=0; iGetString( 1 ); + + ptr = name.c_str(); + + // skip over the $ + ptr++; + + found = false; + + tlist = world->GetTargetList( str( ptr ) ); + for ( i = 1; i <= tlist->list.NumObjects(); i++ ) + { + Entity * ent; + + ent = tlist->list.ObjectAt( i ); + assert( ent ); + + if ( ent->isSubclassOf( Item ) ) + { + Item *item; + + item = ( Item * )ent; + item->SetOwner( this ); + item->ProcessPendingEvents(); + AddItem( item ); + found = true; + } + } + + if ( !found ) + { + ev->Error( "Could not give item with targetname %s to this sentient.\n", name.c_str() ); + } +} + +Item *Sentient::giveItem( const str &itemname, int amount, bool pickedUp, float skillLevel ) +{ + ClassDef *cls; + Item *item; + + item = FindItem( itemname ); + if ( item ) + { + item->Add( amount ); + item->SetSkillLevel( skillLevel ); + return item; + } + else + { + qboolean set_the_model = false; + + // we don't have it, so lets try to resolve the item name + // first lets see if it is a registered class name + cls = getClass( itemname ); + if ( !cls ) + { + SpawnArgs args; + + // if that didn't work lets try to resolve it as a model + args.setArg( "model", itemname ); + + cls = args.getClassDef(); + if ( !cls ) + { + gi.WDPrintf( "No item called '%s'\n", itemname.c_str() ); + return NULL; + } + set_the_model = true; + } + assert( cls ); + item = ( Item * )cls->newInstance(); + + if ( !item ) + { + gi.WDPrintf( "Could not spawn an item called '%s'\n", itemname.c_str() ); + return NULL; + } + + if ( !item->isSubclassOf( Item ) ) + { + gi.WDPrintf( "Could not spawn an item called '%s'\n", itemname.c_str() ); + delete item; + return NULL; + } + + if ( set_the_model ) + { + // Set the model + item->setModel( itemname ); + } + + item->SetOwner( this ); + item->ProcessPendingEvents(); + if ( amount ) + item->setAmount( amount ); + item->hideModel(); + + item->SetSkillLevel( skillLevel ); + AddItem( item ); + + if ( item->isSubclassOf( Weapon ) ) + { + // Post an event to give the ammo to the sentient + Event *ev1; + + // Remove the fullbright from the weapon + + item->edict->s.renderfx &= ~RF_FULLBRIGHT; + + ev1 = new Event( EV_Weapon_GiveStartingAmmo ); + ev1->AddEntity( this ); + item->ProcessEvent( ev1 ); + + if ( this->isSubclassOf( Player ) ) + { + Player *player = (Player *)this; + Weapon *weapon = (Weapon *)item; + Weapon *currentWeapon; + + if ( pickedUp && player->getAutoSwitchWeapons() ) + { + currentWeapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( ( !currentWeapon ) || ( weapon->getWeaponPriority() < currentWeapon->getWeaponPriority() ) ) + { + ev1 = new Event( EV_Player_UseItem ); + ev1->AddString( item->getName() ); + PostEvent( ev1, FRAMETIME ); + } + } + } + } + + return item; + } +} + +void Sentient::takeItem( const char *name ) +{ + Item * item; + + item = FindItem( name ); + if ( item ) + { + gi.DPrintf( "Taking item %s away from player\n", item->getName().c_str() ); + + item->PostEvent( EV_Remove, 0.0f ); + return; + } + + Ammo *ammo; + ammo = FindAmmoByName( name ); + if ( ammo ) + { + gi.DPrintf( "Taking ammo %s away from player\n", name ); + + ammo->setAmount( 0 ); + } +} + +bool Sentient::useWeapon( const char *weaponname, weaponhand_t hand ) +{ + Weapon *weapon; + + assert( weaponname ); + + if ( !weaponname ) + { + warning( "Sentient::useWeapon", "weaponname is NULL\n" ); + return false; + } + + if ( deadflag || ( multiplayerManager.inMultiplayer() && this->isSubclassOf( Player ) && multiplayerManager.isPlayerSpectator( (Player *)this ) ) ) + return false; + + // Find the item in the sentient's inventory + weapon = ( Weapon * )FindItem( weaponname ); + + // If it exists, then make the change to the slot number specified + if ( weapon ) + { + return ChangeWeapon( weapon, hand ); + } + return false; +} + +void Sentient::EventTake( Event *ev ) +{ + takeItem( ev->GetString( 1 ) ); +} + +void Sentient::EventGiveAmmo( Event *ev ) +{ + int amount,maxamount=-1; + const char *type; + + type = ev->GetString( 1 ); + amount = ev->GetInteger( 2 ); + + if ( ev->NumArgs() == 3 ) + maxamount = ev->GetInteger( 3 ); + + GiveAmmo( type, amount, false, maxamount ); +} + +void Sentient::giveAmmoOverTime( Event *ev ) +{ + Event *event; + float ammoToAdd; + float ammoToAddThisFrame; + float timeLeft; + int numFrames; + const char *ammoType; + + ammoType = ev->GetString( 1 ); + ammoToAdd = ev->GetInteger( 2 ); + timeLeft = ev->GetFloat( 3 ); + + // Figure out how much ammo to add this frame + + if ( timeLeft < level.frametime ) + numFrames = 1; + else + numFrames = timeLeft / level.frametime; + + ammoToAddThisFrame = ammoToAdd / numFrames; + + // Actually add the ammo to the entity + + GiveAmmo( ammoType, ammoToAddThisFrame, false, -1 ); + + // Post the event for the next frame + + ammoToAdd -= ammoToAddThisFrame; + timeLeft -= level.frametime; + + //CancelEventsOfType( EV_Sentient_GiveAmmoOverTime ); + + if ( timeLeft > 0.0f ) + { + event = new Event( EV_Sentient_GiveAmmoOverTime ); + event->AddString( ammoType ); + event->AddInteger( ammoToAdd ); + event->AddFloat( timeLeft ); + PostEvent( event, level.frametime ); + } +} + +void Sentient::EventGiveItem( Event *ev ) +{ + const char *type; + float amount; + + type = ev->GetString( 1 ); + if ( ev->NumArgs() > 1 ) + amount = ev->GetInteger( 2 ); + else + amount = 1; + + giveItem( type, amount ); +} + +void Sentient::EventGiveArmor ( Event *ev ) +{ + const char *type; + Item* currentArmor; + float amount; + bool pickedup; + + currentArmor = NULL; + currentArmor = FindBaseArmor(); + + //We can only have 1 base armor + //if ( currentArmor ) + // RemoveItem( currentArmor ); + + // Do the giveItem to put the armor in inventory + // and to get it instantiated + type = ev->GetString( 1 ); + + if ( stricmp(type , "none" ) == 0 ) + { + currentArmor = NULL; + currentBaseArmor = NULL; + return; + } + + if ( ev->NumArgs() > 1 ) + amount = ev->GetFloat( 2 ); + else + amount = 0; + + if ( ev->NumArgs() > 2 ) + pickedup = ev->GetBoolean( 3 ); + else + pickedup = false; + + giveItem( type , amount ); + + currentArmor = NULL; + currentArmor = FindBaseArmor(); + + if ( pickedup && this->isSubclassOf( Player ) && currentArmor ) + { + int iconIndex; + + iconIndex = gi.imageindex( "sysimg/icons/items/armor" ); + + ((Player *)this)->setItemText( iconIndex, va( "$$PickedUp$$ %d $$Item-Armor$$", (int)amount ) ); + //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$PickedUp$$ %d armor", (int)amount ); + } + + // Cast is safe because FindBaseArmor will only return + // items of type armor + if ( currentArmor ) + currentBaseArmor = (Armor*)currentArmor; +} + +qboolean Sentient::DoGib( int meansofdeath ) +{ + if ( !com_blood->integer ) + { + return false; + } + + if ( + ( meansofdeath == MOD_TELEFRAG ) || + ( meansofdeath == MOD_LAVA ) + ) + { + return true; + } + + if ( health > -75.0f ) + { + return false; + } + + // Impact and Crush < -75 health + if ( ( meansofdeath == MOD_IMPACT ) || ( meansofdeath == MOD_CRUSH ) ) + { + return true; + } + + return false; +} + +//#define WATER_CONVERSION_FACTOR 1.0f + +void Sentient::ArmorDamage( ::Damage &damage ) +{ + CheckDamageThreshold( damage.damage ); + + if ( damageModSystem ) + damageModSystem->resolveDamage(damage); + + ArmorDamage( + damage.damage, + damage.inflictor, + damage.attacker, + damage.position, + damage.direction, + damage.normal, + damage.knockback, + damage.dflags, + damage.meansofdeath, + damage.surfaceNumber, + damage.boneNumber, + damage.weapon, + damage.showPain + ); +} + +void Sentient::ArmorDamage( Event *ev ) +{ + float damage; + Entity *inflictor; + Entity *attacker; + Entity *weapon = 0; + Vector position; + Vector direction; + Vector normal; + int knockback; + int dflags; + int meansofdeath; + int surfaceNumber = -1; + int boneNumber = -1; + + damage = ev->GetFloat ( 1 ); + inflictor = ev->GetEntity ( 2 ); + attacker = ev->GetEntity ( 3 ); + position = ev->GetVector ( 4 ); + direction = ev->GetVector ( 5 ); + normal = ev->GetVector ( 6 ); + knockback = ev->GetInteger( 7 ); + dflags = ev->GetInteger( 8 ); + meansofdeath = ev->GetInteger( 9 ); + + if ( ev->NumArgs() > 9 ) + surfaceNumber = ev->GetInteger( 10 ); + + if ( ev->NumArgs() > 10 ) + boneNumber = ev->GetInteger( 11 ); + + if ( ev->NumArgs() > 11 ) + weapon = ev->GetEntity( 12 ); + + ArmorDamage( damage, inflictor, attacker, position, direction, normal, knockback, dflags, meansofdeath, surfaceNumber, + boneNumber, weapon , false ); +} + +void Sentient::ArmorDamage( float damage, Entity *inflictor, Entity *attacker, const Vector &position, const Vector &direction, + const Vector &normal, int knockback, int dflags, int meansofdeath, int surfaceNumber, int boneNumber, Entity *weapon , bool showPain ) +{ + Vector momentum; + Event *event; + Event *injuredEvent; + float damage_red; + float damage_green; + float damage_time; + float resistance_modifier; + qboolean blocked = false; + qboolean set_means_of_death; + Vector normalizedDirection; + + + if ( ( takedamage == DAMAGE_NO ) || ( movetype == MOVETYPE_NOCLIP ) ) + { + return; + } + + // See if we should set means of death + + set_means_of_death = true; + + if ( this->isSubclassOf( Actor ) ) + { + Actor *act = (Actor *)this; + + if ( ( act->state_flags & STATE_FLAG_SMALL_PAIN ) && ( meansofdeath == MOD_ON_FIRE || Immune( meansofdeath ) ) ) + set_means_of_death = false; + } + + // See if sentient is immune to this type of damage + + if ( Immune ( meansofdeath ) ) + { + if ( set_means_of_death ) + means_of_death = meansofdeath; + + // Send pain event + event = new Event( EV_Pain ); + event->AddFloat( 0.0f ); + event->AddEntity( attacker ); + event->AddInteger( meansofdeath ); + event->AddVector( position ); + event->AddVector( direction ); + ProcessEvent( event ); + return; + } + + // Apply any resistances if any + resistance_modifier = GetResistanceModifier( meansofdeath ); + + if( resistance_modifier ) + { + resistance_modifier /= 100.0f; + float damagemod = resistance_modifier * damage; + damage -= damagemod; + } + + // See if the damage is melee and high enough on actor + + if ( this->isSubclassOf( Actor ) ) + { + Actor *act = ( Actor * )this; + + // Check the attack if it is melee + + if ( ( meansofdeath == MOD_SWORD ) || ( meansofdeath == MOD_CHAINSWORD ) || ( meansofdeath == MOD_AXE ) || + ( meansofdeath == MOD_FIRESWORD ) || ( meansofdeath == MOD_ELECTRICSWORD ) || ( meansofdeath == MOD_LIGHTSWORD ) ) + { + // Make sure attack is high enough + + if ( position.z - origin.z < act->minimum_melee_height ) + { + Entity::SpawnEffect("fx/fx-sparkblock.tik", position, vec_zero, 2.0f); + return; + } + + // Make sure attack is within the damage angles + + if ( act->damage_angles ) + { + Vector attack_angle; + float yaw_diff; + + attack_angle = direction.toAngles(); + + yaw_diff = angles[YAW] - attack_angle[YAW] + 180.0f; + + yaw_diff = AngleNormalize180( yaw_diff ); + + if ( ( yaw_diff < -act->damage_angles ) || ( yaw_diff > act->damage_angles ) ) + { + Entity::SpawnEffect("fx_sparkblock.tik", position, vec_zero, 2.0f); + return; + } + } + } + } + + if ( deadflag ) + { + // Spawn a blood spurt if this model has one + if ( ShouldBleed( meansofdeath, true ) ) + { + AddBloodSpurt( position, direction, boneNumber ); + + if ( ShouldGib( meansofdeath, damage ) ) + ProcessEvent( EV_Sentient_SpawnBloodyGibs ); + } + + if ( set_means_of_death ) + means_of_death = meansofdeath; + + if ( ( meansofdeath == MOD_FIRE ) || ( meansofdeath == MOD_FIRESWORD ) || ( meansofdeath == MOD_FIRE_BLOCKABLE ) ) + { + if ( GetResistanceModifier(meansofdeath) < 100 ) + TryLightOnFire( meansofdeath, attacker ); + } + + + if ( damage > 0.0f ) + { + // Send pain event + event = new Event( EV_Pain ); + event->AddFloat( damage ); + event->AddEntity( attacker ); + event->AddInteger( meansofdeath ); + event->AddVector( position ); + event->AddVector( direction ); + event->AddInteger( showPain ); + ProcessEvent( event ); + } + + return; + } + + if ( flags & FL_GODMODE ) + { + return; + } + + if ( currentBaseArmor ) + { + if ( this->isSubclassOf(Actor) ) + { + Actor *act; + qboolean resolveDamage; + + act = (Actor*)this; + + if ( act->GetActorFlag( ACTOR_FLAG_DISABLED ) || act->GetActorFlag( ACTOR_FLAG_CRIPPLED ) ) + resolveDamage = false; + else + resolveDamage = true; + + if ( resolveDamage ) + damage = currentBaseArmor->ResolveDamage( damage , meansofdeath , direction , position, attacker ); + } + else + damage = currentBaseArmor->ResolveDamage( damage , meansofdeath , direction , position , attacker ); + } + + if ( this->isSubclassOf( Player ) ) + { + Vector attack_angle; + Vector player_angle; + float yaw_diff; + Player *player; + + player = ( Player * )this; + + + player_angle = player->GetTorsoAngles(); + attack_angle = direction.toAngles(); + yaw_diff = player_angle[YAW] - attack_angle[YAW] + 180.0f; + yaw_diff = AngleNormalize180( yaw_diff ); + + // Get real knockback value + knockback = player->GetKnockback( knockback, blocked ); + + //If we're not in multiplayer AND we don't have a ground entity + //cut our knockback in half + if ( !multiplayerManager.inMultiplayer() && !player->groundentity) + knockback*=0.5; + + } + + // Do the kick + if (!(dflags & DAMAGE_NO_KNOCKBACK)) + { + if ((knockback) && + (movetype != MOVETYPE_NONE) && + (movetype != MOVETYPE_STATIONARY) && + (movetype != MOVETYPE_BOUNCE) && + (movetype != MOVETYPE_PUSH) && + (movetype != MOVETYPE_STOP)) + { + float m; + Event *immunity_event; + Event *resistance_event; + + if (mass < 50) + m = 50; + else + m = mass; + + normalizedDirection = direction; + normalizedDirection.normalize(); + if ( isClient() && ( attacker == this ) && multiplayerManager.inMultiplayer() ) + momentum = normalizedDirection * ( 1000.0f * ( float )knockback / m ); // the rocket jump hack... + else + momentum = normalizedDirection * ( 500.0f * ( float )knockback / m ); + + if ( dflags & DAMAGE_BULLET ) + { + // Clip the z velocity for bullet weapons + if ( momentum.z > 75.0f ) + momentum.z = 75.0f; + } + velocity += momentum; + + // Make this sentient vulnerable to falling damage now + + if ( Immune( MOD_FALLING ) ) + { + immunity_event = new Event( EV_Sentient_RemoveImmunity ); + immunity_event->AddString( "falling" ); + ProcessEvent( immunity_event ); + } + + if ( Resistant( MOD_FALLING ) ) + { + resistance_event = new Event( EV_Sentient_RemoveResistance ); + resistance_event->AddString( "falling" ); + ProcessEvent( resistance_event ); + } + + + } + } + + Vector attack_angle; + float yaw_diff; + attack_angle = attacker->angles; + yaw_diff = angles[YAW] - attack_angle[YAW] + 180.0f; + yaw_diff = AngleNormalize180( yaw_diff ); + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + float clashchance = 0.0f; + if ( gpm->isDefined("clash_chance") ) + clashchance = atof(gpm->getDefine("clash_chance")); + + // Consider attacker == inflictor, so we don't clash vs. projectiles. + if ( in_melee_attack && G_Random() < clashchance && attacker == inflictor) + { + if ( ( yaw_diff > -45.0f ) && ( yaw_diff < 45.0f ) ) + { + if ( attacker->isSubclassOf( Sentient ) ) + { + Sentient *sent = ( Sentient * )attacker; + sent->SetAttackBlocked( true ); + } + + SetAttackBlocked( true ); + WeaponEffectsAndSound( weapon, "Clash", position ); + means_of_death = meansofdeath; + return; + } + } + + // Blocking of the attack by an enemy. + if ( in_block && ( meansofdeath != MOD_ON_FIRE ) && ( meansofdeath != MOD_EXPLOSION ) ) + { + if ( ( yaw_diff > -45.0f ) && ( yaw_diff < 45.0f ) ) + { + if ( ( meansofdeath != MOD_FIRE ) && ( meansofdeath != MOD_LIFEDRAIN ) ) + { + if ( attacker->isSubclassOf( Sentient ) ) + { + Sentient *sent = ( Sentient * )attacker; + sent->SetAttackBlocked( true ); + } + if ( this->isSubclassOf( Actor ) ) + { + Actor *act = ( Actor * )this; + act->AddStateFlag( STATE_FLAG_BLOCKED_HIT ); + } + + WeaponEffectsAndSound( weapon, "Blocked", position ); + } + + means_of_death = meansofdeath; + return; + } + + in_block = false; + } + + if ( g_debugdamage->integer ) + G_DebugDamage( damage, this, attacker, inflictor ); + + // Handle statis damage + + if ( meansofdeath == MOD_STASIS ) + { + + if ( damage > 0.0f ) + { + //Stupid last minute hack to make sure we can't stasis people who will break + if ( this->isSubclassOf(Actor) ) + { + Actor *me; + me = (Actor*)this; + if ( me->GetActorFlag(ACTOR_FLAG_CANNOT_FREEZE ) ) + { + return; + } + } + + startStasis(); + PostEvent( EV_StopStasis, damage ); + } + + return; + } + + if ( meansofdeath != MOD_LIFEDRAIN ) + { + if ( damage < 0 ) + { + //Because a resistance of over 100 will Add to health, need to make + //sure that the health doesn't go over the max + if( health < max_health ) + { + health -= damage; + + if ( health > max_health ) + health = max_health; + } + } + else + { + health -= damage; + } + } + + if ( ( damage > 0.0f ) && ( surfaceNumber != -1 ) ) + { + if ( this->isSubclassOf( Actor ) ) + { + Actor* theActor = (Actor*)this; + if( theActor->GetActorFlag(ACTOR_FLAG_USE_DAMAGESKINS) ) + { + if ( !edict->s.surfaces[ surfaceNumber ] ) + edict->s.surfaces[ surfaceNumber ]++; + + } + } + } + + // Do all of the regional damage stuff + + SetRegionalDamage( surfaceNumber, boneNumber, direction ); + + if ( ( ( meansofdeath == MOD_FIRE ) || ( meansofdeath == MOD_FIRESWORD ) || ( meansofdeath == MOD_FIRE_BLOCKABLE ) ) && !blocked ) + { + if ( GetResistanceModifier(meansofdeath) < 100 ) + TryLightOnFire( meansofdeath, attacker ); + } + + + // Set means of death + + if ( set_means_of_death ) + means_of_death = meansofdeath; + + // Spawn a blood spurt if this model has one + if ( ( damage > 0.0f ) && ShouldBleed( meansofdeath, false ) && !blocked ) + { + AddBloodSpurt( position, direction, boneNumber ); + + if ( ( this->isSubclassOf( Actor ) || ( damage > 10.0f ) ) && ShouldGib( meansofdeath, damage ) ) + ProcessEvent( EV_Sentient_SpawnBloodyGibs ); + } + + if ( health < 0.1f ) + { + // See if we can kill this actor or not + + if ( this->isSubclassOf( Actor ) ) + { + Actor *act = ( Actor * )this; + + if ( act->IsImmortal() ) + health = 1; + } + } + + + if ( health < 0.1f ) + { + // Recalculate damage so that it is only damage needed to kill + + damage += health; + + // Make sure health is now 0 + + health = 0; + + if ( ( meansofdeath == MOD_VAPORIZE ) || ( meansofdeath == MOD_VAPORIZE_COMP ) || + ( meansofdeath == MOD_VAPORIZE_DISRUPTOR ) || ( meansofdeath == MOD_VAPORIZE_PHOTON ) || + ( meansofdeath == MOD_SNIPER ) ) + { + bool canDisintegrate = true; + + //YAY!!! ANOTHER SPECIAL CASE!! WOOPEE!! YEE-HAW!!! MY EXCITEMENT KNOWS NO BOUNDS!!! + Actor *stupidSpecialCaseActor; + if ( isSubclassOf( Actor ) ) + { + //YAY!!!! I get to cast myself!!! YAY!!!! + stupidSpecialCaseActor = ( Actor* )this; + if(stupidSpecialCaseActor->GetActorFlag(ACTOR_FLAG_CANNOT_DISINTEGRATE) ) + canDisintegrate = false; + } + + if ( canDisintegrate ) + { + event = new Event( EV_DisplayEffect ); + + event->AddString( "FadeOut" ); + + if ( ( meansofdeath == MOD_VAPORIZE_DISRUPTOR ) ) + event->AddString( "Disruptor" ); + else if ( ( meansofdeath == MOD_VAPORIZE_PHOTON ) ) + event->AddString( "Photon" ); + else if ( ( meansofdeath == MOD_SNIPER ) ) + event->AddString( "Sniper" ); + else + event->AddString( "Phaser" ); + + ProcessEvent( event ); + + edict->s.renderfx |= RF_CHILDREN_DONT_INHERIT_ALPHA; + + if ( !isSubclassOf( Player ) ) + PostEvent( EV_Remove, 5.0f ); + } + } + + event = new Event( EV_Killed ); + event->AddEntity( attacker ); + event->AddFloat( damage ); + event->AddEntity( inflictor ); + event->AddInteger( meansofdeath ); + event->AddInteger( false ); + event->AddEntity( weapon ); + event->AddVector( direction ); + ProcessEvent( event ); + } + + if ( + ( meansofdeath == MOD_GAS ) || + ( meansofdeath == MOD_GAS_BLOCKABLE ) || + ( meansofdeath == MOD_SLIME ) || + ( meansofdeath == MOD_POISON ) + ) + { + damage_green = damage / 50.0f; + if ( damage_green > 1.0f ) + damage_green = 1.0f; + if ( ( damage_green < 0.2f ) && ( damage_green > 0.0f ) ) + damage_green = 0.2f; + damage_red = 0.0f; + } + else + { + damage_red = damage / 50.0f; + if ( damage_red > 0.5f ) + damage_red = 0.5f; + if ( ( damage_red < 0.2f ) && ( damage_red > 0.0f ) ) + damage_red = 0.2f; + damage_green = 0.0f; + } + + damage_time = damage / 50.0f; + + if ( damage_time > 0.5f ) + damage_time = 0.5f; + + if ( sv_showdamagecolors->integer && !this->isSubclassOf( Player ) ) + { + SetOffsetColor( damage_red, damage_green, 0.0f, damage_time ); + } + + if ( health > 0.0f && damage > 0.0f ) + { + // Send pain event + event = new Event( EV_Pain ); + event->AddFloat( damage ); + event->AddEntity( attacker ); + event->AddInteger( meansofdeath ); + event->AddVector( position ); + event->AddVector( direction ); + event->AddInteger( showPain ); + ProcessEvent( event ); + } + + // Send the injured event to our group if we are at or below our critical health and + // we can send the event; + if ( _canSendInjuredEvent && ( health / max_health ) < GetCriticalHealthPercentage() ) + { + injuredEvent = new Event (EV_Sentient_GroupMemberInjured); + injuredEvent->AddInteger( 1 ); + groupcoordinator->SendEventToGroup(injuredEvent , GetGroupID()); + _canSendInjuredEvent = false; + } + + + return; +} + +void Sentient::SetRegionalDamage( int surface_number, int bone_number, Vector direction ) +{ + Vector attack_angle; + float yaw_diff; + + // Save the last surface that was hit + + last_surface_hit = surface_number; + + // Save the last bone that was hit + + last_bone_hit = bone_number; + + // Save the last general region that was hit + + last_region_hit = 0; + + attack_angle = direction.toAngles(); + + yaw_diff = angles[YAW] - attack_angle[YAW] + 180.0f; + + yaw_diff = AngleNormalize180( yaw_diff ); + + if ( ( yaw_diff < -90.0f ) || ( yaw_diff > 90.0f ) ) + last_region_hit |= REGIONAL_DAMAGE_BACK; + else + last_region_hit |= REGIONAL_DAMAGE_FRONT; +} + +qboolean Sentient::CanBlock( int meansofdeath, qboolean full_block ) +{ + + // Check to see what a full block can't even block + + switch ( meansofdeath ) + { + case MOD_DROWN : + case MOD_TELEFRAG : + case MOD_SLIME : + case MOD_LAVA : + case MOD_FALLING : + case MOD_RADIATION : + case MOD_IMPALE : + case MOD_ON_FIRE : + case MOD_ELECTRICWATER : + case MOD_EAT : + return false; + } + + // Full blocks block everything else + + if ( full_block ) + return true; + + // Check to see what a small block can't block + + switch ( meansofdeath ) + { + case MOD_FIRE : + case MOD_GAS : + case MOD_CRUSH_EVERY_FRAME : + return false; + } + + // Everything else is blocked + + return true; +} + +void Sentient::AddBloodSpurt( const Vector &position, const Vector &direction, int bone_number ) +{ + Entity *blood; + Vector dir; + Event *event; + str blood_splat_name; + float blood_splat_size; + float length; + trace_t trace; + float scale; + + + if ( !com_blood->integer ) + return; + + next_bleed_time = level.time + .5f; + + // Calculate a good scale for the blood + + if ( mass < 50 ) + scale = .5f; + else if ( mass > 300 ) + scale = 1.5f; + else if ( mass >= 200 ) + scale = mass / 200.0; + else + scale = .5f + ( (float)mass - 50.0f ) / 300.0f; + + // Add blood spurt + + blood = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + blood->setModel( blood_model ); + + if ( !isSubclassOf( Player ) ) + { + // Network optimization + + dir[0] = -direction[0]; + dir[1] = -direction[1]; + dir[2] = -direction[2]; + blood->angles = dir.toAngles(); + + blood->setAngles( blood->angles ); + } + + // Make the blood come from the centroid of the bone if possible + + if ( bone_number >= 0 ) + { + int parent_bone_number; + Vector bone_pos; + Vector parent_bone_pos; + Vector bone_centroid; + + parent_bone_number = gi.Bone_GetParentNum( edict->s.modelindex, bone_number ); + + if ( parent_bone_number >= 0 ) + { + GetTag( bone_number, &bone_pos ); + GetTag( parent_bone_number, &parent_bone_pos ); + + bone_centroid = (bone_pos + parent_bone_pos ) * 0.5f; + + blood->setOrigin( bone_centroid ); + } + } + else + { + // Default the blood spurt to the position where the trace stopped + + blood->setOrigin( position ); + } + + + //blood->origin.copyTo( blood->edict->s.origin2 ); + blood->setSolidType( SOLID_NOT ); + blood->setScale( scale ); + + event = new Event( EV_Remove ); + blood->PostEvent( event, 1.0f ); + + // Add blood splats near feet + + blood_splat_name = GetBloodSplatName(); + blood_splat_size = GetBloodSplatSize(); + + if ( blood_splat_name.length() && G_Random() < 0.5f ) + { + dir = origin - centroid; + dir.z -= 50.0f; + dir.x += G_CRandom( 20.0f ); + dir.y += G_CRandom( 20.0f ); + + length = dir.length(); + + dir.normalize(); + + dir = dir * ( length + 10.0f ); + + trace = G_Trace( centroid, vec_zero, vec_zero, centroid + dir, NULL, MASK_DEADSOLID, false, "AddBloodSpurt" ); + + if ( trace.fraction < 1.0f ) + { + Decal *decal = new Decal; + decal->setShader( blood_splat_name ); + decal->setOrigin( Vector( trace.endpos ) + ( Vector( trace.plane.normal ) * 0.2f ) ); + decal->setDirection( trace.plane.normal ); + decal->setOrientation( "random" ); + decal->setRadius( blood_splat_size + G_Random( blood_splat_size ) ); + } + } +} + +qboolean Sentient::ShouldBleed( int meansofdeath, qboolean dead ) +{ + // Make sure we have a blood model + + if ( !blood_model.length() ) + return false; + + // Don't let the player bleed in single player + + if ( !multiplayerManager.inMultiplayer() && isSubclassOf( Player ) ) + return false; + + // Make sure if we are dead we are allowed to bleed after death + + if ( dead && this->isSubclassOf( Actor ) ) + { + Actor *act = (Actor *)this; + + if ( !act->GetActorFlag( ACTOR_FLAG_BLEED_AFTER_DEATH ) ) + return false; + } + + // See if we can bleed now based on means of death + + switch ( meansofdeath ) + { + // Sometimes bleed (based on time) + + case MOD_BULLET : + case MOD_STING : + case MOD_STING2 : + case MOD_VORTEX : + case MOD_CHAINSWORD : + case MOD_PLASMABEAM : + case MOD_CRUSH_EVERY_FRAME : + case MOD_POISON : + case MOD_ELECTRICWATER : + case MOD_EAT : + case MOD_CIRCLEOFPROTECTION : + + if ( next_bleed_time > level.time ) + return false; + + break; + + // Sometimes bleed (based on chance) + + case MOD_PLASMASHOTGUN : + + if ( next_bleed_time < level.time ) + return true; + + if ( G_Random() > 0.1f ) + return false; + + break; + + // Never bleed + + case MOD_LIFEDRAIN : + case MOD_SLIME : + case MOD_LAVA : + case MOD_GAS : + case MOD_GAS_BLOCKABLE : + case MOD_FIRE : + case MOD_FIRE_BLOCKABLE : + case MOD_SLING : + case MOD_DROWN : + case MOD_FLASHBANG : + case MOD_ON_FIRE : + case MOD_FALLING : + case MOD_RADIATION : + case MOD_DEATH_QUAD : + return false; + } + + // Always bleed by default + + return true; +} + +// ShouldGib assumes that ShouldBleed has already been called + +qboolean Sentient::ShouldGib( int meansofdeath, float damage ) +{ + // See if we can gib based on means of death + + switch ( meansofdeath ) + { + // Always gib + + case MOD_CHAINSWORD : + case MOD_CRUSH_EVERY_FRAME : + case MOD_PLASMASHOTGUN : + case MOD_EAT : + case MOD_EXPLOSION : + return true; + + break; + + // Sometimes gib + + case MOD_BULLET : + case MOD_STING : + case MOD_STING2 : + + if ( G_Random( 100.0f ) < damage * 10.0f ) + return true; + + break; + + case MOD_PLASMABEAM : + + if ( G_Random( 100.0f ) < damage * 5.0f ) + return true; + + break; + + // Never gib + + case MOD_LIFEDRAIN : + case MOD_SLIME : + case MOD_LAVA : + case MOD_GAS : + case MOD_GAS_BLOCKABLE : + case MOD_POISON : + case MOD_FIRE : + case MOD_FIRE_BLOCKABLE : + case MOD_SLING : + case MOD_DROWN : + case MOD_FLASHBANG : + case MOD_ON_FIRE : + case MOD_FALLING : + case MOD_RADIATION : + case MOD_VORTEX : + case MOD_ELECTRICWATER : + return false; + } + + // Default is random based on how much damage done + + if ( G_Random( 100.0f ) < damage * 2.0f ) + return true; + + return false; +} + +str Sentient::GetBloodSpurtName( void ) +{ + str blood_spurt_name; + + if ( blood_model == "fx/fx_bspurt.tik" ) + blood_spurt_name = "fx/fx_bspurt2.tik"; + else if ( blood_model == "fx/fx_gspurt.tik" ) + blood_spurt_name = "fx/fx_gspurt2.tik"; + else if ( blood_model == "fx/fx_bspurt_blue.tik" ) + blood_spurt_name = "fx/fx_bspurt2_blue.tik"; + + return blood_spurt_name; +} + +str Sentient::GetBloodSplatName( void ) +{ + str blood_splat_name; + + if ( blood_model == "fx/fx_bspurt.tik" ) + blood_splat_name = "bloodsplat.spr"; + else if ( blood_model == "fx/fx_gspurt.tik" ) + blood_splat_name = "greensplat.spr"; + else if ( blood_model == "fx/fx_bspurt_blue.tik" ) + blood_splat_name = "bluesplat.spr"; + + return blood_splat_name; +} + +float Sentient::GetBloodSplatSize( void ) +{ + float m; + + m = (int)mass; + + if ( m < 50.0f ) + m = 50.0f; + else if ( m > 250.0f ) + m = 250.0f; + + return ( 10.0f + ( m - 50.0f ) / 200.0f * 6.0f ); +} + +str Sentient::GetGibName( void ) +{ + str gib_name; + + if ( blood_model == "fx/fx_bspurt.tik" ) + gib_name = "fx/fx_rgib"; + else if ( blood_model == "fx/fx_gspurt.tik" ) + gib_name = "fx/fx_ggib"; + else if ( blood_model == "fx/fx_bspurt_green.tik" ) + gib_name = "fx/fx_ggib"; + + return gib_name; +} + +int Sentient::NumInventoryItems( void ) +{ + return inventory.NumObjects(); +} + +Item *Sentient::NextItem( Item *item ) +{ + Item *next_item; + int i; + int n; + qboolean item_found = false; + + if ( !item ) + { + item_found = true; + } + else if ( !inventory.ObjectInList( item->entnum ) ) + { + error( "NextItem", "Item not in list" ); + } + + n = inventory.NumObjects(); + + for( i = 1; i <= n; i++ ) + { + next_item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + assert( next_item ); + + if ( ( next_item->isSubclassOf( InventoryItem ) || ( next_item->isSubclassOf( Weapon ) ) )&& item_found ) + return next_item; + + if ( next_item == item ) + item_found = true; + } + + return NULL; +} + +Item *Sentient::PrevItem( Item *item ) +{ + Item *prev_item; + int i; + int n; + qboolean item_found = false; + + if ( !item ) + { + item_found = true; + } + else if ( !inventory.ObjectInList( item->entnum ) ) + { + error( "NextItem", "Item not in list" ); + } + + n = inventory.NumObjects(); + + for( i = n; i >= 1; i-- ) + { + prev_item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + assert( prev_item ); + + if ( prev_item->isSubclassOf( InventoryItem ) && item_found) + return prev_item; + + if ( prev_item == item ) + item_found = true; + } + + return NULL; +} + +void Sentient::DropInventoryItems( void ) +{ + int num; + int i; + Item *item; + + // Drop any inventory items + num = inventory.NumObjects(); + for( i = num; i >= 1; i-- ) + { + item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + if ( item->isSubclassOf( InventoryItem ) ) + { + item->Drop(); + } + } +} + +void Sentient::setModel( const char *mdl ) +{ + // Rebind all active weapons + + DetachAllActiveWeapons(); + Entity::setModel( mdl ); + AttachAllActiveWeapons(); +} + +void Sentient::TurnOffShadow( Event *ev ) +{ + edict->s.renderfx &= ~RF_SHADOW; +} + +void Sentient::TurnOnShadow( Event *ev ) +{ + edict->s.renderfx |= RF_SHADOW; +} + +void Sentient::Archive( Archiver &arc ) +{ + int i; + int num; + Resistance *resistance; + + + Entity::Archive( arc ); + + arc.ArchiveFloat( &_criticalHealthPercentage ); + + // Archive Damage Threshold; + arc.ArchiveFloat( &_damageThreshold.duration ); + arc.ArchiveFloat( &_damageThreshold.startTime ); + arc.ArchiveFloat( &_damageThreshold.maxDamage ); + arc.ArchiveFloat( &_damageThreshold.currentDamage ); + arc.ArchiveFloat( &shotsFiredThisVolley ); + + arc.ArchiveFloat( &_hateModifier ); + + inventory.Archive( arc ); + + if ( arc.Saving() ) + { + num = ammo_inventory.NumObjects(); + } + else + { + ammo_inventory.ClearObjectList(); + } + + arc.ArchiveInteger( &num ); + + for( i = 1; i <= num; i++ ) + { + Ammo * ptr; + + if ( arc.Loading() ) + { + ptr = new Ammo; + ammo_inventory.AddObject( ptr ); + } + else + { + ptr = ammo_inventory.ObjectAt( i ); + } + + arc.ArchiveObject( ptr ); + } + + arc.ArchiveSafePointer( &newWeapon ); + arc.ArchiveSafePointer( ¤tBaseArmor ); + + //arc.ArchiveInteger( &firing_frame ); + //arc.ArchiveInteger( &firing_anim ); + + arc.ArchiveVector( &offset_color ); + arc.ArchiveVector( &offset_delta ); + arc.ArchiveFloat( &offset_time ); + + arc.ArchiveFloat( &knock_start_time ); + + arc.ArchiveString( &blood_model ); + + arc.ArchiveInteger( &last_surface_hit ); + arc.ArchiveInteger( &last_bone_hit ); + arc.ArchiveUnsigned( &last_region_hit ); + + immunities.Archive( arc ); + + if ( arc.Saving() ) + { + num = resistances.NumObjects(); + + arc.ArchiveInteger( &num ); + + for( i = 1 ; i <= num ; i++ ) + { + resistance = resistances.ObjectAt( i ); + + arc.ArchiveInteger( &resistance->MODIndex ); + arc.ArchiveInteger( &resistance->ResistanceAmount ); + } + } + else + { + arc.ArchiveInteger( &num ); + + for( i = 1 ; i <= num ; i++ ) + { + resistance = new Resistance; + resistances.AddObject( resistance ); + + arc.ArchiveInteger( &resistance->MODIndex ); + arc.ArchiveInteger( &resistance->ResistanceAmount ); + } + } + + for( i = 0; i < MAX_ACTIVE_WEAPONS; i++ ) + { + arc.ArchiveSafePointer( &activeWeaponList[ i ] ); + } + + arc.ArchiveUnsigned( &_viewMode ); + + arc.ArchiveBool( &_canSendInjuredEvent ); + arc.ArchiveBool( &_headWatchAllowed ); + arc.ArchiveBool( &_displayFireEffect ); + + arc.ArchiveVector( &gunoffset ); + arc.ArchiveVector( &eyeposition ); + arc.ArchiveInteger( &viewheight ); + arc.ArchiveInteger( &means_of_death ); + arc.ArchiveBoolean( &in_melee_attack ); + arc.ArchiveBoolean( &in_ranged_attack ); + arc.ArchiveBoolean( &in_block ); + arc.ArchiveBoolean( &in_stun ); + + arc.ArchiveBoolean( &on_fire ); + arc.ArchiveFloat( &on_fire_stop_time ); + arc.ArchiveFloat( &next_catch_on_fire_time ); + arc.ArchiveInteger( &on_fire_tagnums[ 0 ] ); + arc.ArchiveInteger( &on_fire_tagnums[ 1 ] ); + arc.ArchiveInteger( &on_fire_tagnums[ 2 ] ); + arc.ArchiveSafePointer( &fire_owner ); + + arc.ArchiveBoolean( &attack_blocked ); + arc.ArchiveFloat( &attack_blocked_time ); + + arc.ArchiveFloat( &max_mouth_angle ); + arc.ArchiveInteger( &max_gibs ); + + arc.ArchiveFloat( &next_bleed_time ); +} + +void Sentient::ArchivePersistantData( Archiver &arc, qboolean sublevelTransition ) +{ + int i; + int num; + + + if ( arc.Loading() ) + FreeInventory(); + + // archive the inventory + if ( arc.Saving() ) + { + // count up the total number + num = inventory.NumObjects(); + } + else + { + inventory.ClearObjectList(); + } + // archive the number + arc.ArchiveInteger( &num ); + // archive each item + for( i = 1; i <= num; i++ ) + { + str name; + int amount; + Item * item = NULL; + + if ( arc.Saving() ) + { + Entity * ent; + + ent = G_GetEntity( inventory.ObjectAt( i ) ); + + if ( ent && ent->isSubclassOf( Armor ) ) + { + item = ( Item * )ent; + name = item->getName(); + amount = item->getAmount(); + } + else if ( ent && ent->isSubclassOf( Item ) ) + { + item = ( Item * )ent; + name = item->model; + amount = item->getAmount(); + } + else + { + error( "ArchivePersistantData", "Non Item in inventory\n" ); + } + } + arc.ArchiveString( &name ); + arc.ArchiveInteger( &amount ); + if ( arc.Loading() ) + { + item = giveItem( name, amount ); + item->CancelEventsOfType( EV_Weapon_GiveStartingAmmo ); + item->ProcessPendingEvents(); + } + + if ( item ) + { + item->ArchivePersistantData( arc ); + } + } + + // archive the ammo inventory + if ( arc.Saving() ) + { + // count up the total number + num = ammo_inventory.NumObjects(); + } + else + { + ammo_inventory.ClearObjectList(); + } + // archive the number + arc.ArchiveInteger( &num ); + // archive each item + for( i = 1; i <= num; i++ ) + { + str name; + int amount; + int maxamount; + Ammo * ptr; + + if ( arc.Saving() ) + { + ptr = ammo_inventory.ObjectAt( i ); + name = ptr->getName(); + amount = ptr->getAmount(); + maxamount = ptr->getMaxAmount(); + } + arc.ArchiveString( &name ); + arc.ArchiveInteger( &amount ); + arc.ArchiveInteger( &maxamount ); + if ( arc.Loading() ) + { + GiveAmmo( name, amount, false, maxamount ); + } + } + + for( i = 0; i < MAX_ACTIVE_WEAPONS; i++ ) + { + str name; + if ( arc.Saving() ) + { + if ( activeWeaponList[ i ] ) + { + name = activeWeaponList[ i ]->getName(); + } + else + { + name = "none"; + } + } + arc.ArchiveString( &name ); + if ( arc.Loading() ) + { + if ( name != "none" ) + { + Weapon * weapon; + weapon = ( Weapon * )FindItem( name ); + if ( weapon ) + { + ChangeWeapon( weapon, ( weaponhand_t )i ); + } + } + } + } + + arc.ArchiveFloat( &health ); + arc.ArchiveFloat( &max_health ); + + if ( arc.Loading() ) + { + currentBaseArmor = (Armor *)FindBaseArmor(); + } +} + +void Sentient::WeaponKnockedFromHands( void ) +{ + str realname; + + realname = GetRandomAlias( "snd_lostweapon" ); + if ( realname.length() > 1 ) + { + Sound( realname.c_str() , CHAN_VOICE ); + } + else + { + Sound( "snd_pain", CHAN_VOICE ); + } +} + +void Sentient::AddImmunity( Event *ev ) +{ + str immune_string; + int new_immunity; + int number_of_immunities; + int i; + + // No Longer using Immunities. Now using Resistances + // A resistance of 100 is immune. However due to a large + // amount of legacy code, the immunity functions will still + // be inplace, however, they will instead be used as wrappers + // for the resistance functions + /* + number_of_immunities = ev->NumArgs(); + + for ( i = 1 ; i <= number_of_immunities ; i++ ) + { + immune_string = ev->GetString( i ); + + new_immunity = MOD_NameToNum( immune_string ); + + if ( new_immunity != -1 ) + immunities.AddUniqueObject( new_immunity ); + } + */ + + Resistance *new_resistance; + number_of_immunities = ev->NumArgs(); + + for ( i = 1 ; i <= number_of_immunities ; i ++ ) + { + immune_string = ev->GetString( i ); + + new_immunity = MOD_NameToNum( immune_string ); + + if ( new_immunity != -1 ) + { + new_resistance = new Resistance; + new_resistance->MODIndex = new_immunity; + new_resistance->ResistanceAmount = 100; + resistances.AddUniqueObject( new_resistance ); + } + + } +} + +void Sentient::AddResistance( Event *ev ) +{ + Resistance *new_resistance = new Resistance; + + if(new_resistance) + { + new_resistance->MODIndex = MOD_NameToNum(ev->GetString(1)); + new_resistance->ResistanceAmount = ev->GetInteger(2); + resistances.AddUniqueObject( new_resistance ); + } +} + +void Sentient::RemoveImmunity( Event *ev ) +{ + str immune_string; + int old_immunity; + int number_of_immunities; + int i; + + Resistance *old_resistance; + // No Longer using Immunities. Now using Resistances + // A resistance of 100 is immune. However due to a large + // amount of legacy code, the immunity functions will still + // be inplace, however, they will instead be used as wrappers + // for the resistance functions + + /* + number_of_immunities = ev->NumArgs(); + + for ( i = 1 ; i <= number_of_immunities ; i++ ) + { + immune_string = ev->GetString( i ); + + old_immunity = MOD_NameToNum( immune_string ); + + if ( old_immunity != -1 && immunities.ObjectInList( old_immunity ) ) + immunities.RemoveObject( old_immunity ); + }*/ + + number_of_immunities = ev->NumArgs(); + + for ( i = 1 ; i <= number_of_immunities ; i++ ) + { + immune_string = ev->GetString( i ); + + old_immunity = MOD_NameToNum( immune_string ); + + for ( int x = resistances.NumObjects(); x > 0 ; x-- ) + { + old_resistance = resistances.ObjectAt(i); + if( old_resistance->MODIndex == old_immunity ) + { + resistances.RemoveObjectAt( x ); + delete old_resistance; + old_resistance = 0; + } + } + } +} + +void Sentient::RemoveResistance( Event *ev ) +{ + Resistance *old_resistance; + int resistance_idx; + + resistance_idx = MOD_NameToNum( ev->GetString(1) ); + + for ( int i = resistances.NumObjects(); i > 0 ; i-- ) + { + old_resistance = resistances.ObjectAt(i); + if( old_resistance->MODIndex == resistance_idx ) + { + resistances.RemoveObjectAt( i ); + delete old_resistance; + old_resistance = 0; + } + } + +} + +qboolean Sentient::Immune( int meansofdeath ) +{ + int number_of_immunities, i; + + number_of_immunities = immunities.NumObjects(); + + for( i = 1 ; i <= number_of_immunities ; i++ ) + { + if ( meansofdeath == immunities.ObjectAt( i ) ) + return true; + } + + return false; +} + +qboolean Sentient::Resistant( int meansofdeath ) +{ + int number_of_resistances = resistances.NumObjects(); + Resistance *checkResistance; + + for( int i = 1; i <= number_of_resistances; i++ ) + { + checkResistance = resistances.ObjectAt(i); + + if( meansofdeath == checkResistance->MODIndex ) + return true; + } + + return false; + +} + +int Sentient::GetResistanceModifier( int meansofdeath ) +{ + int number_of_resistances = resistances.NumObjects(); + Resistance *checkResistance; + + for( int i = 1; i <= number_of_resistances; i++ ) + { + checkResistance = resistances.ObjectAt(i); + + if( meansofdeath == checkResistance->MODIndex ) + return checkResistance->ResistanceAmount; + } + + return 0; + +} + +Ammo *Sentient::FindAmmoByName( const str &name ) +{ + int count, i; + + count = ammo_inventory.NumObjects(); + + for ( i=1; i<=count; i++ ) + { + Ammo *ammo = ammo_inventory.ObjectAt( i ); + if ( name == ammo->getName() ) + { + return ammo; + } + } + return NULL; +} + +int Sentient::AmmoIndex( const str &type ) +{ + Ammo *ammo; + + ammo = FindAmmoByName( type ); + + if ( ammo ) + return ammo->getIndex(); + else + return 0; +} + +int Sentient::AmmoCount( const str &type ) +{ + Ammo *ammo; + + if ( !type.length() ) + return 0; + + ammo = FindAmmoByName( type ); + + if ( ammo ) + return ammo->getAmount(); + else + return 0; +} + +int Sentient::MaxAmmoCount( const str &type ) +{ + Ammo *ammo; + + ammo = FindAmmoByName( type ); + + if ( ammo ) + return ammo->getMaxAmount(); + else + return 0; +} + +int Sentient::GiveAmmo( const str &type, int amount, bool pickedUp, int maxamount ) +{ + Ammo *ammo; + int startAmount = 0; + int totalAmount = 0; + int amountLeft; + + ammo = FindAmmoByName( type ); + + if ( ammo ) + { + // Add amount to current amount + startAmount = ammo->getAmount(); + + if ( maxamount >= 0 ) + ammo->setMaxAmount( maxamount ); + + ammo->setAmount( ammo->getAmount() + amount ); + } + else + { + // Create a new inventory entry with this name + ammo = new Ammo; + + if ( maxamount >= 0 ) + ammo->setMaxAmount( maxamount ); + + ammo->setAmount( amount ); + + ammo->setName( type ); + ammo_inventory.AddObject( ammo ); + } + + totalAmount = ammo->getAmount() - startAmount; + + amountLeft = amount - totalAmount; + + if ( amountLeft > 0 ) + { + int entnum; + Item *item; + Weapon *weapon; + int roomLeftInClip; + int amountToAdd; + int i; + + // Try to give directly to weapons now + + for ( i = 1 ; i <= inventory.NumObjects() ; i++ ) + { + entnum = inventory.ObjectAt( i ); + + item = ( Item * )G_GetEntity( entnum ); + + if ( item->isSubclassOf( Weapon ) ) + { + weapon = (Weapon *)item; + + if ( weapon->HasFullClip() ) + continue; + + if ( stricmp( weapon->GetAmmoType( FIRE_MODE1 ).c_str(), type.c_str() ) != 0 ) + continue; + + roomLeftInClip = weapon->GetClipSize( FIRE_MODE1 ) - weapon->getAmmoInClip( FIRE_MODE1 ); + + if ( roomLeftInClip ) + { + if ( amountLeft > roomLeftInClip ) + amountToAdd = roomLeftInClip; + else + amountToAdd = amountLeft; + + amountLeft -= amountToAdd; + + weapon->SetAmmoAmount( weapon->getAmmoInClip( FIRE_MODE1 ) + amountToAdd, FIRE_MODE1 ); + } + + if ( amountLeft <= 0 ) + { + break; + } + } + } + + } + + if ( pickedUp && this->isSubclassOf( Player ) && ( amount - amountLeft > 0 ) ) + { + int iconIndex; + str imageName; + + imageName = "sysimg/icons/items/ammo_"; + imageName += type; + + iconIndex = gi.imageindex( imageName ); + + ((Player *)this)->setItemText( iconIndex, va( "$$PickedUp$$ %d $$Ammo-%s$$\n", amount, type.c_str() ) ); + //((Player *)this)->setItemText( iconIndex, va( "$$PickedUp$$ %d $$Ammo-%s$$\n", amount - amountLeft, type.c_str() ) ); + //gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$PickedUp$$ %d %s\n", totalAmount, type.c_str() ); + } + + AmmoAmountChanged( ammo ); + + return amount - amountLeft; +} + +int Sentient::UseAmmo( const str &type, int amount ) +{ + int count, i; + + count = ammo_inventory.NumObjects(); + + for ( i=1; i<=count; i++ ) + { + Ammo *ammo = ammo_inventory.ObjectAt( i ); + if ( type == ammo->getName() ) + { + int ammo_amount = ammo->getAmount(); + + // Less ammo than what we specified to use + if ( ammo_amount < amount ) + { + ammo->setAmount( 0 ); + AmmoAmountChanged( ammo ); + return ammo_amount; + } + else + { + ammo->setAmount( ammo->getAmount() - amount ); + AmmoAmountChanged( ammo ); + return amount; + } + } + } + return 0; +} + +void Sentient::AmmoAmountInClipChanged( const str &type, int amount_in_clip ) +{ + int count, i; + + count = ammo_inventory.NumObjects(); + + for ( i=1; i<=count; i++ ) + { + Ammo *ammo = ammo_inventory.ObjectAt( i ); + if ( type == ammo->getName() ) + { + AmmoAmountChanged( ammo, amount_in_clip ); + } + } +} + +void Sentient::JumpXY( Event *ev ) +{ + float forwardmove; + float sidemove; + float distance; + float time; + float speed; + Vector yaw_forward; + Vector yaw_left; + + forwardmove = ev->GetFloat( 1 ); + sidemove = ev->GetFloat( 2 ); + speed = ev->GetFloat( 3 ); + + Vector( 0.0f, angles.y, 0.0f ).AngleVectors( &yaw_forward, &yaw_left ); + + velocity = ( yaw_forward * forwardmove ) - ( yaw_left * sidemove ); + distance = velocity.length(); + velocity *= speed / distance; + time = distance / speed; + velocity[ 2 ] = sv_currentGravity->integer * time * 0.5f; +} + +void Sentient::MeleeAttackStart( Event *ev ) +{ + int i; + Entity *victim; + Vector mins, maxs, pos, end; + Container potential_victimlist; + + mins = Vector( -8, -8, -8 ); + maxs = Vector( 8, 8, 8 ); + pos = centroid; + end = centroid + Vector( orientation[0] ) * 96.0f; + + G_TraceEntities( pos, mins, maxs, end, &potential_victimlist, MASK_MELEE ); + + for( i = 1 ; i <= potential_victimlist.NumObjects() ; i++ ) + { + victim = potential_victimlist.ObjectAt( i ); + Event *event = new Event(EV_Sentient_AddMeleeAttacker); + event->AddEntity(this); + victim->ProcessEvent(event); + } + + SetMeleeAttack(true); +} + +void Sentient::MeleeAttackEnd( Event *ev ) +{ + SetMeleeAttack(false); +} + +void Sentient::SetMeleeAttack(bool value) +{ + in_melee_attack = value; +} + +void Sentient::RangedAttackStart( Event *ev ) +{ + in_ranged_attack = true; +} + +void Sentient::RangedAttackEnd( Event *ev ) +{ + in_ranged_attack = false; +} + +void Sentient::BlockStart( Event *ev ) +{ + in_block = true; +} + +void Sentient::BlockEnd( Event *ev ) +{ + in_block = false; +} + +void Sentient::StunStart( Event *ev ) +{ + in_stun = true; +} + +void Sentient::StunEnd( Event *ev ) +{ + in_stun = false; +} + +void Sentient::ListInventory( void ) +{ + int i,count; + + // Display normal inventory + count = inventory.NumObjects(); + + gi.Printf( "'Name' : 'Amount'\n" ); + + for ( i=1; i<=count; i++ ) + { + int entnum = inventory.ObjectAt( i ); + Item *item = ( Item * )G_GetEntity( entnum ); + gi.Printf( "'%s' : '%d'\n", item->getName().c_str(), item->getAmount() ); + } + + // Display ammo inventory + count = ammo_inventory.NumObjects(); + + for ( i=1; i<=count; i++ ) + { + Ammo *ammo = ammo_inventory.ObjectAt( i ); + gi.Printf( "'%s' : '%d'\n", ammo->getName().c_str(), ammo->getAmount() ); + } +} + +void Sentient::SetAttackBlocked( qboolean blocked ) +{ + attack_blocked = blocked; + attack_blocked_time = level.time; +} + +void Sentient::SetMaxMouthAngle( Event *ev ) +{ + max_mouth_angle = ev->GetFloat( 1 ); +} + + +//---------------------------------------------------------------- +// Name: CatchOnFire +// Class: Sentient +// +// Description: Sets the character on fire +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Sentient::CatchOnFire( Event *ev ) +{ + TryLightOnFire(MOD_FIRE, NULL); +} + +void Sentient::TryLightOnFire( int meansofdeath, Entity *attacker ) +{ + float chance; + float min_time; + float add_time; + + + if ( !com_blood->integer ) + return; + + // Get the base chance of catching on fire + + if ( meansofdeath == MOD_FIRESWORD ) + chance = .5f; + else + chance = .05f; + + // Get some values based on whether or not the sentient is a player or an actor + + if ( this->isSubclassOf( Player ) ) + { + chance = chance / 2.0f; + min_time = 4.0f; + add_time = 2.0f; + } + else + { + min_time = 8.0f; + add_time = 4.0f; + + // Going to use MOD_FIRE to force fire on Actors - not players + if ( meansofdeath == MOD_FIRE ) + chance = 1.0f; + } + + // Make sure not immune to on_fire + + if ( Immune( MOD_ON_FIRE ) ) + return; + + // See if we should catch the victim on fire + + if ( G_Random() < chance ) + { + Event *event; + int number_of_tags; + int i; + const char *tag_name; + int number_of_fires_to_add; + float scale; + float chanceToChange; + + if ( !on_fire ) + { + on_fire_tagnums[0] = -1; + on_fire_tagnums[1] = -1; + on_fire_tagnums[2] = -1; + } + + if ( mass >= 200 ) + number_of_fires_to_add = 3; + else if ( mass >= 100 ) + number_of_fires_to_add = 2; + else + number_of_fires_to_add = 1; + + if ( mass > 200 ) + scale = 1.25f; + else if ( mass >= 300 ) + scale = 1.75f; + else + scale = 1.00f; + + chanceToChange = 0.05f; + + number_of_tags = gi.NumTags( edict->s.modelindex ); + + if ( number_of_tags ) + { + for ( i = 0 ; i < number_of_fires_to_add ; i++ ) + { + if ( !on_fire || ( G_Random() < chanceToChange ) ) + { + if ( on_fire && ( on_fire_tagnums[i] >= 0 ) ) + { + tag_name = gi.Tag_NameForNum( edict->s.modelindex, on_fire_tagnums[i] ); + + if ( _displayFireEffect ) + { + event = new Event( EV_RemoveAttachedModel ); + event->AddString( tag_name ); + event->AddFloat( 0.0f ); + event->AddString( "models/fx_catchfire.tik" ); + ProcessEvent( event ); + } + } + + on_fire_tagnums[i] = G_Random( number_of_tags ); + tag_name = gi.Tag_NameForNum( edict->s.modelindex, on_fire_tagnums[i] ); + + if ( _displayFireEffect ) + { + event = new Event( EV_AttachModel ); + event->AddString( "fx_catchfire.tik" ); + event->AddString( tag_name ); + event->AddFloat( scale ); + ProcessEvent( event ); + } + } + } + + if ( !on_fire ) + { + on_fire = true; + fire_owner = attacker; + PostEvent( EV_Sentient_OnFire, FRAMETIME ); + next_catch_on_fire_time = level.time + .15f + G_Random( .2f ); + on_fire_stop_time = level.time + min_time + G_Random( add_time ); + } + } + } +} + +void Sentient::OnFire( Event *ev ) +{ + float damage; + + // See if we should stop being on fire + + if ( !on_fire || ( on_fire_stop_time <= level.time ) || ( gi.pointcontents( origin, 0 ) & MASK_WATER ) ) + { + // Stop the fire + + ProcessEvent( EV_Sentient_StopOnFire ); + return; + } + + // Cause a little bit of damage + + if ( this->isSubclassOf( Player ) ) + damage = 0.05; + else + damage = 0.5; + + if ( fire_owner && fire_owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)(Entity *)fire_owner; + + damage = player->getDamageDone( damage, MOD_ON_FIRE, false ); + } + + Damage( fire_owner, fire_owner, damage, vec_zero, vec_zero, vec_zero, 0, 0, MOD_ON_FIRE ); + + // Try to set other sentients on fire + + if ( next_catch_on_fire_time <= level.time ) + { + MeleeAttack( centroid, centroid, 0.05f, fire_owner, MOD_FIRE, maxs[0] + 10, -maxs[2] / 2, maxs[2] / 2, 0.0f, false ); + next_catch_on_fire_time = level.time + .15f + G_Random( .2f ); + } + + // Call this event again next frame + + PostEvent( EV_Sentient_OnFire, FRAMETIME ); +} + +void Sentient::StopOnFire( Event *ev ) +{ + int i; + const char *tag_name; + Event *event; + + + for( i = 0 ; i < 3 ; i++ ) + { + if ( ( on_fire_tagnums[i] >= 0 ) && ( on_fire_tagnums[i] < gi.NumTags( edict->s.modelindex ) ) ) + { + tag_name = gi.Tag_NameForNum( edict->s.modelindex, on_fire_tagnums[i] ); + + if ( _displayFireEffect ) + { + event = new Event( EV_RemoveAttachedModel ); + event->AddString( tag_name ); + event->AddFloat( 0.0f ); + event->AddString( "models/fx_catchfire.tik" ); + ProcessEvent( event ); + } + } + } + + on_fire_tagnums[ 0 ] = -1; + on_fire_tagnums[ 1 ] = -1; + on_fire_tagnums[ 2 ] = -1; + + on_fire = false; + + CancelEventsOfType( EV_Sentient_OnFire ); +} + +void Sentient::SpawnBloodyGibs( Event *ev ) +{ + str gib_name; + int number_of_gibs; + float scale; + Entity *ent; + str real_gib_name; + + if ( !com_blood->integer ) + return; + + //if ( GetActorFlag( ACTOR_FLAG_FADING_OUT ) ) + // return; + + gib_name = GetGibName(); + + if ( !gib_name.length() ) + return; + + // Determine the number of gibs to spawn + + if ( ev->NumArgs() > 0 ) + { + number_of_gibs = ev->GetInteger( 1 ); + } + else + { + if ( max_gibs == 0 ) + return; + + if ( deadflag ) + //number_of_gibs = G_Random( max_gibs / 2 ) + 1; + number_of_gibs = max_gibs; + else + number_of_gibs = (int)G_Random( (float)max_gibs ) + 1; + } + + // Make sure we don't have too few or too many gibs + + if ( ( number_of_gibs <= 0 ) || ( number_of_gibs > 9 ) ) + return; + + if ( ev->NumArgs() > 1 ) + { + scale = ev->GetFloat( 2 ); + } + else + { + // Calculate a good scale value + + if ( mass <= 50 ) + scale = 1.0; + else if ( mass <= 100 ) + scale = 1.1; + else if ( mass <= 250 ) + scale = 1.2; + else + scale = 1.3; + } + + // Spawn the gibs + + real_gib_name = gib_name; + real_gib_name += number_of_gibs; + real_gib_name += ".tik"; + + ent = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + ent->setModel( real_gib_name.c_str() ); + ent->setScale( scale ); + ent->setOrigin( centroid ); + ent->animate->RandomAnimate( "idle" ); + ent->PostEvent( EV_Remove, 1.0f ); + + Sound( "snd_decap", CHAN_BODY, 1.0f, 300.0f ); +} + +void Sentient::SetMaxGibs( Event *ev ) +{ + max_gibs = ev->GetInteger( 1 ); +} + +void Sentient::GetStateAnims( Container *c ) +{ +} + +void Sentient::CheckAnimations( Event *ev ) +{ + int i,j; + Containerco; + const char *cs; + + GetStateAnims( &co ); + + gi.DPrintf( "Unused Animations in TIKI\n" ); + gi.DPrintf( "-------------------------\n" ); + for( i=0; iNumAnims(); i++ ) + { + const char *c; + + c = gi.Anim_NameForNum( edict->s.modelindex, i ); + + for ( j=1; j<=co.NumObjects(); j++ ) + { + cs = co.ObjectAt( j ); + + if ( !stricmp( c, cs ) ) + { + goto out; + } + else if ( !strnicmp( c, cs, strlen( cs ) ) ) // partial match + { + int state_len = strlen( cs ); + + // Animation in tik file is longer than the state machine's anim + if ( strlen( c ) > state_len ) + { + if ( c[state_len] != '_' ) // If next character is an '_' then no match + { + goto out; + } + } + else + { + goto out; + } + } + } + // No match made + gi.DPrintf( "%s used in TIK file but not statefile\n", c ); +out: + ; + } + + gi.DPrintf( "Unknown Animations in Statefile\n" ); + gi.DPrintf( "-------------------------------\n" ); + for ( j=1; j<=co.NumObjects(); j++ ) + { + if ( !animate->HasAnim( co.ObjectAt( j ) ) ) + { + gi.DPrintf( "%s in statefile is not in TIKI\n", co.ObjectAt( j ) ); + } + } +} + +void Sentient::SetStateFile( Event *ev ) +{ + Event *temp=ev; + if (ev != NULL) + { + temp=NULL; + } + + // This function is not defined for a Sentient + // If Sentient was an abstract class then this would be pure virtual +} + +void Sentient::ReceivedItem( Item * item ) +{ +} + +void Sentient::RemovedItem( Item * item ) +{ +} + +void Sentient::AmmoAmountChanged( Ammo * ammo, int ammo_in_clip ) +{ +} + +// +// Armor Stuff +// +void Sentient::SetArmorValue(int armorVal) +{ + if ( currentBaseArmor ) + currentBaseArmor->setAmount( armorVal ); + +} + +int Sentient::GetArmorValue() +{ + if ( currentBaseArmor ) + return currentBaseArmor->getAmount(); + + return 0; +} + +//---------------------------------------------------------------- +// Name: setViewMode +// Class: Sentient +// +// Description: Sets the players current view mode +// +// Parameters: Event *ev - event (the name of the view mode to go to) +// +// Returns: None +//---------------------------------------------------------------- + +void Sentient::setViewMode( Event *ev ) +{ + str viewMode; + bool override; + + viewMode = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + override = ev->GetBoolean( 2 ); + else + override = true; + + if ( _viewMode && !override ) + return; + + setViewMode( ev->GetString( 1 ) ); +} + +//---------------------------------------------------------------- +// Name: setViewMode +// Class: Sentient +// +// Description: Sets the players current view mode +// +// Parameters: const str &viewModeName - the name of the view mode to go to +// +// Returns: None +//---------------------------------------------------------------- + +void Sentient::setViewMode( const str &viewModeName ) +{ + unsigned int newViewMode; + + newViewMode = gi.GetViewModeMask( viewModeName.c_str() ); + + if ( _viewMode != newViewMode ) + { + _viewMode = newViewMode; + + if ( this->isSubclassOf( Player ) ) + { + gi.centerprintf ( edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$SwitchedTo$$ $$ViewMode-%s$$ $$Viewmode$$", viewModeName.c_str() ); + } + } +} + +//---------------------------------------------------------------- +// Name: getViewMode +// Class: Sentient +// +// Description: Gets the current view mode of this sentient +// +// Parameters: None +// +// Returns: unsigned int - the current view mode bit(s) +//---------------------------------------------------------------- + +unsigned int Sentient::getViewMode( void ) +{ + return _viewMode; +} + +//---------------------------------------------------------------- +// Name: getActiveWeaponName +// Class: Sentient +// +// Description: Gets the name of the active weapon +// +// Parameters: Event *ev - contains name of the hand (left, right, or dual) +// +// Returns: str (through the event) - name of the active weapon +//---------------------------------------------------------------- + +void Sentient::getActiveWeaponName( Event *ev ) +{ + weaponhand_t hand = WEAPON_ANY; + str weaponName; + + // See which hand we are concerned about + + if ( ev->NumArgs() > 0 ) + { + hand = WeaponHandNameToNum( ev->GetString( 1 ) ); + } + else + { + hand = WEAPON_ANY; + } + + getActiveWeaponName(hand, weaponName); + + ev->ReturnString(weaponName); +} + +void Sentient::getActiveWeaponName( weaponhand_t hand, str& weaponName) +{ + Weapon* weapon; + + weaponName = "None"; + // Get the name of the weapon + if ( hand == WEAPON_ANY ) + { + // Try the left hand + + weapon = GetActiveWeapon( WEAPON_LEFT ); + + if ( weapon ) + weaponName = weapon->getName(); + + // Try the right hand + + if ( !weapon ) + { + weapon = GetActiveWeapon( WEAPON_RIGHT ); + + if ( weapon ) + weaponName = weapon->getName(); + } + + // Try the both hands + + if ( !weapon ) + { + weapon = GetActiveWeapon( WEAPON_DUAL ); + + if ( weapon ) + weaponName = weapon->getName(); + } + } + else if ( ( hand == WEAPON_LEFT ) || ( hand == WEAPON_RIGHT ) || ( hand == WEAPON_DUAL ) ) + { + weapon = GetActiveWeapon( hand ); + + if ( weapon ) + weaponName = weapon->getName(); + } + +} + +//-------------------------------------------------------------- +// +// Name: SwipeOn +// Class: Sentient +// +// Description: Turn the weapon swipes on for this sentient's weapons +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Sentient::SwipeOn( Event *ev ) +{ + Weapon *weapon; + weaponhand_t hand; + + hand = WeaponHandNameToNum( ev->GetString( 1 ) ); + + if ( hand == WEAPON_ERROR ) + return; + + weapon = GetActiveWeapon( hand ); + + if ( weapon ) + weapon->SetAnim( "swipeon", NULL ); +} + +//-------------------------------------------------------------- +// +// Name: SwipeOff +// Class: Sentient +// +// Description: Turn the weapon swipes off for this sentient's weapons +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Sentient::SwipeOff( Event *ev ) +{ + Weapon *weapon; + weaponhand_t hand; + + hand = WeaponHandNameToNum( ev->GetString( 1 ) ); + + if ( hand == WEAPON_ERROR ) + return; + + weapon = GetActiveWeapon( hand ); + + if ( weapon ) + weapon->SetAnim( "swipeoff", NULL ); +} + +//-------------------------------------------------------------- +// Name: AddHealthOverTime() +// Class: Sentient +// +// Description: Adds the specified percentage of health right away, +// then posts an event to AddHealthAtInterval to +// kick start a health regeneration event chain +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Sentient::AddHealthOverTime( Event *ev ) +{ + float percentageToAdd = ev->GetFloat( 1 ); + float percentageToRegen = ev->GetFloat( 2 ); + float interval = ev->GetFloat( 3 ); + float maxPercentage = ev->GetFloat( 4 ); + float addHealth; + float maxHealth; + Event *regenEvent; + + //Translate our percentages into real numbers + addHealth = percentageToAdd * max_health; + maxHealth = maxPercentage * max_health; + + //Check if adding the initial health will put us + //over our maximum allowed + if ( (addHealth + health) >= maxHealth ) + { + health = maxHealth; + return; + } + else + health+= addHealth; + + //Now Generate our AddHealthAtInterval Event + //which will continue the regeneration process + regenEvent = new Event(EV_Sentient_HealAtInterval); + + if ( !regenEvent) + { + assert(regenEvent); + return; + } + + regenEvent->AddFloat(percentageToRegen); + regenEvent->AddFloat(interval); + regenEvent->AddFloat(maxPercentage); + + PostEvent( regenEvent, interval ); +} + +//-------------------------------------------------------------- +// Name: AddHealthAtInterval() +// Class: Sentient +// +// Description: Adds the regen percentage to the health +// of the percentage... if the health is +// still not the maxPercentage, then it +// will generate a new event of its own +// type +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Sentient::AddHealthAtInterval( Event *ev ) +{ + float percentageToRegen = ev->GetFloat( 1 ); + float interval = ev->GetFloat( 2 ); + float maxPercentage = ev->GetFloat( 3 ); + float addHealth; + float maxHealth; + Event *regenEvent; + + //Translate our percentages into real numbers + addHealth = percentageToRegen * max_health; + maxHealth = maxPercentage * max_health; + + //Check if adding the initial health will put us + //over our maximum allowed + if ( (addHealth + health) >= maxHealth ) + { + SetHealth(maxHealth); + return; + } + else + SetHealth(health + addHealth); + + //Now Generate a new AddHealthAtInterval Event + //which will continue the regeneration process + regenEvent = new Event(EV_Sentient_HealAtInterval); + + if ( !regenEvent) + { + assert(regenEvent); + return; + } + + regenEvent->AddFloat(percentageToRegen); + regenEvent->AddFloat(interval); + regenEvent->AddFloat(maxPercentage); + + PostEvent( regenEvent, interval ); +} + +void Sentient::SetHealth( float newHealth ) +{ + health = newHealth; + + if ( ( health / max_health ) > GetCriticalHealthPercentage() ) + _canSendInjuredEvent = true; +} + +//---------------------------------------------------------------- +// Name: HeadWatchAllowed +// Class: Sentient +// +// Description: Sets whether to headwatch or not, defaults to true +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Sentient::HeadWatchAllowed(Event *ev) +{ + if ( ev->NumArgs() > 0 ) + HeadWatchAllowed( ev->GetBoolean( 1 ) ); + else + HeadWatchAllowed( true ); +} + +void Sentient::HeadWatchAllowed( bool allowed ) +{ + _headWatchAllowed = allowed; +} + +//-------------------------------------------------------------- +// +// Name: SetWeaponAnim +// Class: Sentient +// +// Description: Put the weapon in the specified animation +// +// Parameters: Event *ev +// +// Returns: None +// +//-------------------------------------------------------------- +void Sentient::SetWeaponAnim( Event *ev ) +{ + Weapon *weapon; + weaponhand_t hand; + str animname; + + animname = ev->GetString( 1 ); + hand = WeaponHandNameToNum( ev->GetString( 2 ) ); + + if ( hand == WEAPON_ERROR ) + return; + + weapon = GetActiveWeapon( hand ); + + if ( weapon ) + weapon->playAnim( animname ); +} + +//-------------------------------------------------------------- +// Name: SetDamageThreshold() +// Class: Sentient +// +// Description: Sets Values on our damageThreshold structure +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Sentient::SetDamageThreshold( Event *ev ) +{ + _damageThreshold.maxDamage = ev->GetFloat( 1 ); + _damageThreshold.duration = ev->GetFloat( 2 ); + _damageThreshold.startTime = 0; +} + +//-------------------------------------------------------------- +// Name: CheckDamageThreshold() +// Class: Sentient +// +// Description: Checks if we have hit our damageThreshold +// meaning, we have taken at least X damage +// in Y amount of time. +// +// Parameters: float damageValue +// +// Returns: None +//-------------------------------------------------------------- +void Sentient::CheckDamageThreshold( float damageValue ) +{ + //First Check if we even need to bother doing further + //checks + float timeDiff; + + if ( _damageThreshold.maxDamage < 0 ) + return; + + if( _damageThreshold.startTime == 0 ) + _damageThreshold.startTime = level.time; + + timeDiff = level.time - _damageThreshold.startTime; + + //Now we check if our timeDiff is greater than our + //damageThreshold's duration. If it is, then obviously + //we didn't hit our threshold, so we need to update + //our starttime and currentDamage; + if ( _damageThreshold.duration >= 0 && timeDiff > _damageThreshold.duration ) + { + _damageThreshold.startTime = level.time; + _damageThreshold.currentDamage = damageValue; + return; + } + + //Well, we're still within our timeframe to hit our + //threshold, so we'll add the damageValue to our + //damageThreshold's currentDamage and check to see + //if we hit the wall. + _damageThreshold.currentDamage += damageValue; + + if ( _damageThreshold.currentDamage >= _damageThreshold.maxDamage ) + { + //Yay, more casting... I'm so excited about this + if ( this->isSubclassOf(Actor) ) + { + Actor *actor; + actor = (Actor*)this; + actor->AddStateFlag(STATE_FLAG_DAMAGE_THRESHOLD_EXCEEDED); + } + + //Clear out our structure + ClearDamageThreshold(); + } +} + +//-------------------------------------------------------------- +// Name: DisplayFireEffect +// Class: Sentient +// +// Description: Sets the flag to display the fire effect while this entity +// is on fire. Note: this doesn't prevent the "on fire" state, +// nor does it prevent the damage from the fire. It's just +// the fire effect itself that's not displayed. +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Sentient::DisplayFireEffect( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + _displayFireEffect = ev->GetBoolean( 1 ); + else + _displayFireEffect = true; +} + +void Sentient::ClearDamageThreshold( Event *ev ) +{ + ClearDamageThreshold(); +} + +void Sentient::ClearDamageThreshold() +{ + _damageThreshold.startTime = level.time; + _damageThreshold.currentDamage = 0.0f; +} + + +//-------------------------------------------------------------- +// +// Name: DropItemEvent +// Class: Sentient +// +// Description: Drops the specified item to the ground as a pickup +// +// Parameters: Event *ev +// -- str itenName +// +// Returns: None +// +//-------------------------------------------------------------- +void Sentient::DropItemEvent( Event *ev ) +{ + int num, i; + Item *item; + + str itemName = ev->GetString( 1 ); + num = inventory.NumObjects(); + for( i = num; i >= 1; i-- ) + { + item = ( Item * )G_GetEntity( inventory.ObjectAt( i ) ); + if ( item->getName() == itemName ) + { + DropItem(item); + return; + } + } +} + + +//-------------------------------------------------------------- +// +// Name: DropItem +// Class: Sentient +// +// Description: Drops the specified item. This function +// may grow. +// +// Parameters: Item *itemToDrop -- Item pointer to drop +// +// Returns: None +// +//-------------------------------------------------------------- +void Sentient::DropItem( Item *itemToDrop ) +{ + if ( !itemToDrop ) + return; + + itemToDrop->Drop(); +} + + +//-------------------------------------------------------------- +// +// Name: DropItemsOnDeath +// Class: Sentient +// +// Description: Drops the item/weapon when the sentient dies +// +// Parameters: None +// +// Returns: None +// +//-------------------------------------------------------------- +void Sentient::DropItemsOnDeath() +{ + Weapon *weap = 0; + weap = GetActiveWeapon(WEAPON_RIGHT); + if ( !weap ) + weap = GetActiveWeapon(WEAPON_LEFT); + if ( weap ) + { + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( gpm->hasProperty(weap->getArchetype(), "dropchance") ) + { + float dropchance = gpm->getFloatValue(weap->getArchetype(), "dropchance"); + if ( G_Random() < dropchance ) + DropItem(weap); + } + + if ( gpm->hasProperty(getArchetype(), "potionchance") ) + { + str model; + + float potionchance = gpm->getFloatValue(getArchetype(), "potionchance"); + if ( G_Random() < potionchance ) + { + model = gpm->getStringValue( "HealthPotion", "model"); + } + else if ( G_Random() < potionchance * 2.0f ) + { + model = gpm->getStringValue( "RedPotion", "model" ); + } + + if ( model.length() ) + { + // Spawn the potion + SpawnArgs args; + Entity *ent; + Item *item; + args.setArg( "model", model ); + ent = args.Spawn(); + if ( !ent || !ent->isSubclassOf( Item ) ) + return; + item = (Item *)ent; + item->setOrigin( centroid ); + item->ProcessPendingEvents(); + item->PlaceItem(); + item->setOrigin( centroid ); + item->velocity = Vector( G_CRandom( 100.0f ), G_CRandom( 100.0f ), 200.0f + G_Random( 200.0f ) ); + item->edict->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + item->targetname = targetname; + item->targetname += "_item"; + item->SetTargetName( item->targetname ); + } + } + } +} + +//-------------------------------------------------------------- +// +// Name: WeaponEffectsAndSound +// Class: Sentient +// +// Description: Plays a weapon specified tiki effect at pos. +// Also plays a sound. All this comes from the gameplay database +// +// Parameters: Entity *weapon -- The weapon to use (only need Entity portion) +// const str& objname -- Object name to reference +// Vector pos -- location for the tiki effect +// +// Returns: None +// +//-------------------------------------------------------------- +void Sentient::WeaponEffectsAndSound( Entity *weapon, const str& objname, Vector pos ) +{ + if ( !weapon ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + str scopestr = weapon->getArchetype() + "." + objname; + str effect = gpm->getStringValue(scopestr, "tikifx"); + if ( effect.length() ) + Entity::SpawnEffect(effect, pos, vec_zero, 2.0f); + + str snd = gpm->getStringValue(scopestr, "wav"); + if ( snd.length() ) + { + float minpitch = gpm->getFloatValue(scopestr, "minpitch"); + float maxpitch = gpm->getFloatValue(scopestr, "maxpitch"); + float pitch = G_Random(maxpitch - minpitch) + minpitch; + int channel = CHAN_BODY; + float volume = -1.0f; + float mindist = -1.0f; + if ( gpm->hasProperty(scopestr,"channel") ) + channel = (int)gpm->getFloatValue(scopestr, "channel"); + if ( gpm->hasProperty(scopestr,"volume") ) + volume = (int)gpm->getFloatValue(scopestr, "volume"); + if ( gpm->hasProperty(scopestr,"mindist") ) + mindist = (int)gpm->getFloatValue(scopestr, "mindist"); + weapon->Sound(snd, channel, volume, mindist, NULL, pitch); + } +} + +void Sentient::SetArmorActiveStatus( Event *ev ) +{ + bool status = ev->GetBoolean( 1 ); + + SetArmorActiveStatus( status ); +} + +void Sentient::SetArmorActiveStatus(bool status) +{ + if ( currentBaseArmor ) + { + Event* statusEvent; + + statusEvent = new Event ( EV_Armor_SetActiveStatus ); + statusEvent->AddInteger( status ); + currentBaseArmor->ProcessEvent ( statusEvent ); + } +} + +void Sentient::SetArmorMultiplier( Event *ev ) +{ + if ( currentBaseArmor ) + { + float multiplier = ev->GetFloat( 1 ); + SetArmorMultiplier( multiplier ); + } +} + +void Sentient::SetArmorMultiplier( float multiplier ) +{ + Event* multiplierEvent; + multiplierEvent = new Event ( EV_Armor_SetMultiplier ); + + multiplierEvent->AddFloat( multiplier ); + + if ( currentBaseArmor ) + currentBaseArmor->ProcessEvent ( multiplierEvent ); +} + + +void Sentient::AddToMyArmor( Event *ev ) +{ + AddToMyArmor( ev->GetFloat( 1 ) ); +} + +void Sentient::AddToMyArmor( float amountToAdd ) +{ + if ( currentBaseArmor ) + currentBaseArmor->setAmount( currentBaseArmor->getAmount() + amountToAdd ); +} + +void Sentient::SetMyArmorAmount( Event *ev ) +{ + SetMyArmorAmount( ev->GetFloat(1) ); +} + +void Sentient::SetMyArmorAmount( float amount ) +{ + if ( currentBaseArmor ) + currentBaseArmor->setAmount( amount ); + +} + +void Sentient::ArmorEvent( Event *ev ) +{ + if ( !currentBaseArmor ) + return; + + Event *e; + e = new Event( ev->GetToken( 1 ) ); + + for( int i=2; i<=ev->NumArgs(); i++ ) + e->AddToken( ev->GetToken( i ) ); + + currentBaseArmor->ProcessEvent( e ); +} + +void Sentient::SetHateModifier( Event *ev ) +{ + float modifier; + modifier = ev->GetFloat( 1 ); + SetHateModifier ( modifier ); +} + +void Sentient::SetHateModifier( float modifier ) +{ + _hateModifier = modifier; +} + +float Sentient::GetHateModifier() +{ + return _hateModifier; +} + +void Sentient::cacheStateMachineAnims( Event *ev ) +{ + G_CacheStateMachineAnims( this, ev->GetString( 1 ) ); +} + +void Sentient::freeConditionals( Container &conditionalsToDelete ) +{ + int i; + Conditional *conditional; + + + for ( i = conditionalsToDelete.NumObjects() ; i > 0 ; i-- ) + { + conditional = conditionalsToDelete.ObjectAt( i ); + + if ( conditional ) + { + delete conditional; + } + } + + conditionalsToDelete.FreeObjectList(); +} diff --git a/dlls/game/sentient.h b/dlls/game/sentient.h new file mode 100644 index 0000000..0cffbb2 --- /dev/null +++ b/dlls/game/sentient.h @@ -0,0 +1,371 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/sentient.h $ +// $Revision:: 77 $ +// $Author:: Sketcher $ +// $Date:: 5/04/03 5:49p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// DESCRIPTION: +// Base class of entity that can carry other entities, and use weapons. +// + +#ifndef __SENTIENT_H__ +#define __SENTIENT_H__ + +#include "g_local.h" +#include "container.h" +#include "animate.h" +#include "characterstate.h" + +extern Event EV_Sentient_Attack; +extern Event EV_Sentient_StartChargeFire; +extern Event EV_Sentient_ReleaseAttack; +extern Event EV_Sentient_GiveWeapon; +extern Event EV_Sentient_GiveAmmo; +extern Event EV_Sentient_GiveAmmoOverTime; +extern Event EV_Sentient_GiveArmor; +extern Event EV_Sentient_GiveItem; +extern Event EV_Sentient_GiveTargetname; +extern Event EV_Sentient_GiveInventoryItem; +extern Event EV_Sentient_GiveHealth; +extern Event EV_Sentient_SetBloodModel; +extern Event EV_Sentient_TurnOffShadow; +extern Event EV_Sentient_TurnOnShadow; +extern Event EV_Sentient_AddImmunity; +extern Event EV_Sentient_AddResistance; +extern Event EV_Sentient_RemoveImmunity; +extern Event EV_Sentient_RemoveResistance; +extern Event EV_Sentient_UpdateOffsetColor; +extern Event EV_Sentient_JumpXY; +extern Event EV_Sentient_MeleeAttackStart; +extern Event EV_Sentient_MeleeAttackEnd; +extern Event EV_Sentient_BlockStart; +extern Event EV_Sentient_BlockEnd; +extern Event EV_Sentient_SetMouthAngle; +extern Event EV_Sentient_SpawnBloodyGibs; +extern Event EV_Sentient_StopOnFire; +extern Event EV_Sentient_AddMeleeAttacker; +extern Event EV_Sentient_HeadWatchAllowed; +extern Event EV_Sentient_WeaponAnim; +extern Event EV_Sentient_SetViewMode; +extern Event EV_Armor_SetActiveStatus; +extern Event EV_Armor_SetMultiplier; + +extern Event EV_Weapon_DrawBowStrain; +extern Event EV_Weapon_AltDrawBowStrain; + +extern Event EV_Sentient_SetMyArmorAmount; + +// Shutup compiler +class Armor; +class Weapon; +class Item; +class InventoryItem; +class Ammo; + +#define MAX_ACTIVE_WEAPONS NUM_ACTIVE_WEAPONS + +#define REGIONAL_DAMAGE_BACK (1<<0) +#define REGIONAL_DAMAGE_FRONT (1<<1) + +typedef SafePtr WeaponPtr; +typedef SafePtr ArmorPtr; + +typedef struct + { + int MODIndex; + int ResistanceAmount; + } Resistance; + +typedef struct +{ + float duration; + float startTime; + float maxDamage; + float currentDamage; +} damagethreshold_t; + +/* typedef enum + { + POWERUP_NONE, + POWERUP_SPEED, + POWERUP_STEALTH, + POWERUP_PROTECTION, + POWERUP_FLIGHT, + POWERUP_STRENGTH, + POWERUP_ACCURACY + } powerup_t; */ + +typedef enum + { + ARMORTYPE_NONE, + ARMORTYPE_HEAVY, + ARMORTYPE_MEDIUM, + ARMORTYPE_LIGHT, + ARMORTYPE_SHARD + } armortype_t; + +class Sentient : public Entity + { + private: + float _criticalHealthPercentage; + damagethreshold_t _damageThreshold; + float _hateModifier; + + + protected: + Container inventory; + Container ammo_inventory; + + WeaponPtr newWeapon; + ArmorPtr currentBaseArmor; + //int firing_frame; + //int firing_anim; + Vector offset_color; + Vector offset_delta; + float offset_time; + float knock_start_time; + str blood_model; + int last_surface_hit; + int last_bone_hit; + unsigned int last_region_hit; + Container immunities; + Container resistances; + WeaponPtr activeWeaponList[ MAX_ACTIVE_WEAPONS ]; + unsigned int _viewMode; + bool _canSendInjuredEvent; + bool _headWatchAllowed; + bool _displayFireEffect; + + + virtual void EventTake ( Event *ev ); + virtual void EventGiveAmmo ( Event *ev ); + virtual void giveAmmoOverTime ( Event *ev ); + virtual void EventGiveItem ( Event *ev ); + virtual void EventGiveArmor ( Event *ev ); + virtual void EventGiveHealth ( Event *ev ); + virtual void EventGiveTargetname ( Event *ev ); + virtual void TurnOffShadow ( Event *ev ); + virtual void TurnOnShadow ( Event *ev ); + virtual void AddImmunity ( Event *ev ); + virtual void RemoveImmunity ( Event *ev ); + virtual void AddResistance ( Event *ev ); + virtual void RemoveResistance ( Event *ev ); + void AddHealth ( Event *ev ); + void SetBloodModel ( Event *ev ); + void UpdateOffsetColor ( Event *ev ); + void CheckAnimations ( Event *ev ); + + void ArmorEvent ( Event *ev ); + void ArmorDamage ( ::Damage &damage ); + virtual void ArmorDamage ( Event *ev ); + void ArmorDamage ( float damage, Entity *inflictor, Entity *attacker, const Vector &position, + const Vector &direction, const Vector &normal, int knockback, int dflags, + int meansofdeath, int surfaceNumber, int boneNumber, Entity *weapon , bool showPain ); + + void SetRegionalDamage ( int surface_number, int bone_number, Vector direction ); + virtual qboolean CanBlock ( int meansofdeath, qboolean full_block ); + void AddBloodSpurt ( const Vector &position, const Vector &direction, int bone_number ); + qboolean ShouldBleed ( int meansofdeath, qboolean dead ); + qboolean ShouldGib ( int meansofdeath, float damage ); + str GetBloodSpurtName ( void ); + str GetBloodSplatName ( void ); + float GetBloodSplatSize ( void ); + str GetGibName ( void ); + virtual void WeaponKnockedFromHands ( void ); + + + public: + Vector gunoffset; + Vector eyeposition; + int viewheight; + int means_of_death; + qboolean in_melee_attack; + qboolean in_ranged_attack; + qboolean in_block; + qboolean in_stun; + qboolean on_fire; + float on_fire_stop_time; + float next_catch_on_fire_time; + int on_fire_tagnums[3]; + EntityPtr fire_owner; + qboolean attack_blocked; + float attack_blocked_time; + float max_mouth_angle; + int max_gibs; + float next_bleed_time; + float shotsFiredThisVolley; + + CLASS_PROTOTYPE( Sentient ); + + Sentient(); + virtual ~Sentient(); + Vector EyePosition ( void ); + virtual Vector GunPosition ( void ); + + // + // Event Interface + // + void BeginAttack ( Event *ev ); + void EndAttack ( Event *ev ); + virtual void FireWeapon ( Event *ev ); + virtual void StopFireWeapon ( Event *ev ); + void StartChargeFire ( Event *ev ); + virtual void ReleaseFireWeapon ( Event *ev ); + void JumpXY ( Event *ev ); + void MeleeAttackStart ( Event *ev ); + void MeleeAttackEnd ( Event *ev ); + void RangedAttackStart ( Event *ev ); + void RangedAttackEnd ( Event *ev ); + void BlockStart ( Event *ev ); + void BlockEnd ( Event *ev ); + void StunStart ( Event *ev ); + void StunEnd ( Event *ev ); + void SetMaxMouthAngle ( Event *ev ); + void OnFire ( Event *ev ); + void StopOnFire ( Event *ev ); + void SpawnBloodyGibs ( Event *ev ); + void SetMaxGibs ( Event *ev ); + virtual void SetStateFile ( Event *ev ); + void setViewMode ( Event *ev ); + void getActiveWeaponName ( Event *ev ); + void getActiveWeaponName ( weaponhand_t hand, str& weaponName ); + void CatchOnFire ( Event *ev ); + void SwipeOn ( Event *ev ); + void SwipeOff ( Event *ev ); + void AddHealthOverTime ( Event *ev ); + void AddHealthAtInterval ( Event *ev ); + void SetCriticalHealthPercentage ( Event *ev ); + void HeadWatchAllowed ( Event *ev ); + void HeadWatchAllowed ( bool allowed ); + void SetWeaponAnim ( Event *ev ); + void SetDamageThreshold ( Event *ev ); + void DisplayFireEffect ( Event *ev ); + void DropItemEvent ( Event *ev ); + + void SetArmorActiveStatus ( Event *ev ); + void SetArmorActiveStatus ( bool status ); + + void SetArmorMultiplier ( Event *ev ); + void SetArmorMultiplier ( float multiplier ); + + void AddToMyArmor ( Event *ev ); + void AddToMyArmor ( float amountToAdd ); + + void SetMyArmorAmount ( Event *ev ); + void SetMyArmorAmount ( float amount ); + + void ClearDamageThreshold ( Event *ev ); + void ClearDamageThreshold (); + + + void SetHateModifier ( Event *ev ); + void SetHateModifier ( float modifier ); + float GetHateModifier (); + + + void DropItemsOnDeath (); + void DropItem ( Item *itemToDrop ); + bool ChangeWeapon ( Weapon *weapon, weaponhand_t hand ); + Weapon *GetActiveWeapon ( weaponhand_t hand ); + Weapon *BestWeapon ( Weapon *ignore = NULL ); + Weapon *NextWeapon ( Weapon *weapon ); + Weapon *PreviousWeapon ( Weapon *weapon ); + virtual bool useWeapon ( const char *weaponname, weaponhand_t hand ); + int NumWeapons ( void ); + //inline int GetFiringFrame( void ){ return firing_frame; }; + //inline int GetFiringAnim( void ){ return firing_anim; }; + int AmmoCount ( const str &ammo_type ); + int MaxAmmoCount ( const str &ammo_type ); + int AmmoIndex ( const str &ammo_type ); + int UseAmmo ( const str &ammo_type, int amount ); + int GiveAmmo ( const str &type, int amount, bool pickedUp, int max_amount=-1 ); + Ammo *FindAmmoByName ( const str &name ); + Item *giveItem ( const str &itemname , int amount = 1, bool pickedUp = false, float skillLevel = 1.0f ); + void takeItem ( const char *itemname ); + void AddItem ( const Item *object ); + void RemoveItem ( Item *object ); + Item *FindItemByClassName ( const char *classname, Item *current = 0 ); + Item *FindItemByModelname ( const char *modelname, Item *current = 0 ); + Item *FindItemByExternalName ( const char *externalname, Item *current = 0 ); + Item *FindItem ( const char *itemname, Item *current = 0 ); + Item *FindBaseArmor (); + void FreeInventory ( void ); + void FreeInventory ( Event *ev ); + qboolean HasItem ( const char *itemname ); + int NumInventoryItems ( void ); + Item *NextItem ( Item *item ); + Item *PrevItem ( Item *item ); + virtual void DropInventoryItems ( void ); + void ListInventory ( void ); + virtual void setModel ( const char *model ); + virtual void Archive ( Archiver &arc ); + virtual void ArchivePersistantData ( Archiver &arc, qboolean sublevelTransition ); + virtual qboolean DoGib ( int meansofdeath ); + qboolean Immune ( int meansofdeath ); + qboolean Resistant ( int meansofdeath ); + int GetResistanceModifier ( int meansofdeath ); + void SetMeleeAttack ( bool value ); + void SetAttackBlocked ( qboolean blocked ); + void SetOffsetColor ( float r, float g, float b, float time ); + virtual void ReceivedItem ( Item * item ); + virtual void RemovedItem ( Item * item ); + virtual void AmmoAmountChanged ( Ammo * ammo, int inclip = 0 ); + void AmmoAmountInClipChanged ( const str &ammo_type, int amount ); + void TryLightOnFire ( int meansofdeath, Entity *attacker ); + virtual void GetStateAnims ( Container *c ); + void SetArmorValue ( int armorVal ); + int GetArmorValue ( void ); + virtual void setViewMode ( const str &viewModeName ); + unsigned int getViewMode ( void ); + void AddHealth ( float healthToAdd, float maxHealth = 0.0f ); + void DetachAllActiveWeapons ( void ); + void AttachAllActiveWeapons ( void ); + qboolean IsActiveWeapon ( const Weapon *weapon ); + void ActivateWeapon ( Weapon *weapon, weaponhand_t hand ); + void DeactivateWeapon ( Weapon *weapon ); + void DeactivateWeapon ( weaponhand_t hand ); + void SetHealth ( float newHealth ); + void CheckDamageThreshold ( float damageValue ); + void WeaponEffectsAndSound ( Entity *weapon, const str& objname, Vector pos ); + + void cacheStateMachineAnims( Event *ev ); + + // + // Accessors + // + void SetCriticalHealthPercentage ( float percentage ); + float GetCriticalHealthPercentage (); + bool getHeadWatchAllowed () { return _headWatchAllowed; } + virtual const str getName() const { return ""; } + + void freeConditionals( Container &conditionalsToDelete ); + }; + + +inline void Sentient::SetCriticalHealthPercentage( float percentage ) +{ + _criticalHealthPercentage = percentage; +} + +inline void Sentient::SetCriticalHealthPercentage( Event *ev ) +{ + SetCriticalHealthPercentage(ev->GetFloat( 1 ) ); +} + +inline float Sentient::GetCriticalHealthPercentage() +{ + return _criticalHealthPercentage; +} + +typedef SafePtr SentientPtr; + +extern Container SentientList; + +#endif /* sentient.h */ diff --git a/dlls/game/shrapnelbomb.cpp b/dlls/game/shrapnelbomb.cpp new file mode 100644 index 0000000..cccee9e --- /dev/null +++ b/dlls/game/shrapnelbomb.cpp @@ -0,0 +1,143 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/shrapnelbomb.cpp $ +// $Revision:: 11 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: + +#include "_pch_cpp.h" +#include "shrapnelbomb.h" + +Event EV_ShrapnelBomb_ShrapnelModel +( + "setshrapnelmodel", + EV_TIKIONLY, + "s", + "modelname", + "The model of the shrapnel pieces that are spawned when the main bomb explodes" +); +Event EV_ShrapnelBomb_ShrapnelCount +( + "setshrapnelcount", + EV_TIKIONLY, + "i", + "count", + "The number of shrapnel pieces to spawn" +); + +CLASS_DECLARATION( Projectile, ShrapnelBomb, NULL ) +{ + { &EV_ShrapnelBomb_ShrapnelCount, &ShrapnelBomb::SetShrapnelCount }, + { &EV_ShrapnelBomb_ShrapnelModel, &ShrapnelBomb::SetShrapnelModel }, + + + { NULL, NULL } +}; + +ShrapnelBomb::ShrapnelBomb() +{ + if ( LoadingSavegame ) return; + + shrapnelCount = 5; + + turnThinkOn(); + + _splitOnDescent = true; + + _randomSpread = true; +} + +void ShrapnelBomb::Think( void ) +{ + if ( _splitOnDescent && ( level.time >= startTime + minlife ) ) + { + if ( velocity.z < 0.0f ) + { + Explode(); + } + } +} + +void ShrapnelBomb::Explode( Event *ev ) +{ + Explode(); +} + +void ShrapnelBomb::Explode( void ) +{ + Vector dir; + int i; + Vector angles; + Vector left; + + //Spawn shrapnel + for ( i = 0 ; i < shrapnelCount ; i++ ) + { + dir = velocity; + dir.normalize(); + + angles = dir.toAngles(); + + angles.AngleVectors( NULL, &left, NULL ); + + if ( _randomSpread ) + { + dir += left * G_CRandom( .5f ); + } + else + { + //dir += left * G_CRandom( .5f ); + dir += left * ( ( i * 0.1f ) - ( ( shrapnelCount - 1 ) * 0.1f / 2.0f ) ); + dir += left * 0.05; + } + + dir.normalize(); + + /* dir = origin; + dir.normalize(); + + //Adjust Vector + dir += Vector ( G_CRandom( .5f ), G_CRandom( .5f ), G_CRandom( .5f ) ); + + //Get a Random Yaw + float yaw = G_Random( 360.0f ); + Vector angles = dir.toAngles(); + + //Set the new Yaw + angles[YAW] = yaw; + + //Set the new dir vector for the spawned shrapnel + Vector newDir; + angles.AngleVectors( &newDir ); + + + ProjectileAttack( this->origin, newDir, this, shrapnelModel, 1.0f, ( velocity.length() / 2.0f ) ); */ + ProjectileAttack( this->origin, dir, this, shrapnelModel, 1.0f, ( velocity.length() / 2.0f ) ); + } + + //velocity = Vector ( 0.0f, 0.0f, 0.0f ); + //setMoveType( MOVETYPE_NONE ); + this->animate->RandomAnimate("explode"); + + //PostEvent( EV_Remove, 1.0f ); + PostEvent( EV_Remove, 0.0f ); +} + +void ShrapnelBomb::SetShrapnelCount( Event *ev ) +{ + shrapnelCount = ev->GetInteger( 1 ); +} + +void ShrapnelBomb::SetShrapnelModel( Event *ev ) +{ + shrapnelModel = ev->GetString( 1 ); +} diff --git a/dlls/game/shrapnelbomb.h b/dlls/game/shrapnelbomb.h new file mode 100644 index 0000000..8c53735 --- /dev/null +++ b/dlls/game/shrapnelbomb.h @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/shrapnelbomb.h $ +// $Revision:: 7 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: + +#ifndef __SHRAPNELBOMB_H__ +#define __SHRAPNELBOMB_H__ + +#include "weapon.h" +#include "weaputils.h" + +class ShrapnelBomb : public Projectile + { + private: + str shrapnelModel; + int shrapnelCount; + bool _splitOnDescent; + bool _randomSpread; + + public: + CLASS_PROTOTYPE( ShrapnelBomb ); + + ShrapnelBomb(); + void SetShrapnelModel ( Event *ev ); + void SetShrapnelCount ( Event *ev ); + + //Override + void Explode ( Event *ev ); + + void Explode( void ); + + /* virtual */ void Think( void ); + + virtual void Archive ( Archiver &arc ); + + }; + +inline void ShrapnelBomb::Archive ( Archiver &arc ) + { + Projectile::Archive( arc ); + + arc.ArchiveString( &shrapnelModel ); + arc.ArchiveInteger( &shrapnelCount ); + arc.ArchiveBool( &_splitOnDescent ); + arc.ArchiveBool( &_randomSpread ); + } + + +#endif //__SHRAPNELBOMB_H__ diff --git a/dlls/game/snipeEnemy.cpp b/dlls/game/snipeEnemy.cpp new file mode 100644 index 0000000..e7a122a --- /dev/null +++ b/dlls/game/snipeEnemy.cpp @@ -0,0 +1,856 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/snipeEnemy.cpp $ +// $Revision:: 10 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// torsoAimAndFireWeapon Implementation +// +// PARAMETERS: +// +// ANIMATIONS: +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "snipeEnemy.hpp" +#include + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, SnipeEnemy, NULL ) + { + { &EV_Behavior_Args, &SnipeEnemy::SetArgs }, + { &EV_Behavior_AnimDone, &SnipeEnemy::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SnipeEnemy() +// Class: SnipeEnemy +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +SnipeEnemy::SnipeEnemy() +{ + _maxTorsoTurnSpeed = 15.0f; + _maxTorsoYaw = 90.0; + _maxTorsoPitch = 40.0; + _shots = 1; + _fireFailed = false; + +} + +//-------------------------------------------------------------- +// Name: ~SnipeEnemy() +// Class: SnipeEnemy +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +SnipeEnemy::~SnipeEnemy() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: SnipeEnemy +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::SetArgs( Event *ev ) +{ + _aimTime = ev->GetFloat( 1 ); + _lockDownTime = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + { + _targetSpread.x = ev->GetFloat(3); + _targetSpread.y = ev->GetFloat(4); + _targetSpread.z = ev->GetFloat(5); + } + +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: SnipeEnemy +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::AnimDone( Event *ev ) +{ + _animDone = true; +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: SnipeEnemy +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::Begin( Actor &self ) +{ + init( self ); + + if (_preFireAnim.length()) + transitionToState ( SNIPE_AIM_AND_FIRE_PRE_FIRE ); + else + transitionToState ( SNIPE_AIM_AND_FIRE_AIM ); +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: SnipeEnemy +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t SnipeEnemy::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case SNIPE_AIM_AND_FIRE_PRE_FIRE: + //--------------------------------------------------------------------- + stateResult = evaluateStatePreFire(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( SNIPE_AIM_AND_FIRE_AIM ); + } + + if ( stateResult == BEHAVIOR_FAILED ) + { + transitionToState( SNIPE_AIM_AND_FIRE_FAILED ); + } + break; + + //--------------------------------------------------------------------- + case SNIPE_AIM_AND_FIRE_AIM: + //--------------------------------------------------------------------- + stateResult = evaluateStateAim(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( SNIPE_AIM_AND_FIRE_ATTACK ); + } + + if ( stateResult == BEHAVIOR_FAILED ) + { + transitionToState( SNIPE_AIM_AND_FIRE_FAILED ); + } + break; + + + //--------------------------------------------------------------------- + case SNIPE_AIM_AND_FIRE_ATTACK: + //--------------------------------------------------------------------- + stateResult = evaluateStateAttack(); + + if ( stateResult == BEHAVIOR_FAILED ) + { + _fireWeapon.End( *_self ); + + if ( _postFireAnim.length() ) + transitionToState( SNIPE_AIM_AND_FIRE_POST_FIRE ); + else + transitionToState( SNIPE_AIM_AND_FIRE_FAILED ); + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + _fireWeapon.End( *_self ); + + if ( _postFireAnim.length() ) + transitionToState( SNIPE_AIM_AND_FIRE_POST_FIRE ); + else + transitionToState( SNIPE_AIM_AND_FIRE_SUCCESS ); + } + break; + + //--------------------------------------------------------------------- + case SNIPE_AIM_AND_FIRE_POST_FIRE: + //--------------------------------------------------------------------- + stateResult = evaluateStatePostFire(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( SNIPE_AIM_AND_FIRE_SUCCESS ); + } + break; + + //--------------------------------------------------------------------- + case SNIPE_AIM_AND_FIRE_SUCCESS: + //--------------------------------------------------------------------- + _self->SetControllerAngles( ACTOR_TORSO_TAG, vec_zero ); + return BEHAVIOR_SUCCESS; + + break; + + + //--------------------------------------------------------------------- + case SNIPE_AIM_AND_FIRE_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + + break; + + + } + + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: SnipeEnemy +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::End(Actor &self) +{ + if ( !_self ) + return; + + + _fireWeapon.End(*_self); + //gi.Printf( "TorsoAimAndFireWeapon::End()\n"); + _self->SetControllerAngles( ACTOR_TORSO_TAG, vec_zero ); + _self->ClearTorsoAnim(); + _self->SetEnemyTargeted( false ); + //_self->SetAnim( _aimAnim , NULL , torso ); +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: SnipeEnemy +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::transitionToState( SnipeAimAndFireStates_t state ) +{ + switch ( state ) + { + case SNIPE_AIM_AND_FIRE_AIM: + setupStateAim(); + setInternalState( state , "SNIPE_AIM_AND_FIRE_AIM" ); + break; + + case SNIPE_AIM_AND_FIRE_PRE_FIRE: + setupStatePreFire(); + setInternalState( state , "SNIPE_AIM_AND_FIRE_PRE_FIRE" ); + break; + + case SNIPE_AIM_AND_FIRE_ATTACK: + setupStateAttack(); + setInternalState( state , "SNIPE_AIM_AND_FIRE_ATTACK" ); + break; + + case SNIPE_AIM_AND_FIRE_POST_FIRE: + setupStatePostFire(); + setInternalState( state , "SNIPE_AIM_AND_FIRE_POST_FIRE" ); + break; + + case SNIPE_AIM_AND_FIRE_SUCCESS: + setInternalState( state , "SNIPE_AIM_AND_FIRE_SUCCESS" ); + break; + + case SNIPE_AIM_AND_FIRE_FAILED: + setInternalState( state , "SNIPE_AIM_AND_FIRE_FAILED" ); + break; + } + +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: SnipeEnemy +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::setInternalState( SnipeAimAndFireStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: TorsoAimAndFireWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::init( Actor &self ) +{ + _self = &self; + + //Set Our Controller Tag and set up our angles + self.SetControllerTag( ACTOR_TORSO_TAG, gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Spine1" ) ); + _currentTorsoAngles = self.GetControllerAngles( ACTOR_TORSO_TAG ); + + _animDone = false; + _canAttack = true; + + _aimAnim = "idle"; + _preFireAnim = "idle"; + _fireAnim = "idle"; + _postFireAnim = "idle"; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(self.getArchetype()) ) + return; + + str objname = self.combatSubsystem->GetActiveWeaponArchetype(); + objname = "Hold" + objname; + + if ( gpm->hasProperty(objname, "Aim") ) + _aimAnim = gpm->getStringValue( objname , "Aim" ); + + if ( gpm->hasProperty( objname , "PreFire" ) ) + _preFireAnim = gpm->getStringValue( objname , "PreFire" ); + + if ( gpm->hasProperty( objname , "Fire" ) ) + _fireAnim = gpm->getStringValue( objname , "Fire" ); + + if ( gpm->hasProperty( objname , "PostFire" ) ) + _postFireAnim = gpm->getStringValue( objname , "PostFire" ); + + if ( gpm->hasProperty( objname , "ShotCount" ) ) + _shots = (int)gpm->getFloatValue( objname , "ShotCount" ); + + float spreadX, spreadY; + spreadX = self.combatSubsystem->GetDataForMyWeapon( "spreadx" ); + spreadY = self.combatSubsystem->GetDataForMyWeapon( "spready" ); + self.combatSubsystem->OverrideSpread( spreadX , spreadY ); + + + //Clear Out Our VolleyCount + _self->shotsFiredThisVolley = 0; + + updateEnemy(); + +} + +//-------------------------------------------------------------- +// Name: think() +// Class: SnipeEnemy +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::think() +{ + int tagNum; + Vector tagPos; + Vector watchPosition; + Actor *actTarget; + actTarget = NULL; + str targetBone; + + + tagNum = gi.Tag_NumForName( _self->edict->s.modelindex, "Bip01 Spine1" ); + + if ( tagNum < 0 ) + return; + + _self->GetTag( "Bip01 Spine1", &tagPos ); + + if ( !_currentEnemy ) + { + LerpTorsoBySpeed( vec_zero ); + return; + } + + if ( _currentEnemy->isSubclassOf( Actor ) ) + { + actTarget = (Actor *)(Entity *)_currentEnemy; + + // Don't watch if the target is dead. + if ( !actTarget->isThinkOn() ) + { + _currentEnemy = NULL; + actTarget = NULL; + return; + } + } + + targetBone = _currentEnemy->getTargetPos(); + + watchPosition = _currentEnemy->centroid; + + if ( targetBone.length() ) + { + if ( gi.Tag_NumForName( _currentEnemy->edict->s.modelindex , targetBone ) > 0 ) + { + _currentEnemy->GetTag( targetBone.c_str() , &watchPosition , NULL , NULL , NULL ); + } + } + + + + + AdjustTorsoAngles( tagPos , watchPosition ); + +} + + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: SnipeEnemy +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::updateEnemy() +{ + // + // First, we try and get our current enemy. If we don't have one + // then we ask the enemyManager to evaluate and try again. + // If that fails, then we look at our _forceAttack status. + // If we are forcing and attack, then that means we want to + // fire pretty much no matter what... So if we don't have a + // currentEnemy, then I'm going to see if the Player is a valid + // target, and set that to be the current enemy + + Entity *currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + Player* player; + player = GetPlayer( 0 ); + } + + } + + _currentEnemy = currentEnemy; +} + +//-------------------------------------------------------------- +// Name: LerpTorsoBySpeed() +// Class: SnipeEnemy +// +// Description: Lerps the torso +// +// Parameters: const Vector &angleDelta +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::LerpTorsoBySpeed( const Vector &angleDelta ) + { + Vector anglesDiff; + Vector change; + Vector finalAngles; + Vector currentTorsoAngles; + + anglesDiff = angleDelta; + + //Reset our Controller Tag + _self->SetControllerTag( ACTOR_TORSO_TAG, gi.Tag_NumForName( _self->edict->s.modelindex, "Bip01 Spine1" ) ); + + + // Make sure we don't change our head angles too much at once + change = anglesDiff - _currentTorsoAngles; + + if ( change[YAW] > _maxTorsoTurnSpeed ) + anglesDiff[YAW] = _currentTorsoAngles[YAW] + _maxTorsoTurnSpeed; + else if ( change[YAW] < -_maxTorsoTurnSpeed ) + anglesDiff[YAW] = _currentTorsoAngles[YAW] - _maxTorsoTurnSpeed; + + if ( change[PITCH] > _maxTorsoTurnSpeed ) + anglesDiff[PITCH] = _currentTorsoAngles[PITCH] + _maxTorsoTurnSpeed; + else if ( change[PITCH] < -_maxTorsoTurnSpeed ) + anglesDiff[PITCH] = _currentTorsoAngles[PITCH] - _maxTorsoTurnSpeed; + + + finalAngles = anglesDiff; + + _self->SetControllerAngles( ACTOR_TORSO_TAG, finalAngles ); + + + _currentTorsoAngles = anglesDiff; + + } + +//-------------------------------------------------------------- +// Name: AdjustTorsoAngles() +// Class: TorsoAimAndFireWeapon +// +// Description: Adjusts the Torso Angles +// +// Parameters: const Vector &tagPos +// const Vector &watchPos +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::AdjustTorsoAngles( const Vector &tagPos , const Vector &watchPosition ) + { + Vector dir; + Vector angles; + Vector anglesDiff; + float yawChange; + float pitchChange; + + + dir = watchPosition - tagPos; + angles = dir.toAngles(); + + anglesDiff = angles - _self->angles; + + + anglesDiff[YAW] = AngleNormalize180( anglesDiff[YAW] ); + anglesDiff[PITCH] = AngleNormalize180( anglesDiff[PITCH] ); + + yawChange = anglesDiff[YAW]; + pitchChange = anglesDiff[PITCH]; + + // Make sure we don't turn torso too far + if ( anglesDiff[YAW] < -_maxTorsoYaw ) + anglesDiff[YAW] = -_maxTorsoYaw; + else if ( anglesDiff[YAW] > _maxTorsoYaw ) + anglesDiff[YAW] = _maxTorsoYaw; + + if ( anglesDiff[PITCH] < -_maxTorsoPitch ) + anglesDiff[PITCH] = -_maxTorsoPitch; + else if ( anglesDiff[PITCH] > _maxTorsoPitch ) + anglesDiff[PITCH] = _maxTorsoPitch; + + anglesDiff[ROLL] = 0.0f; + + + LerpTorsoBySpeed( anglesDiff ); + + } + + +//-------------------------------------------------------------- +// Name: setupStateAim() +// Class: SnipeEnemy +// +// Description: Sets up the Aim State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::setupStateAim() +{ + Weapon *weapon; + _animDone = false; + _endAimTime = level.time + _aimTime; + _endLockDownTime = level.time + _lockDownTime; + _self->SetAnim( _aimAnim , EV_Actor_NotifyTorsoBehavior , torso ); + _self->SetEnemyTargeted( true ); + + weapon = _self->GetActiveWeapon( WeaponHandNameToNum( "dual" ) ); + + if ( weapon ) + weapon->playAnim( "aim" ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateAim() +// Class: SnipeEnemy +// +// Description: Evaluates the Aim State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t SnipeEnemy::evaluateStateAim() +{ + str targetBone; + + if ( level.time > _endAimTime ) + { + _self->SetEnemyTargeted( false ); + return BEHAVIOR_SUCCESS; + } + + if ( _self->combatSubsystem->CanAttackEnemy() ) + { + targetBone = _currentEnemy->getTargetPos(); + _lastGoodPosition = _currentEnemy->centroid; + + if ( targetBone.length() ) + { + if ( gi.Tag_NumForName( _currentEnemy->edict->s.modelindex , targetBone ) > 0 ) + { + _currentEnemy->GetTag( targetBone.c_str() , &_lastGoodPosition , NULL , NULL , NULL ); + } + } + } + else + { + if ( level.time < _endLockDownTime ) + { + _self->SetEnemyTargeted( false ); + return BEHAVIOR_FAILED; + } + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAim() +// Class: SnipeEnemy +// +// Description: Failure Handler for the Failure State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::failureStateAim( const str& failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: setupStatePreFire() +// Class: SnipeEnemy +// +// Description: Sets up the PreFire State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::setupStatePreFire() +{ + _animDone = false; + _self->SetAnim( _preFireAnim , EV_Actor_NotifyTorsoBehavior , torso ); +} + +//-------------------------------------------------------------- +// Name: evaluateStatePreFire() +// Class: SnipeEnemy +// +// Description: Evaluates the Pre Fire state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t SnipeEnemy::evaluateStatePreFire() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + if ( !_canAttack ) + return BEHAVIOR_FAILED; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStatePreFire() +// Class: SnipeEnemy +// +// Description: Failure Handler for the Pre Fire State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::failureStatePreFire( const str& failureReason ) +{ + +} + + +//-------------------------------------------------------------- +// Name: setupStateAttack() +// Class: SnipeEnemy +// +// Description: Setup Attack State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::setupStateAttack() +{ + float spreadx = G_CRandom(_targetSpread.x ) + _targetSpread.x; + float spready = G_CRandom(_targetSpread.y ) + _targetSpread.y; + float spreadz = G_CRandom(_targetSpread.z ) + _targetSpread.z; + + _lastGoodPosition.x = _lastGoodPosition.x + spreadx; + _lastGoodPosition.y = _lastGoodPosition.y + spready; + _lastGoodPosition.z = _lastGoodPosition.z + spreadz; + + _animDone = false; + _fireWeapon.SetTargetPosition(_lastGoodPosition); + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); + +} + +//-------------------------------------------------------------- +// Name: evaluateStateAttack() +// Class: SnipeEnemy +// +// Description: Evaluates Attack State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t SnipeEnemy::evaluateStateAttack() +{ + BehaviorReturnCode_t result; + + if ( _fireFailed ) + return BEHAVIOR_FAILED; + + result = _fireWeapon.Evaluate( *_self ); + if ( _self->shotsFiredThisVolley >= _shots ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAttack() +// Class: SnipeEnemy +// +// Description: Failure Handler for Attack State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::failureStateAttack( const str& failureReason ) +{ + SetFailureReason( failureReason ); + _fireWeapon.End( *_self ); +} + +//-------------------------------------------------------------- +// Name: setupStatePostFire() +// Class: SnipeEnemy +// +// Description: Sets up Post Fire State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::setupStatePostFire() +{ + _animDone = false; + _self->SetAnim( _postFireAnim , EV_Actor_NotifyTorsoBehavior , torso ); +} + +//-------------------------------------------------------------- +// Name: evaluateStatePostFire() +// Class: SnipeEnemy +// +// Description: Evaluates State Post Fire +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t SnipeEnemy::evaluateStatePostFire() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_FAILED; +} + +//-------------------------------------------------------------- +// Name: failureStatePostFire() +// Class: SnipeEnemy +// +// Description: Failure State Post Fire +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void SnipeEnemy::failureStatePostFire( const str& failureReason ) +{ + +} + diff --git a/dlls/game/snipeEnemy.hpp b/dlls/game/snipeEnemy.hpp new file mode 100644 index 0000000..8a95827 --- /dev/null +++ b/dlls/game/snipeEnemy.hpp @@ -0,0 +1,193 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/torsoAimAndFireWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// SnipeEnemy Behavior Definition +// +//-------------------------------------------------------------------------------- + + +//============================== +// Forward Declarations +//============================== +class SnipeEnemy; + +#ifndef __SNIPE_ENEMY___ +#define __SNIPE_ENEMY___ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: TorsoAimAndFireWeapon +// Base Class: Behavior +// +// Description: Aims the torso at the current enemy ( If the +// current enemy is within the passed in limits ) +// +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class SnipeEnemy : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + SNIPE_AIM_AND_FIRE_AIM, + SNIPE_AIM_AND_FIRE_PRE_FIRE, + SNIPE_AIM_AND_FIRE_ATTACK, + SNIPE_AIM_AND_FIRE_POST_FIRE, + SNIPE_AIM_AND_FIRE_SUCCESS, + SNIPE_AIM_AND_FIRE_FAILED + } SnipeAimAndFireStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + float _aimTime; + float _lockDownTime; + float _maxTorsoYaw; + float _maxTorsoPitch; + float _maxTorsoTurnSpeed; + int _shots; + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( SnipeAimAndFireStates_t state ); + void setInternalState ( SnipeAimAndFireStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + void AdjustTorsoAngles ( const Vector &tagPos , const Vector &watchPosition ); + void LerpTorsoBySpeed ( const Vector &angleDelta ); + + void setupStateAim (); + BehaviorReturnCode_t evaluateStateAim (); + void failureStateAim ( const str& failureReason ); + + void setupStatePreFire (); + BehaviorReturnCode_t evaluateStatePreFire (); + void failureStatePreFire ( const str& failureReason ); + + void setupStateAttack (); + BehaviorReturnCode_t evaluateStateAttack (); + void failureStateAttack ( const str& failureReason ); + + void setupStatePostFire (); + BehaviorReturnCode_t evaluateStatePostFire (); + void failureStatePostFire ( const str& failureReason ); + + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( SnipeEnemy ); + + SnipeEnemy(); + ~SnipeEnemy(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + // Accessors + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + FireWeapon _fireWeapon; + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + SnipeAimAndFireStates_t _state; + Vector _currentTorsoAngles; + float _endAimTime; + float _endLockDownTime; + EntityPtr _currentEnemy; + bool _canAttack; + bool _animDone; + str _aimAnim; + str _preFireAnim; + str _fireAnim; + str _postFireAnim; + bool _fireFailed; + Vector _lastGoodPosition; + Vector _targetSpread; + Actor *_self; + + + }; + +inline void SnipeEnemy::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveFloat( &_aimTime ); + arc.ArchiveFloat( &_lockDownTime ); + arc.ArchiveFloat( &_maxTorsoYaw ); + arc.ArchiveFloat( &_maxTorsoPitch ); + arc.ArchiveFloat( &_maxTorsoTurnSpeed ); + arc.ArchiveInteger( &_shots ); + arc.ArchiveVector( &_lastGoodPosition ); + arc.ArchiveVector( &_targetSpread ); + + + // + // Archive Components + // + arc.ArchiveObject ( &_fireWeapon ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, SnipeAimAndFireStates_t ); + arc.ArchiveVector ( &_currentTorsoAngles ); + + arc.ArchiveFloat( &_endAimTime ); + arc.ArchiveFloat( &_endLockDownTime ); + arc.ArchiveSafePointer( &_currentEnemy ); + arc.ArchiveBool( &_canAttack ); + arc.ArchiveBool( &_animDone ); + arc.ArchiveString( &_aimAnim ); + arc.ArchiveString( &_preFireAnim ); + arc.ArchiveString( &_fireAnim ); + arc.ArchiveString( &_postFireAnim ); + arc.ArchiveBool( &_fireFailed ); + + arc.ArchiveObjectPointer( ( Class ** )&_self ); +} + + +#endif /* __SNIPE_ENEMY__ */ + diff --git a/dlls/game/soundman.cpp b/dlls/game/soundman.cpp new file mode 100644 index 0000000..38e2dcc --- /dev/null +++ b/dlls/game/soundman.cpp @@ -0,0 +1,1655 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/soundman.cpp $ +// $Revision:: 10 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Sound Manager +// + +#include "_pch_cpp.h" +//#include "g_local.h" +#include "entity.h" +#include "trigger.h" +#include "player.h" +#include "soundman.h" + +SoundManager SoundMan; + +/****************************************************************************** + + Sound Manager + +******************************************************************************/ + +Event EV_SoundManager_AddSpeaker +( + "addspeaker", + EV_CONSOLE, + NULL, + NULL, + "Add a new sound where the player is standing." +); +Event EV_SoundManager_AddRandomSpeaker +( + "addrandomspeaker", + EV_CONSOLE, + NULL, + NULL, + "Add a new sound where the player is standing." +); +Event EV_SoundManager_AddMusicTrigger +( + "addmusictrigger", + EV_CONSOLE, + NULL, + NULL, + "Add a new music trigger where the player is standing." +); +Event EV_SoundManager_AddReverbTrigger +( + "addreverbtrigger", + EV_CONSOLE, + NULL, + NULL, + "Add a new reverb trigger where the player is standing." +); +Event EV_SoundManager_Replace +( + "replace", + EV_CONSOLE, + NULL, + NULL, + "Replace the current sound position with the player's." +); +Event EV_SoundManager_Delete +( + "delete", + EV_CONSOLE, + NULL, + NULL, + "Delete the current sound." +); +Event EV_SoundManager_MovePlayer +( + "moveplayer", + EV_CONSOLE, + NULL, + NULL, + "Move the player to the current sound position." +); +Event EV_SoundManager_Next +( + "next", + EV_CONSOLE, + NULL, + NULL, + "Go to the next sound." +); +Event EV_SoundManager_Previous +( + "prev", + EV_CONSOLE, + NULL, + NULL, + "Go to the previous sound." +); +Event EV_SoundManager_Show +( + "show", + EV_DEFAULT, + "E", + "path", + "Show all the sounds." +); +Event EV_SoundManager_ShowingSounds +( + "_showing_sounds", + EV_CONSOLE, + NULL, + NULL, + "Internal event for showing the sounds." +); +Event EV_SoundManager_Hide +( + "hide", + EV_DEFAULT, + NULL, + NULL, + "Hides the sounds." +); +Event EV_SoundManager_Save +( + "save", + EV_CONSOLE, + NULL, + NULL, + "Saves the sounds." +); +Event EV_SoundManager_UpdateInput +( + "updateinput", + EV_CONSOLE, + NULL, + NULL, + "Updates the current sound with user interface values." +); +Event EV_SoundManager_Reset +( + "reset", + EV_CONSOLE, + NULL, + NULL, + "Resets the state of all sounds and triggers." +); +Event EV_SoundManager_GlobalTranslate +( + "globaltranslate", + EV_CONSOLE, + "v", + "translate_amount", + "Translates all sounds and triggers by specified amount." +); +Event EV_SoundManager_SwitchFacet +( + "switchfacet", + EV_CONSOLE, + NULL, + NULL, + "Switch the current facet that we are editing." +); +Event EV_SoundManager_PreviewReverb +( + "previewreverb", + EV_CONSOLE, + NULL, + NULL, + "Test out the current reverb settings." +); +Event EV_SoundManager_ResetReverb +( + "resetreverb", + EV_CONSOLE, + NULL, + NULL, + "reset the reverb settings to a normal." +); + +CLASS_DECLARATION( Listener, SoundManager, NULL ) +{ + { &EV_SoundManager_AddSpeaker, &SoundManager::AddSpeaker }, + { &EV_SoundManager_AddRandomSpeaker, &SoundManager::AddRandomSpeaker }, + { &EV_SoundManager_AddMusicTrigger, &SoundManager::AddMusicTrigger }, + { &EV_SoundManager_AddReverbTrigger, &SoundManager::AddReverbTrigger }, + { &EV_SoundManager_Replace, &SoundManager::Replace }, + { &EV_SoundManager_Delete, &SoundManager::Delete }, + { &EV_SoundManager_MovePlayer, &SoundManager::MovePlayer }, + { &EV_SoundManager_Next, &SoundManager::Next }, + { &EV_SoundManager_Previous, &SoundManager::Previous }, + { &EV_SoundManager_Show, &SoundManager::Show }, + { &EV_SoundManager_ShowingSounds, &SoundManager::ShowingSounds }, + { &EV_SoundManager_Hide, &SoundManager::Hide }, + { &EV_SoundManager_Save, &SoundManager::Save }, + { &EV_SoundManager_UpdateInput, &SoundManager::UpdateEvent }, + { &EV_SoundManager_Reset, &SoundManager::ResetEvent }, + { &EV_SoundManager_GlobalTranslate, &SoundManager::GlobalTranslateEvent }, + { &EV_SoundManager_SwitchFacet, &SoundManager::SwitchFacetEvent }, + { &EV_SoundManager_PreviewReverb, &SoundManager::PreviewReverbEvent }, + { &EV_SoundManager_ResetReverb, &SoundManager::ResetReverbEvent }, + + { NULL, NULL } +}; + +Player *SoundManager_GetPlayer( void ) +{ + assert( g_entities[ 0 ].entity && g_entities[ 0 ].entity->isSubclassOf( Player ) ); + if ( !g_entities[ 0 ].entity || !( g_entities[ 0 ].entity->isSubclassOf( Player ) ) ) + { + gi.WPrintf( "No player found.\n" ); + return NULL; + } + + return ( Player * )g_entities[ 0 ].entity; +} + +SoundManager::SoundManager() +{ + Init(); +} + +void SoundManager::Init( void ) +{ + currentFacet = 0; + Reset(); +} + +void SoundManager::Reset( void ) +{ + current = NULL; + soundList.ClearObjectList(); +} + +void SoundManager::UpdateUI( void ) +{ + if ( current ) + { + gi.cvar_set( "snd_multifaceted", "" ); + gi.cvar_set( "snd_currentfacet", "" ); + gi.cvar_set( "snd_onetime", "0" ); + gi.cvar_set( "snd_useangles", "0" ); + gi.cvar_set( "snd_yaw", "0" ); + + gi.cvar_set( "snd_origin", va( "%.2f %.2f %.2f", current->origin[ 0 ], current->origin[ 1 ], current->origin[ 2 ] ) ); + gi.cvar_set( "snd_targetname", current->targetname.c_str() ); + if ( current->isSubclassOf( TriggerSpeaker ) ) + { + TriggerSpeaker * speaker; + + speaker = ( TriggerSpeaker * )current; + if ( speaker->volume != DEFAULT_VOL ) + { + gi.cvar_set( "snd_volume", va( "%.1f", speaker->volume ) ); + } + else + { + gi.cvar_set( "snd_volume", "Default" ); + } + if ( speaker->min_dist != DEFAULT_MIN_DIST ) + { + gi.cvar_set( "snd_mindist", va( "%.1f", speaker->min_dist ) ); + } + else + { + gi.cvar_set( "snd_mindist", "Default" ); + } + + // setup the sound + gi.cvar_set( "ui_pickedsound", speaker->Noise().c_str() ); + + if ( current->isSubclassOf( RandomSpeaker ) ) + { + RandomSpeaker * random; + + random = ( RandomSpeaker * )current; + gi.cvar_set( "snd_mindelay", va( "%.1f", random->mindelay ) ); + gi.cvar_set( "snd_maxdelay", va( "%.1f", random->maxdelay ) ); + gi.cvar_set( "snd_channel", va( "%d", random->channel ) ); + gi.cvar_set( "snd_chance", va( "%.1f", random->chance ) ); + gi.cvar_set( "snd_type", "RandomSpeaker" ); + } + else + { + gi.cvar_set( "snd_type", "Speaker" ); + } + } + else if ( current->isSubclassOf( TriggerMusic ) || current->isSubclassOf( TriggerReverb ) ) + { + int multiFaceted; + Trigger *trigger; + + trigger = ( Trigger * )current; + gi.cvar_set( "snd_width", va( "%.0f", trigger->maxs[ 0 ] ) ); + gi.cvar_set( "snd_length", va( "%.0f", trigger->maxs[ 1 ] ) ); + gi.cvar_set( "snd_height", va( "%.0f", trigger->maxs[ 2 ] ) ); + if ( trigger->UsingTriggerDir() ) + { + gi.cvar_set( "snd_useangles", "1" ); + } + else + { + gi.cvar_set( "snd_useangles", "0" ); + } + gi.cvar_set( "snd_yaw", va( "%.0f", trigger->angles[ 1 ] ) ); + multiFaceted = trigger->GetMultiFaceted(); + if ( multiFaceted ) + { + if ( multiFaceted == 1 ) + { + gi.cvar_set( "snd_multifaceted", "North/South" ); + if ( currentFacet ) + { + gi.cvar_set( "snd_currentfacet", "South" ); + } + else + { + gi.cvar_set( "snd_currentfacet", "North" ); + } + } + else + { + gi.cvar_set( "snd_multifaceted", "East/West" ); + if ( currentFacet ) + { + gi.cvar_set( "snd_currentfacet", "West" ); + } + else + { + gi.cvar_set( "snd_currentfacet", "East" ); + } + } + } + else + { + gi.cvar_set( "snd_multifaceted", "Not" ); + } + + if ( current->isSubclassOf( TriggerMusic ) ) + { + TriggerMusic * music; + + music = ( TriggerMusic * )trigger; + + gi.cvar_set( "snd_type", "MusicTrigger" ); + if ( music->oneshot ) + { + gi.cvar_set( "snd_onetime", "1" ); + } + else + { + gi.cvar_set( "snd_onetime", "0" ); + } + + if ( !currentFacet ) + { + gi.cvar_set( "snd_currentmood", music->current.c_str() ); + gi.cvar_set( "snd_fallbackmood", music->fallback.c_str() ); + } + else + { + gi.cvar_set( "snd_currentmood", music->altcurrent.c_str() ); + gi.cvar_set( "snd_fallbackmood", music->altfallback.c_str() ); + } + } + else if ( current->isSubclassOf( TriggerReverb ) ) + { + TriggerReverb * reverb; + + reverb = ( TriggerReverb * )trigger; + + gi.cvar_set( "snd_type", "ReverbTrigger" ); + if ( reverb->oneshot ) + { + gi.cvar_set( "snd_onetime", "1" ); + } + else + { + gi.cvar_set( "snd_onetime", "0" ); + } + + if ( !currentFacet ) + { + gi.cvar_set( "snd_reverbtypedisplay", EAXMode_NumToName( reverb->reverbtype ) ); + gi.cvar_set( "snd_reverbtype", va( "%d", reverb->reverbtype ) ); + gi.cvar_set( "snd_reverblevel", va( "%.2f", reverb->reverblevel ) ); + } + else + { + gi.cvar_set( "snd_reverbtypedisplay", EAXMode_NumToName( reverb->altreverbtype ) ); + gi.cvar_set( "snd_reverbtype", va( "%d", reverb->altreverbtype ) ); + gi.cvar_set( "snd_reverblevel", va( "%.2f", reverb->altreverblevel ) ); + } + } + } + if ( EventPending( EV_SoundManager_ShowingSounds ) ) + { + gi.cvar_set( "snd_hiddenstate", "visible" ); + } + else + { + gi.cvar_set( "snd_hiddenstate", "hidden" ); + } + gi.cvar_set( "snd_speakernum", va( "%d", soundList.IndexOfObject( current ) - 1 ) ); + } +} + +void SoundManager::UpdateRandomSpeaker( RandomSpeaker * speaker ) +{ + cvar_t * cvar; + Vector tempvec; + + if ( !speaker ) + { + return; + } + + // get origin + cvar = gi.cvar( "snd_origin", "", 0 ); + sscanf( cvar->string, "%f %f %f", &tempvec[ 0 ], &tempvec[ 1 ], &tempvec[ 2 ] ); + speaker->setOrigin( tempvec ); + + // get targetname + cvar = gi.cvar( "snd_targetname", "", 0 ); + speaker->SetTargetName( cvar->string ); + + // get volume + cvar = gi.cvar( "snd_volume", "", 0 ); + if ( str( cvar->string ) == "Default" ) + { + speaker->SetVolume( DEFAULT_VOL ); + } + else + { + speaker->SetVolume( cvar->value ); + } + + // get min_dist + cvar = gi.cvar( "snd_mindist", "", 0 ); + if ( str( cvar->string ) == "Default" ) + { + speaker->SetMinDist( DEFAULT_MIN_DIST ); + } + else + { + speaker->SetMinDist( cvar->value ); + } + + // get mindelay + cvar = gi.cvar( "snd_mindelay", "", 0 ); + speaker->SetMinDelay( cvar->value ); + + // get maxdelay + cvar = gi.cvar( "snd_maxdelay", "", 0 ); + speaker->SetMaxDelay( cvar->value ); + + // get chance + cvar = gi.cvar( "snd_chance", "", 0 ); + speaker->SetChance( cvar->value ); + + // get sound + cvar = gi.cvar( "ui_pickedsound", "", 0 ); + if ( cvar->string[ 0 ] ) + { + speaker->SetNoise( cvar->string ); + speaker->ScheduleSound(); + } +} + + +void SoundManager::UpdateSpeaker( TriggerSpeaker * speaker ) +{ + cvar_t * cvar; + Vector tempvec; + + if ( !speaker ) + { + return; + } + + // + // make sure the speaker is an ambient speaker + // + speaker->ambient = true; + + // get origin + cvar = gi.cvar( "snd_origin", "", 0 ); + sscanf( cvar->string, "%f %f %f", &tempvec[ 0 ], &tempvec[ 1 ], &tempvec[ 2 ] ); + speaker->setOrigin( tempvec ); + + // get targetname + cvar = gi.cvar( "snd_targetname", "", 0 ); + speaker->SetTargetName( cvar->string ); + + // get volume + cvar = gi.cvar( "snd_volume", "", 0 ); + if ( str( cvar->string ) == "Default" ) + { + speaker->SetVolume( DEFAULT_VOL ); + } + else + { + speaker->SetVolume( cvar->value ); + } + + // get min_dist + cvar = gi.cvar( "snd_mindist", "", 0 ); + if ( str( cvar->string ) == "Default" ) + { + speaker->SetMinDist( DEFAULT_MIN_DIST ); + } + else + { + speaker->SetMinDist( cvar->value ); + } + + // get sound + cvar = gi.cvar( "ui_pickedsound", "", 0 ); + if ( cvar->string[ 0 ] ) + { + speaker->SetNoise( cvar->string ); + speaker->StartSound(); + } +} + +void SoundManager::UpdateTriggerMusic( TriggerMusic * music ) +{ + cvar_t *cvar; + str current; + str fallback; + str faceted; + Vector tempvec; + + if ( !music ) + { + return; + } + + // + // go to a known state + // + + music->SetOneShot( false ); + // no angle use + music->useTriggerDir = false; + // make sure it doesn't trigger + music->triggerable = false; + + // get origin + cvar = gi.cvar( "snd_origin", "", 0 ); + sscanf( cvar->string, "%f %f %f", &tempvec[ 0 ], &tempvec[ 1 ], &tempvec[ 2 ] ); + music->setOrigin( tempvec ); + + // get targetname + cvar = gi.cvar( "snd_targetname", "", 0 ); + music->SetTargetName( cvar->string ); + + // get width + cvar = gi.cvar( "snd_width", "", 0 ); + music->mins[ 0 ] = -cvar->value; + music->maxs[ 0 ] = cvar->value; + + // get length + cvar = gi.cvar( "snd_length", "", 0 ); + music->mins[ 1 ] = -cvar->value; + music->maxs[ 1 ] = cvar->value; + + // get height + cvar = gi.cvar( "snd_height", "", 0 ); + music->mins[ 2 ] = 0; + music->maxs[ 2 ] = cvar->value; + + music->setSize( music->mins, music->maxs ); + + // get current multi faceted ness + cvar = gi.cvar( "snd_multifaceted", "", 0 ); + faceted = cvar->string; + + if ( faceted == "North/South" ) + { + music->SetMultiFaceted( 1 ); + } + else if ( faceted == "East/West" ) + { + music->SetMultiFaceted( 2 ); + } + else + { + music->SetMultiFaceted( 0 ); + } + + // get current mood + cvar = gi.cvar( "snd_currentmood", "", 0 ); + current = cvar->string; + + // get fallback mood + cvar = gi.cvar( "snd_fallbackmood", "", 0 ); + fallback = cvar->string; + + if ( music->multiFaceted && currentFacet ) + { + music->SetAltMood( current, fallback ); + } + else + { + music->SetMood( current, fallback ); + } + + // get onetime + cvar = gi.cvar( "snd_onetime", "", 0 ); + if ( cvar->integer ) + { + music->SetOneShot( true ); + } + + // get yaw + cvar = gi.cvar( "snd_yaw", "", 0 ); + music->angles[ YAW ] = cvar->value; + music->setAngles(); + + // get useangles + cvar = gi.cvar( "snd_useangles", "", 0 ); + if ( cvar->integer ) + { + music->SetTriggerDir( music->angles[ YAW ] ); + } + UpdateUI(); +} + +void SoundManager::UpdateTriggerReverb( TriggerReverb * reverb ) +{ + cvar_t *cvar; + int reverbtype; + float reverblevel; + str faceted; + Vector tempvec; + + if ( !reverb ) + { + return; + } + + // + // go to a known state + // + + reverb->SetOneShot( false ); + // no angle use + reverb->useTriggerDir = false; + // make sure it doesn't trigger + reverb->triggerable = false; + + // get origin + cvar = gi.cvar( "snd_origin", "", 0 ); + sscanf( cvar->string, "%f %f %f", &tempvec[ 0 ], &tempvec[ 1 ], &tempvec[ 2 ] ); + reverb->setOrigin( tempvec ); + + // get targetname + cvar = gi.cvar( "snd_targetname", "", 0 ); + reverb->SetTargetName( cvar->string ); + + // get width + cvar = gi.cvar( "snd_width", "", 0 ); + reverb->mins[ 0 ] = -cvar->value; + reverb->maxs[ 0 ] = cvar->value; + + // get length + cvar = gi.cvar( "snd_length", "", 0 ); + reverb->mins[ 1 ] = -cvar->value; + reverb->maxs[ 1 ] = cvar->value; + + // get height + cvar = gi.cvar( "snd_height", "", 0 ); + reverb->mins[ 2 ] = 0; + reverb->maxs[ 2 ] = cvar->value; + + reverb->setSize( reverb->mins, reverb->maxs ); + + // get current multi faceted ness + cvar = gi.cvar( "snd_multifaceted", "", 0 ); + faceted = cvar->string; + + if ( faceted == "North/South" ) + { + reverb->SetMultiFaceted( 1 ); + } + else if ( faceted == "East/West" ) + { + reverb->SetMultiFaceted( 2 ); + } + else + { + reverb->SetMultiFaceted( 0 ); + } + + // get reverb type + cvar = gi.cvar( "snd_reverbtype", "", 0 ); + reverbtype = cvar->integer; + + // get reverb level + cvar = gi.cvar( "snd_reverblevel", "", 0 ); + reverblevel = cvar->value; + + if ( reverb->multiFaceted && currentFacet ) + { + reverb->SetAltReverb( reverbtype, reverblevel ); + } + else + { + reverb->SetReverb( reverbtype, reverblevel ); + } + + // get onetime + cvar = gi.cvar( "snd_onetime", "", 0 ); + if ( cvar->integer ) + { + reverb->SetOneShot( true ); + } + + // get yaw + cvar = gi.cvar( "snd_yaw", "", 0 ); + reverb->angles[ YAW ] = cvar->value; + reverb->setAngles(); + + // get useangles + cvar = gi.cvar( "snd_useangles", "", 0 ); + if ( cvar->integer ) + { + reverb->SetTriggerDir( reverb->angles[ YAW ] ); + } + UpdateUI(); +} + +void SoundManager::UpdateEvent( Event *ev ) +{ + if ( !current ) + { + return; + } + if ( current->isSubclassOf( RandomSpeaker ) ) + { + UpdateRandomSpeaker( ( RandomSpeaker * )current ); + } + else if ( current->isSubclassOf( TriggerSpeaker ) ) + { + UpdateSpeaker( ( TriggerSpeaker * )current ); + } + else if ( current->isSubclassOf( TriggerMusic ) ) + { + UpdateTriggerMusic( ( TriggerMusic * )current ); + } + else if ( current->isSubclassOf( TriggerReverb ) ) + { + UpdateTriggerReverb( ( TriggerReverb * )current ); + } +} + +void SoundManager::AddSpeaker( Event *ev ) +{ + Player *player; + Vector ang; + Vector pos; + + player = SoundManager_GetPlayer(); + if ( player ) + { + player->GetPlayerView( &pos, &ang ); + + current = new TriggerSpeaker; + current->setOrigin( pos ); + current->setAngles( ang ); + + soundList.AddUniqueObject( current ); + + Show(); + } + UpdateUI(); +} + +void SoundManager::AddRandomSpeaker( Event *ev ) +{ + Player *player; + Vector ang; + Vector pos; + + player = SoundManager_GetPlayer(); + if ( player ) + { + player->GetPlayerView( &pos, &ang ); + + current = new RandomSpeaker; + current->setOrigin( pos ); + current->setAngles( ang ); + + soundList.AddUniqueObject( current ); + + Show(); + } + UpdateUI(); +} + +void SoundManager::AddMusicTrigger( Event *ev ) +{ + Player *player; + Vector ang; + + player = SoundManager_GetPlayer(); + if ( player ) + { + player->GetPlayerView( NULL, &ang ); + + current = new TriggerMusic; + // we grab the origin from the feet of the player + current->setOrigin( player->origin ); + current->setAngles( ang ); + current->setSize( Vector(-16, -16, 0), Vector(16, 16, 64) ); + // make sure it doesn't trigger + ( ( TriggerMusic * )current )->triggerable = false; + + soundList.AddUniqueObject( current ); + + Show(); + } + UpdateUI(); +} + +void SoundManager::AddReverbTrigger( Event *ev ) +{ + Player *player; + Vector ang; + + player = SoundManager_GetPlayer(); + if ( player ) + { + player->GetPlayerView( NULL, &ang ); + + current = new TriggerReverb; + // we grab the origin from the feet of the player + current->setOrigin( player->origin ); + current->setAngles( ang ); + current->setSize( Vector(-16, -16, 0), Vector(16, 16, 64) ); + // make sure it doesn't trigger + ( ( TriggerReverb * )current )->triggerable = false; + + soundList.AddUniqueObject( current ); + + Show(); + } + UpdateUI(); +} + +void SoundManager::Replace( Event *ev ) +{ + Player *player; + Vector ang; + Vector pos; + + player = SoundManager_GetPlayer(); + if ( current && player ) + { + player->GetPlayerView( &pos, &ang ); + + if ( current->isSubclassOf( TriggerMusic ) || current->isSubclassOf( TriggerReverb ) ) + { + current->setOrigin( player->origin ); + } + else + { + current->setOrigin( pos ); + } + current->setAngles( ang ); + } + UpdateUI(); +} + +void SoundManager::Delete( Event *ev ) +{ + int index = 0; + + if ( !current ) + return; + + if ( soundList.ObjectInList( current ) ) + { + index = soundList.IndexOfObject( current ); + // remove the speaker + soundList.RemoveObject( current ); + } + current->PostEvent( EV_Remove, 0.0f ); + + if ( ( index > 0 ) && ( index < soundList.NumObjects() ) ) + { + current = soundList.ObjectAt( index ); + CurrentGainsFocus(); + } + else + { + current = NULL; + } + + UpdateUI(); +} + +void SoundManager::MovePlayer( Event *ev ) +{ + Player *player; + Vector pos; + + player = SoundManager_GetPlayer(); + if ( current && player ) + { + player->GetPlayerView( &pos, NULL ); + + if ( current->isSubclassOf( TriggerMusic ) || current->isSubclassOf( TriggerReverb ) ) + { + player->setOrigin( current->origin ); + } + else + { + player->setOrigin( current->origin - pos + player->origin ); + } + player->SetViewAngles( current->angles ); + } +} + +void SoundManager::CurrentLostFocus( void ) +{ + if ( current ) + { + if ( current->isSubclassOf( TriggerMusic ) || current->isSubclassOf( TriggerReverb ) ) + { + current->PostEvent( EV_Trigger_SetTriggerable, 0.1f ); + } + } +} + +void SoundManager::CurrentGainsFocus( void ) +{ + if ( current ) + { + if ( current->isSubclassOf( TriggerMusic ) || current->isSubclassOf( TriggerReverb ) ) + { + current->ProcessEvent( EV_Trigger_SetNotTriggerable ); + } + } +} + +void SoundManager::Next( Event *ev ) +{ + int index; + + currentFacet = 0; + + if ( current ) + { + // + // find current sound in container of sounds + // + index = soundList.IndexOfObject( current ); + if ( index < soundList.NumObjects() ) + { + index++; + } + else + { + index = 1; + } + CurrentLostFocus(); + } + else + { + index = 1; + } + + if ( index <= soundList.NumObjects() ) + { + current = soundList.ObjectAt( index ); + CurrentGainsFocus(); + UpdateUI(); + } +} + +void SoundManager::Previous( Event *ev ) +{ + int index; + + currentFacet = 0; + + if ( current ) + { + // + // find current sound in container of sounds + // + index = soundList.IndexOfObject( current ); + if ( index > 1 ) + { + index--; + } + else + { + index = soundList.NumObjects(); + } + CurrentLostFocus(); + } + else + { + index = 1; + } + + if ( index <= soundList.NumObjects() ) + { + current = soundList.ObjectAt( index ); + CurrentGainsFocus(); + UpdateUI(); + } +} + +void SoundManager::ResetEvent( Event *ev ) +{ + int i; + Entity * ent; + + for( i = 1; i <= soundList.NumObjects(); i++ ) + { + ent = soundList.ObjectAt( i ); + if ( ent->isSubclassOf( TriggerSpeaker ) ) + { + // nothing to reset + } + else if ( ent->isSubclassOf( TriggerMusic ) ) + { + TriggerMusic * music; + + music = ( TriggerMusic * )ent; + music->SetOneShot( music->oneshot ); + } + else if ( ent->isSubclassOf( TriggerReverb ) ) + { + TriggerReverb * reverb; + + reverb = ( TriggerReverb * )ent; + reverb->SetOneShot( reverb->oneshot ); + } + } +} + +void SoundManager::GlobalTranslateEvent( Event *ev ) +{ + int i; + Entity * ent; + Vector amount; + + amount = ev->GetVector( 1 ); + + for( i = 1; i <= soundList.NumObjects(); i++ ) + { + ent = soundList.ObjectAt( i ); + ent->addOrigin( amount ); + } +} + +void SoundManager::SwitchFacetEvent( Event *ev ) +{ + if ( current && current->isSubclassOf( Trigger ) ) + { + Trigger * trigger; + + trigger = ( Trigger * )current; + if ( trigger->GetMultiFaceted() && !currentFacet ) + { + currentFacet = 1; + } + else + { + currentFacet = 0; + } + } + UpdateUI(); +} + +void SoundManager::PreviewReverbEvent( Event *ev ) +{ + cvar_t *cvar; + int reverbtype; + float reverblevel; + + // get reverb type + cvar = gi.cvar( "snd_reverbtype", "", 0 ); + reverbtype = cvar->integer; + + // get reverb level + cvar = gi.cvar( "snd_reverblevel", "", 0 ); + reverblevel = cvar->value; + + if ( g_entities[ 0 ].inuse && g_entities[ 0 ].client ) + { + Player *client; + + client = ( Player * )g_entities[ 0 ].entity; + client->SetReverb( reverbtype, reverblevel ); + } +} + +void SoundManager::ResetReverbEvent( Event *ev ) +{ + if ( g_entities[ 0 ].inuse && g_entities[ 0 ].client ) + { + Player *client; + + client = ( Player * )g_entities[ 0 ].entity; + client->SetReverb( "Generic", 0.0f ); + } +} + +void SoundManager::ShowingSounds( Event *ev ) +{ + int i; + Entity * ent; + + for( i = 1; i <= soundList.NumObjects(); i++ ) + { + ent = soundList.ObjectAt( i ); + + if ( !ent ) + continue; + + if ( ent->isSubclassOf( TriggerSpeaker ) ) + { + TriggerSpeaker * speaker; + + speaker = ( TriggerSpeaker * )ent; + if ( current == ent ) + { + if ( speaker->volume != DEFAULT_VOL ) + G_DrawDebugNumber( speaker->origin + Vector( 0.0f, 0.0f, 10.0f ), speaker->volume, 0.5f, 0.0f, 1.0f, 0.0f, 2 ); + if ( speaker->min_dist != DEFAULT_MIN_DIST ) + G_DrawDebugNumber( speaker->origin + Vector( 0.0f, 0.0f, 20.0f ), speaker->min_dist, 0.5f, 0.0f, 0.0f, 1.0f, 0 ); + + // falloff circles + if ( speaker->min_dist != DEFAULT_VOL ) + { + G_DebugCircle( speaker->origin, speaker->min_dist, 0.0f, 1.0f, 1.0f, 1.0f, true ); + G_DebugCircle( speaker->origin, 2.0f * speaker->min_dist, 0.0f, 0.5f, 0.5f, 0.5f, true ); + G_DebugCircle( speaker->origin, 4.0f * speaker->min_dist, 0.0f, 0.25f, 0.25f, 0.25f, true ); + } + + if ( speaker->isSubclassOf( RandomSpeaker ) ) + { + RandomSpeaker * random; + + G_DebugPyramid( speaker->origin, 24.0f, 1.0f, 1.0f, 0.5f, 1.0f ); + random = ( RandomSpeaker * )ent; + + G_DrawDebugNumber( random->origin + Vector( 0.0f, 0.0f, 30.0f ), random->mindelay, 0.5f, 0.25f, 0.0f, 1.0f, 1 ); + G_DrawDebugNumber( random->origin + Vector( 0.0f, 0.0f, 38.0f ), random->maxdelay, 0.5f, 0.25f, 0.0f, 1.0f, 1 ); + G_DrawDebugNumber( random->origin + Vector( 0.0f, 0.0f, 46.0f ), random->chance, 0.5f, 0.25f, 0.0f, 1.0f, 2 ); + } + else + { + G_DebugPyramid( speaker->origin, 24.0f, 1.0f, 1.0f, 0.0f, 1.0f ); + } + } + else + { + if ( speaker->isSubclassOf( RandomSpeaker ) ) + { + G_DebugPyramid( speaker->origin, 24.0f, 1.0f, 0.0f, 1.0f, 1.0f ); + } + else + { + G_DebugPyramid( speaker->origin, 24.0f, 1.0f, 0.0f, 0.0f, 1.0f ); + } + } + } + else if ( ent->isSubclassOf( TriggerMusic ) ) + { + int facet; + TriggerMusic * music; + + music = ( TriggerMusic * )ent; + + if ( current == ent ) + { + if ( music->oneshot ) + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 0.0f, 1.0f, 1.0f, 1.0f ); + else + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 1.0f, 1.0f, 0.0f, 1.0f ); + } + else + { + if ( music->oneshot ) + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 0.0f, 0.5f, 0.5f, 1.0f ); + else + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 1.0f, 0.0f, 0.0f, 1.0f ); + } + + if ( music->useTriggerDir ) + { + Vector org; + + org = ent->origin; + org[ 2 ] += 0.5f * ent->maxs[ 2 ]; + + G_DebugArrow( org, music->GetTriggerDir(), 48.0f, 0.5f, 1.0f, 1.0f, 1.0f ); + } + facet = music->GetMultiFaceted(); + if ( facet ) + { + if ( current == ent ) + { + G_DebugHighlightFacet( ent->origin, ent->mins, ent->maxs, ( facet_t )( ( ( facet - 1 ) << 1 ) + currentFacet ), 1.0f, 1.0f, 1.0f, 1.0f ); + } + else + { + G_DebugHighlightFacet( ent->origin, ent->mins, ent->maxs, ( facet_t )( ( ( facet - 1 ) << 1 ) + 0 ), 0.6f, 0.3f, 0.2f, 0.1f ); + G_DebugHighlightFacet( ent->origin, ent->mins, ent->maxs, ( facet_t )( ( ( facet - 1 ) << 1 ) + 1 ), 0.6f, 0.3f, 0.2f, 0.1f ); + } + } + } + else if ( ent->isSubclassOf( TriggerReverb ) ) + { + int facet; + TriggerReverb *reverb; + + reverb = ( TriggerReverb * )ent; + + if ( current == ent ) + { + if ( reverb->oneshot ) + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 0.5f, 0.5f, 1.0f, 1.0f ); + else + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 0.0f, 0.5f, 1.0f, 1.0f ); + } + else + { + if ( reverb->oneshot ) + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 0.5f, 0.0f, 1.0f, 1.0f ); + else + G_DebugBBox( ent->origin, ent->mins, ent->maxs, 0.0f, 0.0f, 1.0f, 1.0f ); + } + + if ( reverb->useTriggerDir ) + { + Vector org; + + org = ent->origin; + org[ 2 ] += 0.5f * ent->maxs[ 2 ]; + + G_DebugArrow( org, reverb->GetTriggerDir(), 48.0f, 0.5f, 1.0f, 1.0f, 1.0f ); + } + facet = reverb->GetMultiFaceted(); + if ( facet ) + { + if ( current == ent ) + { + G_DebugHighlightFacet( ent->origin, ent->mins, ent->maxs, ( facet_t )( ( ( facet - 1 ) << 1 ) + currentFacet ), 1.0f, 1.0f, 1.0f, 1.0f ); + } + else + { + G_DebugHighlightFacet( ent->origin, ent->mins, ent->maxs, ( facet_t )( ( ( facet - 1 ) << 1 ) + 0 ), 0.3f, 0.3f, 0.6f, 0.1f ); + G_DebugHighlightFacet( ent->origin, ent->mins, ent->maxs, ( facet_t )( ( ( facet - 1 ) << 1 ) + 1 ), 0.3f, 0.3f, 0.6f, 0.1f ); + } + } + } + } + PostEvent( EV_SoundManager_ShowingSounds, FRAMETIME ); +} + +void SoundManager::Show( void ) +{ + CurrentGainsFocus(); + CancelEventsOfType( EV_SoundManager_ShowingSounds ); + PostEvent( EV_SoundManager_ShowingSounds, FRAMETIME ); + UpdateUI(); +} + +void SoundManager::Show( Event *ev ) +{ + Show(); +} + +void SoundManager::Hide( Event *ev ) +{ + CurrentLostFocus(); + CancelEventsOfType( EV_SoundManager_ShowingSounds ); + UpdateUI(); +} + +void SoundManager::Save( void ) +{ + Entity *ent; + str buf; + str filename; + int i; + + // get the name of the sound file from the world + filename = "maps/"; + filename += level.mapname; + for( i = filename.length() - 1; i >= 0; i-- ) + { + if ( filename[ i ] == '.' ) + { + filename[ i ] = 0; + break; + } + } + + filename += ".snd"; + + gi.Printf( "Saving soundmanager file to '%s'...\n", filename.c_str() ); + + buf = ""; + buf += va( "//\n" ); + buf += va( "// Sound Manager File \"%s\", %d Sound Entities.\n", filename.c_str(), soundList.NumObjects() ); + buf += va( "//\n" ); + + // + // save out normal TriggerSpeakers + // + buf += va( "// TriggerSpeakers\n" ); + for( i = 1; i <= soundList.NumObjects(); i++ ) + { + TriggerSpeaker * speaker; + + ent = soundList.ObjectAt( i ); + if ( !ent->isSubclassOf( TriggerSpeaker ) || ent->isSubclassOf( RandomSpeaker ) ) + continue; + + speaker = ( TriggerSpeaker * )ent; + // + // start off the spawn command + // + buf += "spawn TriggerSpeaker"; + // + // set the targetname + // + if ( ent->targetname != "" ) + { + buf += va( " targetname %s", ent->targetname.c_str() ); + } + // + // set the origin + // + buf += va( " origin \"%.2f %.2f %.2f\"", ent->origin.x, ent->origin.y, ent->origin.z ); + // + // make the speaker ambient and on + // + buf += " spawnflags 1"; + // + // set the volume + // + if ( speaker->volume != DEFAULT_VOL ) + buf += va( " volume %.2f", speaker->volume ); + // + // set the mindist + // + if ( speaker->min_dist != DEFAULT_MIN_DIST ) + buf += va( " min_dist %.1f", speaker->min_dist ); + // + // save the sound + // + buf += va( " sound \"%s\"", speaker->Noise().c_str() ); + // + // make sure it gets re-added to the sound manager + // + buf += " _addtosoundmanager 0"; + buf += va( "\n" ); + } + // + // save out RandomSpeakers + // + buf += va( "// RandomSpeakers\n" ); + for( i = 1; i <= soundList.NumObjects(); i++ ) + { + RandomSpeaker * speaker; + + ent = soundList.ObjectAt( i ); + if ( !ent->isSubclassOf( TriggerSpeaker ) || !ent->isSubclassOf( RandomSpeaker ) ) + continue; + + speaker = ( RandomSpeaker * )ent; + // + // start off the spawn command + // + buf += "spawn RandomSpeaker"; + // + // set the targetname + // + if ( ent->targetname != "" ) + { + buf += va( " targetname %s", ent->targetname.c_str() ); + } + // + // set the origin + // + buf += va( " origin \"%.2f %.2f %.2f\"", ent->origin.x, ent->origin.y, ent->origin.z ); + // + // set the volume + // + if ( speaker->volume != DEFAULT_VOL ) + buf += va( " volume %.2f", speaker->volume ); + // + // set the mindist + // + if ( speaker->min_dist != DEFAULT_MIN_DIST ) + buf += va( " min_dist %.1f", speaker->min_dist ); + // + // set the channel + // + buf += va( " channel %d", speaker->channel ); + // + // set the mindelay + // + buf += va( " mindelay %.2f", speaker->mindelay ); + // + // set the maxdelay + // + buf += va( " maxdelay %.2f", speaker->maxdelay ); + // + // set the chance + // + buf += va( " chance %.2f", speaker->chance ); + // + // save the sound + // + buf += va( " sound \"%s\"", speaker->Noise().c_str() ); + // + // make sure it gets re-added to the sound manager + // + buf += " _addtosoundmanager 0"; + buf += va( "\n" ); + } + // + // save out TriggerMusic + // + buf += va( "// TriggerMusics\n" ); + for( i = 1; i <= soundList.NumObjects(); i++ ) + { + TriggerMusic * music; + + ent = soundList.ObjectAt( i ); + if ( !ent->isSubclassOf( TriggerMusic ) ) + continue; + + music = ( TriggerMusic * )ent; + // + // start off the spawn command + // + buf += "spawn TriggerMusic"; + // + // set the targetname + // + if ( ent->targetname != "" ) + { + buf += va( " targetname %s", ent->targetname.c_str() ); + } + // + // set the origin + // + buf += va( " origin \"%.2f %.2f %.2f\"", ent->origin.x, ent->origin.y, ent->origin.z ); + // + // set the angle + // + if ( music->useTriggerDir ) + { + buf += va( " angle %.1f", AngleMod( ent->angles.y ) ); + } + // + // set the oneshot + // + if ( music->oneshot ) + { + buf += " oneshot 0"; + } + // + // set current + // + buf += va( " current %s", music->current.c_str() ); + + // + // set fallback + // + buf += va( " fallback %s", music->fallback.c_str() ); + + if ( music->GetMultiFaceted() ) + { + // + // save out multi faceted + // + buf += va( " multifaceted %d", music->GetMultiFaceted() ); + + // + // set alt current + // + buf += va( " altcurrent %s", music->altcurrent.c_str() ); + + // + // set alt fallback + // + buf += va( " altfallback %s", music->altfallback.c_str() ); + } + + // + // set mins + // + buf += va( " _setmins \"%.2f %.2f %.2f\"", ent->mins.x, ent->mins.y, ent->mins.z ); + + // + // set maxs + // + buf += va( " _setmaxs \"%.2f %.2f %.2f\"", ent->maxs.x, ent->maxs.y, ent->maxs.z ); + // + // make sure it gets re-added to the sound manager + // + buf += " _addtosoundmanager 0"; + buf += va( "\n" ); + } + + // + // save out TriggerReverb + // + buf += va( "// TriggerReverbs\n" ); + for( i = 1; i <= soundList.NumObjects(); i++ ) + { + TriggerReverb * reverb; + + ent = soundList.ObjectAt( i ); + if ( !ent->isSubclassOf( TriggerReverb ) ) + continue; + + reverb = ( TriggerReverb * )ent; + // + // start off the spawn command + // + buf += "spawn TriggerReverb"; + // + // set the targetname + // + if ( ent->targetname != "" ) + { + buf += va( " targetname %s", ent->targetname.c_str() ); + } + // + // set the origin + // + buf += va( " origin \"%.2f %.2f %.2f\"", ent->origin.x, ent->origin.y, ent->origin.z ); + // + // set the angle + // + if ( reverb->useTriggerDir ) + { + buf += va( " angle %.1f", AngleMod( ent->angles.y ) ); + } + // + // set the oneshot + // + if ( reverb->oneshot ) + { + buf += " oneshot 0"; + } + // + // set reverb type + // + buf += va( " reverbtype %d", reverb->reverbtype ); + + // + // set reverb level + // + buf += va( " reverblevel %.2f", reverb->reverblevel ); + + if ( reverb->GetMultiFaceted() ) + { + // + // save out multi faceted + // + buf += va( " multifaceted %d", reverb->GetMultiFaceted() ); + + // + // set alt reverb type + // + buf += va( " altreverbtype %d", reverb->altreverbtype ); + + // + // set alt reverb level + // + buf += va( " altreverblevel %.2f", reverb->altreverblevel ); + } + + // + // set mins + // + buf += va( " _setmins \"%.2f %.2f %.2f\"", ent->mins.x, ent->mins.y, ent->mins.z ); + + // + // set maxs + // + buf += va( " _setmaxs \"%.2f %.2f %.2f\"", ent->maxs.x, ent->maxs.y, ent->maxs.z ); + // + // make sure it gets re-added to the sound manager + // + buf += " _addtosoundmanager 0"; + buf += va( "\n" ); + } + + buf += "end\n"; + + gi.FS_WriteFile( filename.c_str(), buf.c_str(), buf.length() + 1 ); + gi.Printf( "done.\n" ); +} + +void SoundManager::Save( Event *ev ) +{ + Save(); +} + +void SoundManager::Load( void ) +{ + str filename; + int i; + + // get the name of the sound file from the world + filename = "maps/"; + filename += level.mapname; + for( i = filename.length() - 1; i >= 0; i-- ) + { + if ( filename[ i ] == '.' ) + { + filename[ i ] = 0; + break; + } + } + + filename += ".snd"; + + // If there isn't a script with the same name as the map, then don't try to load script + if ( gi.FS_ReadFile( filename.c_str(), NULL, true ) != -1 ) + { + Reset(); + level.consoleThread->Parse( filename.c_str() ); + } +} + +void SoundManager::AddEntity( Entity * ent ) +{ + if ( ent ) + { + soundList.AddUniqueObject( ent ); + } +} diff --git a/dlls/game/soundman.h b/dlls/game/soundman.h new file mode 100644 index 0000000..330d58c --- /dev/null +++ b/dlls/game/soundman.h @@ -0,0 +1,108 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/soundman.h $ +// $Revision:: 4 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Sound Manager +// + +#ifndef __SOUND_MANAGER_H__ +#define __SOUND_MANAGER_H__ + +#include "g_local.h" +#include "entity.h" +#include "trigger.h" + +class SoundManager : public Listener + { + protected: + int currentFacet; + Entity *current; + Container soundList; + + void AddSpeaker( Event *ev ); + void AddRandomSpeaker( Event *ev ); + void AddMusicTrigger( Event *ev ); + void AddReverbTrigger( Event *ev ); + void Replace( Event *ev ); + void Delete( Event *ev ); + void MovePlayer( Event *ev ); + void Next( Event *ev ); + void Previous( Event *ev ); + void ShowingSounds( Event *ev ); + void Show( Event *ev ); + void Hide( Event *ev ); + void Save( Event *ev ); + void UpdateEvent( Event *ev ); + void ResetEvent( Event *ev ); + void GlobalTranslateEvent( Event *ev ); + void SwitchFacetEvent( Event *ev ); + void PreviewReverbEvent( Event *ev ); + void ResetReverbEvent( Event *ev ); + + void Show( void ); + void UpdateUI( void ); + void Save( void ); + void CurrentLostFocus( void ); + void CurrentGainsFocus( void ); + void UpdateSpeaker( TriggerSpeaker * speaker ); + void UpdateRandomSpeaker( RandomSpeaker * speaker ); + void UpdateTriggerMusic( TriggerMusic * music ); + void UpdateTriggerReverb( TriggerReverb * reverb ); + + public: + CLASS_PROTOTYPE( SoundManager ); + + SoundManager(); + void Init( void ); + void Reset( void ); + void Load( void ); + void AddEntity( Entity * ent ); + virtual void Archive( Archiver &arc ); + }; + +inline void SoundManager::Archive + ( + Archiver &arc + ) + + { + int i; + int num; + int currentFacet; + + Listener::Archive( arc ); + + arc.ArchiveInteger( ¤tFacet ); + arc.ArchiveObjectPointer( ( Class ** )¤t ); + + if ( arc.Saving() ) + { + num = soundList.NumObjects(); + arc.ArchiveInteger( &num ); + } + else + { + soundList.ClearObjectList(); + arc.ArchiveInteger( &num ); + soundList.Resize( num ); + } + for( i = 1; i <= num; i++ ) + { + arc.ArchiveObjectPointer( ( Class ** )soundList.AddressOfObjectAt( i ) ); + } + } + +extern SoundManager SoundMan; + +#endif /* camera.h */ diff --git a/dlls/game/spawners.cpp b/dlls/game/spawners.cpp new file mode 100644 index 0000000..72f274f --- /dev/null +++ b/dlls/game/spawners.cpp @@ -0,0 +1,900 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/spawners.cpp $ +// $Revision:: 32 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Various spawning entities + +/*****************************************************************************/ +/*QUAKED func_spawn(0 0.25 0.5) (-8 -8 -8) (8 8 8) +"modelName" The name of the TIKI file you wish to spawn. (Required) +"spawnTargetName" This will be the targetname of the spawned model. (default is null) +"spawnTarget" This will be the target of the spawned model. (default is null) +"pickupThread" passed on to the spawned model +"key" The item needed to activate this. (default nothing) +"attackMode" Attacking mode of the spawned actor (default 0) +******************************************************************************/ + +#include "_pch_cpp.h" +//#include "g_local.h" +#include "spawners.h" + + +Event EV_Spawn_ModelName +( + "modelname", + EV_SCRIPTONLY, + "sSSSSSSSS", + "model_name1 model_name2 model_name3 model_name4 model_name5 model_name6 model_name7 model_name8 model_name9", + "Sets up to nine model names for this spawn entity." +); +Event EV_Spawn_SpawnTargetName +( + "spawntargetname", + EV_SCRIPTONLY, + "s", + "spawntargetname", + "Sets spawn target name for this spawn entity." +); +Event EV_Spawn_SpawnTarget +( + "spawntarget", + EV_SCRIPTONLY, + "s", + "spawntarget", + "Sets spawn target for this spawn entity." +); +Event EV_Spawn_SpawnNow +( + "spawnnow", + EV_SCRIPTONLY, + "@e", + "return_entity", + "Spawns the entity and returns it." +); +Event EV_Spawn_AttackMode +( + "attackmode", + EV_SCRIPTONLY, + "i", + "attackmode", + "Sets the _attackMode for this spawn entity." +); +Event EV_Spawn_PickupThread +( + "pickupthread", + EV_SCRIPTONLY, + "s", + "threadName", + "Sets the pickup thread for the spawned entity." +); +Event EV_Spawn_AddSpawnItem +( + "spawn_spawnitem", + EV_SCRIPTONLY, + "s", + "spawn_item_name", + "Adds this named item to what will be spawned when this spawned entity is killed, if it is an actor." +); +Event EV_Spawn_SetSpawnChance +( + "spawn_spawnchance", + EV_SCRIPTONLY, + "f", + "spawn_chance", + "Sets the chance that this spawned entity will spawn something when killed, if it is an actor." +); +Event EV_Spawn_SetStartHidden +( + "starthidden", + EV_SCRIPTONLY, + "B", + "hidden_bool", + "Sets whether or not the entity starts hidden." +); +Event EV_Spawn_SetSpawnEffect +( + "spawneffectname", + EV_SCRIPTONLY, + "sS", + "effectType effectName", + "Displays this effect on spawn." +); +Event EV_Spawn_SetAnimName +( + "startanim", + EV_SCRIPTONLY, + "s", + "animname", + "Animation to spawn the actor with" +); +Event EV_Spawn_SetSpawnVelocity +( + "setspawnvelocity", + EV_SCRIPTONLY, + "v", + "velocity", + "Sets the velocity to spawn with" +); +Event EV_Spawn_SetSpawnGroupID +( + "setspawngroupID", + EV_SCRIPTONLY, + "i", + "groupID", + "Sets the groupID to spawn with" +); +Event EV_Spawn_SetSpawnMasterStateMap +( + "setspawnmasterstatemap", + EV_SCRIPTONLY, + "s", + "masterstatemap", + "Sets the masterstatemap to spawn with" +); +Event EV_Spawn_SetSpawnGroupDeathThread +( + "setspawngroupdeaththread", + EV_SCRIPTONLY, + "s", + "group_death_thread", + "Sets the group death thread to spawn with" +); +Event EV_Spawn_SetSpawnKeyValue +( + "setSpawnKeyValue", + EV_SCRIPTONLY, + "ss", + "key value", + "Sets any key/value pair to set on the spawned entity." +); +Event EV_Spawn_ClearSpawnKeyValues +( + "clearSpawnKeyValues", + EV_SCRIPTONLY, + NULL, + NULL, + "Clears all of the key/value pairs for the spawner." +); +Event EV_Spawn_CheckForSpace +( + "checkForSpace", + EV_SCRIPTONLY, + NULL, + NULL, + "Checks to see if there is space for the spawn entity." +); + +CLASS_DECLARATION( ScriptSlave, Spawn, "func_spawn" ) +{ + { &EV_Activate, &Spawn::DoSpawn }, + { &EV_Spawn_ModelName, &Spawn::ModelName }, + { &EV_Spawn_SpawnTargetName, &Spawn::SpawnTargetName }, + { &EV_Spawn_AttackMode, &Spawn::AttackMode }, + { &EV_Spawn_SpawnTarget, &Spawn::SpawnTarget }, + { &EV_Spawn_PickupThread, &Spawn::SetPickupThread }, + { &EV_SetAngle, &Spawn::SetAngleEvent }, + { &EV_Spawn_AddSpawnItem, &Spawn::SetSpawnItem }, + { &EV_Spawn_SetSpawnChance, &Spawn::SetSpawnChance }, + { &EV_Spawn_SetStartHidden, &Spawn::SetStartHidden }, + { &EV_Spawn_SetSpawnEffect, &Spawn::SetSpawnEffect }, + { &EV_Spawn_SetAnimName, &Spawn::SetAnimName }, + { &EV_Spawn_SpawnNow, &Spawn::SpawnNow }, + { &EV_Spawn_SetSpawnVelocity, &Spawn::SetSpawnVelocity }, + { &EV_Spawn_SetSpawnGroupID, &Spawn::SetSpawnGroupID }, + { &EV_Spawn_SetSpawnMasterStateMap, &Spawn::SetSpawnMasterStateMap }, + { &EV_Spawn_SetSpawnGroupDeathThread, &Spawn::SetSpawnGroupDeathThread }, + { &EV_Spawn_SetSpawnKeyValue, &Spawn::setSpawnKeyValue }, + { &EV_Spawn_ClearSpawnKeyValues, &Spawn::clearSpawnKeyValues }, + { &EV_Spawn_CheckForSpace, &Spawn::setCheckForSpace }, + + { NULL, NULL } +}; + +void Spawn::SetAngleEvent( Event *ev ) +{ + Entity::SetAngleEvent( ev ); +} + +void Spawn::SetPickupThread( Event *ev ) +{ + _pickupThread = ev->GetString( 1 ); +} + +void Spawn::SetAnimName( Event *ev ) +{ + _animName = ev->GetString( 1 ); +} + +void Spawn::SetSpawnVelocity( Event *ev ) +{ + _velocity = ev->GetVector( 1 ); +} + +void Spawn::SetSpawnGroupID( Event *ev ) +{ + _spawnGroupID = ev->GetInteger( 1 ); +} + +void Spawn::ModelName( Event *ev ) +{ + assert( ev->NumArgs() > 0 ); + + _modelNames.ClearObjectList(); + + for (int i = 1; i <= ev->NumArgs(); i++) + { + str modelname = ev->GetString( i ); + _modelNames.AddObject( modelname ); + CacheResource( modelname.c_str(), this ); + } +} + +void Spawn::SpawnTargetName( Event *ev ) +{ + _spawnTargetName = ev->GetString( 1 ); +} + +void Spawn::SpawnTarget( Event *ev ) +{ + _spawnTarget = ev->GetString( 1 ); +} + +void Spawn::AttackMode( Event *ev ) +{ + _spawnTarget = ev->GetInteger( 1 ); +} + +void Spawn::SetSpawnItem( Event *ev ) +{ + _spawnItem = ev->GetString( 1 ); +} + +void Spawn::SetSpawnChance( Event *ev ) +{ + _spawnChance = ev->GetFloat( 1 ); +} + +void Spawn::SetStartHidden( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + _startHidden = ev->GetBoolean( 1 ); + else + _startHidden = true; +} + +void Spawn::SetSpawnEffect( Event *ev ) +{ + _effectType = ev->GetString( 1 ); + + if ( ev->NumArgs() > 1 ) + { + _effectName = ev->GetString( 2 ); + } + else + { + _effectName = ""; + } +} + +void Spawn::SetSpawnMasterStateMap( Event *ev ) +{ + _masterStateMap = ev->GetString( 1 ); +} + +void Spawn::SetSpawnGroupDeathThread( Event *ev ) +{ + _spawnGroupDeathThread = ev->GetString( 1 ); +} + +Spawn::Spawn() +{ + setSolidType( SOLID_NOT ); + setMoveType( MOVETYPE_NONE ); + hideModel(); + + _spawnChance = 0; + _attackMode = 0; + _startHidden = false; + _animName = "idle"; + _velocity = Vector(0, 0, -1); + _spawnGroupID = -9999; + _masterStateMap = ""; + _spawnGroupDeathThread = ""; + + _checkForSpace = false; +} + +Spawn::~Spawn() +{ + _modelNames.FreeObjectList(); + + _keys.FreeObjectList(); + _values.FreeObjectList(); +} + +void Spawn::SetArgs( SpawnArgs &args ) +{ + int i; + + args.setArg( "origin", va( "%f %f %f", origin[ 0 ], origin[ 1 ], origin[ 2 ] ) ); + args.setArg( "angle", va( "%f", angles[ 1 ] ) ); + args.setArg( "angles", va( "%f %f %f", angles[ 0 ], angles[ 1 ], angles[ 2 ] ) ); + + + if ( _modelNames.NumObjects() > 0 ) + { + int randomModelIndex = ceil(G_Random() * _modelNames.NumObjects() ); + randomModelIndex = iClamp( randomModelIndex, 1, _modelNames.NumObjects() ); + args.setArg( "model", _modelNames.ObjectAt( randomModelIndex ).c_str() ); + } + + args.setArg( "attackMode", va( "%i",_attackMode ) ); + args.setArg( "scale", va( "%f",edict->s.scale ) ); + if ( _spawnTargetName.length() ) + { + args.setArg( "targetname", _spawnTargetName.c_str() ); + } + if ( _spawnTarget.length() ) + { + args.setArg( "target", _spawnTarget.c_str() ); + } + if ( _pickupThread.length() ) + { + args.setArg( "pickupThread", _pickupThread.c_str() ); + } + if ( _spawnItem.length() ) + { + args.setArg( "spawnItem", _spawnItem.c_str() ); + args.setArg( "spawnChance", va( "%f", _spawnChance ) ); + } + if ( _startHidden ) + { + args.setArg( "hide", "" ); + } + + // Add in all of the key/value pairs + + for ( i = 1 ; i <= _keys.NumObjects() ; i++ ) + { + args.setArg( _keys.ObjectAt( i ).c_str(), _values.ObjectAt( i ).c_str() ); + } + + if ( _effectType.length() ) + { + str effectString; + + effectString = _effectType; + + if ( _effectName.length() ) + { + effectString += "-"; + effectString += _effectName; + } + + args.setArg( "displayeffect", effectString.c_str() ); + } + +} + +void Spawn::postSpawn( Entity *spawn ) +{ + if ( !spawn ) + return; + + if ( _spawnGroupID > 0 ) + spawn->AddToGroup( _spawnGroupID ); + + Event *e = new Event( EV_Anim ); + e->AddString( _animName ); + spawn->PostEvent( e, EV_SPAWNARG ); + + if ( g_debugtargets->integer ) + { + G_DebugTargets( spawn, "Spawn::DoSpawn" ); + } + + if ( _masterStateMap.length() ) + { + Event *smapEvent = new Event ( "masterstatemap" ); + smapEvent->AddString( _masterStateMap ); + spawn->PostEvent( smapEvent, EV_POSTSPAWN ); + } + + if ( _spawnGroupDeathThread.length() ) + { + Event *groupDeathThreadEvent = new Event ( "groupdeaththread" ); + groupDeathThreadEvent->AddString( _spawnGroupDeathThread ); + spawn->PostEvent( groupDeathThreadEvent, EV_POSTSPAWN ); + } +} + +void Spawn::DoSpawn( Event *ev ) +{ + Entity *spawn; + SpawnArgs args; + + if ( _modelNames.NumObjects() == 0 ) + { + warning("Spawn", "No models set" ); + } + + SetArgs( args ); + + spawn = args.Spawn(); + + if ( spawn ) + { + spawn->velocity = _velocity; + + postSpawn( spawn ); + + if ( _checkForSpace && checkStuck( spawn ) ) + { + spawn->PostEvent( EV_Remove, 0.0f ); + } + } +} + +//=============================================================== +// Name: SpawnNow +// Class: Spawn +// +// Description: Spawns a new entity immediately, per the properties +// already set on this object. +// +// Parameters: Event* -- return value is the spawned entity. +// +// Returns: None +// +//=============================================================== +void Spawn::SpawnNow( Event *ev ) +{ + Entity *spawn; + SpawnArgs args; + + if ( _modelNames.NumObjects() == 0 ) + { + warning("Spawn", "No models set" ); + } + + SetArgs( args ); + + spawn = args.Spawn(); + + if ( spawn ) + { + spawn->CancelEventsOfType( EV_ProcessInitCommands ); + spawn->ProcessInitCommands( spawn->edict->s.modelindex ); + + // make sure spawned entity starts falling if necessary + spawn->velocity = Vector(0, 0, -1); + + postSpawn( spawn ); + + if ( _checkForSpace && checkStuck( spawn ) ) + { + spawn->CancelEventsOfType( EV_DisplayEffect ); + spawn->PostEvent( EV_Remove, 0.0f ); + ev->ReturnEntity( NULL ); + return; + } + } + + ev->ReturnEntity( spawn ); +} + +bool Spawn::checkStuck( Entity *spawn ) +{ + int i; + int num; + int touch[ MAX_GENTITIES ]; + gentity_t *hit; + Vector min; + Vector max; + + + min = origin + spawn->mins; + max = origin + spawn->maxs; + + num = gi.AreaEntities( min, max, touch, MAX_GENTITIES, qfalse ); + + for( i = 0; i < num; i++ ) + { + hit = &g_entities[ touch[ i ] ]; + + if ( hit->inuse && hit->entity && ( hit->entity != spawn ) && ( hit->entity->edict->solid == SOLID_BBOX ) ) + { + return true; + } + } + + return false; +} + +void Spawn::setSpawnKeyValue( Event *ev ) +{ + str key; + str value; + + key = ev->GetString( 1 ); + value = ev->GetString( 2 ); + + _keys.AddObject( key ); + _values.AddObject( value ); +} + +void Spawn::clearSpawnKeyValues( Event *ev ) +{ + _keys.ClearObjectList(); + _values.ClearObjectList(); +} + +void Spawn::setCheckForSpace( Event *ev ) +{ + _checkForSpace = true; +} + + +/*****************************************************************************/ +/*QUAKED func_randomspawn(0 0.25 0.5) (-8 -8 -8) (8 8 8) START_OFF +Randomly spawns an entity. The time between spawns is determined by min_time and max_time +The entity can be turned off and on by triggering it +"modelName" The name of the TIKI file you wish to spawn. (Required) +"key" The item needed to activate this. (default nothing) +"min_time" The minimum time between spawns (default 0.2 seconds) +"max_time" The maximum time between spawns (default 1 seconds) +START_OFF - spawn is off by default +******************************************************************************/ + +Event EV_RandomSpawn_MinTime +( + "min_time", + EV_SCRIPTONLY, + "f", + "minTime", + "Minimum time between random spawns." +); +Event EV_RandomSpawn_MaxTime +( + "max_time", + EV_SCRIPTONLY, + "f", + "maxTime", + "Maximum time between random spawns." +); +Event EV_RandomSpawn_Think +( + "_randomspawn_think", + EV_CODEONLY, + NULL, + NULL, + "The function that actually spawns things in." +); + +CLASS_DECLARATION( Spawn, RandomSpawn, "func_randomspawn" ) +{ + { &EV_Activate, &RandomSpawn::ToggleSpawn }, + { &EV_RandomSpawn_MinTime, &RandomSpawn::MinTime }, + { &EV_RandomSpawn_MaxTime, &RandomSpawn::MaxTime }, + { &EV_RandomSpawn_Think, &RandomSpawn::Think }, + + { NULL, NULL } +}; + +RandomSpawn::RandomSpawn() +{ + min_time = 0.2f; + max_time = 1.0f; + if ( !LoadingSavegame && !( spawnflags & 1 ) ) + { + PostEvent( EV_RandomSpawn_Think, min_time + ( G_Random( max_time - min_time ) ) ); + } +} + +void RandomSpawn::MinTime( Event *ev ) +{ + min_time = ev->GetFloat( 1 ); +} + +void RandomSpawn::MaxTime( Event *ev ) +{ + max_time = ev->GetFloat( 1 ); +} + +void RandomSpawn::ToggleSpawn( Event *ev ) +{ + if ( EventPending( EV_RandomSpawn_Think ) ) + { + // if currently on, turn it off + CancelEventsOfType( EV_RandomSpawn_Think ); + } + else + { + Think( NULL ); + } +} + +void RandomSpawn::Think( Event *ev ) +{ + CancelEventsOfType( EV_RandomSpawn_Think ); + + // + // spawn our entity + // + DoSpawn( NULL ); + + // + // post the next time + // + PostEvent( EV_RandomSpawn_Think, min_time + ( G_Random( max_time - min_time ) ) ); +} + +/*****************************************************************************/ +/*QUAKED func_respawn(0 0.25 0.5) (-8 -8 -8) (8 8 8) +When the thing that is spawned is killed, this func_respawn will get +triggered. +"modelName" The name of the TIKI file you wish to spawn. (Required) +"key" The item needed to activate this. (default nothing) +******************************************************************************/ + + +CLASS_DECLARATION( Spawn, ReSpawn, "func_respawn" ) +{ + { NULL, NULL } +}; + +void ReSpawn::DoSpawn( Event *ev ) +{ + Entity *spawn; + SpawnArgs args; + + SetArgs( args ); + + // This will trigger the func_respawn when the thing dies + args.setArg( "targetname", TargetName() ); + args.setArg( "target", TargetName() ); + + spawn = args.Spawn(); + if ( spawn ) + { + // make sure spawned entity starts falling if necessary + spawn->velocity = Vector(0, 0, -1); + } +} + +/*****************************************************************************/ +/*QUAKED func_spawnoutofsight(0 0.25 0.5) (-8 -8 -8) (8 8 8) +Will only spawn something out of sight of the players. +By default, that means doing a trace from the spawner to the player. +Use "checkFOV" to indicate that it's OK to spawn as long as the spawner is not in the player's FOV. +"modelName" The name of the TIKI file you wish to spawn. (Required) +"spawnTargetName" This will be the targetname of the spawned model. (default is null) +"spawnTarget" This will be the target of the spawned model. (default is null) +"key" The item needed to activate this. (default nothing) +"checkFOV" The spawner will check that if it is in the players' FOV +******************************************************************************/ +Event EV_SpawnOutOfSight_CheckFOV +( + "checkFOV", + EV_DEFAULT, + "B", + "boolean", + "Use checkFOV to indicate that it's OK to spawn as long as the spawner is not in the player's FOV." +); + +//------------------------------------------------------------------------------------------------- +CLASS_DECLARATION( Spawn, SpawnOutOfSight, "func_spawnoutofsight" ) +{ + { &EV_SpawnOutOfSight_CheckFOV, &SpawnOutOfSight::CheckFOV }, + + { NULL, NULL } +}; + +//------------------------------------------------------------------------------------------------- +SpawnOutOfSight::SpawnOutOfSight() +{ + checkFOV = false; +} + +//------------------------------------------------------------------------------------------------- +void SpawnOutOfSight::CheckFOV( Event* ev ) +{ + if( ev->NumArgs() >= 1 ) + checkFOV = ev->GetBoolean( 1 ); + else + checkFOV = true; +} + +//------------------------------------------------------------------------------------------------- +void SpawnOutOfSight::DoSpawn( Event *ev ) +{ + Entity *ent; + gentity_t *ed; + trace_t trace; + + // check if any players can see this entity before spawning + for( int i = 0; i < game.maxclients; i++ ) + { + // get the gentity_t and skip it if invalid + ed = &g_entities[ i ]; + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + // get the Entity from the gentity_t and skip it if invalid + ent = ed->entity; + if ( ( ent->health < 0.0f ) || ( ent->flags & FL_NOTARGET ) ) + { + continue; + } + + if( checkFOV ) + { + // get angle between client forward vector and vector between client and spawner + Vector clientFwd( ed->client->ps.viewangles ); + clientFwd.AngleVectors( &clientFwd ); + clientFwd.z = 0.0f; + clientFwd.normalize(); + + Vector clientToSpawner( origin ); + clientToSpawner -= Vector( ed->centroid ); + clientToSpawner.normalize(); + + // compare the angle to the client's FOV and see if it's inside or not + float clientFOV = DEG2RAD( ed->client->ps.fov + 10.0f ); + if( (clientFOV / 2.0f) > Vector::AngleBetween( clientToSpawner, clientFwd ) ) + { + // this client can see the spawner, don't need to do anything else + return; + } + } + else + { + // just do a simple trace from the spawner to the client entity's centroid + trace = G_Trace( origin, vec_zero, vec_zero, ent->centroid, this, MASK_OPAQUE, false, "SpawnOutOfSight::DoSpawn" ); + if ( trace.fraction == 1.0f ) + { + // this client can see the spawner, don't need to do anything else + return; + } + } + } + + // getting here must mean that no clients can see the spawner + Spawn::DoSpawn( ev ); +} + + +/*****************************************************************************/ +/*QUAKED func_spawnchain(0 0.25 0.5) (-8 -8 -8) (8 8 8) +Tries to spawn something out of the sight of players. If it fails, it will +trigger its targets. +"modelName" The name of the TIKI file you wish to spawn. (Required) +"spawnTargetName" This will be the targetname of the spawned model. (default is null) +"spawnTarget" This will be the target of the spawned model. (default is null) +"use_3rd_person_camera" Whether or not to check from the camera or character.(default false) +******************************************************************************/ + +Event EV_SpawnChain_Use3rdPersonCamera +( + "use_3rd_person_camera", + EV_SCRIPTONLY, + "b", + "bool", + "Whether or not to check from the camera or character." +); + +CLASS_DECLARATION( Spawn, SpawnChain, "func_spawnchain" ) +{ + { &EV_SpawnChain_Use3rdPersonCamera, &SpawnChain::Use3rdPersonCamera }, + + { NULL, NULL } +}; + +SpawnChain::SpawnChain( void ) +{ + use3rdPersonCamera = false; +} + +void SpawnChain::Use3rdPersonCamera( Event *ev ) +{ + use3rdPersonCamera = ev->GetBoolean( 1 ); +} + +void SpawnChain::DoSpawn( Event *ev ) +{ + qboolean seen = false; + + // Check to see if this can see any players before spawning + for( int i = 0; i < game.maxclients; i++ ) + { + gentity_t *ed = &g_entities[ i ]; + if ( ed->inuse && ed->entity ) + { + Vector playerPosition; + Vector entityForward; + float entityFOV( DEG2RAD(ed->client->ps.fov) ); + if ( use3rdPersonCamera ) + { + const Vector cameraAngles( ed->client->ps.camera_angles ); + cameraAngles.AngleVectors( &entityForward ); + playerPosition = ed->client->ps.camera_origin; + entityFOV = DEG2RAD(ed->client->ps.fov + 10.0f); + } + else + { + const Vector viewAngles( ed->client->ps.viewangles ); + viewAngles.AngleVectors( &entityForward ); + playerPosition = ed->centroid; + entityFOV = DEG2RAD(ed->client->ps.fov + 10.0f ); + } + + Vector directionFromEntityToMe( origin - playerPosition ); + directionFromEntityToMe.normalize(); + + entityForward.z = 0.0f; + entityForward.normalize(); + + + assert( fCloseEnough( directionFromEntityToMe.length(), 1.0f, fEpsilon() ) ); + assert( fCloseEnough( entityForward.length(), 1.0f, fEpsilon() ) ); + + const float angleToMe( acos( Vector::Dot( directionFromEntityToMe, entityForward ) ) ); + // Check to see if I am in the field of view of the entity + if ( angleToMe < entityFOV/2.0f ) + { + Entity *entity = ed->entity; + if ( ( entity->health >= 0.0f ) && !( entity->flags & FL_NOTARGET ) ) + { + trace_t trace = G_Trace( origin, vec_zero, vec_zero, entity->centroid, this, MASK_OPAQUE, false, "SpawnChain::DoSpawn" ); + if ( trace.fraction == 1.0f ) + { + seen = true; + break; + } + } + } + } + } + + // Couldn't spawn anything, so activate targets + Vector mins(-64,-64,16); + Vector maxs(64,64,112); + trace_t trace = G_Trace( origin, mins, maxs, origin, NULL, MASK_MONSTERSOLID, false, "spawnchain" ); + + if ( trace.fraction != 1.0f || trace.startsolid || seen ) + { + const char *name = Target(); + if ( name && strcmp( name, "" ) ) + { + Entity *entity = NULL; + do + { + entity = G_FindTarget( entity, name ); + if ( !entity ) + { + break; + } + Event *event = new Event( EV_Activate ); +// event->AddEntity( world ); + event->AddEntity( this ); + entity->PostEvent( event, 0.0f ); + } while ( 1 ); + } + return; + } + + Spawn::DoSpawn( ev ); +} diff --git a/dlls/game/spawners.h b/dlls/game/spawners.h new file mode 100644 index 0000000..12cdd63 --- /dev/null +++ b/dlls/game/spawners.h @@ -0,0 +1,189 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/spawners.h $ +// $Revision:: 18 $ +// $Author:: Jmartel $ +// $Date:: 9/26/02 7:07p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Various spawning entities + +#ifndef __SPAWNWERS_H__ +#define __SPAWNWERS_H__ + +#include "g_local.h" +#include "scriptslave.h" +#include "actor.h" + +class Spawn : public ScriptSlave +{ +private: + Container _modelNames; + str _spawnTargetName; + str _spawnTarget; + str _pickupThread; + str _spawnItem; + float _spawnChance; + int _attackMode; + qboolean _startHidden; + str _effectType; + str _effectName; + str _animName; + Vector _velocity; + int _spawnGroupID; + str _masterStateMap; + str _spawnGroupDeathThread; + bool _checkForSpace; + + Container _keys; + Container _values; + + void SetAngleEvent( Event *ev ); + void SetPickupThread( Event *ev ); + void ModelName( Event *ev ); + void SpawnTargetName( Event *ev ); + void SpawnTarget( Event *ev ); + void AttackMode( Event *ev ); + void SetSpawnItem( Event *ev ); + void SetSpawnChance( Event *ev ); + void SetStartHidden( Event *ev ); + void SetSpawnEffect( Event *ev ); + void SetAnimName( Event *ev ); + void SpawnNow( Event *ev ); + void SetSpawnVelocity( Event *ev ); + void SetSpawnGroupID( Event *ev ); + void SetSpawnMasterStateMap( Event *ev ); + void SetSpawnGroupDeathThread( Event *ev ); + void setCheckForSpace( Event *ev ); + + void setSpawnKeyValue( Event *ev ); + void clearSpawnKeyValues( Event *ev ); + +protected: + virtual ~Spawn(); + void SetArgs( SpawnArgs &args ); + virtual void DoSpawn( Event *ev ); + void postSpawn( Entity *spawn ); + bool checkStuck( Entity *spawn ); + +public: + CLASS_PROTOTYPE( Spawn ); + + + Spawn(); + virtual void Archive( Archiver &arc ); +}; + +inline void Spawn::Archive +( + Archiver &arc + ) +{ + ScriptSlave::Archive( arc ); + + _modelNames.Archive( arc ); + + arc.ArchiveString( &_spawnTargetName ); + arc.ArchiveString( &_spawnTarget ); + arc.ArchiveString( &_pickupThread ); + arc.ArchiveString( &_spawnItem ); + arc.ArchiveFloat( &_spawnChance ); + arc.ArchiveInteger( &_attackMode ); + arc.ArchiveBoolean( &_startHidden ); + arc.ArchiveString( &_effectType ); + arc.ArchiveString( &_effectName ); + arc.ArchiveString( &_animName ); + arc.ArchiveVector ( &_velocity ); + arc.ArchiveInteger ( &_spawnGroupID ); + arc.ArchiveString ( &_masterStateMap ); + arc.ArchiveString ( &_spawnGroupDeathThread ); + arc.ArchiveBool( &_checkForSpace ); + + _keys.Archive( arc ); + _values.Archive( arc ); +} + +class RandomSpawn : public Spawn + { + private: + float min_time; + float max_time; + + void MinTime( Event *ev ); + void MaxTime( Event *ev ); + void ToggleSpawn( Event *ev ); + void Think( Event *ev ); + + public: + CLASS_PROTOTYPE( RandomSpawn ); + + + RandomSpawn(); + virtual void Archive( Archiver &arc ); + }; + +inline void RandomSpawn::Archive + ( + Archiver &arc + ) + { + Spawn::Archive( arc ); + + arc.ArchiveFloat( &min_time ); + arc.ArchiveFloat( &max_time ); + } + +class ReSpawn : public Spawn + { + protected: + virtual void DoSpawn( Event *ev ); + public: + CLASS_PROTOTYPE( ReSpawn ); + }; + +class SpawnOutOfSight : public Spawn + { + private: + bool checkFOV; + protected: + virtual void DoSpawn( Event *ev ); + void CheckFOV( Event* ev ); + public: + CLASS_PROTOTYPE( SpawnOutOfSight ); + SpawnOutOfSight(); + virtual void Archive( Archiver& arc ); + }; +inline void SpawnOutOfSight::Archive( Archiver& arc ) +{ + Spawn::Archive( arc ); + arc.ArchiveBool( &checkFOV ); +} + +class SpawnChain : public Spawn + { + private: + bool use3rdPersonCamera; + protected: + virtual void DoSpawn( Event *ev ); + public: + CLASS_PROTOTYPE( SpawnChain ); + SpawnChain( void ); + void Use3rdPersonCamera( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void SpawnChain::Archive( Archiver &arc ) +{ + Spawn::Archive( arc ); + + arc.ArchiveBool( &use3rdPersonCamera ); +} + +#endif //__SPAWNWERS_H__ diff --git a/dlls/game/specialfx.cpp b/dlls/game/specialfx.cpp new file mode 100644 index 0000000..b27a5ba --- /dev/null +++ b/dlls/game/specialfx.cpp @@ -0,0 +1,880 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/specialfx.cpp $ +// $Revision:: 6 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Special fx +// + +#include "_pch_cpp.h" +//#include "g_local.h" +#include "specialfx.h" + +/*****************************************************************************/ +/*QUAKED func_fulcrum (0 0 1) ? X_AXIS_ONLY Y_AXIS_ONLY + +This creates a fulcrum that when you stand on it, it will rotate due to +the weight exerted it will start rotating, when not standing on it, it +will return to its rest position. +"speed" - set the speed at which the fulcrum will operate (default is 48) +"resetspeed" - speed at whcih fulcrum resets, (default speed * 0.002) +"dampening" - dampen constant (default 0.95) +"limit" - limit the movement of the fulcrum (default 90 degrees) +"movesound" - sound to be played while fulcrum is moving + +X_AXIS_ONLY - only adjust the X axis +Y_AXIS_ONLY - only adjust the Y axis + +******************************************************************************/ + +#define X_AXIS_ONLY ( 1 << 0 ) +#define Y_AXIS_ONLY ( 1 << 1 ) + +Event EV_Fulcrum_SetSpeed +( + "speed", + EV_DEFAULT, + "f", + "speed", + "Speed at which fulcrum operates itself." +); +Event EV_Fulcrum_Reset +( + "reset", + EV_CONSOLE, + NULL, + NULL, + "Reset the fulcrum right now." +); +Event EV_Fulcrum_AdjustFulcrum +( + "_adjust_fulcrum", + EV_CODEONLY, + NULL, + NULL, + "Called periodically to adjust the frustum every frame and update its angular velocity." +); +Event EV_Fulcrum_SetResetSpeed +( + "resetspeed", + EV_SCRIPTONLY, + "f", + "newResetspeed", + "Speed at which fulcrum resets itself, defaults to 0.002 * speed." +); +Event EV_Fulcrum_SetDampening +( + "dampening", + EV_SCRIPTONLY, + "f", + "newDampening", + "dampening of fulcrum." +); +Event EV_Fulcrum_SetLimit +( + "limit", + EV_SCRIPTONLY, + "f", + "newLimit", + "angular limit for the fulcrum." +); +Event EV_Fulcrum_Setup +( + "_setup", + EV_CODEONLY, + NULL, + NULL, + "setup the fulcrum for the first time." +); +Event EV_Fulcrum_SetMoveSound +( + "movesound", + EV_SCRIPTONLY, + "s", + "newSinkSound", + "Sound played when fulcrum is moving." +); + +CLASS_DECLARATION( ScriptSlave, Fulcrum, "func_fulcrum" ) +{ + { &EV_Fulcrum_SetSpeed, &Fulcrum::SetSpeed }, + { &EV_Fulcrum_Reset, &Fulcrum::Reset }, + { &EV_Fulcrum_AdjustFulcrum, &Fulcrum::Adjust }, + { &EV_Touch, &Fulcrum::Touched }, + { &EV_Fulcrum_SetResetSpeed, &Fulcrum::SetResetSpeed }, + { &EV_Fulcrum_SetDampening, &Fulcrum::SetDampening }, + { &EV_Fulcrum_SetLimit, &Fulcrum::SetLimit }, + { &EV_Fulcrum_Setup, &Fulcrum::Setup }, + { &EV_Fulcrum_SetMoveSound, &Fulcrum::SetMoveSound }, + + { NULL, NULL } +}; + +Fulcrum::Fulcrum() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + touched = false; + speed = 48; + resetspeed = speed * 0.002f; + dampening = 0.95f; + limit = 90; + setMoveType( MOVETYPE_PUSH ); + PostEvent( EV_Fulcrum_Setup, FRAMETIME ); +} + +void Fulcrum::Setup( Event *ev ) +{ + startangles = angles; +} + +void Fulcrum::SetSpeed( Event *ev ) +{ + speed = ev->GetFloat( 1 ); + resetspeed = speed * 0.002f; +} + +void Fulcrum::SetResetSpeed( Event *ev ) +{ + resetspeed = ev->GetFloat( 1 ); +} + +void Fulcrum::SetDampening( Event *ev ) +{ + dampening = ev->GetFloat( 1 ); +} + +void Fulcrum::SetLimit( Event *ev ) +{ + limit = ev->GetFloat( 1 ); +} + +void Fulcrum::SetMoveSound( Event *ev ) +{ + movesound = ev->GetString( 1 ); +} + +void Fulcrum::Reset( Event *ev ) +{ + StopLoopSound(); + touched = false; + avelocity = vec_zero; + setAngles( startangles ); + CancelEventsOfType( EV_Fulcrum_AdjustFulcrum ); +} + +void Fulcrum::Touched( Event *ev ) +{ + Vector diff; + vec3_t dest; + Vector delta; + Entity * other; + + other = ev->GetEntity( 1 ); + + assert( other ); + if ( !other ) + return; + + // + // only things resting on me will affect me + // + if ( other->groundentity != this->edict ) + return; + + if ( movesound.length() ) + { + LoopSound( movesound ); + } + + delta = getLocalVector( other->origin - origin ); + + // + // clear out angular velocity + // + avelocity = vec_zero; + + // only look at x and y since z doesn't really concern us. + if ( !( spawnflags & X_AXIS_ONLY ) ) + { + avelocity[ PITCH ] += (float)cos( DEG2RAD( startangles[ YAW ] ) ) * speed * delta[ 0 ] / maxs[ 0 ]; + avelocity[ ROLL ] += (float)sin( DEG2RAD( startangles[ YAW ] ) ) * speed * delta[ 0 ] / maxs[ 0 ]; + } + if ( !( spawnflags & Y_AXIS_ONLY ) ) + { + avelocity[ ROLL ] += (float)cos( DEG2RAD( startangles[ YAW ] ) ) * -speed * delta[ 1 ] / maxs[ 1 ]; + avelocity[ PITCH ] += (float)sin( DEG2RAD( startangles[ YAW ] ) ) * -speed * delta[ 1 ] / maxs[ 1 ]; + } + + AnglesSubtract( startangles, angles, dest ); + diff = Vector( dest ); + if ( fabs( diff[ PITCH ] ) >= limit ) + { + Vector newAngles; + StopLoopSound(); + if ( diff[ PITCH ] > 0.0f ) + { + if ( avelocity[ PITCH ] < 0.0f ) + { + avelocity[ PITCH ] = 0.0f; + } + } + else if ( diff[ PITCH ] < 0.0f ) + { + if ( avelocity[ PITCH ] > 0.0f ) + { + avelocity[ PITCH ] = 0.0f; + } + } + } + + if ( fabs( diff[ ROLL ] ) >= limit ) + { + StopLoopSound(); + if ( diff[ ROLL ] > 0.0f ) + { + if ( avelocity[ ROLL ] < 0.0f ) + { + avelocity[ ROLL ] = 0.0f; + } + } + else if ( diff[ ROLL ] < 0.0f ) + { + if ( avelocity[ ROLL ] > 0.0f ) + { + avelocity[ ROLL ] = 0.0f; + } + } + } + + touched = true; + CancelEventsOfType( EV_Fulcrum_AdjustFulcrum ); + PostEvent( EV_Fulcrum_AdjustFulcrum, 0.0f ); +} + +void Fulcrum::Adjust( Event *ev ) +{ + if ( !touched ) + { + int i; + float f; + Vector diff; + vec3_t dest; + qboolean post; + + if ( movesound.length() ) + { + LoopSound( movesound ); + } + AnglesSubtract( startangles, angles, dest ); + diff = Vector( dest ); + post = false; + + for( i = 0; i < 3; i++ ) + { + if ( diff[ i ] ) + { + avelocity[ i ] += resetspeed * diff[ i ]; + } + avelocity[ i ] *= dampening; + f = fabs( avelocity[ i ] ); + if ( f > 0.01f ) + { + post = true; + } + } + if ( !post ) + { + Reset( NULL ); + return; + } + } + else + { + touched = false; + } + + PostEvent( EV_Fulcrum_AdjustFulcrum, FRAMETIME ); +} + +/*****************************************************************************/ +/*QUAKED func_runthrough (0 0 1) ? + +This is a trigger field that the player can run through and spawn tiki models +at that position. Used for releasing chaff from grass or butterflys from +flower gardens +"speed" - speed at which you have to be moving to trigger ( default 100 ) +"delay" - time between triggering ( default 0.1 ) +"chance" - chance that the trigger will spawn something( default 0.5 ) +"lip" - how far below the surface of the trigger we should spawn these things ( default 3 ) +"offset" - vector offset oriented along velocity vector( default "0 0 0" ) +"spawnmodel" - thing to spawn when triggered + +******************************************************************************/ + + +Event EV_RunThrough_SetSpeed +( + "speed", + EV_DEFAULT, + "f", + "speed", + "threshold speed at which RunThrough is activated." +); +Event EV_RunThrough_SetDelay +( + "delay", + EV_DEFAULT, + "f", + "delay", + "time between RunThrough being activated." +); +Event EV_RunThrough_SetChance +( + "chance", + EV_DEFAULT, + "f", + "chance", + "chance that trigger will spawn something." +); +Event EV_RunThrough_SetLip +( + "lip", + EV_DEFAULT, + "f", + "lip", + "distance below trigger we should spawn things." +); +Event EV_RunThrough_SetSpawnModel +( + "spawnmodel", + EV_DEFAULT, + "s", + "model_to_spawn", + "When triggered, what to spawn." +); +Event EV_RunThrough_SetOffset +( + "offset", + EV_DEFAULT, + "v", + "spawn_offset", + "When triggered, what to offset the spawned object by." +); + +CLASS_DECLARATION( Entity, RunThrough, "func_runthrough" ) +{ + { &EV_RunThrough_SetSpeed, &RunThrough::SetSpeed }, + { &EV_RunThrough_SetDelay, &RunThrough::SetDelay }, + { &EV_RunThrough_SetChance, &RunThrough::SetChance }, + { &EV_RunThrough_SetLip, &RunThrough::SetLip }, + { &EV_RunThrough_SetSpawnModel, &RunThrough::SetSpawnModel }, + { &EV_RunThrough_SetOffset, &RunThrough::SetOffset }, + { &EV_Touch, &RunThrough::Touched }, + + { NULL, NULL } +}; + +RunThrough::RunThrough() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + offset = vec_zero; + speed = 100.0f; + chance = 0.5f; + delay = 0.1f; + lip = 3.0f; + spawnmodel = ""; + lasttriggertime = 0; + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_TRIGGER ); +} + +void RunThrough::SetSpeed( Event *ev ) +{ + speed = ev->GetFloat( 1 ); +} + +void RunThrough::SetChance( Event *ev ) +{ + chance = ev->GetFloat( 1 ); +} + +void RunThrough::SetDelay( Event *ev ) +{ + delay = ev->GetFloat( 1 ); +} + +void RunThrough::SetLip( Event *ev ) +{ + lip = ev->GetFloat( 1 ); +} + +void RunThrough::SetOffset( Event *ev ) +{ + offset = ev->GetVector( 1 ); +} + +void RunThrough::SetSpawnModel( Event *ev ) +{ + spawnmodel = ev->GetString( 1 ); + CacheResource( spawnmodel, this ); +} + +void RunThrough::Touched( Event *ev ) +{ + Vector forward, left, up; + Vector ang; + Vector org; + Entity * other; + + other = ev->GetEntity( 1 ); + + assert( other ); + if ( !other ) + return; + + // don't trigger on the world + if ( other == world ) + return; + + // don't re-trigger too soon + if ( level.time < lasttriggertime ) + return; + + // don't trigger if not moving fast enough + if ( other->velocity.length() < speed ) + return; + + // don't trigger if chance says not to. + if ( G_Random( 1.0f ) > chance ) + return; + + ang = other->velocity.toAngles(); + ang.AngleVectors( &forward, &left, &up ); + + // get the origin from the thing that triggered me + org = other->origin; + + // set the height of the origin based on the bounds of this trigger minus the lip + org.z = absmax.z - lip; + + org += offset.x * forward; + org += offset.y * left; + org += offset.z * up; + + if ( spawnmodel.length() ) + { + Entity * ent; + // purposely declared here so that we don't do it every frame + SpawnArgs args; + + args.setArg( "origin", va( "%f %f %f", org[ 0 ], org[ 1 ], org[ 2 ] ) ); + args.setArg( "angles", va( "%f %f %f", ang[ 0 ], ang[ 1 ], ang[ 2 ] ) ); + args.setArg( "model", spawnmodel.c_str() ); + + ent = args.Spawn(); + if ( ent ) + { + ent->ProcessPendingEvents(); + } + } + // set trigger time for next time + lasttriggertime = level.time + delay; +} + +/*****************************************************************************/ +/*QUAKED func_sinkobject (0 0 1) ? x FALLAWAY NO_RESET + +This creates an object which gradually sinks downward when stepped on. +"delay" - delay between when object starts reacting towards weight (default 0 seconds) +"speed" - set the speed at which sinkobject sinks (default is 50) +"resetspeed" - speed at which sinkobject resets its position, (default speed * 0.1) +"dampening" - dampening constant to mitigate acceleration (default 0.95) +"limit" - limit the movement of the sinkobject how far down it should go (default 1000 units) +"resetdelay" - time between player gets off platform, and platform starts resetting itself. +"sinksound" - sound to be played while platform is sinking. +"resetsound" - sound to be played while platform is resetting. +"active" - make the sink object active +"notactive" - make the sink object not active + +FALLAWAY - the sink object will progressively fall down faster and faster +NO_RESET - the sink object will not reset, only move downward + +******************************************************************************/ + +#define FALLAWAY ( 1 << 1 ) +#define NO_RESET ( 1 << 2 ) + +Event EV_SinkObject_SetSpeed +( + "speed", + EV_DEFAULT, + "f", + "speed", + "Speed at which SinkObject starts falling." +); +Event EV_SinkObject_SetDelay +( + "delay", + EV_SCRIPTONLY, + "f", + "delay", + "Delay until SinkObject starts falling." +); +Event EV_SinkObject_Reset +( + "reset", + EV_CONSOLE, + NULL, + NULL, + "Reset the SinkObject right now." +); +Event EV_SinkObject_AdjustSinkObject +( + "_adjust_SinkObject", + EV_CODEONLY, + NULL, + NULL, + "Called periodically to adjust the sinkobject every frame and adjust its velocity." +); +Event EV_SinkObject_Fall +( + "_fall_SinkObject", + EV_CODEONLY, + NULL, + NULL, + "Called periodically to make a sink object fall away." +); +Event EV_SinkObject_SetResetSpeed +( + "resetspeed", + EV_SCRIPTONLY, + "f", + "newResetspeed", + "Speed at which SinkObject resets itself, defaults to 0.002 * speed." +); +Event EV_SinkObject_SetResetDelay +( + "resetdelay", + EV_SCRIPTONLY, + "f", + "newResetDelay", + "Delay between when sinkobject starts resetting." +); +Event EV_SinkObject_SetSinkSound +( + "sinksound", + EV_SCRIPTONLY, + "s", + "newSinkSound", + "Sound played when sinkobject is sinking." +); +Event EV_SinkObject_SetResetSound +( + "resetsound", + EV_SCRIPTONLY, + "s", + "newResetSound", + "Sound played when sinkobject is resetting." +); +Event EV_SinkObject_SetDampening +( + "dampening", + EV_SCRIPTONLY, + "f", + "newDampening", + "dampening of SinkObject." +); +Event EV_SinkObject_SetLimit +( + "limit", + EV_SCRIPTONLY, + "f", + "newLimit", + "maximum displacement of the SinkObject." +); +Event EV_SinkObject_Setup +( + "_setup", + EV_CODEONLY, + NULL, + NULL, + "setup the SinkObject for the first time." +); +Event EV_SinkObject_MakeActive +( + "active", + EV_SCRIPTONLY, + NULL, + NULL, + "make the SinkObject active, so that it will respond to players touching it." +); +Event EV_SinkObject_MakeNonActive +( + "notactive", + EV_SCRIPTONLY, + NULL, + NULL, + "make the SinkObject not active, so that it won't respond to players touching it." +); + +CLASS_DECLARATION( ScriptSlave, SinkObject, "func_sinkobject" ) +{ + { &EV_SinkObject_SetSpeed, &SinkObject::SetSpeed }, + { &EV_SinkObject_SetDelay, &SinkObject::SetDelay }, + { &EV_SinkObject_Reset, &SinkObject::Reset }, + { &EV_SinkObject_AdjustSinkObject, &SinkObject::Adjust }, + { &EV_SinkObject_Fall, &SinkObject::Fall }, + { &EV_Touch, &SinkObject::Touched }, + { &EV_SinkObject_SetResetSpeed, &SinkObject::SetResetSpeed }, + { &EV_SinkObject_SetDampening, &SinkObject::SetDampening }, + { &EV_SinkObject_SetLimit, &SinkObject::SetLimit }, + { &EV_SinkObject_Setup, &SinkObject::Setup }, + { &EV_SinkObject_SetResetDelay, &SinkObject::SetResetDelay }, + { &EV_SinkObject_SetResetSound, &SinkObject::SetResetSound }, + { &EV_SinkObject_SetSinkSound, &SinkObject::SetSinkSound }, + { &EV_SinkObject_MakeActive, &SinkObject::MakeActive }, + { &EV_SinkObject_MakeNonActive, &SinkObject::MakeNonActive }, + + { NULL, NULL } +}; + +#define RESET_SCALE 0.1f +#define SPEED_DIVISOR ( 1.0f / 250.0f ) + +SinkObject::SinkObject() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + active = true; + sinksound = ""; + resetsound = ""; + resetdelay = 0; + delay = 0; + touched = false; + speed = 50.0f * SPEED_DIVISOR; + resetspeed = speed * RESET_SCALE; + dampening = 0.95f; + limit = 1000; + time_touched = -1; + time_reset = -1; + setMoveType( MOVETYPE_PUSH ); + PostEvent( EV_SinkObject_Setup, FRAMETIME ); +} + +void SinkObject::Setup( Event *ev ) +{ + startpos = origin; +} + +void SinkObject::SetSpeed( Event *ev ) +{ + speed = ev->GetFloat( 1 ) * SPEED_DIVISOR; + resetspeed = speed * RESET_SCALE; +} + +void SinkObject::SetDelay( Event *ev ) +{ + delay = ev->GetFloat( 1 ); +} + +void SinkObject::SetResetSpeed( Event *ev ) +{ + resetspeed = ev->GetFloat( 1 ) * SPEED_DIVISOR; +} + +void SinkObject::SetResetDelay( Event *ev ) +{ + resetdelay = ev->GetFloat( 1 ); +} + +void SinkObject::SetSinkSound( Event *ev ) +{ + sinksound = ev->GetString( 1 ); +} + +void SinkObject::SetResetSound( Event *ev ) +{ + resetsound = ev->GetString( 1 ); +} + +void SinkObject::SetDampening( Event *ev ) +{ + dampening = ev->GetFloat( 1 ); +} + +void SinkObject::SetLimit( Event *ev ) +{ + limit = ev->GetFloat( 1 ); +} + +void SinkObject::Reset( Event *ev ) +{ + time_reset = -1; + time_touched = -1; + touched = false; + velocity = vec_zero; + setOrigin( startpos ); + CancelEventsOfType( EV_SinkObject_AdjustSinkObject ); + StopLoopSound(); +} + +void SinkObject::Touched( Event *ev ) +{ + Entity * other; + + // if we aren't active, don't let anyone touch us + if ( !active ) + return; + + other = ev->GetEntity( 1 ); + + assert( other ); + if ( !other ) + return; + + // + // only things resting on me will affect me + // + if ( other->groundentity != this->edict ) + return; + + if ( delay ) + { + if ( time_touched == -1.0f ) + time_touched = level.time + delay; + // + // not time yet + // + if ( level.time < time_touched ) + return; + } + if ( sinksound.length() ) + { + LoopSound( sinksound ); + } + + velocity.z -= speed; + + if ( origin.z < ( startpos.z - limit ) ) + { + origin.z = startpos.z - limit; + setOrigin( origin ); + velocity = vec_zero; + } + + touched = true; + + if ( spawnflags & FALLAWAY ) + { + CancelEventsOfType( EV_SinkObject_Fall ); + PostEvent( EV_SinkObject_Fall, FRAMETIME ); + return; + } + + CancelEventsOfType( EV_SinkObject_AdjustSinkObject ); + PostEvent( EV_SinkObject_AdjustSinkObject, FRAMETIME ); +} + +void SinkObject::Fall( Event *ev ) +{ + velocity.z -= speed; + + if ( origin.z < ( startpos.z - limit ) ) + { + origin.z = startpos.z - limit; + setOrigin( origin ); + velocity = vec_zero; + StopLoopSound(); + } + else + { + CancelEventsOfType( EV_SinkObject_Fall ); + PostEvent( EV_SinkObject_Fall, FRAMETIME ); + } +} + +void SinkObject::Adjust( Event *ev ) +{ + if ( !touched ) + { + float diff; + + if ( spawnflags & NO_RESET ) + { + StopLoopSound(); + time_touched = -1; + velocity.z = 0; + return; + } + else + { + if ( resetdelay ) + { + // stop the object + velocity.z = 0; + // kill its sound + StopLoopSound(); + if ( time_reset == -1.0f ) + time_reset = level.time + resetdelay; + // + // not time yet + // + if ( level.time < time_reset ) + { + PostEvent( EV_SinkObject_AdjustSinkObject, FRAMETIME ); + return; + } + } + if ( resetsound.length() ) + { + LoopSound( resetsound ); + } + diff = startpos.z - origin.z; + + velocity.z += resetspeed * diff; + velocity.z *= dampening; + if ( ( fabs( diff ) < 0.5f ) && ( fabs( velocity.z ) < 1.0f ) ) + { + Reset( NULL ); + return; + } + } + } + else + { + touched = false; + } + + PostEvent( EV_SinkObject_AdjustSinkObject, FRAMETIME ); +} + +void SinkObject::MakeActive( Event *ev ) +{ + active = true; +} + +void SinkObject::MakeNonActive( Event *ev ) +{ + active = false; +} diff --git a/dlls/game/specialfx.h b/dlls/game/specialfx.h new file mode 100644 index 0000000..4f5cbf9 --- /dev/null +++ b/dlls/game/specialfx.h @@ -0,0 +1,171 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/specialfx.h $ +// $Revision:: 3 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// special effects +// + +#ifndef __SPECIAL_FX_H__ +#define __SPECIAL_FX_H__ + +#include "g_local.h" +#include "scriptslave.h" + +class Fulcrum : public ScriptSlave + { + private: + float resetspeed; + float dampening; + float limit; + float speed; + qboolean touched; + Vector startangles; + str movesound; + + public: + CLASS_PROTOTYPE( Fulcrum ); + Fulcrum(); + + void Setup( Event *ev ); + void SetSpeed( Event *ev ); + void SetResetSpeed( Event *ev ); + void SetDampening( Event *ev ); + void SetLimit( Event *ev ); + void SetMoveSound( Event *ev ); + void Reset( Event *ev ); + void Touched( Event *ev ); + void Adjust( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void Fulcrum::Archive + ( + Archiver &arc + ) + { + ScriptSlave::Archive( arc ); + + arc.ArchiveFloat( &resetspeed ); + arc.ArchiveFloat( &dampening ); + arc.ArchiveFloat( &limit ); + arc.ArchiveFloat( &speed ); + arc.ArchiveBoolean( &touched ); + arc.ArchiveVector( &startangles ); + arc.ArchiveString( &movesound ); + } + + +class RunThrough : public Entity + { + private: + Vector offset; + float speed; + float chance; + float delay; + float lasttriggertime; + float lip; + str spawnmodel; + + void SetSpeed( Event *ev ); + void SetChance( Event *ev ); + void SetDelay( Event *ev ); + void SetLip( Event *ev ); + void SetSpawnModel( Event *ev ); + void SetOffset( Event *ev ); + void Touched( Event *ev ); + public: + CLASS_PROTOTYPE( RunThrough ); + RunThrough(); + + virtual void Archive( Archiver &arc ); + }; + +inline void RunThrough::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveVector( &offset ); + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &chance ); + arc.ArchiveFloat( &delay ); + arc.ArchiveFloat( &lasttriggertime ); + arc.ArchiveFloat( &lip ); + arc.ArchiveString( &spawnmodel ); + } + +class SinkObject : public ScriptSlave + { + private: + float resetspeed; + float resetdelay; + float dampening; + float limit; + float speed; + float delay; + float time_touched; + float time_reset; + str sinksound; + str resetsound; + qboolean touched; + qboolean active; + Vector startpos; + + public: + CLASS_PROTOTYPE( SinkObject ); + SinkObject(); + + void Setup( Event *ev ); + void SetSpeed( Event *ev ); + void SetDelay( Event *ev ); + void SetResetSpeed( Event *ev ); + void SetResetDelay( Event *ev ); + void SetDampening( Event *ev ); + void SetLimit( Event *ev ); + void Reset( Event *ev ); + void Touched( Event *ev ); + void Adjust( Event *ev ); + void Fall( Event *ev ); + void SetResetSound( Event *ev ); + void SetSinkSound( Event *ev ); + void MakeActive( Event *ev ); + void MakeNonActive( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void SinkObject::Archive + ( + Archiver &arc + ) + { + ScriptSlave::Archive( arc ); + + arc.ArchiveFloat( &resetspeed ); + arc.ArchiveFloat( &resetdelay ); + arc.ArchiveFloat( &dampening ); + arc.ArchiveFloat( &limit ); + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &delay ); + arc.ArchiveFloat( &time_touched ); + arc.ArchiveFloat( &time_reset ); + arc.ArchiveString( &sinksound ); + arc.ArchiveString( &resetsound ); + arc.ArchiveBoolean( &touched ); + arc.ArchiveBoolean( &active ); + arc.ArchiveVector( &startpos ); + } + +#endif /* specialfx.h */ diff --git a/dlls/game/stack.h b/dlls/game/stack.h new file mode 100644 index 0000000..81a1795 --- /dev/null +++ b/dlls/game/stack.h @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/stack.h $ +// $Revision:: 3 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Generic Stack object. +// + +#ifndef __STACK_H__ +#define __STACK_H__ + +#include "g_local.h" +#include "class.h" + +template +class StackNode : public Class + { + public: + Type data; + StackNode *next; + + StackNode( Type d ); + }; + +template +inline StackNode::StackNode( Type d ) : data( d ) + { + next = NULL; + } + +template +class Stack : public Class + { + private: + StackNode *head; + + public: + Stack(); + ~Stack(); + void Clear( void ); + qboolean Empty( void ); + void Push( Type data ); + Type Pop( void ); + }; + +template +inline Stack::Stack() + { + head = NULL; + } + +template +inline Stack::~Stack() + { + Clear(); + } + +template +inline void Stack::Clear + ( + void + ) + + { + while( !Empty() ) + { + Pop(); + } + } + +template +inline qboolean Stack::Empty + ( + void + ) + + { + if ( head == NULL ) + { + return true; + } + return false; + } + +template +inline void Stack::Push + ( + Type data + ) + + { + StackNode *tmp; + + tmp = new StackNode( data ); + if ( !tmp ) + { + assert( NULL ); + gi.Error( ERR_DROP, "Stack::Push : Out of memory" ); + } + + tmp->next = head; + head = tmp; + } + +template +inline Type Stack::Pop + ( + void + ) + + { + Type ret; + StackNode *node; + + if ( !head ) + { + return NULL; + } + + node = head; + ret = node->data; + head = node->next; + + delete node; + + return ret; + } + +#endif /* stack.h */ diff --git a/dlls/game/stationaryFireCombat.cpp b/dlls/game/stationaryFireCombat.cpp new file mode 100644 index 0000000..67791f5 --- /dev/null +++ b/dlls/game/stationaryFireCombat.cpp @@ -0,0 +1,601 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/stationaryFireCombat.cpp $ +// $Revision:: 7 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// StationaryFireCombat Implementation +// +// PARAMETERS: +// +// ANIMATIONS: +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "stationaryFireCombat.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, StationaryFireCombat, NULL ) + { + { &EV_Behavior_Args, &StationaryFireCombat::SetArgs }, + { &EV_Behavior_AnimDone, &StationaryFireCombat::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: StationaryFireCombat() +// Class: StationaryFireCombat +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +StationaryFireCombat::StationaryFireCombat() +{ + _endFireTime = 0.0f; + _endAimTime = 0.0f; +} + +//-------------------------------------------------------------- +// Name: ~StationaryFireCombat() +// Class: StationaryFireCombat +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +StationaryFireCombat::~StationaryFireCombat() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: StationaryFireCombat +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::SetArgs( Event *ev ) +{ + _aimAnim = ev->GetString( 1 ); + _preFireAnim = ev->GetString( 2 ); + _fireAnim = ev->GetString( 3 ); + _postFireAnim = ev->GetString( 4 ); + _stance = ev->GetString( 5 ); + _aimTimeMin = ev->GetFloat( 6 ); + _aimTimeMax = ev->GetFloat( 7 ); + _fireTimeMin = ev->GetFloat( 8 ); + _fireTimeMax = ev->GetFloat( 9 ); +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: StationaryFireCombat +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::AnimDone( Event *ev ) +{ + _animDone = true; +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: StationaryFireCombat +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::Begin( Actor &self ) +{ + init( self ); + transitionToState ( STATIONARY_FIRE_AIM ); +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: StationaryFireCombat +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t StationaryFireCombat::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case STATIONARY_FIRE_AIM: + //--------------------------------------------------------------------- + stateResult = evaluateStateAim(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + updateEnemy(); + if ( _preFireAnim.length() ) + transitionToState( STATIONARY_FIRE_PRE_FIRE ); + else + transitionToState( STATIONARY_FIRE_ATTACK ); + } + + break; + + //--------------------------------------------------------------------- + case STATIONARY_FIRE_PRE_FIRE: + //--------------------------------------------------------------------- + stateResult = evaluateStatePreFire(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( STATIONARY_FIRE_ATTACK ); + } + + if ( stateResult == BEHAVIOR_FAILED ) + { + transitionToState( STATIONARY_FIRE_FAILED ); + } + + break; + + //--------------------------------------------------------------------- + case STATIONARY_FIRE_ATTACK: + //--------------------------------------------------------------------- + stateResult = evaluateStateAttack(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + _fireWeapon.End( *_self ); + + if ( _postFireAnim.length() ) + transitionToState( STATIONARY_FIRE_POST_FIRE ); + else + transitionToState( STATIONARY_FIRE_SUCCESS ); + } + + if ( stateResult == BEHAVIOR_FAILED ) + { + _fireWeapon.End( *_self ); + + if ( _postFireAnim.length() ) + transitionToState( STATIONARY_FIRE_POST_FIRE ); + else + transitionToState( STATIONARY_FIRE_SUCCESS ); + } + + break; + + + //--------------------------------------------------------------------- + case STATIONARY_FIRE_POST_FIRE: + //--------------------------------------------------------------------- + stateResult = evaluateStatePostFire(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( STATIONARY_FIRE_SUCCESS ); + } + break; + + //--------------------------------------------------------------------- + case STATIONARY_FIRE_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + + break; + + + //--------------------------------------------------------------------- + case STATIONARY_FIRE_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + + break; + + + } + + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: StationaryFireCombat +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::End(Actor &self) +{ + if ( !_self ) + return; + + _fireWeapon.End(*_self); + _self->SetAnim( _aimAnim , NULL , torso ); + + _self->ClearTorsoAnim(); +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: StationaryFireCombat +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::transitionToState( StationaryFireStates_t state ) +{ + switch ( state ) + { + case STATIONARY_FIRE_AIM: + setupStateAim(); + setInternalState( state , "STATIONARY_FIRE_AIM" ); + break; + + case STATIONARY_FIRE_PRE_FIRE: + setupStatePreFire(); + setInternalState( state , "STATIONARY_FIRE_PRE_FIRE" ); + break; + + case STATIONARY_FIRE_ATTACK: + setupStateAttack(); + setInternalState( state , "STATIONARY_FIRE_ATTACK" ); + break; + + case STATIONARY_FIRE_POST_FIRE: + setupStatePostFire(); + setInternalState( state , "STATIONARY_FIRE_POST_FIRE" ); + break; + + case STATIONARY_FIRE_SUCCESS: + setInternalState( state , "STATIONARY_FIRE_SUCCESS" ); + break; + + case STATIONARY_FIRE_FAILED: + setInternalState( state , "SUPPRESSION_FIRE_FAILED" ); + break; + } + +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: StationaryFireCombat +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::setInternalState( StationaryFireStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: StationaryFireCombat +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::init( Actor &self ) +{ + _self = &self; + _animDone = false; + _canAttack = true; + updateEnemy(); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: StationaryFireCombat +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::think() +{ +} + +//-------------------------------------------------------------- +// Name: StationaryFireCombat() +// Class: setupStateAttack +// +// Description: Sets up our Attack State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::setupStateAttack() +{ + faceEnemy(); + _endFireTime = level.time + G_Random(_fireTimeMax - _fireTimeMin) + _fireTimeMin; + + if ( _self->combatSubsystem->CanAttackEnemy() ) + { + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); + } + +} + +//-------------------------------------------------------------- +// Name: evaluateStateAttack() +// Class: StationaryFireCombat +// +// Description: Evaluates our Attack State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t StationaryFireCombat::evaluateStateAttack() +{ + BehaviorReturnCode_t result; + _rotateToEntity.Evaluate( *_self ); + + if ( _self->combatSubsystem->CanAttackEnemy() ) + result = _fireWeapon.Evaluate( *_self ); + else + { + _fireWeapon.End( *_self ); + result = BEHAVIOR_FAILED; + } + + if ( result == BEHAVIOR_FAILED ) + failureStateAttack( "StationaryFireCombat::evaluateStateAttack -- FAILED" ); + + if ( level.time > _endFireTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAttack() +// Class: StationaryFireCombat +// +// Description: Failure Handler for Attack State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::failureStateAttack( const str &failureReason ) +{ + SetFailureReason( failureReason ); + _fireWeapon.End( *_self ); +} + +//-------------------------------------------------------------- +// Name: setupStatePause() +// Class: StationaryFireCombat +// +// Description: Sets up our Pause State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::setupStateAim() +{ + faceEnemy(); + _endAimTime = level.time + G_Random(_aimTimeMax - _aimTimeMin ) + _aimTimeMin; + _self->SetAnim( _aimAnim , NULL , torso ); + _self->SetAnim( _stance , NULL , legs ); + +} + +//-------------------------------------------------------------- +// Name: evaluateStatePause() +// Class: StationaryFireCombat +// +// Description: Evaluates our Pause State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t StationaryFireCombat::evaluateStateAim() +{ + _rotateToEntity.Evaluate( *_self ); + if ( level.time > _endAimTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + + +//-------------------------------------------------------------- +// Name: failureStatePause() +// Class: StationaryFireCombat +// +// Description: Failure Handler for our Pause State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::failureStateAim( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + + +void StationaryFireCombat::setupStatePreFire() +{ + if ( _self->combatSubsystem->CanAttackEnemy() ) + { + _animDone = false; + _self->SetAnim( _preFireAnim , EV_Actor_NotifyBehavior , torso ); + } + else + _canAttack = false; +} + +BehaviorReturnCode_t StationaryFireCombat::evaluateStatePreFire() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + if ( !_canAttack ) + return BEHAVIOR_FAILED; + + return BEHAVIOR_EVALUATING; +} + +void StationaryFireCombat::failureStatePreFire( const str &failureReason ) +{ +} + +void StationaryFireCombat::setupStatePostFire() +{ + _animDone = false; + _self->SetAnim( _postFireAnim , EV_Actor_NotifyBehavior , torso ); + +} + +BehaviorReturnCode_t StationaryFireCombat::evaluateStatePostFire() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_FAILED; + +} + +void StationaryFireCombat::failureStatePostFire( const str &failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: StationaryFireCombat +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::updateEnemy() +{ + Entity *currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + SetFailureReason( "STATIONARY_FIRE_FAILED::updateEnemy -- No Enemy" ); + transitionToState( STATIONARY_FIRE_FAILED ); + return; + } + + } + + _currentEnemy = currentEnemy; + //_self->turnTowardsEntity( _currentEnemy, 0.0f ); +} + +//-------------------------------------------------------------- +// Name: faceEnemy() +// Class: StationaryFireCombat +// +// Description: Sets up the Rotate Component +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombat::faceEnemy() +{ + _rotateToEntity.SetEntity( _currentEnemy ); + _rotateToEntity.SetTurnSpeed( 30.0f ); + _rotateToEntity.Begin( *_self ); + +} + + + + + + + + + + + + + + + + + + + + + + diff --git a/dlls/game/stationaryFireCombat.hpp b/dlls/game/stationaryFireCombat.hpp new file mode 100644 index 0000000..114cb4c --- /dev/null +++ b/dlls/game/stationaryFireCombat.hpp @@ -0,0 +1,195 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/stationaryFireCombat.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// StationaryFireCombat Behavior Definition +// +//-------------------------------------------------------------------------------- + + +//============================== +// Forward Declarations +//============================== +class StationaryFireCombat; + +#ifndef __STATIONARY_FIRE_COMBAT___ +#define __STATIONARY_FIRE_COMBAT___ + +#include "behavior.h" +#include "behaviors_general.h" +#include "gotoHelperNode.hpp" +#include "rotateToEntity.hpp" + +//------------------------- CLASS ------------------------------ +// +// Name: CoverCombatWithRangedWeapon +// Base Class: Behavior +// +// Description: +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class StationaryFireCombat : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + STATIONARY_FIRE_AIM, + STATIONARY_FIRE_PRE_FIRE, + STATIONARY_FIRE_ATTACK, + STATIONARY_FIRE_POST_FIRE, + STATIONARY_FIRE_SUCCESS, + STATIONARY_FIRE_FAILED + } StationaryFireStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _aimAnim; + str _preFireAnim; + str _fireAnim; + str _postFireAnim; + str _stance; + float _aimTimeMin; + float _aimTimeMax; + float _fireTimeMin; + float _fireTimeMax; + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( StationaryFireStates_t state ); + void setInternalState ( StationaryFireStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + void faceEnemy (); + + void setupStateAim (); + BehaviorReturnCode_t evaluateStateAim (); + void failureStateAim ( const str& failureReason ); + + void setupStatePreFire (); + BehaviorReturnCode_t evaluateStatePreFire (); + void failureStatePreFire ( const str& failureReason ); + + void setupStateAttack (); + BehaviorReturnCode_t evaluateStateAttack (); + void failureStateAttack ( const str& failureReason ); + + void setupStatePostFire (); + BehaviorReturnCode_t evaluateStatePostFire (); + void failureStatePostFire ( const str& failureReason ); + + + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( StationaryFireCombat ); + + StationaryFireCombat(); + ~StationaryFireCombat(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + // Accessors + void SetAimAnim ( const str &anim ) { _aimAnim = anim; } + void SetFireAnim ( const str &anim ) { _fireAnim = anim; } + void SetPreFireAnim ( const str &anim ) { _preFireAnim = anim; } + void SetPostFireAnim ( const str &anim ) { _postFireAnim = anim; } + void SetStance ( const str &anim ) { _stance = anim; } + void SetAimTimeMin ( float minTime ) { _aimTimeMin = minTime; } + void SetAimTimeMax ( float maxTime ) { _aimTimeMax = maxTime; } + void SetFireTimeMin ( float minTime ) { _fireTimeMin = minTime; } + void SetFireTimeMax ( float maxTime ) { _fireTimeMax = maxTime; } + + + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + FireWeapon _fireWeapon; + RotateToEntity _rotateToEntity; + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + StationaryFireStates_t _state; + float _endFireTime; + float _endAimTime; + EntityPtr _currentEnemy; + bool _animDone; + bool _canAttack; + Actor *_self; + + + }; + +inline void StationaryFireCombat::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveString ( &_aimAnim ); + arc.ArchiveString ( &_preFireAnim ); + arc.ArchiveString ( &_fireAnim ); + arc.ArchiveString ( &_postFireAnim ); + arc.ArchiveString ( &_stance ); + arc.ArchiveFloat ( &_aimTimeMin ); + arc.ArchiveFloat ( &_aimTimeMax ); + arc.ArchiveFloat ( &_fireTimeMin ); + arc.ArchiveFloat ( &_fireTimeMax ); + + // + // Archive Components + // + arc.ArchiveObject ( &_fireWeapon ); + arc.ArchiveObject ( &_rotateToEntity ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, StationaryFireStates_t ); + arc.ArchiveFloat ( &_endFireTime ); + arc.ArchiveFloat ( &_endAimTime ); + arc.ArchiveSafePointer ( &_currentEnemy ); + arc.ArchiveBool ( &_animDone ); + arc.ArchiveBool ( &_canAttack ); + arc.ArchiveObjectPointer( ( Class ** )&_self ); +} + + +#endif /* __STATIONARY_FIRE_COMBAT___ */ + diff --git a/dlls/game/stationaryFireCombatEX.cpp b/dlls/game/stationaryFireCombatEX.cpp new file mode 100644 index 0000000..48f31a1 --- /dev/null +++ b/dlls/game/stationaryFireCombatEX.cpp @@ -0,0 +1,647 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/stationaryFireCombatEX.cpp $ +// $Revision:: 7 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// StationaryFireCombatEX Implementation +// +// PARAMETERS: +// +// ANIMATIONS: +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "stationaryFireCombatEX.hpp" +#include + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, StationaryFireCombatEX, NULL ) + { + { &EV_Behavior_Args, &StationaryFireCombatEX::SetArgs }, + { &EV_Behavior_AnimDone, &StationaryFireCombatEX::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: StationaryFireCombatEX() +// Class: StationaryFireCombatEX +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +StationaryFireCombatEX::StationaryFireCombatEX() +{ + _endFireTime = 0.0f; + _endAimTime = 0.0f; +} + +//-------------------------------------------------------------- +// Name: ~StationaryFireCombatEX() +// Class: StationaryFireCombatEX +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +StationaryFireCombatEX::~StationaryFireCombatEX() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: StationaryFireCombatEX +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::SetArgs( Event *ev ) +{ + _stance = ev->GetString( 1 ); + _aimTimeMin = ev->GetFloat( 2 ); + _aimTimeMax = ev->GetFloat( 3 ); + _fireTimeMin = ev->GetFloat( 4 ); + _fireTimeMax = ev->GetFloat( 5 ); + _forceAttack = ev->GetBoolean( 6 ); + +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: StationaryFireCombatEX +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::AnimDone( Event *ev ) +{ + _animDone = true; +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: StationaryFireCombatEX +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::Begin( Actor &self ) +{ + init( self ); + transitionToState ( STATIONARY_FIRE_AIM ); +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: StationaryFireCombatEX +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t StationaryFireCombatEX::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case STATIONARY_FIRE_AIM: + //--------------------------------------------------------------------- + stateResult = evaluateStateAim(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + updateEnemy(); + if ( _preFireAnim.length() ) + transitionToState( STATIONARY_FIRE_PRE_FIRE ); + else + transitionToState( STATIONARY_FIRE_ATTACK ); + } + + break; + + //--------------------------------------------------------------------- + case STATIONARY_FIRE_PRE_FIRE: + //--------------------------------------------------------------------- + stateResult = evaluateStatePreFire(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( STATIONARY_FIRE_ATTACK ); + } + + if ( stateResult == BEHAVIOR_FAILED ) + { + transitionToState( STATIONARY_FIRE_FAILED ); + } + + break; + + //--------------------------------------------------------------------- + case STATIONARY_FIRE_ATTACK: + //--------------------------------------------------------------------- + stateResult = evaluateStateAttack(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + _fireWeapon.End( *_self ); + + if ( _postFireAnim.length() ) + transitionToState( STATIONARY_FIRE_POST_FIRE ); + else + transitionToState( STATIONARY_FIRE_SUCCESS ); + } + + if ( stateResult == BEHAVIOR_FAILED ) + { + _fireWeapon.End( *_self ); + + if ( _postFireAnim.length() ) + transitionToState( STATIONARY_FIRE_POST_FIRE ); + else + transitionToState( STATIONARY_FIRE_SUCCESS ); + } + + break; + + + //--------------------------------------------------------------------- + case STATIONARY_FIRE_POST_FIRE: + //--------------------------------------------------------------------- + stateResult = evaluateStatePostFire(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( STATIONARY_FIRE_SUCCESS ); + } + break; + + //--------------------------------------------------------------------- + case STATIONARY_FIRE_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + + break; + + + //--------------------------------------------------------------------- + case STATIONARY_FIRE_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + + break; + + + } + + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: StationaryFireCombatEX +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::End(Actor &self) +{ + if ( !_self ) + return; + + _fireWeapon.End(*_self); + _self->SetAnim( _aimAnim , NULL , torso ); +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: StationaryFireCombatEX +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::transitionToState( StationaryFireStates_t state ) +{ + switch ( state ) + { + case STATIONARY_FIRE_AIM: + setupStateAim(); + setInternalState( state , "STATIONARY_FIRE_AIM" ); + break; + + case STATIONARY_FIRE_PRE_FIRE: + setupStatePreFire(); + setInternalState( state , "STATIONARY_FIRE_PRE_FIRE" ); + break; + + case STATIONARY_FIRE_ATTACK: + setupStateAttack(); + setInternalState( state , "STATIONARY_FIRE_ATTACK" ); + break; + + case STATIONARY_FIRE_POST_FIRE: + setupStatePostFire(); + setInternalState( state , "STATIONARY_FIRE_POST_FIRE" ); + break; + + case STATIONARY_FIRE_SUCCESS: + setInternalState( state , "STATIONARY_FIRE_SUCCESS" ); + break; + + case STATIONARY_FIRE_FAILED: + setInternalState( state , "SUPPRESSION_FIRE_FAILED" ); + break; + } + +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: StationaryFireCombatEX +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::setInternalState( StationaryFireStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: StationaryFireCombatEX +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::init( Actor &self ) +{ + _self = &self; + _animDone = false; + _canAttack = true; + + _aimAnim = "idle"; + _preFireAnim = "idle"; + _fireAnim = "idle"; + _postFireAnim = "idle"; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(self.getArchetype()) ) + return; + + str objname = self.combatSubsystem->GetActiveWeaponArchetype(); + objname = "Hold" + objname; + + if ( gpm->hasProperty(objname, "Idle") ) + _aimAnim = gpm->getStringValue( objname , "Idle" ); + + if ( gpm->hasProperty( objname , "PreFire" ) ) + _preFireAnim = gpm->getStringValue( objname , "PreFire" ); + + if ( gpm->hasProperty( objname , "Fire" ) ) + _fireAnim = gpm->getStringValue( objname , "Fire" ); + + if ( gpm->hasProperty( objname , "PostFire" ) ) + _postFireAnim = gpm->getStringValue( objname , "PostFire" ); + + _stance = self.GetStateVar( _stance ); + + if ( !_stance.length() ) + _stance = "idle"; + + updateEnemy(); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: StationaryFireCombatEX +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::think() +{ +} + +//-------------------------------------------------------------- +// Name: StationaryFireCombat() +// Class: StationaryFireCombatEX +// +// Description: Sets up our Attack State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::setupStateAttack() +{ + faceEnemy(); + _endFireTime = level.time + G_Random(_fireTimeMax - _fireTimeMin) + _fireTimeMin; + + if ( _self->combatSubsystem->CanAttackEnemy() || _forceAttack ) + { + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); + } + +} + +//-------------------------------------------------------------- +// Name: evaluateStateAttack() +// Class: StationaryFireCombatEX +// +// Description: Evaluates our Attack State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t StationaryFireCombatEX::evaluateStateAttack() +{ + BehaviorReturnCode_t result; + _rotateToEntity.Evaluate( *_self ); + + if ( _self->combatSubsystem->CanAttackEnemy() || _forceAttack ) + result = _fireWeapon.Evaluate( *_self ); + else + { + _fireWeapon.End( *_self ); + result = BEHAVIOR_FAILED; + } + + if ( result == BEHAVIOR_FAILED ) + failureStateAttack( "StationaryFireCombat::evaluateStateAttack -- FAILED" ); + + if ( level.time > _endFireTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAttack() +// Class: StationaryFireCombatEX +// +// Description: Failure Handler for Attack State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::failureStateAttack( const str &failureReason ) +{ + SetFailureReason( failureReason ); + _fireWeapon.End( *_self ); +} + +//-------------------------------------------------------------- +// Name: setupStatePause() +// Class: StationaryFireCombatEX +// +// Description: Sets up our Pause State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::setupStateAim() +{ + faceEnemy(); + _endAimTime = level.time + G_Random(_aimTimeMax - _aimTimeMin ) + _aimTimeMin; + _self->SetAnim( _aimAnim , NULL , torso ); + _self->SetAnim( _stance , NULL , legs ); + +} + +//-------------------------------------------------------------- +// Name: evaluateStatePause() +// Class: StationaryFireCombatEX +// +// Description: Evaluates our Pause State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t StationaryFireCombatEX::evaluateStateAim() +{ + _rotateToEntity.Evaluate( *_self ); + if ( level.time > _endAimTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + + +//-------------------------------------------------------------- +// Name: failureStatePause() +// Class: StationaryFireCombatEX +// +// Description: Failure Handler for our Pause State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::failureStateAim( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + + +void StationaryFireCombatEX::setupStatePreFire() +{ + if ( _self->combatSubsystem->CanAttackEnemy() || _forceAttack ) + { + _animDone = false; + _self->SetAnim( _preFireAnim , EV_Actor_NotifyBehavior , torso ); + } + else + _canAttack = false; +} + +BehaviorReturnCode_t StationaryFireCombatEX::evaluateStatePreFire() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + if ( !_canAttack ) + return BEHAVIOR_FAILED; + + return BEHAVIOR_EVALUATING; +} + +void StationaryFireCombatEX::failureStatePreFire( const str &failureReason ) +{ +} + +void StationaryFireCombatEX::setupStatePostFire() +{ + _animDone = false; + _self->SetAnim( _postFireAnim , EV_Actor_NotifyBehavior , torso ); + +} + +BehaviorReturnCode_t StationaryFireCombatEX::evaluateStatePostFire() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_FAILED; + +} + +void StationaryFireCombatEX::failureStatePostFire( const str &failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: StationaryFireCombatEX +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::updateEnemy() +{ + // + // First, we try and get our current enemy. If we don't have one + // then we ask the enemyManager to evaluate and try again. + // If that fails, then we look at our _forceAttack status. + // If we are forcing and attack, then that means we want to + // fire pretty much no matter what... So if we don't have a + // currentEnemy, then I'm going to see if the Player is a valid + // target, and set that to be the current enemy + Entity *currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + Player* player; + player = GetPlayer( 0 ); + + if ( player && GetSelf()->enemyManager->Hates(player) && _forceAttack ) + { + currentEnemy = player; + GetSelf()->enemyManager->SetCurrentEnemy( currentEnemy ); + } + else + { + SetFailureReason( "STATIONARY_FIRE_FAILED::updateEnemy -- No Enemy" ); + transitionToState( STATIONARY_FIRE_FAILED ); + return; + } + } + + } + + _currentEnemy = currentEnemy; + _self->turnTowardsEntity( _currentEnemy, 0.0f ); +} + +//-------------------------------------------------------------- +// Name: faceEnemy() +// Class: StationaryFireCombat +// +// Description: Sets up the Rotate Component +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void StationaryFireCombatEX::faceEnemy() +{ + _rotateToEntity.SetEntity( _currentEnemy ); + _rotateToEntity.SetTurnSpeed( 30.0f ); + _rotateToEntity.Begin( *_self ); + +} + + + + + + + + + + + + + + + + + + + + + + diff --git a/dlls/game/stationaryFireCombatEX.hpp b/dlls/game/stationaryFireCombatEX.hpp new file mode 100644 index 0000000..eb818f1 --- /dev/null +++ b/dlls/game/stationaryFireCombatEX.hpp @@ -0,0 +1,199 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/stationaryFireCombatEX.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// StationaryFireCombat Behavior Definition +// +//-------------------------------------------------------------------------------- + + +//============================== +// Forward Declarations +//============================== +class StationaryFireCombatEX; + +#ifndef __STATIONARY_FIRE_COMBAT_EX___ +#define __STATIONARY_FIRE_COMBAT_EX___ + +#include "behavior.h" +#include "behaviors_general.h" +#include "gotoHelperNode.hpp" +#include "rotateToEntity.hpp" + +//------------------------- CLASS ------------------------------ +// +// Name: StationaryFireCombatEx +// Base Class: Behavior +// +// Description: +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class StationaryFireCombatEX : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + STATIONARY_FIRE_AIM, + STATIONARY_FIRE_PRE_FIRE, + STATIONARY_FIRE_ATTACK, + STATIONARY_FIRE_POST_FIRE, + STATIONARY_FIRE_SUCCESS, + STATIONARY_FIRE_FAILED + } StationaryFireStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _stance; + float _aimTimeMin; + float _aimTimeMax; + float _fireTimeMin; + float _fireTimeMax; + bool _forceAttack; + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( StationaryFireStates_t state ); + void setInternalState ( StationaryFireStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + void faceEnemy (); + + void setupStateAim (); + BehaviorReturnCode_t evaluateStateAim (); + void failureStateAim ( const str& failureReason ); + + void setupStatePreFire (); + BehaviorReturnCode_t evaluateStatePreFire (); + void failureStatePreFire ( const str& failureReason ); + + void setupStateAttack (); + BehaviorReturnCode_t evaluateStateAttack (); + void failureStateAttack ( const str& failureReason ); + + void setupStatePostFire (); + BehaviorReturnCode_t evaluateStatePostFire (); + void failureStatePostFire ( const str& failureReason ); + + + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( StationaryFireCombatEX ); + + StationaryFireCombatEX(); + ~StationaryFireCombatEX(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + // Accessors + void SetAimAnim ( const str &anim ) { _aimAnim = anim; } + void SetFireAnim ( const str &anim ) { _fireAnim = anim; } + void SetPreFireAnim ( const str &anim ) { _preFireAnim = anim; } + void SetPostFireAnim ( const str &anim ) { _postFireAnim = anim; } + void SetStance ( const str &anim ) { _stance = anim; } + void SetAimTimeMin ( float minTime ) { _aimTimeMin = minTime; } + void SetAimTimeMax ( float maxTime ) { _aimTimeMax = maxTime; } + void SetFireTimeMin ( float minTime ) { _fireTimeMin = minTime; } + void SetFireTimeMax ( float maxTime ) { _fireTimeMax = maxTime; } + + + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + FireWeapon _fireWeapon; + RotateToEntity _rotateToEntity; + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + StationaryFireStates_t _state; + float _endFireTime; + float _endAimTime; + EntityPtr _currentEnemy; + bool _animDone; + bool _canAttack; + str _aimAnim; + str _preFireAnim; + str _fireAnim; + str _postFireAnim; + + Actor *_self; + + + }; + +inline void StationaryFireCombatEX::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveString ( &_stance ); + arc.ArchiveFloat ( &_aimTimeMin ); + arc.ArchiveFloat ( &_aimTimeMax ); + arc.ArchiveFloat ( &_fireTimeMin ); + arc.ArchiveFloat ( &_fireTimeMax ); + arc.ArchiveBool ( &_forceAttack ); + + // + // Archive Components + // + arc.ArchiveObject ( &_fireWeapon ); + arc.ArchiveObject ( &_rotateToEntity ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, StationaryFireStates_t ); + arc.ArchiveFloat ( &_endFireTime ); + arc.ArchiveFloat ( &_endAimTime ); + arc.ArchiveSafePointer ( &_currentEnemy ); + arc.ArchiveBool ( &_animDone ); + arc.ArchiveBool ( &_canAttack ); + arc.ArchiveString ( &_aimAnim ); + arc.ArchiveString ( &_preFireAnim ); + arc.ArchiveString ( &_fireAnim ); + arc.ArchiveString ( &_postFireAnim ); + + arc.ArchiveObjectPointer( ( Class ** )&_self ); +} + + +#endif /* __STATIONARY_FIRE_COMBAT_EX___ */ + diff --git a/dlls/game/stationaryvehicle.cpp b/dlls/game/stationaryvehicle.cpp new file mode 100644 index 0000000..1fdf14c --- /dev/null +++ b/dlls/game/stationaryvehicle.cpp @@ -0,0 +1,213 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/stationaryvehicle.cpp $ +// $Revision:: 8 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// + +#include "stationaryvehicle.hpp" +#include "player.h" + + +CLASS_DECLARATION(Vehicle, StationaryVehicle, NULL) +{ + { NULL, NULL } +}; + + + +//----------------------------------------------------- +// +// Name: StationaryVehicle +// Class: StationaryVehicle +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +StationaryVehicle::StationaryVehicle() +{ + _yawDeltaDegrees = 0.0f; + _pitchDeltaDegrees = 0.0f; + +} + + +//----------------------------------------------------- +// +// Name: ~StationaryVehicle +// Class: StationaryVehicle +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +StationaryVehicle::~StationaryVehicle() +{ + +} + + + +//----------------------------------------------------- +// +// Name: Postthink +// Class: StationaryVehicle +// +// Description: Updates the vehicle location based upon the user input. +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void StationaryVehicle::Postthink() +{ + if(drivable) + { + turnangle = turnangle * 0.25f + turnimpulse; + pitchangle = pitchangle * 0.25f + pitchimpulse; + + float turn = turnangle * ( 1.0f / 200.0f ); + float pitch = pitchangle * ( 1.0f / 200.0f ); + + avelocity *= 0.05f; + avelocity.y += turn * 200.0f; + avelocity.x += pitch * 200.0f; + + angles += avelocity * level.frametime; + setAngles(angles); + } + + PositionVehicleAndDriver(); + +} + +//----------------------------------------------------- +// +// Name: PositionVehicleAndDriver +// Class: StationaryVehicle +// +// Description: Positions the vehicle and driver of the vehicle. This positioning is based +// upon the amount of delta angles the user has moved the mouse. +// Pitch and yaw restrictions can be set through the tiki file to restrict the players +// view angles. +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void StationaryVehicle::PositionVehicleAndDriver(void) +{ + if(driver == 0) + return; + + if(!driver->isSubclassOf(Player)) + return; + + Vector i,j,k; + i = Vector( orientation[ 0 ] ); + j = Vector( orientation[ 1 ] ); + k = Vector( orientation[ 2 ] ); + + Player * player; + player = ( Player * )( Sentient * )driver; + player->setOrigin( origin + ( i * driveroffset[PITCH] ) + ( j * driveroffset[YAW] ) + ( k * driveroffset[ROLL] ) ); + + if(!drivable) + return; + + player->velocity = vec_zero; + + //Adjust the pitch and normalize the degrees based upon the location of our pitch seam + angles[PITCH] += _pitchDeltaDegrees; + angles[PITCH] = AngleNormalizeArbitrary( angles[PITCH], _pitchSeam); + if( _restrictPitch ) + { + if(angles[PITCH] < _minimumPitch) + { + angles[PITCH] = _minimumPitch; + } + + if(angles[PITCH] > _maximumPitch) + { + angles[PITCH] = _maximumPitch; + } + } + + /// Adjust the yaw and normalize the degrees based upon the location of our yaw seam. + angles[YAW] += _yawDeltaDegrees; + angles[YAW] = AngleNormalizeArbitrary( angles[YAW], _yawSeam); + if( _restrictYaw ) + { + + /// Clamp yaw to the range [ _minimumRotate, _maximumRotate ] + if( angles[ YAW ] > _maximumYaw ) + { + angles[ YAW ] = _maximumYaw; + } + + if( angles[ YAW ] < _minimumYaw ) + { + angles[ YAW ] = _minimumYaw; + } + } + + //Set the player view angles based on the resulting vehicle angles + player->SetViewAngles(angles); +} + + + +//----------------------------------------------------- +// +// Name: Drive +// Class: StationaryVehicle +// +// Description: Retrieves the user commands to determine the location of the vehicle. +// +// Parameters: ucmd - the user command structure +// +// Returns: qtrue - always +//----------------------------------------------------- +//virtual +qboolean StationaryVehicle::Drive(usercmd_t* ucmd) +{ + Vector i, j, k; + + if ( !driver || !driver->isClient() ) + { + return false; + } + + if ( !drivable ) + { + driver->client->ps.pm_flags |= PMF_FROZEN; + ucmd->forwardmove = 0.0f; + ucmd->rightmove = 0.0f; + return false; + } + + if(_noPrediction ) + { + driver->client->ps.pm_flags |= PMF_NO_PREDICTION; + } + + turnimpulse = angledist( SHORT2ANGLE( ucmd->angles[ YAW ] ) - driver->client->cmd_angles[ YAW ] ); + pitchimpulse = angledist( SHORT2ANGLE( ucmd->angles[ PITCH ] ) - driver->client->cmd_angles[ PITCH ] ); + + _yawDeltaDegrees = SHORT2ANGLE(ucmd->deltaAngles[ YAW ] ); + _pitchDeltaDegrees = SHORT2ANGLE(ucmd->deltaAngles[ PITCH ] ); + + return qtrue; +} diff --git a/dlls/game/stationaryvehicle.hpp b/dlls/game/stationaryvehicle.hpp new file mode 100644 index 0000000..eae148a --- /dev/null +++ b/dlls/game/stationaryvehicle.hpp @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/specialfx.cpp $ +// $Revision:: 4 $ +// $Author:: Squirrel $ +// $Date:: 5/13/02 2:49p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Code/DLLs/game/specialfx.cpp $ + +#ifndef STATIONARY_VEHICLE_HPP +#define STATIONARY_VEHICLE_HPP + +#include "vehicle.h" + + +//-------------------------- CLASS ---------------------------------- +// +// Name: StationaryVehicle +// Base Class: Vehicle +// +// Description: This class is used for all stationary type vehicles. Stationary type vehicles do not +// move and can steer in place. An example of this would be a turret +// +// Method Of Use: +// +//------------------------------------------------------------------- +class StationaryVehicle : public Vehicle +{ + public: + CLASS_PROTOTYPE( StationaryVehicle ); + StationaryVehicle(); + virtual ~StationaryVehicle(); + + /*virtual*/ void Postthink(); + /*virtual*/ qboolean Drive( usercmd_t *ucmd ); + void PositionVehicleAndDriver(void); + + void Killed(Event* event); + + virtual void Archive( Archiver &arc ); + + + private: + float _yawDeltaDegrees; + float _pitchDeltaDegrees; +}; + +inline void StationaryVehicle::Archive( Archiver &arc ) +{ + Vehicle::Archive( arc ); + + arc.ArchiveFloat( &_yawDeltaDegrees ); + arc.ArchiveFloat( &_pitchDeltaDegrees ); +} + +#endif //STATIONARY_VEHICLE_HPP diff --git a/dlls/game/steering.cpp b/dlls/game/steering.cpp new file mode 100644 index 0000000..7e91c32 --- /dev/null +++ b/dlls/game/steering.cpp @@ -0,0 +1,264 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/steering.cpp $ +// $Revision:: 29 $ +// $Author:: Steven $ +// $Date:: 10/01/02 5:47p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Steering behaviors for AI. +// + +#include "_pch_cpp.h" +#include "steering.h" +#include "actor.h" + +//----------------------------------------------------------------------------- +// +// Steering Class Definition +// +//----------------------------------------------------------------------------- +CLASS_DECLARATION( Listener, Steering, NULL ) +{ + { NULL, NULL } +}; + +//----------------------------------------------------------------------------- +Steering::Steering() : +_steeringForce(vec_zero), +_origin(vec_zero), +_moveDirection(vec_zero), +_maxSpeed(320) +{ +} + +//----------------------------------------------------------------------------- +void Steering::ShowInfo(Actor &self) +{ + gi.Printf( "steeringforce: ( %f, %f, %f )\n", _steeringForce.x, _steeringForce.y, _steeringForce.z ); + gi.Printf( "origin: ( %f, %f, %f )\n", _origin.x, _origin.y, _origin.z ); + gi.Printf( "movedir: ( %f, %f, %f )\n", _moveDirection.x, _moveDirection.y, _moveDirection.z ); + gi.Printf( "maxspeed: %f\n", _maxSpeed ); +} + +//----------------------------------------------------------------------------- +void Steering::Begin(Actor &self) +{ +} + +//----------------------------------------------------------------------------- +const Steering::ReturnValue Steering::Evaluate(Actor &self) +{ + return Steering::FAILED; +} + +//----------------------------------------------------------------------------- +void Steering::End(Actor &self) +{ +} + +//----------------------------------------------------------------------------- +void Steering::DrawForces(void) +{ + G_Color3f( 0.3, 0.5, 1 ); + G_BeginLine(); + G_Vertex( _origin ); + G_Vertex( _origin + _steeringForce * FRAMETIME ); + G_EndLine(); + + G_Color3f( 1, 0, 1 ); + G_BeginLine(); + G_Vertex( _origin ); + G_Vertex( _origin + _moveDirection * _maxSpeed * FRAMETIME ); + G_EndLine(); +} + +//----------------------------------------------------------------------------- +void Steering::ResetForces(void) +{ + _steeringForce = vec_zero; +} + +//----------------------------------------------------------------------------- +const Vector & Steering::GetSteeringForce( void ) const +{ + return _steeringForce; +} + +//----------------------------------------------------------------------------- +void Steering::SetSteeringForce( const Vector &steeringForce ) +{ + Vector tempSteeringForce( steeringForce ); + tempSteeringForce.EulerNormalize(); + _steeringForce=tempSteeringForce; +} + +//----------------------------------------------------------------------------- +const Vector & Steering::GetMoveDirection(void) const +{ + return _moveDirection; +} + +//----------------------------------------------------------------------------- +void Steering::SetMoveDirection(const Vector &dir) +{ + _moveDirection = dir; +} + +//----------------------------------------------------------------------------- +const float Steering::GetMaxSpeed(void) const +{ + return _maxSpeed; +} + +//----------------------------------------------------------------------------- +void Steering::SetMaxSpeed(float speed) +{ + _maxSpeed = speed; +} + +//----------------------------------------------------------------------------- +const Vector & Steering::GetPosition(void) const +{ + return _origin; +} + +//----------------------------------------------------------------------------- +void Steering::SetPosition(const Vector &pos) +{ + _origin = pos; +} + +//----------------------------------------------------------------------------- +// +// Jump Class Definition +// +//----------------------------------------------------------------------------- + +CLASS_DECLARATION( Steering, Jump, NULL ) +{ + { NULL, NULL } +}; + +//----------------------------------------------------------------------------- +Jump::Jump(): +_launchAngle( 0.0f ), +_endtime( 0.0f ), +_state( 0.0f ) +{ +} + +//----------------------------------------------------------------------------- +void Jump::SetGoal( const Vector &goalPosition ) +{ + _goal = goalPosition; +} + +//----------------------------------------------------------------------------- +void Jump::SetEntity(Entity *entity_to_jump_to) +{ + if ( entity_to_jump_to ) + _goal = entity_to_jump_to->origin; +} + +//----------------------------------------------------------------------------- +void Jump::Begin(Actor &self) +{ + self.SetAnim( "jump", EV_Anim_Done ); + + float traveltime = self.movementSubsystem->JumpTo( _goal, _launchAngle ); + _endtime = traveltime + level.time; + + self.last_jump_time = _endtime; + + _state = 0; +} +//----------------------------------------------------------------------------- +const Steering::ReturnValue Jump::Evaluate(Actor &self) +{ + if (self.GetActorFlag( ACTOR_FLAG_ANIM_DONE )) + { + _animdone = true; + } + else + { + _animdone = false; + } + self.SetActorFlag( ACTOR_FLAG_ANIM_DONE, false ); + + switch( _state ) + { + case 0: + _state = 1; + // this is here so that we at least hit this function at least once + // this gaves the character the chance to leave the ground, nulling out + // self.groundentity + break; + case 1: + if ( _animdone ) + { + if ( self.animate->HasAnim( "fall" ) ) + { + _animdone = false; + self.SetAnim( "fall", EV_Anim_Done ); + _state = 2; + } + } + if ( self.groundentity ) + _state = 2; + break; + case 2: + // + // wait for the character to hit the ground + // + if ( self.groundentity ) + { + _state = 3; + // + // if we have an anim, we go to state 3 + // + if ( self.animate->HasAnim( "land" ) ) + { + _animdone = false; + self.SetAnim( "land", EV_Anim_Done ); + _state = 4; + } + else + { + return Steering::EVALUATING; + } + } + break; + case 3: + // + // we are on the ground and waiting to timeout + // + if ( level.time > _endtime ) + return Steering::SUCCESS; + break; + case 4: + // + // we are on the ground and waiting for our landing animation to finish + // + if ( _animdone ) + { + return Steering::SUCCESS; + } + break; + } + + return Steering::EVALUATING; +} + +//----------------------------------------------------------------------------- +void Jump::End(Actor &self) +{ + self.SetAnim( "idle" ); +} diff --git a/dlls/game/steering.h b/dlls/game/steering.h new file mode 100644 index 0000000..5d41d3e --- /dev/null +++ b/dlls/game/steering.h @@ -0,0 +1,142 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/steering.h $ +// $Revision:: 21 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Steering behaviors for AI. +// + +#ifndef __STEERING2_H__ +#define __STEERING2_H__ + +#include "g_local.h" +#include "entity.h" + +class Actor; + +//----------------------------------------------------------------------------- +// +// Steering Class Declaration +// +//----------------------------------------------------------------------------- + +class Steering : public Listener + { + public: + enum ReturnValue + { + EVALUATING, + EVALUATING_REACHED_NODE, + SUCCESS, + FAILED_BLOCKED_BY_ENEMY, + FAILED_BLOCKED_BY_CIVILIAN, + FAILED_BLOCKED_BY_FRIEND, + FAILED_BLOCKED_BY_TEAMMATE, + FAILED_BLOCKED_BY_WORLD, + FAILED_BLOCKED_BY_FALL, + FAILED_BLOCKED_BY_DOOR, + FAILED_CANNOT_GET_TO_PATH, + FAILED_NO_PATH, + FAILED, + ERROR, + NUMBER_OF_RETURN_VALUES, + }; + CLASS_PROTOTYPE( Steering ); + Steering(); + virtual void ShowInfo( Actor &self ); + virtual void Begin( Actor &self ); + virtual const ReturnValue Evaluate( Actor &self ); + virtual void End( Actor &self ); + + virtual void ResetForces( void ); + + virtual const Vector & GetSteeringForce( void ) const; + virtual void SetSteeringForce( const Vector &steeringForce ); + virtual const Vector & GetPosition( void ) const; + virtual void SetPosition( const Vector &pos ); + virtual const Vector & GetMoveDirection( void ) const; + virtual void SetMoveDirection( const Vector &dir ); + virtual const float GetMaxSpeed( void ) const; + virtual void SetMaxSpeed( float speed ); + virtual void DrawForces( void ); + virtual void Archive( Archiver &arc ); + + private: + Vector _steeringForce; + Vector _origin; + Vector _moveDirection; + float _maxSpeed; + }; + +inline void Steering::Archive + ( + Archiver &arc + ) + + { + Listener::Archive( arc ); + + arc.ArchiveVector( &_steeringForce ); + arc.ArchiveVector( &_origin ); + arc.ArchiveVector( &_moveDirection ); + arc.ArchiveFloat( &_maxSpeed ); + } + +enum JumpStates +{ + JUMP_PREPARE, + JUMP_LAUNCH, + JUMP_FALL, + JUMP_LANDING +}; + +class Jump : public Steering + { + public: + CLASS_PROTOTYPE( Jump ); + + Jump(); + void SetGoal( const Vector &goalPosition ); + void SetEntity( Entity *ent_to_jump_to ); + void SetLaunchAngle( const float launchAngle) { _launchAngle = launchAngle; } + void Begin( Actor &self ); + virtual const ReturnValue Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + + private: + float _endtime; + Angle _launchAngle; + str _anim; + int _state; + bool _animdone; + Vector _goal; + + }; + +inline void Jump::Archive + ( + Archiver &arc + ) + { + Steering::Archive( arc ); + + arc.ArchiveFloat( &_endtime ); + arc.ArchiveFloat( &_launchAngle.GetValue() ); + arc.ArchiveString( &_anim ); + arc.ArchiveInteger( &_state ); + arc.ArchiveBool( &_animdone ); + arc.ArchiveVector( &_goal ); + } + +#endif // steering.h diff --git a/dlls/game/str.cpp b/dlls/game/str.cpp new file mode 100644 index 0000000..6f62fa8 --- /dev/null +++ b/dlls/game/str.cpp @@ -0,0 +1,547 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/str.cpp $ +// $Revision:: 9 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 4:08p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Simple, DLL portable string class +// +// WARNING: This file is shared between game, cgame and possibly the user interface. +// It is instanced in each one of these directories because of the way that SourceSafe works. +// + +#include "str.h" +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#pragma warning(disable : 4244) // 'conversion' conversion from 'type1' to 'type2', possible loss of data +#pragma warning(disable : 4710) // function 'blah' not inlined +#endif + +#if defined( GAME_DLL ) + +#include "g_local.h" +#define STR_Error gi.Error + +#elif defined ( CGAME_DLL ) + +#include "cg_local.h" +#define STR_Error cgi.Error + +#else + +//#include "client.h" +#define STR_Error Com_Error + +#endif + +static const int STR_ALLOC_GRAN = 20; + +char *str::tolower( char *s1 ) +{ + char *s; + + s = s1; + while( *s ) + { + *s = ::tolower( *s ); + s++; + } + + return s1; +} + +char *str::toupper( char *s1 ) +{ + char *s; + + s = s1; + while( *s ) + { + *s = ::toupper( *s ); + s++; + } + + return s1; +} + +int str::icmpn( const char *s1, const char *s2, int n ) +{ + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) + { + // strings are equal until end point + return 0; + } + + if ( c1 != c2 ) + { + if ( c1 >= 'a' && c1 <= 'z' ) + { + c1 -= ( 'a' - 'A' ); + } + + if ( c2 >= 'a' && c2 <= 'z' ) + { + c2 -= ( 'a' - 'A' ); + } + + if ( c1 < c2 ) + { + // strings less than + return -1; + } + else if ( c1 > c2 ) + { + // strings greater than + return 1; + } + } + } + while( c1 ); + + // strings are equal + return 0; +} + +int str::icmp( const char *s1, const char *s2 ) +{ + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( c1 != c2 ) + { + if ( c1 >= 'a' && c1 <= 'z' ) + { + c1 -= ( 'a' - 'A' ); + } + + if ( c2 >= 'a' && c2 <= 'z' ) + { + c2 -= ( 'a' - 'A' ); + } + + if ( c1 < c2 ) + { + // strings less than + return -1; + } + else if ( c1 > c2 ) + { + // strings greater than + return 1; + } + } + } + while( c1 ); + + // strings are equal + return 0; +} + +int str::cmpn( const char *s1, const char *s2, int n ) +{ + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( !n-- ) + { + // strings are equal until end point + return 0; + } + + if ( c1 < c2 ) + { + // strings less than + return -1; + } + else if ( c1 > c2 ) + { + // strings greater than + return 1; + } + } + while( c1 ); + + // strings are equal + return 0; +} + +int str::cmp( const char *s1, const char *s2 ) +{ + int c1; + int c2; + + do + { + c1 = *s1++; + c2 = *s2++; + + if ( c1 < c2 ) + { + // strings less than + return -1; + } + else if ( c1 > c2 ) + { + // strings greater than + return 1; + } + } + while( c1 ); + + // strings are equal + return 0; +} + +/* +============ +IsNumeric + +Checks a string to see if it contains only numerical values. +============ +*/ +bool str::isNumeric( const char *str ) +{ + int len; + int i; + bool dot; + + if ( *str == '-' ) + { + str++; + } + + dot = false; + len = strlen( str ); + for( i = 0; i < len; i++ ) + { + if ( !isdigit( str[ i ] ) ) + { + if ( ( str[ i ] == '.' ) && !dot ) + { + dot = true; + continue; + } + return false; + } + } + + return true; +} + +str operator+( const str& a, const float b ) +{ + char text[ 20 ]; + + str result( a ); + + sprintf( text, "%f", b ); + result.append( text ); + + return result; +} + +str operator+( const str& a, const int b ) +{ + char text[ 20 ]; + + str result( a ); + + sprintf( text, "%d", b ); + result.append( text ); + + return result; +} + +str operator+( const str& a, const unsigned b ) +{ + char text[ 20 ]; + + str result( a ); + + sprintf( text, "%u", b ); + result.append( text ); + + return result; +} + +str& str::operator+=( const float a ) +{ + char text[ 20 ]; + + sprintf( text, "%f", a ); + append( text ); + + return *this; +} + +str& str::operator+=( const int a ) +{ + char text[ 20 ]; + + sprintf( text, "%d", a ); + append( text ); + + return *this; +} + +str& str::operator+=( const unsigned a ) +{ + char text[ 20 ]; + + sprintf( text, "%u", a ); + append( text ); + + return *this; +} + +void str::CapLength( int newlen ) +{ + assert ( data ); + + if ( length() <= newlen ) + return; + + data[newlen] = 0; + len = newlen; +} + +void str::EnsureAlloced( int amount, bool keepold ) +{ + if ( !data ) + data = buffer; + + char *newbuffer; + bool wasalloced = ( alloced != 0 ); + int old_size; + + // See if enough already allocated + + if ( amount <= alloced ) + return; + + old_size = alloced; + + assert ( amount ); + + // Calculate new alloc size + + int newsize, mod; + + mod = amount % STR_ALLOC_GRAN; + + if ( !mod ) + newsize = amount; + else + newsize = amount + STR_ALLOC_GRAN - mod; + + alloced = newsize; + + // Allocate the new buffer + + newbuffer = new char[ alloced ]; + + if ( !newbuffer ) + STR_Error( ERR_FATAL, "Str: failed on allocation of %i bytes", alloced ); + + if ( wasalloced && keepold ) + { + strncpy ( newbuffer, data, old_size ); + } + + // Delete old buffer if necessary + + if ( data != buffer ) + { + delete [] data; + } + + data = newbuffer; +} + +void str::BackSlashesToSlashes( void ) +{ + int i; + + for ( i = 0 ; i < len ; i++ ) + { + if ( data[i] == '\\' ) + data[i] = '/'; + } +} + +void str::snprintf( char *dst, int size, const char *fmt, ... ) +{ + char buffer[0x10000]; + int len; + va_list argptr; + + va_start (argptr,fmt); + len = vsprintf (buffer,fmt,argptr); + va_end (argptr); + + assert ( len < size ); + + strncpy (dst, buffer, size-1); +} + +#ifdef _WIN32 +#pragma warning(disable : 4189) // local variable is initialized but not referenced +#endif + +/* +================= +TestStringClass + +This is a fairly rigorous test of the str class's functionality. +Because of the fairly global and subtle ramifications of a bug occuring +in this class, it should be run after any changes to the class. +Add more tests as functionality is changed. Tests should include +any possible bounds violation and NULL data tests. +================= +*/ +void TestStringClass( void ) +{ + char ch; // ch == ? + str *t; // t == ? + str a; // a.len == 0, a.data == "\0" + str b; // b.len == 0, b.data == "\0" + str c( "test" ); // c.len == 4, c.data == "test\0" + str d( c ); // d.len == 4, d.data == "test\0" + str e( reinterpret_cast(NULL) ); + // e.len == 0, e.data == "\0" ASSERT! + int i; // i == ? + + i = a.length(); // i == 0 + i = c.length(); // i == 4 + + const char *s1 = a.c_str(); // s1 == "\0" + const char *s2 = c.c_str(); // s2 == "test\0" + + t = new str(); // t->len == 0, t->data == "\0" + delete t; // t == ? + + b = "test"; // b.len == 4, b.data == "test\0" + t = new str( "test" ); // t->len == 4, t->data == "test\0" + delete t; // t == ? + + a = c; // a.len == 4, a.data == "test\0" + // a = ""; + a = NULL; // a.len == 0, a.data == "\0" ASSERT! + a = c + d; // a.len == 8, a.data == "testtest\0" + a = c + "wow"; // a.len == 7, a.data == "testwow\0" + a = c + reinterpret_cast(NULL); + // a.len == 4, a.data == "test\0" ASSERT! + a = "this" + d; // a.len == 8, a.data == "thistest\0" + a = reinterpret_cast(NULL) + d; + // a.len == 4, a.data == "test\0" ASSERT! + a += c; // a.len == 8, a.data == "testtest\0" + a += "wow"; // a.len == 11, a.data == "testtestwow\0" + a += reinterpret_cast(NULL); + // a.len == 11, a.data == "testtestwow\0" ASSERT! + + a = "test"; // a.len == 4, a.data == "test\0" + ch = a[ 0 ]; // ch == 't' + ch = a[ -1 ]; // ch == 0 ASSERT! + ch = a[ 1000 ]; // ch == 0 ASSERT! + ch = a[ 0 ]; // ch == 't' + ch = a[ 1 ]; // ch == 'e' + ch = a[ 2 ]; // ch == 's' + ch = a[ 3 ]; // ch == 't' + ch = a[ 4 ]; // ch == '\0' ASSERT! + ch = a[ 5 ]; // ch == '\0' ASSERT! + + a[ 1 ] = 'b'; // a.len == 4, a.data == "tbst\0" + a[ -1 ] = 'b'; // a.len == 4, a.data == "tbst\0" ASSERT! + a[ 0 ] = '0'; // a.len == 4, a.data == "0bst\0" + a[ 1 ] = '1'; // a.len == 4, a.data == "01st\0" + a[ 2 ] = '2'; // a.len == 4, a.data == "012t\0" + a[ 3 ] = '3'; // a.len == 4, a.data == "0123\0" + a[ 4 ] = '4'; // a.len == 4, a.data == "0123\0" ASSERT! + a[ 5 ] = '5'; // a.len == 4, a.data == "0123\0" ASSERT! + a[ 7 ] = '7'; // a.len == 4, a.data == "0123\0" ASSERT! + + a = "test"; // a.len == 4, a.data == "test\0" + b = "no"; // b.len == 2, b.data == "no\0" + + i = ( a == b ); // i == 0 + i = ( a == c ); // i == 1 + + i = ( a == "blow" ); // i == 0 + i = ( a == "test" ); // i == 1 + i = ( a == NULL ); // i == 0 ASSERT! + + i = ( "test" == b ); // i == 0 + i = ( "test" == a ); // i == 1 + i = ( NULL == a ); // i == 0 ASSERT! + + i = ( a != b ); // i == 1 + i = ( a != c ); // i == 0 + + i = ( a != "blow" ); // i == 1 + i = ( a != "test" ); // i == 0 + i = ( a != NULL ); // i == 1 ASSERT! + + i = ( "test" != b ); // i == 1 + i = ( "test" != a ); // i == 0 + i = ( NULL != a ); // i == 1 ASSERT! + + a = "test"; // a.data == "test" + b = a; // b.data == "test" + + a = "not"; // a.data == "not", b.data == "test" + + a = b; // a.data == b.data == "test" + + a += b; // a.data == "testtest", b.data = "test" + + a = b; + + a[1] = '1'; // a.data = "t1st", b.data = "test" + + a = ""; // a.data == "", a.len == 0 + + for( i = 0 ; i < ( STRING_PREALLOC_SIZE + 5 ) ; i++ ) + { + a += "a"; + } + + t = new str( "testtesttesttesttesttesttesttest" ); // t->len == 32 + delete t; // t == ? +} + +#ifdef _WIN32 +#pragma warning(default : 4189) // local variable is initialized but not referenced +#pragma warning(disable : 4514) // unreferenced inline function has been removed +#endif diff --git a/dlls/game/str.h b/dlls/game/str.h new file mode 100644 index 0000000..69958c5 --- /dev/null +++ b/dlls/game/str.h @@ -0,0 +1,801 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/str.h $ +// $Revision:: 16 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Simple, DLL portable string class +// +// WARNING: This file is shared between game, cgame and possibly the user interface. +// It is instanced in each one of these directories because of the way that SourceSafe works. +// + +#ifndef __STR_H__ +#define __STR_H__ + +#include +#include +#include +#include + +#ifdef _WIN32 +#pragma warning(disable : 4710) // function 'blah' not inlined +#endif + +#define STRING_PREALLOC_SIZE 16 + +void TestStringClass( void ); + +#if 1 +class __declspec( dllexport ) str +#else +class str : public Class +#endif + { + protected: + + char *data; + char buffer[ STRING_PREALLOC_SIZE ]; + int alloced; + int len; + + void EnsureAlloced ( int, bool keepold = true ); + + public: + ~str(); + str(); + str( const char *text ); + str( const str& text ); + str( const str text, int start, int end ); + str( const char ch ); + str( const int num ); + str( const float num ); + str( const unsigned num ); + + + int length( void ) const; + inline const char * c_str( void ) const; + + void append( const char *text ); + void append( const str& text ); + + char operator[]( int index ) const; + char& operator[]( int index ); + + str& operator=( const str& text ); + str& operator=( const char *text ); + + friend str operator+( const str& a, const str& b ); + friend str operator+( const str& a, const char *b ); + friend str operator+( const char *a, const str& b ); + + friend str operator+( const str& a, const float b ); + friend str operator+( const str& a, const int b ); + friend str operator+( const str& a, const unsigned b ); + friend str operator+( const str& a, const bool b ); + friend str operator+( const str& a, const char b ); + + str& operator+=( const str& a ); + str& operator+=( const char *a ); + str& operator+=( const float a ); + str& operator+=( const char a ); + str& operator+=( const int a ); + str& operator+=( const unsigned a ); + str& operator+=( const bool a ); + + friend bool operator==( const str& a, const str& b ); + friend bool operator==( const str& a, const char *b ); + friend bool operator==( const char *a, const str& b ); + + friend bool operator!=( const str& a, const str& b ); + friend bool operator!=( const str& a, const char *b ); + friend bool operator!=( const char *a, const str& b ); + + operator const char * () const; + + int icmpn( const char *text, int n ) const; + int icmpn( const str& text, int n ) const; + int icmp( const char *text ) const; + int icmp( const str& text ) const; + int cmpn( const char *text, int n ) const; + int cmpn( const str& text, int n ) const; + int cmp( const char *text ) const; + int cmp( const str& text ) const; + + char *tolower( void ); + char *toupper( void ); + + static char *tolower( char *s1 ); + static char *toupper( char *s1 ); + + static int icmpn( const char *s1, const char *s2, int n ); + static int icmp( const char *s1, const char *s2 ); + static int cmpn( const char *s1, const char *s2, int n ); + static int cmp( const char *s1, const char *s2 ); + + static void snprintf ( char *dst, int size, const char *fmt, ... ); + + static bool isNumeric( const char *str ); + bool isNumeric( void ) const; + + void CapLength ( int ); + + void BackSlashesToSlashes (); + }; + + +inline char str::operator[]( int index ) const + { + assert ( data ); + + if ( !data ) + return 0; + + // don't include the '/0' in the test, because technically, it's out of bounds + assert( ( index >= 0 ) && ( index < len ) ); + + // In release mode, give them a null character + // don't include the '/0' in the test, because technically, it's out of bounds + if ( ( index < 0 ) || ( index >= len ) ) + { + return 0; + } + + return data[ index ]; + } + +inline int str::length( void ) const + { + return len; + } + +inline str::~str() + { + if ( data != buffer ) + delete [] data; + } + +inline str::str() + { + data = buffer; + alloced = STRING_PREALLOC_SIZE; + data[ 0 ] = 0; + len = 0; + } + +inline str::str + ( + const char *text + ) + + { + int stringLength; + + assert( text ); + + data = buffer; + alloced = STRING_PREALLOC_SIZE; + data[ 0 ] = 0; + len = 0; + + if ( text ) + { + stringLength = strlen( text ); + EnsureAlloced ( stringLength + 1 ); + strcpy( data, text ); + len = stringLength; + } + } + +inline str::str + ( + const str& text + ) + + { + data = buffer; + alloced = STRING_PREALLOC_SIZE; + data[ 0 ] = 0; + len = 0; + + EnsureAlloced ( text.length() + 1); + strcpy( data, text.c_str() ); + len = text.length(); + } + +inline str::str + ( + const str text, + int start, + int end + ) + + { + int i; + int stringLength; + + data = buffer; + alloced = STRING_PREALLOC_SIZE; + data[ 0 ] = 0; + len = 0; + + if ( end > text.length() ) + { + end = text.length(); + } + + if ( start > text.length() ) + { + start = text.length(); + } + + stringLength = end - start; + if ( stringLength < 0 ) + { + stringLength = 0; + } + + EnsureAlloced ( stringLength + 1 ); + + for( i = 0; i < stringLength; i++ ) + { + data[ i ] = text[ start + i ]; + } + + data[ stringLength ] = 0; + len = stringLength; + } + +inline str::str + ( + const char ch + ) + + { + data = buffer; + alloced = STRING_PREALLOC_SIZE; + + EnsureAlloced ( 2 ); + + data[ 0 ] = ch; + data[ 1 ] = 0; + len = 1; + } + +inline str::str + ( + const float num + ) + + { + char text[ 32 ]; + int stringLength; + + data = buffer; + alloced = STRING_PREALLOC_SIZE; + + sprintf( text, "%.3f", num ); + stringLength = strlen( text ); + EnsureAlloced( stringLength + 1 ); + strcpy( data, text ); + len = stringLength; + } + +inline str::str + ( + const int num + ) + + { + char text[ 32 ]; + int stringLength; + + data = buffer; + alloced = STRING_PREALLOC_SIZE; + + sprintf( text, "%d", num ); + stringLength = strlen( text ); + EnsureAlloced( stringLength + 1 ); + strcpy( data, text ); + len = stringLength; + } + +inline str::str + ( + const unsigned num + ) + + { + char text[ 32 ]; + int stringLength; + + data = buffer; + alloced = STRING_PREALLOC_SIZE; + + sprintf( text, "%u", num ); + stringLength = strlen( text ); + EnsureAlloced( stringLength + 1 ); + strcpy( data, text ); + len = stringLength; + } + +inline const char *str::c_str( void ) const + { + //assert( data ); + + return data; + } + +inline void str::append + ( + const char *text + ) + + { + int new_length; + + assert( text ); + + if ( text ) + { + new_length = length(); + new_length += strlen( text ); + EnsureAlloced( new_length + 1 ); + + strcat( data, text ); + len = new_length; + } + } + +inline void str::append + ( + const str& text + ) + + { + int new_length; + + new_length = length(); + new_length += text.length(); + EnsureAlloced ( new_length + 1 ); + + strcat ( data, text.c_str () ); + len = new_length; + } + + +inline char& str::operator[] + ( + int index + ) + + { + // Used for result for invalid indices + static char dummy = 0; + assert ( data ); + + if ( !data ) + return dummy; + + // don't include the '/0' in the test, because technically, it's out of bounds + assert( ( index >= 0 ) && ( index < len ) ); + + // In release mode, let them change a safe variable + // don't include the '/0' in the test, because technically, it's out of bounds + if ( ( index < 0 ) || ( index >= len ) ) + { + return dummy; + } + + return data[ index ]; + } + +inline str& str::operator= + ( + const str& text + ) + + { + EnsureAlloced ( text.length() + 1, false ); + strcpy( data, text.c_str() ); + len = text.length(); + return *this; + } + +inline str& str::operator= + ( + const char *text + ) + + { + int stringLength; + + assert( text ); + + if ( !text ) + { + // safe behaviour if NULL + EnsureAlloced ( 1, false ); + data[0] = 0; + len = 0; + return *this; + } + + if ( text == data ) + return *this; // Copying same thing. Punt. + + // Now we need to check if we're aliasing.. + + unsigned int dataStart = reinterpret_cast (data); + unsigned int dataEnd = reinterpret_cast (data) + len; + unsigned int textStart = reinterpret_cast (text); + if ( textStart >= dataStart && textStart <= dataEnd ) + { + // Great, we're aliasing. We're copying from inside ourselves. + // This means that I don't have to ensure that anything is alloced, + // though I'll assert just in case. + int diff = text - data; + int i; + + assert ( strlen ( text ) < (unsigned) len ); + + for ( i = 0; text[i]; i++ ) + { + data[i] = text[i]; + } + + data[i] = 0; + + len -= diff; + + return *this; + } + + stringLength = strlen( text ); + EnsureAlloced ( stringLength + 1, false ); + strcpy( data, text ); + len = stringLength; + return *this; + } + +inline str operator+ + ( + const str& a, + const str& b + ) + + { + str result( a ); + + result.append( b ); + + return result; + } + +inline str operator+ + ( + const str& a, + const char *b + ) + + { + str result( a ); + + result.append( b ); + + return result; + } + +inline str operator+ + ( + const char *a, + const str& b + ) + + { + str result( a ); + + result.append( b ); + + return result; + } + +inline str operator+ + ( + const str& a, + const bool b + ) + + { + str result( a ); + + result.append( b ? "true" : "false" ); + + return result; + } + +inline str operator+ + ( + const str& a, + const char b + ) + + { + char text[ 2 ]; + + text[ 0 ] = b; + text[ 1 ] = 0; + + return a + text; + } + +inline str& str::operator+= + ( + const str& a + ) + + { + append( a ); + return *this; + } + +inline str& str::operator+= + ( + const char *a + ) + + { + append( a ); + return *this; + } + +inline str& str::operator+= + ( + const char a + ) + + { + char text[ 2 ]; + + text[ 0 ] = a; + text[ 1 ] = 0; + append( text ); + + return *this; + } + +inline str& str::operator+= + ( + const bool a + ) + + { + append( a ? "true" : "false" ); + return *this; + } + +inline bool operator== + ( + const str& a, + const str& b + ) + + { + /// Check if lengths are equal + if( a.length() != b.length() ) + return( false ); + + return ( !strcmp( a.c_str(), b.c_str() ) ); + } + +inline bool operator== + ( + const str& a, + const char *b + ) + + { + assert( b ); + if ( !b ) + { + return false; + } + return ( !strcmp( a.c_str(), b ) ); + } + +inline bool operator== + ( + const char *a, + const str& b + ) + + { + assert( a ); + if ( !a ) + { + return false; + } + return ( !strcmp( a, b.c_str() ) ); + } + +inline bool operator!= + ( + const str& a, + const str& b + ) + + { + return !( a == b ); + } + +inline bool operator!= + ( + const str& a, + const char *b + ) + + { + return !( a == b ); + } + +inline bool operator!= + ( + const char *a, + const str& b + ) + + { + return !( a == b ); + } + +inline int str::icmpn + ( + const char *text, + int n + ) const + + { + assert( data ); + assert( text ); + + return str::icmpn( data, text, n ); + } + +inline int str::icmpn + ( + const str& text, + int n + ) const + + { + assert( data ); + assert( text.data ); + + return str::icmpn( data, text.data, n ); + } + +inline int str::icmp + ( + const char *text + ) const + + { + assert( data ); + assert( text ); + + return str::icmp( data, text ); + } + +inline int str::icmp + ( + const str& text + ) const + + { + assert( c_str () ); + assert( text.c_str () ); + + return str::icmp( c_str () , text.c_str () ); + } + +inline int str::cmp + ( + const char *text + ) const + + { + assert( data ); + assert( text ); + + return str::cmp( data, text ); + } + +inline int str::cmp + ( + const str& text + ) const + + { + assert( c_str () ); + assert( text.c_str () ); + + return str::cmp( c_str () , text.c_str () ); + } + +inline int str::cmpn + ( + const char *text, + int n + ) const + + { + assert( c_str () ); + assert( text ); + + return str::cmpn( c_str () , text, n ); + } + +inline int str::cmpn + ( + const str& text, + int n + ) const + + { + assert( c_str () ); + assert( text.c_str () ); + + return str::cmpn( c_str () , text.c_str () , n ); + } + +inline char *str::tolower + ( + void + ) + + { + assert( data ); + + return str::tolower( data ); + } + +inline char *str::toupper + ( + void + ) + + { + assert( data ); + + return str::toupper( data ); + } + +inline bool str::isNumeric + ( + void + ) const + + { + assert( data ); + return str::isNumeric( data ); + } + +inline str::operator const char * + ( + void + ) const + + { + return c_str (); + } + +#endif diff --git a/dlls/game/suppressionFireCombat.cpp b/dlls/game/suppressionFireCombat.cpp new file mode 100644 index 0000000..b745c8b --- /dev/null +++ b/dlls/game/suppressionFireCombat.cpp @@ -0,0 +1,651 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/suppressionFireCombat.cpp $ +// $Revision:: 4 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// SuppressionFireCombat Implementation +// +// PARAMETERS: +// str _movementAnim -- The animation to play while moving to the cover node +// str _torsoIdleAnim -- The animation to play on the torso while idling +// str _torsoAttackAnim -- The animation to play on the torso while attacking +// float _maxDistance -- The maximum distance the actor will look for a node +// float _pauseTimeMin -- The minimum time to pause between attacking +// float _pauseTimeMax -- The maximum time to pause between attacking +// float _fireTimeMin -- The minimum time to fire +// float _fireTimeMax -- The maximum time to fire +// +// ANIMATIONS: +// _movementAnim : PARAMETER +// _torsoIdleAnim : PARAMETER +// _torsoAttackAnim : PARAMETER +// "idle" : TIKI Required + +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "suppressionFireCombat.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, SuppressionFireCombat, NULL ) + { + { &EV_Behavior_Args, &SuppressionFireCombat::SetArgs }, + { &EV_Behavior_AnimDone, &SuppressionFireCombat::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SuppressionFireCombat() +// Class: SuppressionFireCombat +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +SuppressionFireCombat::SuppressionFireCombat() +{ + _nextMoveAttempt = 0.0f; + _endFireTime = 0.0f; + _endPauseTime = 0.0f; +} + +//-------------------------------------------------------------- +// Name: SuppressionFireCombat() +// Class: SuppressionFireCombat +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +SuppressionFireCombat::~SuppressionFireCombat() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: SuppressionFireCombat +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::SetArgs( Event *ev ) +{ + _movementAnim = ev->GetString( 1 ); + _torsoIdleAnim = ev->GetString( 2 ); + _torsoAttackAnim = ev->GetString( 3 ); + _maxDistance = ev->GetFloat ( 4 ); + _pauseTimeMin = ev->GetFloat ( 5 ); + _pauseTimeMax = ev->GetFloat ( 6 ); + _fireTimeMin = ev->GetFloat ( 7 ); + _fireTimeMax = ev->GetFloat ( 8 ); + +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: SuppressionFireCombat +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::AnimDone( Event *ev ) +{ +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: SuppressionFireCombat +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::Begin( Actor &self ) +{ + init( self ); + transitionToState ( SUPPRESSION_FIRE_FIND_NODE ); +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: SuppressionFireCombat +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t SuppressionFireCombat::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case SUPPRESSION_FIRE_FIND_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateFindNode(); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( SUPPRESSION_FIRE_FAILED ); + else + transitionToState( SUPPRESSION_FIRE_MOVE_TO_NODE ); + break; + + //--------------------------------------------------------------------- + case SUPPRESSION_FIRE_MOVE_TO_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateMoveToNode(); + + if ( stateResult == BEHAVIOR_FAILED ) + { + _nextMoveAttempt = level.time + G_Random() + 1.0f; + transitionToState( SUPPRESSION_FIRE_ATTACK ); + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + _atNode = true; + transitionToState( SUPPRESSION_FIRE_ATTACK ); + _self->SetAnim ( "idle" , NULL , legs ); + _self->SetAnim ( _torsoIdleAnim , NULL , torso ); + } + + break; + + //--------------------------------------------------------------------- + case SUPPRESSION_FIRE_ATTACK: + //--------------------------------------------------------------------- + if ( !_atNode && level.time > _nextMoveAttempt ) + { + _fireWeapon.End( *_self ); + transitionToState( SUPPRESSION_FIRE_FIND_NODE ); + } + + stateResult = evaluateStateAttack(); + + if ( stateResult != BEHAVIOR_EVALUATING ) + { + _fireWeapon.End( *_self ); + transitionToState( SUPPRESSION_FIRE_PAUSE ); + } + break; + + + //--------------------------------------------------------------------- + case SUPPRESSION_FIRE_PAUSE: + //--------------------------------------------------------------------- + stateResult = evaluateStatePause(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + updateEnemy(); + transitionToState( SUPPRESSION_FIRE_ATTACK ); + } + + break; + + //--------------------------------------------------------------------- + case SUPPRESSION_FIRE_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + + break; + + + //--------------------------------------------------------------------- + case SUPPRESSION_FIRE_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + + break; + + + } + + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: SuppressionFireCombat +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::End(Actor &self) +{ +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: SuppressionFireCombat +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::transitionToState( SuppressionFireStates_t state ) +{ + switch ( state ) + { + case SUPPRESSION_FIRE_FIND_NODE: + setupStateFindNode(); + setInternalState( state , "SUPPRESSION_FIRE_FIND_NODE" ); + break; + + case SUPPRESSION_FIRE_MOVE_TO_NODE: + setupStateMoveToNode(); + setInternalState( state , "SUPPRESSION_FIRE_MOVE_TO_NODE" ); + break; + + case SUPPRESSION_FIRE_ATTACK: + setupStateAttack(); + setInternalState( state , "SUPPRESSION_FIRE_ATTACK" ); + break; + + case SUPPRESSION_FIRE_PAUSE: + setupStatePause(); + setInternalState( state , "SUPPRESSION_FIRE_PAUSE" ); + break; + + case SUPPRESSION_FIRE_SUCCESS: + setInternalState( state , "SUPPRESSION_FIRE_SUCCESS" ); + break; + + case SUPPRESSION_FIRE_FAILED: + setInternalState( state , "SUPPRESSION_FIRE_FAILED" ); + break; + } + +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: SuppressionFireCombat +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::setInternalState( SuppressionFireStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: SuppressionFireCombat +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::init( Actor &self ) +{ + _self = &self; + updateEnemy(); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: SuppressionFireCombat +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::think() +{ +} + +//-------------------------------------------------------------- +// Name: setupStateFindNode() +// Class: SuppressionFireCombat +// +// Description: Sets up our FindNode State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::setupStateFindNode() +{ + _node = HelperNode::FindHelperNodeClosestTo( *_self , _currentEnemy , NODETYPE_COMBAT , _maxDistance ); + + if ( !_node ) + return; +} + +//-------------------------------------------------------------- +// Name: evaluateStateFindNode() +// Class: SuppressionFireCombat +// +// Description: Evaluates our FindNode State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t SuppressionFireCombat::evaluateStateFindNode() +{ + if ( !_node ) + { + str failureMsg = "No Node of Type: COMBAT in range"; + failureStateFindNode( failureMsg ); + return BEHAVIOR_FAILED; + } + + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateFindNode() +// Class: SuppressionFireCombat +// +// Description: Failure Handler for Find Node State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::failureStateFindNode( const str& failureReason ) +{ + SetFailureReason( failureReason ); +} + +//-------------------------------------------------------------- +// Name: setupStateMoveToNode() +// Class: SuppressionFireCombat +// +// Description: Sets up the Move To Cover State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::setupStateMoveToNode() +{ + _gotoHNode.SetNode( _node ); + _gotoHNode.SetMovementAnim( _movementAnim ); + _gotoHNode.Begin( *_self ); +} + + +//-------------------------------------------------------------- +// Name: evaluateStateMoveToNode() +// Class: SuppressionFireCombat +// +// Description: Evaluates the Move To Cover State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t SuppressionFireCombat::evaluateStateMoveToNode() +{ + BehaviorReturnCode_t result = _gotoHNode.Evaluate( *_self ); + + if ( result == BEHAVIOR_FAILED ) + { + str failureReason = "GotoHelperNodeNearestEnemy::evaluateStateMoveToNode -- _gotoHNode component returned: " + _gotoHNode.GetFailureReason(); + failureStateMoveToNode( failureReason ); + } + + return result; + +} + + +//-------------------------------------------------------------- +// Name: failureStateMoveToNode() +// Class: SuppressionFireCombat +// +// Description: Failure Handler for State Move To Cover +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::failureStateMoveToNode( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: SuppressionFireCombat() +// Class: setupStateAttack +// +// Description: Sets up our Attack State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::setupStateAttack() +{ + faceEnemy(); + _endFireTime = level.time + G_Random(_fireTimeMax - _fireTimeMin) + _fireTimeMin; + + _fireWeapon.SetAnim( _torsoAttackAnim ); + _fireWeapon.Begin( *_self ); + +} + +//-------------------------------------------------------------- +// Name: evaluateStateAttack() +// Class: SuppressionFireCombat +// +// Description: Evaluates our Attack State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t SuppressionFireCombat::evaluateStateAttack() +{ + _rotateToEntity.Evaluate( *_self ); + BehaviorReturnCode_t result = _fireWeapon.Evaluate( *_self ); + + if ( result == BEHAVIOR_FAILED ) + failureStateAttack( "SuppressionFireCombat::evaluateStateAttack -- FAILED" ); + + if ( level.time > _endFireTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAttack() +// Class: SuppressionFireCombat +// +// Description: Failure Handler for Attack State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::failureStateAttack( const str &failureReason ) +{ + SetFailureReason( failureReason ); + _fireWeapon.End( *_self ); +} + +//-------------------------------------------------------------- +// Name: setupStatePause() +// Class: SuppressionFireCombat +// +// Description: Sets up our Pause State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::setupStatePause() +{ + _endPauseTime = level.time + G_Random(_pauseTimeMax - _pauseTimeMin ) + _pauseTimeMin; + _self->SetAnim( "idle" , NULL , legs ); + _self->SetAnim( _torsoIdleAnim , NULL , torso ); + +} + +//-------------------------------------------------------------- +// Name: evaluateStatePause() +// Class: SuppressionFireCombat +// +// Description: Evaluates our Pause State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t SuppressionFireCombat::evaluateStatePause() +{ + if ( level.time > _endPauseTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + + +//-------------------------------------------------------------- +// Name: failureStatePause() +// Class: SuppressionFireCombat +// +// Description: Failure Handler for our Pause State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::failureStatePause( const str &failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: GotoHelperNodeNearestEnemy +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::updateEnemy() +{ + Entity *currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + SetFailureReason( "SUPPRESSION_FIRE_FAILED::updateEnemy -- No Enemy" ); + transitionToState( SUPPRESSION_FIRE_FAILED ); + return; + } + + } + + _currentEnemy = currentEnemy; +} + +//-------------------------------------------------------------- +// Name: faceEnemy() +// Class: SuppressionFireCombat +// +// Description: Sets up the Rotate Component +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void SuppressionFireCombat::faceEnemy() +{ + _rotateToEntity.SetEntity( _currentEnemy ); + _rotateToEntity.SetTurnSpeed( 30.0f ); + _rotateToEntity.Begin( *_self ); + +} + + + + + + + + + + + + + + + + + + + + + + diff --git a/dlls/game/suppressionFireCombat.hpp b/dlls/game/suppressionFireCombat.hpp new file mode 100644 index 0000000..7d271af --- /dev/null +++ b/dlls/game/suppressionFireCombat.hpp @@ -0,0 +1,205 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/suppressionWithRangedWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// SuppressionFireCombat Behavior Definition +// +//-------------------------------------------------------------------------------- + + +//============================== +// Forward Declarations +//============================== +class SuppressionFireCombat; + +#ifndef __SUPPRESSION_FIRE_COMBAT___ +#define __SUPPRESSION_FIRE_COMBAT___ + +#include "behavior.h" +#include "behaviors_general.h" +#include "gotoHelperNode.hpp" +#include "rotateToEntity.hpp" + +//------------------------- CLASS ------------------------------ +// +// Name: CoverCombatWithRangedWeapon +// Base Class: Behavior +// +// Description: +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class SuppressionFireCombat : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + SUPPRESSION_FIRE_FIND_NODE, + SUPPRESSION_FIRE_MOVE_TO_NODE, + SUPPRESSION_FIRE_ATTACK, + SUPPRESSION_FIRE_PAUSE, + SUPPRESSION_FIRE_SUCCESS, + SUPPRESSION_FIRE_FAILED + } SuppressionFireStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _movementAnim; + str _torsoIdleAnim; + str _torsoAttackAnim; + float _maxDistance; + float _pauseTimeMin; + float _pauseTimeMax; + float _fireTimeMin; + float _fireTimeMax; + + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( SuppressionFireStates_t state ); + void setInternalState ( SuppressionFireStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + void faceEnemy (); + + void setupStateFindNode (); + BehaviorReturnCode_t evaluateStateFindNode (); + void failureStateFindNode ( const str& failureReason ); + + void setupStateMoveToNode (); + BehaviorReturnCode_t evaluateStateMoveToNode (); + void failureStateMoveToNode ( const str& failureReason ); + + void setupStateAttack (); + BehaviorReturnCode_t evaluateStateAttack (); + void failureStateAttack ( const str& failureReason ); + + void setupStatePause (); + BehaviorReturnCode_t evaluateStatePause (); + void failureStatePause ( const str& failureReason ); + + + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( SuppressionFireCombat ); + + SuppressionFireCombat(); + ~SuppressionFireCombat(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + // Accessors + void SetMovementAnim ( const str &anim ); + void SetTorsoIdleAnim ( const str &anim ); + void SetTorsoAttackAnim ( const str &anim ); + + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + GotoHelperNode _gotoHNode; + FireWeapon _fireWeapon; + RotateToEntity _rotateToEntity; + + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + SuppressionFireStates_t _state; + bool _atNode; + float _nextMoveAttempt; + float _endFireTime; + float _endPauseTime; + HelperNodePtr _node; + EntityPtr _currentEnemy; + Actor *_self; + + + }; + +inline void SuppressionFireCombat::SetMovementAnim ( const str &anim ) +{ + _movementAnim = anim; +} + +inline void SuppressionFireCombat::SetTorsoIdleAnim ( const str &anim ) +{ + _torsoIdleAnim = anim; +} + +inline void SuppressionFireCombat::SetTorsoAttackAnim ( const str &anim ) +{ + _torsoAttackAnim = anim; +} + +inline void SuppressionFireCombat::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveString ( &_movementAnim ); + arc.ArchiveString ( &_torsoIdleAnim ); + arc.ArchiveString ( &_torsoAttackAnim ); + arc.ArchiveFloat ( &_maxDistance ); + arc.ArchiveFloat ( &_pauseTimeMin ); + arc.ArchiveFloat ( &_pauseTimeMax ); + arc.ArchiveFloat ( &_fireTimeMin ); + arc.ArchiveFloat ( &_fireTimeMax ); + + // + // Archive Components + // + arc.ArchiveObject ( &_gotoHNode ); + arc.ArchiveObject ( &_fireWeapon ); + arc.ArchiveObject ( &_rotateToEntity ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, SuppressionFireStates_t ); + arc.ArchiveBool ( &_atNode ); + arc.ArchiveFloat ( &_nextMoveAttempt ); + arc.ArchiveFloat ( &_endFireTime ); + arc.ArchiveFloat ( &_endPauseTime ); + arc.ArchiveSafePointer ( &_node ); + arc.ArchiveSafePointer ( &_currentEnemy ); + arc.ArchiveObjectPointer( ( Class ** )&_self ); +} + + +#endif /* __SUPPRESSION_FIRE_COMBAT___ */ + diff --git a/dlls/game/surfaceflags.h b/dlls/game/surfaceflags.h new file mode 100644 index 0000000..06e4273 --- /dev/null +++ b/dlls/game/surfaceflags.h @@ -0,0 +1,116 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/surfaceflags.h $ +// $Revision:: 12 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:42a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: Surface flag parameters for q3map and game +// + +#ifndef __SURFACEFLAGS_H__ +#define __SURFACEFLAGS_H__ + +// This file must be identical in the game and utils directories + +// contents flags are seperate bits +// a given brush can contribute multiple content bits + +// these definitions also need to be in q_shared.h! + +#define CONTENTS_SOLID (1<<0) // an eye is never valid in a solid +#define CONTENTS_WINDOW (1<<1) // added for BOTLIB +#define CONTENTS_AUX (1<<2) // added for BOTLIB +#define CONTENTS_LAVA (1<<3) +#define CONTENTS_SLIME (1<<4) +#define CONTENTS_WATER (1<<5) +#define CONTENTS_FOG (1<<6) + +// more BOTLIB additions FIXME +#define CONTENTS_MIST (1<<6) // added for BOTLIB, same as CONTENTS_FOG +#define CONTENTS_LADDER (1<<7) // I know this is on a surfaceparm +#define CONTENTS_NOTTEAM2 (1<<8) // FIXME these can't be good +#define CONTENTS_NOTTEAM1 (1<<9) +#define CONTENTS_JUMPPAD (1<<10) +#define CONTENTS_TELEPORTER (1<<11) +#define CONTENTS_MOVER (1<<12) +// end BOTLIB additions + +#define CONTENTS_USABLE (1<<12) +#define CONTENTS_SETCLIP (1<<13) // All members of this content set noclip each other +#define CONTENTS_TARGETABLE (1<<14) +#define CONTENTS_AREAPORTAL (1<<15) +#define CONTENTS_PLAYERCLIP (1<<16) +#define CONTENTS_MONSTERCLIP (1<<17) +#define CONTENTS_CAMERACLIP (1<<18) +#define CONTENTS_WEAPONCLIP (1<<19) // blocks projectiles and weapon attacks as well +#define CONTENTS_SHOOTABLE_ONLY (1<<20) // player can walk through this but can shoot it as well +#define CONTENTS_CLUSTERPORTAL (1 << 21) // added for BOTLIB +#define CONTENTS_BOTCLIP (1 << 22) // added for BOTLIB +#define CONTENTS_DONOTENTER (1 << 23) // added for BOTLIB +#define CONTENTS_ORIGIN (1<<24) // removed before bsping an entity +#define CONTENTS_BODY (1<<25) // should never be on a brush, only in game +#define CONTENTS_CORPSE (1<<26) +#define CONTENTS_DETAIL (1<<27) // brushes not used for the bsp +#define CONTENTS_STRUCTURAL (1<<28) // brushes used for the bsp +#define CONTENTS_TRANSLUCENT (1<<29) // don't consume surface fragments inside +#define CONTENTS_NOBOTCLIP (1<<30) // added for BOTLIB +#define CONTENTS_NODROP (1<<31) // don't leave bodies or items (death fog, lava) + +#define CONTENTS_KEEP (CONTENTS_DETAIL) + +#define SURF_NODAMAGE ( 1<<0 ) // never give falling damage +#define SURF_SLICK ( 1<<1 ) // effects game physics +#define SURF_SKY ( 1<<2 ) // lighting from environment map +#define SURF_LADDER ( 1<<3 ) // ladder surface +#define SURF_NOIMPACT ( 1<<4 ) // don't make missile explosions +#define SURF_NOMARKS ( 1<<5 ) // don't leave missile marks +#define SURF_CASTSHADOW ( 1<<6 ) // used in conjunction with nodraw allows surface to be not drawn but still cast shadows +#define SURF_NODRAW ( 1<<7 ) // don't generate a drawsurface at all +#define SURF_SKIP ( 1<<8 ) // defined for BOTLIB +#define SURF_TYPE_SAND ( 1<<9 ) // sand surface +#define SURF_NOLIGHTMAP ( 1<<10 )// surface doesn't need a lightmap +#define SURF_ALPHASHADOW ( 1<<11 )// do per-pixel shadow tests based on the texture +#define SURF_TYPE_SNOW ( 1<<12 )// snow surface +#define SURF_NOSTEPS ( 1<<13 )// no footstep sounds +#define SURF_NONSOLID ( 1<<14 )// don't collide against curves with this set +#define SURF_RICOCHET ( 1<<15 )// ricochet bullets + +#define SURF_TYPE_METAL_DUCT ( 1<<16 )// metal duct surface +#define SURF_TYPE_METAL ( 1<<17 )// metal surface +#define SURF_TYPE_ROCK ( 1<<18 )// stone surface +#define SURF_TYPE_DIRT ( 1<<19 )// dirt surface +#define SURF_TYPE_GRILL ( 1<<20 )// metal grill surface +#define SURF_TYPE_ORGANIC ( 1<<21 )// oraganic (grass, loamy dirt) +#define SURF_TYPE_SQUISHY ( 1<<22 )// squishy (swamp dirt, flesh) + +#define SURF_NODLIGHT ( 1<<23 )// don't dlight even if solid (solid lava, skies) +#define SURF_HINT ( 1<<24 )// choose this plane as a partitioner +#define SURF_TYPE_DEFAULT ( 1<<25 ) + +#define SURF_TYPE_METAL_HOLLOW ( 1<<26 )// hollow metal surface +#define SURF_TYPE_CARPET ( 1<<27 )// carpet surface + +#define SURF_PATCH ( 1<<29 ) +#define SURF_TERRAIN ( 1<<30 ) + +#define SURF_KEEP (SURF_PATCH | SURF_TERRAIN) + +#define MASK_SURF_TYPE (SURF_TYPE_SAND|SURF_TYPE_SNOW|SURF_TYPE_METAL_DUCT|SURF_TYPE_METAL|SURF_TYPE_ROCK|SURF_TYPE_DIRT|SURF_TYPE_GRILL|SURF_TYPE_ORGANIC|SURF_TYPE_SQUISHY|SURF_TYPE_METAL_HOLLOW|SURF_TYPE_CARPET) + +enum +{ + TRACE_IGNORE_CURVE_PATCHES = ( 1 << 0 ), + TRACE_IGNORE_TERRAIN_BRUSHES = ( 1 << 1 ), +}; + +void ParseSurfaceParm( char *token, int * flags, int * contents ); + +#endif diff --git a/dlls/game/syn.h b/dlls/game/syn.h new file mode 100644 index 0000000..583fe01 --- /dev/null +++ b/dlls/game/syn.h @@ -0,0 +1,22 @@ +//=========================================================================== +// +// Name: syn.h +// Function: synonyms +// Programmer: Mr Elusive +// Last update: - +// Tab Size: 4 (real tabs) +// Notes: - +//=========================================================================== + +#define CONTEXT_ALL 0xFFFFFFFF +#define CONTEXT_NORMAL 1 +#define CONTEXT_NEARBYITEM 2 +#define CONTEXT_CTFREDTEAM 4 +#define CONTEXT_CTFBLUETEAM 8 +#define CONTEXT_REPLY 16 +#define CONTEXT_OBELISKREDTEAM 32 +#define CONTEXT_OBELISKBLUETEAM 64 +#define CONTEXT_HARVESTERREDTEAM 128 +#define CONTEXT_HARVESTERBLUETEAM 256 + +#define CONTEXT_NAMES 1024 diff --git a/dlls/game/talk.cpp b/dlls/game/talk.cpp new file mode 100644 index 0000000..5a6e4e1 --- /dev/null +++ b/dlls/game/talk.cpp @@ -0,0 +1,433 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/talk.cpp $ +// $Revision:: 16 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Talk Implementation +// +// PARAMETERS: +// +// ANIMATIONS: +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "talk.hpp" +#include + +Event EV_TalkBehavior_GreetingDone + ( + "greetingdone", + EV_DEFAULT, + NULL, + NULL, + "Notifies the talk behavior the greeting dialog is done" + ); + +/**************************************************************************** + + Talk Class Definition + +****************************************************************************/ +CLASS_DECLARATION( Behavior, Talk, NULL ) + { + { &EV_TalkBehavior_GreetingDone, &Talk::GreetingDone }, + { &EV_Behavior_AnimDone, &Talk::AnimDone }, + { NULL, NULL } + }; + +void Talk::SetUser + ( + Sentient *user + ) + + { + ent_listening = user; + } + +void Talk::AnimDone + ( + Event *ev + ) + + { + turnto.ProcessEvent( EV_Behavior_AnimDone ); + animDone = true; + } + +void Talk::GreetingDone( Event *ev ) +{ + mode = TALK_MODE_TURN_TO; +} + +void Talk::Begin + ( + Actor &self + ) + + { + Vector dir; + Vector angles; + const char *anim_name; + oldAnimName = ""; + + + anim_name = self.animname; + last_headwatch_target = self.headWatcher->GetWatchTarget(); + animDone = true; + move_allowed = true; + turnto.SetUseTurnAnim( false ); + /* + if ( strncmp( anim_name, "sit_leanover", 12 ) == 0 ) + { + move_allowed = false; + } + else if ( strncmp( anim_name, "sit", 3 ) == 0 ) + { + move_allowed = false; + self.SetAnim( "sit_talk" ); + } + else if ( strncmp( anim_name, "talk_sit_stunned", 15 ) == 0 ) + { + move_allowed = false; + } + else if ( strncmp( anim_name, "talk_headset", 12 ) == 0 ) + { + move_allowed = true; + } + else if ( strncmp( anim_name, "stand_hypnotized", 16 ) == 0 ) + { + move_allowed = false; + } + else if ( strncmp( anim_name, "talk_hipnotic", 13 ) == 0 ) + { + move_allowed = false; + } + else if ( strncmp( anim_name, "rope", 4 ) == 0 ) + { + move_allowed = false; + } + else + { + move_allowed = true; + self.SetAnim( "talk" ); + }*/ + + + if ( self.talkMode == TALK_IGNORE ) + { + move_allowed = false; + } + + if ( self.talkMode == TALK_HEADWATCH ) + { + move_allowed = false; + oldAnimName = self.animname; + } + + if ( self.talkMode != TALK_IGNORE ) + { + if ( ent_listening ) + self.headWatcher->SetWatchTarget( ent_listening ); + } + + mode = TALK_MODE_PLAY_GREETING; + + Entity *currentEnemy = NULL; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + if ( currentEnemy ) + mode = TALK_MODE_COMBAT; + + original_yaw = self.angles[YAW]; + + if ( ent_listening ) + { + dir = ent_listening->centroid - self.centroid; + angles = dir.toAngles(); + yaw = angles[YAW]; + } + + /*if ( !self.GetActorFlag(ACTOR_FLAG_PLAYING_DIALOG_ANIM) ) + { + self.SetAnim( "conv" , EV_Actor_NotifyBehavior ); + self.SetActorFlag( ACTOR_FLAG_PLAYING_DIALOG_ANIM , true ); + } + */ + + } + +BehaviorReturnCode_t Talk::Evaluate + ( + Actor &self + ) + + { + Vector dir; + Vector angles; + str greetingDialog; + str combatDialog; + float greetingDialogLength; + Event *greetingEvent; + char localizedDialogName[MAX_QPATH]; + + //Event *event; + + if ( !ent_listening ) + mode = TALK_MODE_TURN_BACK; + + if ( !self.GetActorFlag(ACTOR_FLAG_PLAYING_DIALOG_ANIM) && animDone && self.GetActorFlag(ACTOR_FLAG_DIALOG_PLAYING) ) + { + if ( self.useConvAnims ) + { + //self.SetActorFlag(ACTOR_FLAG_CAN_CHANGE_ANIM , true); + self.SetAnim( "talk" , EV_Actor_NotifyBehavior ); + self.SetActorFlag( ACTOR_FLAG_PLAYING_DIALOG_ANIM , true ); + self.SetActorFlag(ACTOR_FLAG_CAN_CHANGE_ANIM , false ); + animDone = false; + } + } + + if ( self.useConvAnims && animDone && mode != TALK_MODE_WAIT && mode != TALK_MODE_TURN_TO ) + { + self.SetActorFlag(ACTOR_FLAG_CAN_CHANGE_ANIM , true); + self.SetAnim( "conv-idle" , EV_Actor_NotifyBehavior ); + //self.SetActorFlag(ACTOR_FLAG_CAN_CHANGE_ANIM , false ); + //self.SetAnim( "idle" ); + animDone = false; + } + + if ( self.useConvAnims && /*animDone &&*/ mode == TALK_MODE_WAIT ) + { + self.SetActorFlag(ACTOR_FLAG_CAN_CHANGE_ANIM , true); + if ( oldAnimName.length() ) + self.SetAnim( oldAnimName ); + else + self.SetAnim( "idle" ); + animDone = false; + } + + if ( !self.GetActorFlag(ACTOR_FLAG_PLAYING_DIALOG_ANIM) && !self.GetActorFlag(ACTOR_FLAG_DIALOG_PLAYING) && !self.talkMode == TALK_HEADWATCH ) + { + self.SetAnim( "idle" ); + animDone = true; + } + + if ( self.GetActorFlag(ACTOR_FLAG_PLAYING_DIALOG_ANIM) && animDone ) + { + if ( oldAnimName.length() ) + { + self.SetActorFlag(ACTOR_FLAG_CAN_CHANGE_ANIM , true ); + self.SetAnim( oldAnimName ); + self.SetActorFlag(ACTOR_FLAG_CAN_CHANGE_ANIM , false ); + if ( mode != TALK_MODE_TURN_BACK ) + mode = TALK_MODE_WAIT; + + } + } + + switch( mode ) + { + case TALK_MODE_COMBAT: + combatDialog = self.FindDialog( ent_listening, DIALOG_TYPE_COMBAT ); + if ( !combatDialog.length() ) + return BEHAVIOR_SUCCESS; + + self.PlayDialog( ent_listening , -1.0f, -1.0f, combatDialog.c_str() ); + return BEHAVIOR_SUCCESS; + + break; + + case TALK_MODE_PLAY_GREETING: + greetingDialog = self.FindDialog( ent_listening, DIALOG_TYPE_GREETING ); + if ( !greetingDialog.length() ) + { + mode = TALK_MODE_TURN_TO; + return BEHAVIOR_EVALUATING; + } + + gi.LocalizeFilePath( greetingDialog.c_str(), localizedDialogName ); + greetingDialogLength = gi.SoundLength( localizedDialogName ); + + if ( greetingDialogLength > 0 ) + { + greetingEvent = new Event(EV_TalkBehavior_GreetingDone); + PostEvent(greetingEvent , greetingDialogLength ); + + Event *dialogEvent = new Event( EV_SimplePlayDialog ); + dialogEvent->AddString( greetingDialog.c_str() ); + ent_listening->ProcessEvent( dialogEvent ); + //ent_listening->Sound( greetingDialog ); + + mode = TALK_MODE_WAIT_FOR_GREETING; + } + else + { + mode = TALK_MODE_TURN_TO; + } + + break; + + case TALK_MODE_WAIT_FOR_GREETING: + //Waiting on the Greeting Done Event Here + break; + + case TALK_MODE_TURN_TO : + if ( move_allowed ) + { + turnto.SetDirection( yaw ); + + if ( !turnto.Evaluate( self ) ) + { + mode = TALK_MODE_TALK; + self.PlayDialog( ent_listening ); + + /* event = new Event( EV_Player_WatchActor ); + event->AddEntity( &self ); + ent_listening->PostEvent( event, 0.05 ); */ + } + } + else + { + mode = TALK_MODE_TALK; + self.PlayDialog( ent_listening ); + + /* event = new Event( EV_Player_WatchActor ); + event->AddEntity( &self ); + ent_listening->PostEvent( event, 0.05 ); */ + } + break; + case TALK_MODE_TALK : + + if ( move_allowed ) + { + dir = ent_listening->centroid - self.centroid; + angles = dir.toAngles(); + turnto.SetDirection( angles[YAW] ); + turnto.Evaluate( self ); + } + + if ( !self.GetActorFlag( ACTOR_FLAG_DIALOG_PLAYING ) ) + { + mode = TALK_MODE_WAIT; + self.state_flags &= ~STATE_FLAG_USED; + + // Tell player to stop watching us + + /* event = new Event( EV_Player_StopWatchingActor ); + event->AddEntity( &self ); + ent_listening->PostEvent( event, 0 ); */ + + //ent_listening->CancelEventsOfType( EV_Player_WatchActor ); + } + else if ( !self.GetActorFlag( ACTOR_FLAG_RADIUS_DIALOG_PLAYING ) ) + { + if ( !self.WithinDistance( ent_listening, self.radiusDialogRange ) ) + { + self.PlayRadiusDialog( ent_listening ); + /* + int postive_response = true; + str check_alias; + if (postive_response) + { + check_alias = self.GetRandomAlias("radiusdialog_positive"); + if(check_alias.length()) + { + self.PlayRadiusDialog(ent_listening, "radiusdialog_positive"); + } + } + else + { + check_alias = self.GetRandomAlias("radiusdialog_negative"); + if(check_alias.length()) + { + self.PlayRadiusDialog(ent_listening, "radiusdialog_negative"); + } + } + */ + } + } + break; + case TALK_MODE_WAIT : + + if ( move_allowed ) + { + dir = ent_listening->centroid - self.centroid; + angles = dir.toAngles(); + turnto.SetDirection( angles[YAW] ); + turnto.Evaluate( self ); + } + + + + if ( !self.WithinDistance( ent_listening, 100.0f ) ) + mode = TALK_MODE_TURN_BACK; + + if ( self.state_flags & STATE_FLAG_USED ) + { + mode = TALK_MODE_TURN_TO; + self.SetActorFlag(ACTOR_FLAG_PLAYING_DIALOG_ANIM, false ); + + dir = ent_listening->centroid - self.centroid; + angles = dir.toAngles(); + yaw = angles[YAW]; + + self.state_flags &= ~STATE_FLAG_USED; + + /* event = new Event( EV_Player_WatchActor ); + event->AddEntity( &self ); + ent_listening->PostEvent( event, 0.05 ); */ + } + break; + case TALK_MODE_TURN_BACK : + if ( move_allowed ) + { + turnto.SetDirection( original_yaw ); + + if ( !turnto.Evaluate( self ) ) + return BEHAVIOR_SUCCESS; + } + else + { + return BEHAVIOR_SUCCESS; + } + + break; + } + + return BEHAVIOR_EVALUATING; + } + +void Talk::End( Actor &self ) + { + self.SetActorFlag( ACTOR_FLAG_PLAYING_DIALOG_ANIM , false ); + self.SetActorFlag(ACTOR_FLAG_CAN_CHANGE_ANIM , true ); + + self.ClearLegAnim(); + self.ClearTorsoAnim(); + + if ( oldAnimName.length() ) + { + self.SetAnim( oldAnimName ); + } + else + { + self.SetAnim( "idle" ); + } + + if ( last_headwatch_target ) + self.headWatcher->SetWatchTarget( last_headwatch_target ); + else + self.headWatcher->SetWatchTarget( NULL ); + } + + diff --git a/dlls/game/talk.hpp b/dlls/game/talk.hpp new file mode 100644 index 0000000..a204787 --- /dev/null +++ b/dlls/game/talk.hpp @@ -0,0 +1,101 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/torsoAimAndFireWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// torsoAimAndFireWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + + +//============================== +// Forward Declarations +//============================== +class Talk; + +#ifndef __TALK_HPP___ +#define __TALK_HPP___ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: Talk +// Base Class: Behavior +// +// Description: Makes the actor play appropriate dialog +// and make appropriate turntos. +// +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class Talk : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + TALK_MODE_TURN_TO, + TALK_MODE_COMBAT, + TALK_MODE_PLAY_GREETING, + TALK_MODE_WAIT_FOR_GREETING, + TALK_MODE_TALK, + TALK_MODE_WAIT, + TALK_MODE_TURN_BACK, + TALK_SUCCESS, + TALK_FAILED + } TalkStates_t; + + private: + TurnTo turnto; + SentientPtr ent_listening; + EntityPtr last_headwatch_target; + float original_yaw; + float yaw; + int mode; + qboolean move_allowed; + bool animDone; + str oldAnimName; + + public: + CLASS_PROTOTYPE( Talk ); + + void SetUser( Sentient *user ); + void GreetingDone( Event *ev ); + void AnimDone( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + }; + +inline void Talk::Archive( Archiver &arc ) + { + Behavior::Archive( arc ); + + arc.ArchiveObject( &turnto ); + arc.ArchiveSafePointer( &ent_listening ); + arc.ArchiveSafePointer( &last_headwatch_target ); + arc.ArchiveFloat( &original_yaw ); + arc.ArchiveFloat( &yaw ); + arc.ArchiveInteger( &mode ); + arc.ArchiveBoolean( &move_allowed ); + arc.ArchiveBool( &animDone ); + arc.ArchiveString( &oldAnimName ); + } + +#endif /* __TALK_HPP___ */ + diff --git a/dlls/game/teammateroster.cpp b/dlls/game/teammateroster.cpp new file mode 100644 index 0000000..7457a95 --- /dev/null +++ b/dlls/game/teammateroster.cpp @@ -0,0 +1,330 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/teammateroster.cpp $ +// $Revision:: 10 $ +// $Author:: Steven $ +// $Date:: 10/10/02 1:13p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: + +#include "teammateroster.hpp" +#include "GameplayManager.h" + +TeammateRoster* TeammateRoster::_instance = 0; + +//----------------------------------------------------- +// +// Name: TeammateRoster +// Class: TeammateRoster +// +// Description: Initializes data members +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +TeammateRoster::TeammateRoster() +{ + + GameplayManager* gpm = GameplayManager::getTheGameplayManager(); + if(gpm == 0) + return; + + _healthyShader = gpm->getStringValue("TeammateData", "HealthyShader"); + _injuredShader = gpm->getStringValue("TeammateData", "InjuredShader"); + _criticalShader = gpm->getStringValue("TeammateData", "CriticalShader"); + _defaultShader = gpm->getStringValue("TeammateData", "DefaultShader"); + + _healthyAnimation = gpm->getStringValue("TeammateData", "HealthyAnimation"); + _injuredAnimation = gpm->getStringValue("TeammateData", "InjuredAnimation"); + _criticalAnimation = gpm->getStringValue("TeammateData", "CriticalAnimation"); + + for(int i = 0; i < MAX_TEAMMATES; i++) + { + _teammateList[i].active = false; + _teammateList[i].entNum = ENTITYNUM_NONE; + } +} + + +//----------------------------------------------------- +// +// Name: ~TeammateRoster +// Class: TeammateRoster +// +// Description: Uninitializes data members +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +TeammateRoster::~TeammateRoster() +{ + for(int i = 0; i < MAX_TEAMMATES; i++) + { + removeTeammate(i); + } +} + + +//----------------------------------------------------- +// +// Name: createInstance +// Class: TeammateRoster +// +// Description: Creates an instance of the teammate roster system +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void TeammateRoster::createInstance(void) +{ + if(_instance == 0) + _instance = new TeammateRoster; +} + + +//----------------------------------------------------- +// +// Name: deleteInstance +// Class: TeammateRoster +// +// Description: Deletes the instance of the teammate roster. +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void TeammateRoster::deleteInstance(void) +{ + delete _instance; + _instance = 0; +} + + +//----------------------------------------------------- +// +// Name: addTeammate +// Class: TeammateRoster +// +// Description: Adds a teammate to the roster. If a teammate already exists +// in that current position, the new teammate replaces the old +// teammate. +// +// Parameters: entity - the entity to add as a teammate. +// index - the position on the roster to add the teammate to. +// +// Returns: +//----------------------------------------------------- +void TeammateRoster::addTeammate( const Entity* entity, int index) +{ + if(index > MAX_TEAMMATES - 1 || index < 0 ) + { + return; + } + + if(entity == 0) + return; + + + TeammateData& teammateData = _teammateList[index]; + + teammateData.entNum = entity->edict->s.number; + teammateData.injuredHealthLevel = entity->max_health - (entity->max_health * 0.33f); //injured health level is below 2/3 of their health + teammateData.criticalHealthLevel = entity->max_health - (entity->max_health * 0.66f); //critical health level is below 1/3 of their health + + //get the archetype + teammateData.archeType = entity->getArchetype(); + teammateData.active = true; +} + + +//----------------------------------------------------- +// +// Name: removeTeammate +// Class: TeammateRoster +// +// Description: Removes a teammate from the list. +// +// Parameters: index - the index of the teammate data. +// +// Returns: None +//----------------------------------------------------- +void TeammateRoster::removeTeammate(int index) +{ + if(index > MAX_TEAMMATES - 1 || index < 0 ) + return; + + TeammateData& teammateData = _teammateList[index]; + + teammateData.entNum = ENTITYNUM_NONE; + teammateData.criticalHealthLevel = 0; + teammateData.injuredHealthLevel = 0; + teammateData.active = false; + + removeTeammateDataFromDatabase(index); +} + + +//----------------------------------------------------- +// +// Name: removeTeammateDataFromDatabase +// Class: TeammateRoster +// +// Description: Removes the teammate data from the database +// +// Parameters: index - the index of the teammate data +// +// Returns: None +//----------------------------------------------------- +void TeammateRoster::removeTeammateDataFromDatabase(int index) +{ + GameplayManager* gpm = GameplayManager::getTheGameplayManager(); + if(gpm == 0) + return; + + str objectName = "Teammate"; + objectName += (index + 1); + + gpm->setStringValue(objectName,"Archetype","EMPTY"); + gpm->setStringValue(objectName, "StatusShader", _defaultShader); +} + + +//----------------------------------------------------- +// +// Name: getTeammateStatus +// Class: TeammateRoster +// +// Description: Retrieves the teammates status based upon their current health. +// +// Parameters: index - index into the list for the teammate data. +// +// Returns: TeammateStatus +//----------------------------------------------------- +TeammateStatus TeammateRoster::getTeammateStatus(int index) +{ + if(index > MAX_TEAMMATES - 1 || index < 0 ) + return HEALTHY_STATUS; + + TeammateData& teammateData = _teammateList[index]; + + // Make sure we are referencing an actual entity + + if ( teammateData.entNum == ENTITYNUM_NONE ) + return HEALTHY_STATUS; + + gentity_t* gentity = &g_entities[teammateData.entNum]; + + if(gentity == 0 || gentity->entity == 0) + return HEALTHY_STATUS; + + if(gentity->entity->health > teammateData.injuredHealthLevel) + { + return HEALTHY_STATUS; + } + else if(gentity->entity->health <= teammateData.injuredHealthLevel && + gentity->entity->health > teammateData.criticalHealthLevel) + { + return INJURED_STATUS; + } + else if(gentity->entity->health <= teammateData.criticalHealthLevel) + { + return CRITICAL_STATUS; + } + + return HEALTHY_STATUS; +} + +//----------------------------------------------------- +// +// Name: update +// Class: TeammateRoster +// +// Description: Updates the teammates status via the database. +// +// Parameters: None +// +// Returns: None +//----------------------------------------------------- +void TeammateRoster::update(void) +{ + GameplayManager* gpm; + str objectName; + TeammateStatus status; + str shader; + str animation; + + + gpm = GameplayManager::getTheGameplayManager(); + if(gpm == 0) + return; + + for(int i = 0; i < MAX_TEAMMATES; i++) + { + + if(_teammateList[i].active == false) + continue; + + //update the teammates status. + objectName = "Teammate"; + objectName += (i + 1); + + status = getTeammateStatus(i); + + switch (status) + { + case HEALTHY_STATUS: + shader = _healthyShader; + animation = _healthyAnimation; + break; + + case INJURED_STATUS: + shader = _injuredShader; + animation = _injuredAnimation; + break; + + case CRITICAL_STATUS: + shader = _criticalShader; + animation = _criticalAnimation; + break; + } + + + gpm->setStringValue(objectName, "Archetype", _teammateList[i].archeType, true); + + str objectRenderName = _teammateList[i].archeType; + objectRenderName += ".ModelRendering"; + gpm->setStringValue(objectRenderName, "animation", animation, true); + + gpm->setStringValue(objectName, "StatusShader", shader, true); + } +} + + +//----------------------------------------------------- +// +// Name: clearTeammates +// Class: TeammateRoster +// +// Description: Clears the teammates from the roster. +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void TeammateRoster::clearTeammates( void ) +{ + for( int i = 0; i < MAX_TEAMMATES; i++) + { + removeTeammate(i); + } +} \ No newline at end of file diff --git a/dlls/game/teammateroster.hpp b/dlls/game/teammateroster.hpp new file mode 100644 index 0000000..532edaf --- /dev/null +++ b/dlls/game/teammateroster.hpp @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/surfaceflags.h $ +// $Revision:: 9 $ +// $Author:: Jwaters $ +// $Date:: 7/30/02 8:35p $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// $Log:: /Code/DLLs/game/surfaceflags.h $ + +#ifndef TEAMMATE_ROSTER_HPP +#define TEAMMATE_ROSTER_HPP + +#include "listener.h" +#include "container.h" +#include "str.h" + +const int MAX_TEAMMATES=4; + +//Teammate Data Structure +typedef struct +{ + int entNum; + int injuredHealthLevel; + int criticalHealthLevel; + bool active; + str archeType; + +}TeammateData; + +//-------------------------- CLASS ---------------------------------- +// +// Name: TeammateRoster +// Base Class: None +// +// Description: Handles adding the teammate to the roster. This is a singleton system that will receive events +// (usually through script) to add and remove teammates to a roster. This system then updates +// the teammates health status via the database. The client reads in these database values via the +// user interface and displays this to the user. +// +// Method Of Use: +// +//------------------------------------------------------------------- +class TeammateRoster +{ + public: + TeammateRoster(); + ~TeammateRoster(); + + static void createInstance(void); + static void deleteInstance(void); + + static TeammateRoster* getInstance(void) { if(_instance == 0) TeammateRoster::createInstance(); return _instance; } + + TeammateStatus getTeammateStatus(int index); + + void addTeammate(const Entity* entity, int index); + void removeTeammate(int index); + + void update(void); + + void clearTeammates(void); + + void Archive( Archiver &arc ); + + protected: + void removeTeammateDataFromDatabase(int index); + + + private: + TeammateData _teammateList[MAX_TEAMMATES]; + static TeammateRoster* _instance; + + str _healthyShader; + str _injuredShader; + str _criticalShader; + str _defaultShader; + + str _healthyAnimation; + str _injuredAnimation; + str _criticalAnimation; +}; + + +inline void TeammateRoster::Archive( Archiver& arc ) +{ + TeammateData* teammateData; + + for(int i = 0; i < MAX_TEAMMATES; i++) + { + teammateData = &_teammateList[i]; + arc.ArchiveInteger( &teammateData->entNum); + arc.ArchiveInteger( &teammateData->injuredHealthLevel); + arc.ArchiveInteger( &teammateData->criticalHealthLevel); + arc.ArchiveBool( &teammateData->active); + arc.ArchiveString( &teammateData->archeType); + } + + + arc.ArchiveString(&_healthyShader); + arc.ArchiveString(&_injuredShader); + arc.ArchiveString(&_criticalShader); + arc.ArchiveString(&_defaultShader); + + arc.ArchiveString(&_healthyAnimation); + arc.ArchiveString(&_injuredAnimation); + arc.ArchiveString(&_criticalAnimation); + +} + +#endif + diff --git a/dlls/game/teleportToEntity.cpp b/dlls/game/teleportToEntity.cpp new file mode 100644 index 0000000..2e67363 --- /dev/null +++ b/dlls/game/teleportToEntity.cpp @@ -0,0 +1,296 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/teleportToEntity.cpp $ +// $Revision:: 5 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// TeleportToEntity Behavior Implementation. +// -- Currently, this is misnamed as a result of the transition to individual +// files for each behavior. Additionally, this behavior needs much refactoring +// to make it more generalized and flexible +// +// -- What it does RIGHT NOW +// The Behavior grabs the player's position, offsets it ( Preferring to go behind ) +// It checks if this new position will hold the actor. If that is true, then +// it plays the "start" animation. When that animation is completed, it rechecks +// the spot -- If it's still good, then it sets the actor's origin to that spot +// and plays the "end" animation +// +// ANIMATIONS: +// Start Animation : Parameter +// End Animation : Parameter +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "player.h" +#include "teleportToEntity.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, AnimatedTeleportToPlayer, NULL ) + { + { &EV_Behavior_Args, &AnimatedTeleportToPlayer::SetArgs }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: AnimatedTeleportToPlayer +// +// Description: Sets Variables based on arguments inside the event +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void AnimatedTeleportToPlayer::SetArgs( Event *ev ) +{ + _startAnim = ev->GetString( 1 ); + _endAnim = ev->GetString( 2 ); +} + +//-------------------------------------------------------------- +// Name: Begin() +// Class: AnimatedTeleportToPlayer +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AnimatedTeleportToPlayer::Begin( Actor &self ) +{ + _state = ANIM_TELEPORT_BEGIN; +} + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: AnimatedTeleportToPlayer +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t AnimatedTeleportToPlayer::Evaluate ( Actor &self ) +{ + int current_position; + qboolean teleport_position_found; + Vector new_position; + int i; + Vector dir; + Vector angles; + + Player *player = NULL; + Player *temp_player = NULL; + // Make sure the player is alive and well + for(i = 0; i < game.maxclients; i++) + { + player = GetPlayer(i); + + // don't target while player is not in the game or he's in notarget + if ( temp_player && !( temp_player->flags & FL_NOTARGET ) ) + { + player = temp_player; + break; + } + } + + if ( !player ) + return BEHAVIOR_SUCCESS; + + switch ( _state ) + { + case ANIM_TELEPORT_BEGIN: + // Default the teleport position to where we are now + + _teleportPosition = self.origin; + teleport_position_found = false; + + // Always teleport BEHIND the player - - we don't want him to see us pop in. + current_position = TELEPORT_BEHIND; + + // Test this position + if ( testPosition( self, current_position, new_position, player, true ) ) + { + _teleportPosition = new_position; + teleport_position_found = true; + } + else + { + if ( testPosition( self, current_position, new_position, player, false ) ) + { + _teleportPosition = new_position; + teleport_position_found = true; + } + } + + if ( !teleport_position_found ) + return BEHAVIOR_FAILED; + + _state = ANIM_TELEPORT_START_ANIM; + break; + + + case ANIM_TELEPORT_START_ANIM: + self.SetAnim( _startAnim , EV_Anim_Done , legs ); + _state = ANIM_TELEPORT_START_ANIMATING; + break; + + case ANIM_TELEPORT_START_ANIMATING: + if ( self.GetActorFlag( ACTOR_FLAG_ANIM_DONE ) ) + _state = ANIM_TELEPORT_TELEPORT; + break; + + case ANIM_TELEPORT_TELEPORT: + _teleportPosition = self.origin; + teleport_position_found = false; + + // Always teleport BEHIND the player - - we don't want him to see us pop in. + current_position = TELEPORT_BEHIND; + + // Test this position + if ( testPosition( self, current_position, new_position, player, true ) ) + { + _teleportPosition = new_position; + teleport_position_found = true; + } + else + { + if ( testPosition( self, current_position, new_position, player, false ) ) + { + _teleportPosition = new_position; + teleport_position_found = true; + } + } + + if ( !teleport_position_found ) + { + _state = ANIM_TELEPORT_END_ANIM; + break; + } + + self.setOrigin( _teleportPosition ); + self.NoLerpThisFrame(); + + dir = player->origin - _teleportPosition; + angles = dir.toAngles(); + + angles[ROLL] = 0.0f; + angles[PITCH] = 0.0f; + + self.setAngles( angles ); + _state = ANIM_TELEPORT_END_ANIM; + break; + + case ANIM_TELEPORT_END_ANIM: + self.SetAnim( _endAnim , EV_Anim_Done , legs ); + _state = ANIM_TELEPORT_END_ANIMATING; + break; + + case ANIM_TELEPORT_END_ANIMATING: + if ( self.GetActorFlag( ACTOR_FLAG_ANIM_DONE ) ) + return BEHAVIOR_SUCCESS; + break; + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: End() +// Class: AnimatedTeleportToPlayer +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AnimatedTeleportToPlayer::End( Actor &self ) +{ +} + + +//-------------------------------------------------------------- +// Name: testPosition() +// Class: AnimatedTeleportToPlayer +// +// Description: Tests if the given position is valid for teleporting into +// +// Parameters: Actor &self +// int test_pos +// Vector &good_position +// Entity *player, +// bool use_player_dir +// +// Returns: true or false +//-------------------------------------------------------------- +bool AnimatedTeleportToPlayer::testPosition( Actor &self, int test_pos, Vector &good_position, Entity* player, bool use_player_dir ) +{ + Vector test_position; + Vector player_angles; + Vector player_forward; + Vector player_left; + trace_t trace; + + + // Get the position to test + test_position = player->origin; + + if ( use_player_dir ) + { + // Get the player direction info + + player_angles = player->angles; + player_angles.AngleVectors( &player_forward, &player_left ); + + // Check Behind the Player + test_position -= player_forward * 128.0f; + } + else + { + // Check Behind the Player + test_position += Vector(-128, 0, 0); + } + + // Final Tweaking + test_position += Vector(0, 0, 64); + + // Test to see if we can fit at the new position + + trace = G_Trace( test_position, self.mins, self.maxs, test_position - Vector( "0 0 250" ), &self, self.edict->clipmask, false, "Teleport::TestPosition" ); + + if ( trace.allsolid || trace.startsolid ) + return false; + + if ( trace.fraction == 1.0 ) + return false; + + // Make sure we can see the Player from this position + + /*if ( !self.IsEntityAlive( player ) || !self.sensoryPerception->CanSeeEntity( Vector( trace.endpos ), player , true , true ) ) + return false;*/ + + + // This is a good position + good_position = trace.endpos; + return true; +} + + diff --git a/dlls/game/teleportToEntity.hpp b/dlls/game/teleportToEntity.hpp new file mode 100644 index 0000000..a4d6873 --- /dev/null +++ b/dlls/game/teleportToEntity.hpp @@ -0,0 +1,125 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/teleportToEntity.h $ +// $Revision:: 169 $ +// $Author:: Bschofield $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// TeleportToEntity Behavior Definition ( Behavior Name Needs To Be Changed ) +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class AnimatedTeleportToPlayer; + +#ifndef __TELEPORT_TO_ENTITY_H__ +#define __TELEPORT_TO_ENTITY_H__ + +#include "behavior.h" + +//-------------------------------------------------- +// We need to change the name of this behavior to more appropriately +// reflect the file name AND we need to set it up properly so that it +// doesn't HAVE to be animated... Basically we need to generalize this +// behavior as soon as possible +//---------------------------------------------------- + +//------------------------- CLASS ------------------------------ +// +// Name: AnimatedTeleportToPlayer +// Base Class: Behavior +// +// Description: Teleports Actor to the player ( See .cpp for details ) +// +// Method of Use: State Machine or another behavior +//-------------------------------------------------------------- +class AnimatedTeleportToPlayer : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + ANIM_TELEPORT_BEGIN, + ANIM_TELEPORT_START_ANIM, + ANIM_TELEPORT_START_ANIMATING, + ANIM_TELEPORT_TELEPORT, + ANIM_TELEPORT_END_ANIM, + ANIM_TELEPORT_END_ANIMATING, + } animTeleportStates_t; + + typedef enum + { + TELEPORT_BEHIND, + TELEPORT_TOLEFT, + TELEPORT_TORIGHT, + TELEPORT_INFRONT, + TELEPORT_NUMBER_OF_POSITIONS + } animTeleportPositionModes_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _startAnim; + str _endAnim; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + bool testPosition ( Actor &self, int test_pos, Vector &good_position, Entity* player, bool use_player_dir ); + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( AnimatedTeleportToPlayer ); + + void SetArgs ( Event *ev ); + void Begin ( Actor &self ); + + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + Vector _teleportPosition; + animTeleportStates_t _state; + }; + +inline void AnimatedTeleportToPlayer::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + //------------------------------------- + // Archive Parameters + //------------------------------------- + arc.ArchiveString ( &_startAnim ); + arc.ArchiveString ( &_endAnim ); + + //------------------------------------- + // Archive Member Variables + //------------------------------------- + arc.ArchiveVector ( &_teleportPosition ); + ArchiveEnum ( _state, animTeleportStates_t ); +} + + + +#endif /* __TELEPORT_TO_ENTITY_H__ */ diff --git a/dlls/game/teleportToPosition.cpp b/dlls/game/teleportToPosition.cpp new file mode 100644 index 0000000..62a5422 --- /dev/null +++ b/dlls/game/teleportToPosition.cpp @@ -0,0 +1,218 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/teleportToPosition.cpp $ +// $Revision:: 5 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// TeleportToPosition Behavior Implementation. +// -- Currently, this is misnamed as a result of the transition to individual +// files for each behavior. Additionally, this behavior needs much refactoring +// to make it more generalized and flexible +// +// -- What it does RIGHT NOW +// The Behavior iterates through "position" flagged pathnodes. It picks a random +// one ( based on a naming convention ) and uses that as a teleport position. +// It then checks if this new position will hold the actor. If that is true, then +// it plays the "start" animation. When that animation is completed, it rechecks +// the spot -- If it's still good, then it sets the actor's origin to that spot +// and plays the "end" animation +// +// -- Naming Convention +// 2 of the parameters ( _teleportPositionName , _numberOfTeleportPositions ) +// build up a naming convention for the behavior to use. What this means is +// you will need to coordinate with the level designers to make sure that they +// name the pathnodes appropriately. "WarpNode1" is a good example. For every +// teleport position, they should increment the number at the end. "WarpNode3" , +// "WarpNode2", etc... +// +// For the state machine, ( using this example ) you would pass "WarpNode" and 3 +// for the count of possible positions. The behaivor then picks a random integer +// up to the count number, and concatenates with the Name to get a random node. +// +// ANIMATIONS: +// Start Animation : Parameter +// End Animation : Parameter +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "player.h" +#include "weaputils.h" +#include "teleportToPosition.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, AnimatedTeleportToPosition, NULL ) + { + { &EV_Behavior_Args, &AnimatedTeleportToPosition::SetArgs }, + { NULL, NULL } + }; + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: AnimatedTeleportToPosition +// +// Description: Sets Variables based on arguments inside the event +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void AnimatedTeleportToPosition::SetArgs( Event *ev ) +{ + _teleportPositionName = ev->GetString( 1 ); + _numberOfTeleportPositions = ev->GetInteger( 2 ); + _startAnim = ev->GetString( 3 ); + _endAnim = ev->GetString( 4 ); +} + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: AnimatedTeleportToPosition +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AnimatedTeleportToPosition::Begin( Actor &self ) +{ + _state = ANIM_TELEPORT_BEGIN; +} + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: AnimatedTeleportToPosition +// +// Description: Update for this behavior -- called every server frame +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t AnimatedTeleportToPosition::Evaluate ( Actor &self ) +{ + Vector dir; + Vector angles; + trace_t trace; + str pathnode_name; + Vector teleport_position; + Vector attack_position; + float half_height; + + Entity *currentEnemy; + currentEnemy = self.enemyManager->GetCurrentEnemy(); + + switch ( _state ) + { + case ANIM_TELEPORT_BEGIN: + + // Get the pathnode name to teleport to + pathnode_name = _teleportPositionName; + pathnode_name += (int)G_Random( (float)_numberOfTeleportPositions ) + 1 ; + + // Find the path node + _goal = thePathManager.FindNode( pathnode_name ); + + if ( !_goal ) + { + gi.WDPrintf( "Can't find position %s\n", pathnode_name.c_str() ); + SetFailureReason( "Unable to find a teleport position" ); + return BEHAVIOR_FAILED; + } + + _state = ANIM_TELEPORT_START_ANIM; + break; + + case ANIM_TELEPORT_START_ANIM: + self.SetAnim( _startAnim , EV_Anim_Done , legs ); + _state = ANIM_TELEPORT_START_ANIMATING; + break; + + case ANIM_TELEPORT_START_ANIMATING: + if ( self.GetActorFlag( ACTOR_FLAG_ANIM_DONE ) ) + _state = ANIM_TELEPORT_TELEPORT; + break; + + case ANIM_TELEPORT_TELEPORT: + + // Set the teleport position + teleport_position = _goal->origin; + + // Kill anything at this position + half_height = self.maxs.z / 2.0f; + attack_position = teleport_position; + attack_position.z += half_height; + + MeleeAttack( attack_position, attack_position, 10000.0f, &self, MOD_TELEFRAG, self.maxs.x, -half_height, half_height, 0 ); + + // Test to see if we can fit at the new position + trace = G_Trace( teleport_position + Vector( "0 0 64" ), self.mins, self.maxs, teleport_position - Vector( "0 0 128" ), &self, MASK_PATHSOLID, false, "TeleportToPosition" ); + + if ( trace.allsolid ) + { + gi.WDPrintf( "Failed to teleport to %s\n", _goal->targetname.c_str() ); + SetFailureReason( "Trace at my designated teleport position return All Solid" ); + return BEHAVIOR_FAILED; + } + + teleport_position = trace.endpos; + self.setOrigin( teleport_position ); + self.NoLerpThisFrame(); + + if ( currentEnemy ) + { + dir = currentEnemy->origin - teleport_position; + angles = dir.toAngles(); + + angles[ROLL] = 0.0f; + angles[PITCH] = 0.0f; + + self.setAngles( angles ); + } + + _state = ANIM_TELEPORT_END_ANIM; + break; + + case ANIM_TELEPORT_END_ANIM: + self.SetAnim( _endAnim , EV_Anim_Done , legs ); + _state = ANIM_TELEPORT_END_ANIMATING; + break; + + case ANIM_TELEPORT_END_ANIMATING: + if ( self.GetActorFlag( ACTOR_FLAG_ANIM_DONE ) ) + return BEHAVIOR_SUCCESS; + break; + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: End() +// Class: AnimatedTeleportToPosition +// +// Description: Ends this behavior -- cleans things up +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void AnimatedTeleportToPosition::End( Actor &self ) +{ +} + + diff --git a/dlls/game/teleportToPosition.hpp b/dlls/game/teleportToPosition.hpp new file mode 100644 index 0000000..e714af2 --- /dev/null +++ b/dlls/game/teleportToPosition.hpp @@ -0,0 +1,114 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/teleportToPosition.h $ +// $Revision:: 169 $ +// $Author:: Bschofield $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// TeleportToPosition Behavior Definition ( Behavior Name Needs To Be Changed ) +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class AnimatedTeleportToPosition; + +#ifndef __TELEPORT_TO_POSITION_H__ +#define __TELEPORT_TO_POSITION_H__ + +#include "behavior.h" + +//-------------------------------------------------- +// We need to change the name of this behavior to more appropriately +// reflect the file name AND we need to set it up properly so that it +// doesn't HAVE to be animated... Basically we need to generalize this +// behavior as soon as possible +//---------------------------------------------------- + +//------------------------- CLASS ------------------------------ +// +// Name: AnimatedTeleportToPosition +// Base Class: Behavior +// +// Description: Teleports Actor to the player ( See .cpp for details ) +// +// Method of Use: State Machine or another behavior +//-------------------------------------------------------------- +class AnimatedTeleportToPosition : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + ANIM_TELEPORT_BEGIN, + ANIM_TELEPORT_START_ANIM, + ANIM_TELEPORT_START_ANIMATING, + ANIM_TELEPORT_TELEPORT, + ANIM_TELEPORT_END_ANIM, + ANIM_TELEPORT_END_ANIMATING, + } animTeleportStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _teleportPositionName; + int _numberOfTeleportPositions; + str _startAnim; + str _endAnim; + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( AnimatedTeleportToPosition ); + + void SetArgs( Event *ev ); + void Begin( Actor &self ); + BehaviorReturnCode_t Evaluate( Actor &self ); + void End( Actor &self ); + virtual void Archive( Archiver &arc ); + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + animTeleportStates_t _state; + PathNodePtr _goal; + + }; + +inline void AnimatedTeleportToPosition::Archive( Archiver &arc ) +{ + Behavior::Archive( arc ); + + //------------------------------------- + // Archive Parameters + //------------------------------------- + arc.ArchiveString ( &_teleportPositionName ); + arc.ArchiveInteger ( &_numberOfTeleportPositions ); + arc.ArchiveString ( &_startAnim ); + arc.ArchiveString ( &_endAnim ); + + //------------------------------------- + // Archive Member Variables + //------------------------------------- + ArchiveEnum ( _state, animTeleportStates_t ); + arc.ArchiveSafePointer ( &_goal ); +} + + + +#endif /*__TELEPORT_TO_POSITION_H__ */ diff --git a/dlls/game/torsoAimAndFireWeapon.cpp b/dlls/game/torsoAimAndFireWeapon.cpp new file mode 100644 index 0000000..515d9c8 --- /dev/null +++ b/dlls/game/torsoAimAndFireWeapon.cpp @@ -0,0 +1,962 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/torsoAimAndFireWeapon.cpp $ +// $Revision:: 20 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// torsoAimAndFireWeapon Implementation +// +// PARAMETERS: +// +// ANIMATIONS: +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "torsoAimAndFireWeapon.hpp" +#include + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, TorsoAimAndFireWeapon, NULL ) + { + { &EV_Behavior_Args, &TorsoAimAndFireWeapon::SetArgs }, + { &EV_Behavior_AnimDone, &TorsoAimAndFireWeapon::AnimDone }, + { NULL, NULL } + }; + + +//-------------------------------------------------------------- +// Name: TorsoAimAndFireWeapon() +// Class: StationaryFireCombatEX +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +TorsoAimAndFireWeapon::TorsoAimAndFireWeapon() +{ + _endFireTime = 0.0f; + _endAimTime = 0.0f; + _maxTorsoTurnSpeed = 15.0f; + _maxTorsoYaw = 90.0; + _maxTorsoPitch = 40.0; + _aimOnly = false; + _shots = 1; + _fireFailed = false; + _repeat = false; + +} + +//-------------------------------------------------------------- +// Name: ~TorsoAimAndFireWeapon() +// Class: TorsoAimAndFireWeapon +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +TorsoAimAndFireWeapon::~TorsoAimAndFireWeapon() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: TorsoAimAndFireWeapon +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::SetArgs( Event *ev ) +{ + _forceAttack = ev->GetBoolean( 1 ); + if ( ev->NumArgs() > 1 ) + _aimOnly = ev->GetBoolean( 2 ); + + if ( ev->NumArgs() > 2 ) + _repeat = ev->GetBoolean( 3 ); + + if ( ev->NumArgs() > 3 ) + _endOnAimFail = ev->GetBoolean( 4 ); +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: TorsoAimAndFireWeapon +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::AnimDone( Event *ev ) +{ + + /*if ( _state == TORSO_AIM_AND_FIRE_POST_FIRE ) + { + gi.WDPrintf( "-----------------------------------------\n"); + gi.WDPrintf( "PostFireAnimDone\n" ); + gi.WDPrintf( "-----------------------------------------\n"); + }*/ + + _animDone = true; +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: TorsoAimAndFireWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::Begin( Actor &self ) +{ + init( self ); + + if (_preFireAnim.length() && !_aimOnly ) + transitionToState ( TORSO_AIM_AND_FIRE_PRE_FIRE ); + else + transitionToState ( TORSO_AIM_AND_FIRE_AIM ); +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: TorsoAimAndFireWeapon +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t TorsoAimAndFireWeapon::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case TORSO_AIM_AND_FIRE_PRE_FIRE: + //--------------------------------------------------------------------- + stateResult = evaluateStatePreFire(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( TORSO_AIM_AND_FIRE_AIM ); + } + + if ( stateResult == BEHAVIOR_FAILED ) + { + transitionToState( TORSO_AIM_AND_FIRE_FAILED ); + } + break; + + //--------------------------------------------------------------------- + case TORSO_AIM_AND_FIRE_AIM: + //--------------------------------------------------------------------- + stateResult = evaluateStateAim(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + if ( _aimOnly ) + transitionToState(TORSO_AIM_AND_FIRE_AIM); + else + transitionToState( TORSO_AIM_AND_FIRE_ATTACK ); + } + + if ( stateResult == BEHAVIOR_FAILED ) + return BEHAVIOR_FAILED; + + break; + + + //--------------------------------------------------------------------- + case TORSO_AIM_AND_FIRE_ATTACK: + //--------------------------------------------------------------------- + stateResult = evaluateStateAttack(); + + if ( stateResult == BEHAVIOR_FAILED ) + { + _fireWeapon.End( *_self ); + + if ( _postFireAnim.length() ) + transitionToState( TORSO_AIM_AND_FIRE_POST_FIRE ); + else + transitionToState( TORSO_AIM_AND_FIRE_FAILED ); + } + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + _fireWeapon.End( *_self ); + + if ( _postFireAnim.length() ) + transitionToState( TORSO_AIM_AND_FIRE_POST_FIRE ); + else + transitionToState( TORSO_AIM_AND_FIRE_SUCCESS ); + } + break; + + //--------------------------------------------------------------------- + case TORSO_AIM_AND_FIRE_POST_FIRE: + //--------------------------------------------------------------------- + stateResult = evaluateStatePostFire(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + transitionToState( TORSO_AIM_AND_FIRE_SUCCESS ); + } + break; + + //--------------------------------------------------------------------- + case TORSO_AIM_AND_FIRE_SUCCESS: + //--------------------------------------------------------------------- + _self->SetControllerAngles( ACTOR_TORSO_TAG, vec_zero ); + + if ( _repeat ) + { + Begin(self); + return BEHAVIOR_EVALUATING; + } + + return BEHAVIOR_SUCCESS; + + break; + + + //--------------------------------------------------------------------- + case TORSO_AIM_AND_FIRE_FAILED: + //--------------------------------------------------------------------- + if ( _repeat ) + { + Begin(self); + return BEHAVIOR_EVALUATING; + } + + return BEHAVIOR_FAILED; + + break; + + + } + + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: TorsoAimAndFireWeapon +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::End(Actor &self) +{ + if ( !_self ) + return; + + _fireWeapon.End(*_self); + //gi.Printf( "TorsoAimAndFireWeapon::End()\n"); + _self->SetControllerAngles( ACTOR_TORSO_TAG, vec_zero ); + _self->ClearTorsoAnim(); + //_self->SetAnim( _aimAnim , NULL , torso ); +} + + + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: TorsoAimAndFireWeapon +// +// Description: Transitions the behaviors state +// +// Parameters: coverCombatStates_t state -- The state to transition to +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::transitionToState( TorsoAimAndFireStates_t state ) +{ + switch ( state ) + { + case TORSO_AIM_AND_FIRE_AIM: + setupStateAim(); + setInternalState( state , "TORSO_AIM_AND_FIRE_AIM" ); + break; + + case TORSO_AIM_AND_FIRE_PRE_FIRE: + setupStatePreFire(); + setInternalState( state , "TORSO_AIM_AND_FIRE_PRE_FIRE" ); + break; + + case TORSO_AIM_AND_FIRE_ATTACK: + setupStateAttack(); + setInternalState( state , "TORSO_AIM_AND_FIRE_ATTACK" ); + break; + + case TORSO_AIM_AND_FIRE_POST_FIRE: + setupStatePostFire(); + setInternalState( state , "TORSO_AIM_AND_FIRE_POST_FIRE" ); + break; + + case TORSO_AIM_AND_FIRE_SUCCESS: + setInternalState( state , "TORSO_AIM_AND_FIRE_SUCCESS" ); + break; + + case TORSO_AIM_AND_FIRE_FAILED: + setInternalState( state , "TORSO_AIM_AND_FIRE_FAILED" ); + break; + } + +} + + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: TorsoAimAndFireWeapon +// +// Description: Sets the internal state of the behavior +// +// Parameters: unsigned int state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::setInternalState( TorsoAimAndFireStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +//-------------------------------------------------------------- +// Name: init() +// Class: TorsoAimAndFireWeapon +// +// Description: Initializes the behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::init( Actor &self ) +{ + _self = &self; + + //Set Our Controller Tag and set up our angles + self.SetControllerTag( ACTOR_TORSO_TAG, gi.Tag_NumForName( self.edict->s.modelindex, "Bip01 Spine1" ) ); + _currentTorsoAngles = self.GetControllerAngles( ACTOR_TORSO_TAG ); + + _animDone = false; + _canAttack = true; + + _aimAnim = "idle"; + _preFireAnim = "idle"; + _fireAnim = "idle"; + _postFireAnim = "idle"; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(self.getArchetype()) ) + return; + + str objname = self.combatSubsystem->GetActiveWeaponArchetype(); + objname = "Hold" + objname; + + if ( gpm->hasProperty(objname, "Aim") ) + _aimAnim = gpm->getStringValue( objname , "Aim" ); + + if ( gpm->hasProperty( objname , "PreFire" ) ) + _preFireAnim = gpm->getStringValue( objname , "PreFire" ); + + if ( gpm->hasProperty( objname , "Fire" ) ) + _fireAnim = gpm->getStringValue( objname , "Fire" ); + + if ( gpm->hasProperty( objname , "PostFire" ) ) + _postFireAnim = gpm->getStringValue( objname , "PostFire" ); + + + if ( gpm->hasProperty( objname , "AimTimeMin" ) ) + _aimTimeMin = gpm->getFloatValue( objname , "AimTimeMin" ); + + if ( gpm->hasProperty( objname , "AimTimeMax" ) ) + _aimTimeMax = gpm->getFloatValue( objname , "AimTimeMax" ); + + if ( gpm->hasProperty( objname , "FireTimeMin" ) ) + _fireTimeMin = gpm->getFloatValue( objname , "FireTimeMin" ); + + if ( gpm->hasProperty( objname , "FireTimeMax" ) ) + _fireTimeMax = gpm->getFloatValue( objname , "FireTimeMax" ); + + + if ( gpm->hasProperty( objname , "ShotCount" ) ) + _shots = (int)gpm->getFloatValue( objname , "ShotCount" ); + + float spreadX, spreadY; + spreadX = self.combatSubsystem->GetDataForMyWeapon( "spreadx" ); + spreadY = self.combatSubsystem->GetDataForMyWeapon( "spready" ); + self.combatSubsystem->OverrideSpread( spreadX , spreadY ); + + + //Clear Out Our VolleyCount + _self->shotsFiredThisVolley = 0; + + updateEnemy(); + +} + +//-------------------------------------------------------------- +// Name: think() +// Class: TorsoAimAndFireWeapon +// +// Description: Does any processing required before evaluating states +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::think() +{ + int tagNum; + Vector tagPos; + Vector watchPosition; + Actor *actTarget; + actTarget = NULL; + str targetBone; + + + tagNum = gi.Tag_NumForName( _self->edict->s.modelindex, "Bip01 Spine1" ); + + if ( tagNum < 0 ) + return; + + _self->GetTag( "Bip01 Spine1", &tagPos ); + + if ( !_currentEnemy ) + { + LerpTorsoBySpeed( vec_zero ); + return; + } + + if ( _currentEnemy->isSubclassOf( Actor ) ) + { + actTarget = (Actor *)(Entity *)_currentEnemy; + + // Don't watch if the target is dead. + if ( !actTarget->isThinkOn() ) + { + _currentEnemy = NULL; + actTarget = NULL; + return; + } + } + + /* + if ( actTarget && ( actTarget->watch_offset != vec_zero ) ) + { + MatrixTransformVector( actTarget->watch_offset, _watchTarget->orientation, watchPosition ); + watchPosition += _watchTarget->origin; + } + else + { + tagNum = gi.Tag_NumForName( _watchTarget->edict->s.modelindex, "Bip01 Head" ); + + if ( tagNum < 0 ) + watchPosition = _watchTarget->centroid; + else + { + _watchTarget->GetTag( "Bip01 Head", &watchPosition ); + } + } + */ + targetBone = _currentEnemy->getTargetPos(); + + watchPosition = _currentEnemy->centroid; + + if ( targetBone.length() ) + { + if ( gi.Tag_NumForName( _currentEnemy->edict->s.modelindex , targetBone ) > 0 ) + { + _currentEnemy->GetTag( targetBone.c_str() , &watchPosition , NULL , NULL , NULL ); + } + } + + + + + AdjustTorsoAngles( tagPos , watchPosition ); + +} + + +//-------------------------------------------------------------- +// Name: updateEnemy() +// Class: TorsoAimAndFireWeapon +// +// Description: Sets our _currentEnemy +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::updateEnemy() +{ + // + // First, we try and get our current enemy. If we don't have one + // then we ask the enemyManager to evaluate and try again. + // If that fails, then we look at our _forceAttack status. + // If we are forcing and attack, then that means we want to + // fire pretty much no matter what... So if we don't have a + // currentEnemy, then I'm going to see if the Player is a valid + // target, and set that to be the current enemy + + Entity *currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + _self->enemyManager->FindHighestHateEnemy(); + currentEnemy = _self->enemyManager->GetCurrentEnemy(); + if ( !currentEnemy ) + { + Player* player; + player = GetPlayer( 0 ); + + /* + if ( player && GetSelf()->enemyManager->Hates(player) && _forceAttack ) + { + currentEnemy = player; + GetSelf()->enemyManager->SetCurrentEnemy( currentEnemy ); + } + else + { + SetFailureReason( "TORSO_AIM_AND_FIRE_FAILED::updateEnemy -- No Enemy" ); + transitionToState( TORSO_AIM_AND_FIRE_FAILED ); + return; + } + */ + } + + } + + _currentEnemy = currentEnemy; + //_self->SetControllerAngles( ACTOR_TORSO_TAG, vec_zero ); + //gi.Printf( "TorsoAimAndFireWeapon::_turnTowardsEntity()\n"); + //_self->turnTowardsEntity( _currentEnemy, 0.0f ); + +} + +//-------------------------------------------------------------- +// Name: LerpTorsoBySpeed() +// Class: TorsoAimAndFireWeapon +// +// Description: Lerps the torso +// +// Parameters: const Vector &angleDelta +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::LerpTorsoBySpeed( const Vector &angleDelta ) + { + Vector anglesDiff; + Vector change; + Vector finalAngles; + Vector currentTorsoAngles; + + anglesDiff = angleDelta; + + //Reset our Controller Tag + _self->SetControllerTag( ACTOR_TORSO_TAG, gi.Tag_NumForName( _self->edict->s.modelindex, "Bip01 Spine1" ) ); + + + // Make sure we don't change our head angles too much at once + change = anglesDiff - _currentTorsoAngles; + + if ( change[YAW] > _maxTorsoTurnSpeed ) + anglesDiff[YAW] = _currentTorsoAngles[YAW] + _maxTorsoTurnSpeed; + else if ( change[YAW] < -_maxTorsoTurnSpeed ) + anglesDiff[YAW] = _currentTorsoAngles[YAW] - _maxTorsoTurnSpeed; + + if ( change[PITCH] > _maxTorsoTurnSpeed ) + anglesDiff[PITCH] = _currentTorsoAngles[PITCH] + _maxTorsoTurnSpeed; + else if ( change[PITCH] < -_maxTorsoTurnSpeed ) + anglesDiff[PITCH] = _currentTorsoAngles[PITCH] - _maxTorsoTurnSpeed; + + + finalAngles = anglesDiff; + + _self->SetControllerAngles( ACTOR_TORSO_TAG, finalAngles ); + + + _currentTorsoAngles = anglesDiff; + + } + +//-------------------------------------------------------------- +// Name: AdjustTorsoAngles() +// Class: TorsoAimAndFireWeapon +// +// Description: Adjusts the Torso Angles +// +// Parameters: const Vector &tagPos +// const Vector &watchPos +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::AdjustTorsoAngles( const Vector &tagPos , const Vector &watchPosition ) + { + Vector dir; + Vector angles; + Vector anglesDiff; + float yawChange; + float pitchChange; + + + dir = watchPosition - tagPos; + angles = dir.toAngles(); + + anglesDiff = angles - _self->angles; + + + anglesDiff[YAW] = AngleNormalize180( anglesDiff[YAW] ); + anglesDiff[PITCH] = AngleNormalize180( anglesDiff[PITCH] ); + + yawChange = anglesDiff[YAW]; + pitchChange = anglesDiff[PITCH]; + + // Make sure we don't turn torso too far + if ( anglesDiff[YAW] < -_maxTorsoYaw ) + anglesDiff[YAW] = -_maxTorsoYaw; + else if ( anglesDiff[YAW] > _maxTorsoYaw ) + anglesDiff[YAW] = _maxTorsoYaw; + + if ( anglesDiff[PITCH] < -_maxTorsoPitch ) + anglesDiff[PITCH] = -_maxTorsoPitch; + else if ( anglesDiff[PITCH] > _maxTorsoPitch ) + anglesDiff[PITCH] = _maxTorsoPitch; + + anglesDiff[ROLL] = 0.0f; + + + LerpTorsoBySpeed( anglesDiff ); + + } + + +//-------------------------------------------------------------- +// Name: setupStateAim() +// Class: TorsoAimAndFireWeapon +// +// Description: Sets up the Aim State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::setupStateAim() +{ + _animDone = false; + + if ( _aimTimeMax + _aimTimeMin > 0 ) + _endAimTime = level.time + G_Random(_aimTimeMax - _aimTimeMin ) + _aimTimeMin; + else + _endAimTime = -1.0; + + _self->SetAnim( _aimAnim , EV_Actor_NotifyTorsoBehavior , torso ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateAim() +// Class: TorsoAimAndFireWeapon +// +// Description: Evaluates the Aim State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t TorsoAimAndFireWeapon::evaluateStateAim() +{ + bool tryToFire = false; + + if ( _endAimTime > 0 && level.time > _endAimTime ) + tryToFire = true; + + + if ( _endAimTime < 0 && _animDone ) + tryToFire = true; + + if ( tryToFire ) + { + if ( _self->combatSubsystem->CanAttackEnemy() || _forceAttack ) + return BEHAVIOR_SUCCESS; + + if ( _endOnAimFail ) + return BEHAVIOR_FAILED; + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAim() +// Class: TorsoAimAndFireWeapon +// +// Description: Failure Handler for the Failure State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::failureStateAim( const str& failureReason ) +{ + SetFailureReason( failureReason ); +} + + +//-------------------------------------------------------------- +// Name: setupStatePreFire() +// Class: TorsoAimAndFireWeapon +// +// Description: Sets up the PreFire State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::setupStatePreFire() +{ + /* + if ( _self->combatSubsystem->CanAttackEnemy() || _forceAttack ) + { + _animDone = false; + _self->SetAnim( _preFireAnim , EV_Actor_NotifyTorsoBehavior , torso ); + } + else + _canAttack = false; + */ + + _animDone = false; + _self->SetAnim( _preFireAnim , EV_Actor_NotifyTorsoBehavior , torso ); + + +} + +//-------------------------------------------------------------- +// Name: evaluateStatePreFire() +// Class: TorsoAimAndFireWeapon +// +// Description: Evaluates the Pre Fire state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t TorsoAimAndFireWeapon::evaluateStatePreFire() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + if ( !_canAttack ) + return BEHAVIOR_FAILED; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStatePreFire() +// Class: TorsoAimAndFireWeapon +// +// Description: Failure Handler for the Pre Fire State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::failureStatePreFire( const str& failureReason ) +{ + +} + + +//-------------------------------------------------------------- +// Name: setupStateAttack() +// Class: TorsoAimAndFireWeapon +// +// Description: Setup Attack State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::setupStateAttack() +{ + _animDone = false; + + if ( _fireTimeMax + _fireTimeMax > 0 ) + _endFireTime = level.time + G_Random(_fireTimeMax - _fireTimeMax) + _fireTimeMin; + else + _endFireTime = -1.0; + + + if ( _self->combatSubsystem->CanAttackEnemy() || _forceAttack ) + { + _fireWeapon.SetTarget(_currentEnemy); + _fireWeapon.SetAnim( _fireAnim ); + _fireWeapon.Begin( *_self ); + } + else + { + _fireWeapon.End( *_self ); + _fireFailed = true; + } + +} + +//-------------------------------------------------------------- +// Name: evaluateStateAttack() +// Class: TorsoAimAndFireWeapon +// +// Description: Evaluates Attack State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t TorsoAimAndFireWeapon::evaluateStateAttack() +{ + BehaviorReturnCode_t result; + + /*if ( _self->combatSubsystem->CanAttackEnemy() || _forceAttack ) + result = _fireWeapon.Evaluate( *_self ); + else + { + _fireWeapon.End( *_self ); + result = BEHAVIOR_FAILED; + } + + + //_fireWeapon.End( *_self ); + + if ( result == BEHAVIOR_FAILED ) + { + failureStateAttack( "StationaryFireCombat::evaluateStateAttack -- FAILED" ); + return result; + } + */ + /* + if ( _endFireTime > 0 && level.time > _endFireTime ) + return BEHAVIOR_SUCCESS; + + if ( _endFireTime < 0 && _animDone ) + return BEHAVIOR_SUCCESS; + */ + + if ( _fireFailed ) + return BEHAVIOR_FAILED; + + result = _fireWeapon.Evaluate( *_self ); + if ( _self->shotsFiredThisVolley >= _shots ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAttack() +// Class: TorsoAimAndFireWeapon +// +// Description: Failure Handler for Attack State +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::failureStateAttack( const str& failureReason ) +{ + SetFailureReason( failureReason ); + _fireWeapon.End( *_self ); +} + +//-------------------------------------------------------------- +// Name: setupStatePostFire() +// Class: TorsoAimAndFireWeapon +// +// Description: Sets up Post Fire State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::setupStatePostFire() +{ + _animDone = false; + //gi.WDPrintf( "-----------------------------------------\n"); + //gi.WDPrintf( "setupStatePostFire\n" ); + //gi.WDPrintf( "-----------------------------------------\n"); + + _self->SetAnim( _postFireAnim , EV_Actor_NotifyTorsoBehavior , torso ); +} + +//-------------------------------------------------------------- +// Name: evaluateStatePostFire() +// Class: TorsoAimAndFireWeapon +// +// Description: Evaluates State Post Fire +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t TorsoAimAndFireWeapon::evaluateStatePostFire() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_FAILED; +} + +//-------------------------------------------------------------- +// Name: failureStatePostFire() +// Class: TorsoAimAndFireWeapon +// +// Description: Failure State Post Fire +// +// Parameters: const str& failureReason +// +// Returns: None +//-------------------------------------------------------------- +void TorsoAimAndFireWeapon::failureStatePostFire( const str& failureReason ) +{ + +} diff --git a/dlls/game/torsoAimAndFireWeapon.hpp b/dlls/game/torsoAimAndFireWeapon.hpp new file mode 100644 index 0000000..095c6b1 --- /dev/null +++ b/dlls/game/torsoAimAndFireWeapon.hpp @@ -0,0 +1,199 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/torsoAimAndFireWeapon.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// torsoAimAndFireWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + + +//============================== +// Forward Declarations +//============================== +class TorsoAimAndFireWeapon; + +#ifndef __TORSO_AIM_AND_FIRE_WEAPON___ +#define __TORSO_AIM_AND_FIRE_WEAPON___ + +#include "behavior.h" +#include "behaviors_general.h" + +//------------------------- CLASS ------------------------------ +// +// Name: TorsoAimAndFireWeapon +// Base Class: Behavior +// +// Description: Aims the torso at the current enemy ( If the +// current enemy is within the passed in limits ) +// +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class TorsoAimAndFireWeapon : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + TORSO_AIM_AND_FIRE_AIM, + TORSO_AIM_AND_FIRE_PRE_FIRE, + TORSO_AIM_AND_FIRE_ATTACK, + TORSO_AIM_AND_FIRE_POST_FIRE, + TORSO_AIM_AND_FIRE_SUCCESS, + TORSO_AIM_AND_FIRE_FAILED + } TorsoAimAndFireStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + float _aimTimeMin; + float _aimTimeMax; + float _fireTimeMin; + float _fireTimeMax; + bool _forceAttack; + float _maxTorsoYaw; + float _maxTorsoPitch; + float _maxTorsoTurnSpeed; + int _shots; + bool _aimOnly; + bool _repeat; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( TorsoAimAndFireStates_t state ); + void setInternalState ( TorsoAimAndFireStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + void updateEnemy (); + void AdjustTorsoAngles ( const Vector &tagPos , const Vector &watchPosition ); + void LerpTorsoBySpeed ( const Vector &angleDelta ); + + void setupStateAim (); + BehaviorReturnCode_t evaluateStateAim (); + void failureStateAim ( const str& failureReason ); + + void setupStatePreFire (); + BehaviorReturnCode_t evaluateStatePreFire (); + void failureStatePreFire ( const str& failureReason ); + + void setupStateAttack (); + BehaviorReturnCode_t evaluateStateAttack (); + void failureStateAttack ( const str& failureReason ); + + void setupStatePostFire (); + BehaviorReturnCode_t evaluateStatePostFire (); + void failureStatePostFire ( const str& failureReason ); + + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( TorsoAimAndFireWeapon ); + + TorsoAimAndFireWeapon(); + ~TorsoAimAndFireWeapon(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + // Accessors + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + FireWeapon _fireWeapon; + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + TorsoAimAndFireStates_t _state; + Vector _currentTorsoAngles; + float _endFireTime; + float _endAimTime; + EntityPtr _currentEnemy; + bool _canAttack; + bool _animDone; + str _aimAnim; + str _preFireAnim; + str _fireAnim; + str _postFireAnim; + bool _fireFailed; + bool _endOnAimFail; + Actor *_self; + + + }; + +inline void TorsoAimAndFireWeapon::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // + // Archive Parameters + // + arc.ArchiveFloat( &_aimTimeMin ); + arc.ArchiveFloat( &_aimTimeMax ); + arc.ArchiveFloat( &_fireTimeMin ); + arc.ArchiveFloat( &_fireTimeMax ); + arc.ArchiveBool( &_forceAttack ); + arc.ArchiveFloat( &_maxTorsoYaw ); + arc.ArchiveFloat( &_maxTorsoPitch ); + arc.ArchiveFloat( &_maxTorsoTurnSpeed ); + arc.ArchiveBool( &_aimOnly ); + arc.ArchiveInteger( &_shots ); + arc.ArchiveBool( &_repeat ); + + // + // Archive Components + // + arc.ArchiveObject ( &_fireWeapon ); + + // + // Archive Member Variables + // + ArchiveEnum ( _state, TorsoAimAndFireStates_t ); + arc.ArchiveVector ( &_currentTorsoAngles ); + + arc.ArchiveFloat( &_endFireTime ); + arc.ArchiveFloat( &_endAimTime ); + arc.ArchiveSafePointer( &_currentEnemy ); + arc.ArchiveBool( &_canAttack ); + arc.ArchiveBool( &_animDone ); + arc.ArchiveString( &_aimAnim ); + arc.ArchiveString( &_preFireAnim ); + arc.ArchiveString( &_fireAnim ); + arc.ArchiveString( &_postFireAnim ); + arc.ArchiveBool( &_fireFailed ); + arc.ArchiveBool( &_endOnAimFail ); + + arc.ArchiveObjectPointer( ( Class ** )&_self ); +} + + +#endif /* __TORSO_AIM_AND_FIRE_WEAPON___ */ + diff --git a/dlls/game/trigger.cpp b/dlls/game/trigger.cpp new file mode 100644 index 0000000..a62f864 --- /dev/null +++ b/dlls/game/trigger.cpp @@ -0,0 +1,4460 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/trigger.cpp $ +// $Revision:: 74 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:11a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Environment based triggers. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "trigger.h" +#include "scriptmaster.h" +#include "worldspawn.h" +#include "misc.h" +#include "specialfx.h" +#include "sentient.h" +#include "item.h" +#include "player.h" +#include "camera.h" +#include "actor.h" +#include "g_utils.h" +#include "weaputils.h" +#include "mp_manager.hpp" +#include +#include "spawners.h" + + +Event EV_Trigger_ActivateTargets +( + "activatetrigger", + EV_SCRIPTONLY, + "e", + "triggering_entity", + "Activates all of the targets for this trigger." +); +Event EV_Trigger_SetWait +( + "wait", + EV_SCRIPTONLY, + "f", + "wait_time", + "Set the wait time (time bewteen triggerings) for this trigger" +); +Event EV_Trigger_SetDelay +( + "delay", + EV_SCRIPTONLY, + "f", + "delay_time", + "Set the delay time (time between triggering and firing) for this trigger" +); +Event EV_Trigger_SetCount +( + "cnt", + EV_SCRIPTONLY, + "i", + "count", + "Set the amount of times this trigger can be triggered" +); +Event EV_Trigger_SetMessage +( + "message", + EV_SCRIPTONLY, + "s", + "message", + "Set a message to be displayed when this trigger is activated" +); +Event EV_Trigger_SetNoise +( + "noise", + EV_SCRIPTONLY, + "s", + "sound", + "Set the sound to play when this trigger is activated" +); +Event EV_Trigger_SetSound +( + "sound", + EV_SCRIPTONLY, + "s", + "sound", + "Set the sound to play when this trigger is activated" +); +Event EV_Trigger_SetThread +( + "thread", + EV_SCRIPTONLY, + "s", + "thread", + "Set the thread to execute when this trigger is activated" +); +Event EV_Trigger_Effect +( + "triggereffect", + EV_CODEONLY, + "e", + "triggering_entity", + "Send event to owner of trigger." +); +Event EV_Trigger_Effect_Alt +( + "triggereffectalt", + EV_CODEONLY, + "e", + "triggering_entity", + "Send event to owner of trigger. This event is only triggered when using a trigger\n" + "as a multi-faceted edge trigger." +); +Event EV_Trigger_StartThread +( + "triggerthread", + EV_CODEONLY, + NULL, + NULL, + "Start the trigger thread." +); +Event EV_Trigger_SetKey +( + "key", + EV_SCRIPTONLY, + "s", + "key", + "Set the object needed by the sentient to activate this trigger" +); +Event EV_Trigger_SetTriggerable +( + "triggerable", + EV_SCRIPTONLY, + NULL, + NULL, + "Turn this trigger back on" +); +Event EV_Trigger_SetNotTriggerable +( + "nottriggerable", + EV_SCRIPTONLY, + NULL, + NULL, + "Turn this trigger off" +); +Event EV_Trigger_SetMultiFaceted +( + "multifaceted", + EV_SCRIPTONLY, + "i", + "facetDirection", + "Make this trigger multifaceted. If facet is 1, than trigger is North/South oriented.\n" + "If facet is 2 than trigger is East/West oriented. If facet is 3 than trigger is Up/Down oriented." +); +Event EV_Trigger_SetEdgeTriggered +( + "edgetriggered", + EV_SCRIPTONLY, + "b", + "newEdgeTriggered", + "If true, trigger will only trigger when object enters trigger, not when it is inside it." +); +Event EV_Trigger_SetTriggerCone +( + "cone", + EV_SCRIPTONLY, + "f", + "newTriggerCone", + "Sets the cone in which directed triggers will trigger." +); +Event EV_Trigger_CheckEntList +( + "checkEntList", + EV_CODEONLY, + "f", + "check_the_list", + "Checks the ent list to see if everyone is still in the bounding box" +); +Event EV_Trigger_TriggerExit +( + "triggerexit", + EV_CODEONLY, + "f", + "trigger_exit", + "called when an entity has left the trigger bounding box" +); +Event EV_Trigger_SetPassEvent +( + "setpassevent", + EV_SCRIPTONLY, + "sSSSSSSSS", + "eventName string1 string2 string3 string4 string5 string6 string7 string8", + "Sets up the event to be passed on to the group" +); +Event EV_Trigger_SetGroupNumber +( + "setgroupnumber", + EV_SCRIPTONLY, + "i", + "groupNumber", + "Sets up the group number the event will be passed to" +); +Event EV_Trigger_SetRequiredEntity +( + "requiredentity", + EV_SCRIPTONLY, + "s", + "targetname", + "sets this entity to be required for a trigger to work" +); + +Event EV_Trigger_CheckReady +( + "checkready", + EV_CODEONLY, + "", + "", + "event to check if trigger has all required ents in place and is ready" +); +Event EV_Trigger_SetEntryThread +( + "entryThread", + EV_SCRIPTONLY, + "s", + "thread_name", + "thread to call when entity enters the trigger" +); +Event EV_Trigger_SetExitThread +( + "exitThread", + EV_SCRIPTONLY, + "s", + "thread_name", + "thread to call when entity exits the trigger" +); +Event EV_Trigger_AltSetEntryThread +( + "onEntry", + EV_SCRIPTONLY, + "s", + "thread_name", + "thread to call when entity enters the trigger" +); +Event EV_Trigger_AltSetExitThread +( + "onExit", + EV_SCRIPTONLY, + "s", + "thread_name", + "thread to call when entity exits the trigger" +); +// 1ST PLAYABLE HACK +Event EV_Trigger_Hack_SetTriggerParms +( + "settriggerparms", + EV_DEFAULT, + "ff", + "force_field_number trigger_number", + "HACK HACK HACK HACK HACK HACK HACK" +); +Event EV_Trigger_Hack_GetForceFieldNumber +( + "getforcefieldnumber", + EV_DEFAULT, + "@f", + "number", + "HACK HACK HACK HACK HACK HACK HACK" +); +Event EV_Trigger_Hack_GetTriggerNumber +( + "gettriggernumber", + EV_DEFAULT, + "@f", + "number", + "HACK HACK HACK HACK HACK HACK HACK" +); +Event EV_Trigger_SetTriggerOnDamage +( + "triggerondamage", + EV_DEFAULT, + "b", + "boolean", + "Forces the thread to trigger when it takes damage" +); +Event EV_Trigger_SetTriggerOnDeath +( + "triggerondeath", + EV_DEFAULT, + "b", + "boolean", + "Forces the thread to trigger when it dies" +); +Event EV_Trigger_SetDestructible +( + "destructible", + EV_DEFAULT, + "b", + "boolean", + "Makes a trigger destructible. By default this is false." +); +Event EV_Trigger_SetInstantDeath +( + "instantdeath", + EV_DEFAULT, + "s", + "damage_type", + "Sets a damage type that will instantly kill the trigger, such" + " as impact, sword, gas, bullet, etc." +); +Event EV_Trigger_SetEnter +( + "triggerenter", + EV_DEFAULT, + NULL, + NULL, + "Marks the trigger as entered" +); +Event EV_Trigger_EntityExit +( + "triggerentexit", + EV_DEFAULT, + NULL, + NULL, + "Marks the trigger as exited" +); +Event EV_Trigger_GetLastActivatingEntity +( + "getLastActivatingEntity", + EV_DEFAULT, + "@e", + "returned_entity", + "Returns the activating entity for this object, or 0 if no activating entity." +); + +//#define MULTI_ACTIVATE 1 +//#define INVISIBLE 2 + +#define VISIBLE 1 + +#define TRIGGER_PLAYERS 4 +#define TRIGGER_MONSTERS 8 +#define TRIGGER_PROJECTILES 16 +#define TRIGGER_SCRIPTSLAVE 32 + +/*****************************************************************************/ +/*QUAKED trigger_multiple (1 0 0) ? x x NOT_PLAYERS MONSTERS PROJECTILES SCRIPTSLAVE + +Variable sized repeatable trigger. Must be targeted at one or more entities. + +If "delay" is set, the trigger waits some time after activating before firing. + +If "destructible" is set, the trigger will take damage and lose health. +If "triggerondamage" is set, trigger will fire when ever it takes damage. +If "triggerondeath" is set, trigger will fire when it dies. This forces destructible +to be on, since it would otherwise never die. +If "instantdeath" is set, trigger will die when it takes damage of this type. + +To make a trigger fire every time it takes damage set triggerondamage to true. To make a +trigger fire everytime it takes damage and to eventually die, set triggerondamage +to true, destructible to true, and give it a health. To make a trigger that +triggers on death only, set triggerondeath to be true. This will also turn +on destructible. To make a trigger that triggers everytime it takes damage +and when it dies, set both triggerondamage to be true and triggerondeath to +be true. + +"thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. + +if "angle" is set, the trigger will only fire when someone is facing the +direction of the angle. +"cone" the cone in which a directed trigger can be triggered (default 60 degrees) + +"wait" : Seconds between triggerings. (.2 default) +"cnt" how many times it can be triggered (infinite default) + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) +if SCRIPTSLAVE is set, the trigger will respond to script slaves + +set "message" to text string + +******************************************************************************/ + +CLASS_DECLARATION( Entity, Trigger, "trigger_multiple" ) +{ + { &EV_Trigger_SetWait, &Trigger::EventSetWait }, + { &EV_Trigger_SetDelay, &Trigger::EventSetDelay }, + { &EV_Trigger_SetCount, &Trigger::EventSetCount }, + { &EV_Trigger_SetMessage, &Trigger::EventSetMessage }, + { &EV_Trigger_SetNoise, &Trigger::EventSetNoise }, + { &EV_Trigger_SetSound, &Trigger::EventSetNoise }, + { &EV_Trigger_SetThread, &Trigger::EventSetThread }, + { &EV_Trigger_SetTriggerOnDeath, &Trigger::EventSetTriggerOnDeath }, + { &EV_Trigger_SetTriggerOnDamage, &Trigger::EventSetTriggerOnDamage }, + { &EV_Trigger_SetInstantDeath, &Trigger::EventSetInstantDeath }, + { &EV_Trigger_SetDestructible, &Trigger::EventSetDestructible }, + { &EV_Trigger_GetLastActivatingEntity, &Trigger::EventGetLastActivatingEntity }, + { &EV_SetHealth, &Trigger::EventSetHealth }, + { &EV_Touch, &Trigger::TriggerStuff }, + { &EV_Killed, &Trigger::HandleKilled }, + { &EV_Activate, &Trigger::TriggerStuff }, + { &EV_Damage, &Trigger::HandleDamage }, + { &EV_Trigger_ActivateTargets, &Trigger::ActivateTargets }, + { &EV_Trigger_SetKey, &Trigger::EventSetKey }, + { &EV_Trigger_StartThread, &Trigger::StartThread }, + { &EV_Model, &Trigger::SetModelEvent }, + { &EV_SetAngle, &Trigger::SetTriggerDir }, + { &EV_Trigger_SetTriggerable, &Trigger::SetTriggerable }, + { &EV_Trigger_SetNotTriggerable, &Trigger::SetNotTriggerable }, + { &EV_Trigger_SetMultiFaceted, &Trigger::SetMultiFaceted }, + { &EV_Trigger_SetEdgeTriggered, &Trigger::SetEdgeTriggered }, + { &EV_Trigger_SetTriggerCone, &Trigger::SetTriggerCone }, + { &EV_Trigger_CheckEntList, &Trigger::CheckEntList }, + { NULL, NULL } +}; + +Trigger::Trigger() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + triggerActivated = false; + activator = NULL; + trigger_time = (float)0; + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_TRIGGER ); + + setContents( 0 ); + //edict->contents = CONTENTS_TRIGGER; // replaces the -1 from gi.SetBrushModel + edict->svflags |= SVF_NOCLIENT; + + delay = 0; + wait = 0.2; + health = 0; + max_health = 0; + triggerCone = (float)cos( DEG2RAD( 60.0 ) ); + + useTriggerDir = false; + triggerDir = G_GetMovedir( 0.0f ); + + triggerable = true; + removable = true; + instantdeath = -1 ; // all + triggerondamage = false ; + triggerondeath = false ; + destructible = false ; + + trigger_time = 0.0f; + // normal trigger to begin with + multiFaceted = 0; + // normally, not edge triggered + edgeTriggered = false; + + count = -1; + + //noise = "environment/switch/switch2.wav"; + noise = ""; + + respondto = spawnflags ^ TRIGGER_PLAYERS; +} + +Trigger::~Trigger() +{ +} + +void Trigger::SetTriggerDir( float angle ) +{ + triggerDirYaw = angle; + triggerDir = G_GetMovedir( angle ); + useTriggerDir = true; +} + +Vector Trigger::GetTriggerDir( void ) +{ + return triggerDir; +} + +void Trigger::SetTriggerCone( Event *ev ) +{ + triggerCone = (float)cos( DEG2RAD( ev->GetFloat( 1 ) ) ); +} + +qboolean Trigger::UsingTriggerDir( void ) +{ + return useTriggerDir; +} + +void Trigger::SetTriggerDir( Event *ev ) +{ + SetTriggerDir( ev->GetFloat( 1 ) ); +} + +void Trigger::EventSetTriggerOnDamage( Event *ev ) +{ + if (!ev) return ; + triggerondamage = ev->GetBoolean(1); + + if (triggerondamage) + { + SetTakeDamage(true); + } +} + +void Trigger::EventSetTriggerOnDeath( Event *ev ) +{ + if (!ev) return ; + triggerondeath = ev->GetBoolean(1); + + if (triggerondeath) + { + destructible = true ; // force destructibility + SetTakeDamage(true); + } +} + +void Trigger::EventSetDestructible( Event *ev ) +{ + if (!ev) + return; + destructible = ev->GetBoolean(1); +} + + +void Trigger::EventSetInstantDeath( Event *ev ) +{ + if (!ev) return ; + instantdeath = MOD_NameToNum( ev->GetString(1) ); +} + +void Trigger::SetModelEvent( Event *ev ) +{ + Entity::SetModelEvent( ev ); + setContents( 0 ); + //edict->contents = CONTENTS_TRIGGER; // replaces the -1 from gi.SetBrushModel + edict->svflags |= SVF_NOCLIENT; + link(); +} + +void Trigger::StartThread( Event *ev ) +{ + if ( thread.length() ) + { + if ( !ExecuteThread( thread , true, this ) ) + { + warning( "StartThread", "Null game script" ); + } + } +} + +qboolean Trigger::respondTo( Entity *other ) +{ + return ( ( ( respondto & TRIGGER_PLAYERS ) && other->isClient() ) || + ( ( respondto & TRIGGER_PLAYERS ) && other->isSubclassOf( Vehicle ) ) || + ( ( respondto & TRIGGER_MONSTERS ) && other->isSubclassOf( Actor ) ) || + ( ( respondto & TRIGGER_PROJECTILES ) && other->isSubclassOf( Projectile ) ) || + ( ( respondto & TRIGGER_SCRIPTSLAVE ) && other->isSubclassOf( ScriptSlave ))); +} + +//=============================================================== +// Name: EventGetLastActivatingEntity +// Class: Trigger +// +// Description: Retrieves in the event structure the last entity +// that activated this trigger. +// +// Parameters: Event* -- the event. The return field of this +// event will contain the entity that +// triggered this entity. +// +// Returns: None +// +//=============================================================== +void Trigger::EventGetLastActivatingEntity( Event *ev ) +{ + ev->ReturnEntity( activator ); +} + + +Entity *Trigger::getActivator( Entity *other ) +{ + return other; +} + +//=============================================================== +// Name: setActivatingEntity +// Class: Trigger +// +// Description: Sets the activating entity for a trigger. Called +// whenever a trigger is triggered. +// +// Parameters: Entity* -- the activating entity. +// +// Returns: None +// +//=============================================================== +void Trigger::setActivatingEntity( Entity *activatingEntity ) +{ + activator = activatingEntity ; +} + +bool Trigger::CanTrigger( void ) +{ + // if trigger is shut off return immediately + if ( !triggerable ) return false ; + + // Don't bother with testing anything if we can't trigger yet + if ( ( level.time < trigger_time ) || ( trigger_time == -1.0f ) ) + { + // if we are edgeTriggered, we reset our time until we leave the trigger + if ( edgeTriggered && ( trigger_time != -1.0f ) ) + { + trigger_time = level.time + wait; + } + return false ; + } + return true ; +} + + +void Trigger::HandleDamage( Event *ev ) +{ + if (!CanTrigger()) return ; + if (!ev) return ; + + float damage = ev->GetFloat(1) ; + Entity *inflictor = ev->GetEntity( 2 ); + Entity *attacker = ev->GetEntity( 3 ); + + if ( damageModSystem ) + { + ::Damage modDamage(ev); + damageModSystem->resolveDamage(modDamage); + damage = modDamage.damage; + } + + if (destructible) health -= damage ; + if (instantdeath == ev->GetInteger(9)) health = 0 ; + + if ( (health <= 0.0f)) + { + Event *event = new Event( EV_Killed ); + event->AddEntity( attacker ); + event->AddFloat( damage ); + event->AddEntity( inflictor ); + ProcessEvent( event ); + return; + } + + if (triggerondamage) + { + TriggerStuff(ev); + } +} + +void Trigger::HandleKilled( Event *ev ) +{ + if (!CanTrigger()) return ; + if (triggerondeath) TriggerStuff(ev); +} + +void Trigger::TriggerStuff( Event *ev ) +{ + Entity *other; + Entity *activator; + Event *event; + int whatToTrigger; + + if (!CanTrigger()) return ; + + + if (triggerondamage || triggerondeath) + { + // if we're triggered by damage or death, and this is inappropriate event, jump out. + bool isActiveEvent = ( (int)*ev == (int)EV_Activate)? true : false ; + bool isDamageEvent = ( (int)*ev == (int)EV_Damage ) ? true : false ; + bool isKilledEvent = ( (int)*ev == (int)EV_Killed ) ? true : false ; + if ( !isActiveEvent && !(triggerondamage && isDamageEvent) && !(triggerondeath && isKilledEvent)) return ; + } + + if ((int)*ev == (int)EV_Damage) + { + other = ev->GetEntity(3); + } + else + { + other = ev->GetEntity( 1 ); + } + assert( other != this ); + + // Always respond to activate messages from the world since they're probably from + // the "trigger" command + if ( !respondTo( other ) && !( ( other == world || other->isSubclassOf( SpawnChain ) ) && ( ( int )*ev == ( int )EV_Activate ) ) && + ( !other || !other->isSubclassOf( Camera ) ) ) + { + return; + } + + AddOtherToEntList( other ); + Event *checkEvent; + checkEvent = new Event(EV_Trigger_CheckEntList); + checkEvent->AddInteger( other->entnum ); + PostEvent( checkEvent , CHECK_TIME ); + + + // + // if we setup an angle for this trigger, only trigger if other is within ~60 degrees of the triggers origin + // only test for this case if we were touched, activating or killed should never go through this code + // + if ( other->isSubclassOf( Player ) && useTriggerDir && ( ( int )*ev == ( int )EV_Touch ) ) + { + Vector otherFwd( 0, 0, 0 ); + + // do special case for looking up and down + // triggerDir.z will be set=0.0 directly, so don't bother with epsilon + if( triggerDir.z == 0.0f ) + { + // only consider yaw + otherFwd[YAW] = other->client->ps.viewangles[YAW]; + } + else + { + // only consider pitch + otherFwd[PITCH] = other->client->ps.viewangles[PITCH]; + } + otherFwd.AngleVectors( &otherFwd ); + otherFwd.normalize(); + + float dot = otherFwd * triggerDir; + if( dot < triggerCone ) + { + // don't retrigger for at least a second + trigger_time = level.time + 0.05f; + return; + } + } + + activator = getActivator( other ); + setActivatingEntity( activator ); + + if ( key.length() ) + { + if ( !activator->isSubclassOf( Sentient ) ) + { + return; + } + if ( !( ( (Sentient *)activator )->HasItem( key.c_str() ) ) ) + { + qboolean setModel; + Item *item; + ClassDef *cls; + str dialog; + + cls = FindClass( key.c_str(), &setModel ); + if ( !cls || !checkInheritance( "Item", cls->classname ) ) + { + gi.WDPrintf( "No item named '%s'\n", key.c_str() ); + return; + } + item = ( Item * )cls->newInstance(); + if ( setModel ) + { + item->setModel( key.c_str() ); + } + item->CancelEventsOfType( EV_Item_DropToFloor ); + item->CancelEventsOfType( EV_Remove ); + item->ProcessPendingEvents(); + dialog = item->GetDialogNeeded(); + if ( dialog.length() > 1 ) + { + activator->Sound( dialog ); + } + else + { + gi.centerprintf ( activator->edict, CENTERPRINT_IMPORTANCE_NORMAL, "$$ItemNeeded$$%s", item->getName().c_str() ); + } + delete item; + return; + } + } + + if ( multiFaceted ) + { + Vector delta; + + delta = other->origin - origin; + switch( multiFaceted ) + { + case 1: + if ( delta[ 1 ] > 0.0f ) + { + whatToTrigger = 0; + } + else + { + whatToTrigger = 1; + } + break; + case 2: + if ( delta[ 0 ] > 0.0f ) + { + whatToTrigger = 0; + } + else + { + whatToTrigger = 1; + } + break; + case 3: + default: + if ( delta[ 2 ] > 0.0f ) + { + whatToTrigger = 0; + } + else + { + whatToTrigger = 1; + } + break; + } + } + else + { + whatToTrigger = 0; + } + + trigger_time = level.time + wait; + + if ( !whatToTrigger ) + { + event = new Event( EV_Trigger_Effect ); + event->AddEntity( activator ); + PostEvent( event, delay ); + } + else + { + event = new Event( EV_Trigger_Effect_Alt ); + event->AddEntity( activator ); + PostEvent( event, delay ); + } + + event = new Event( EV_Trigger_ActivateTargets ); + event->AddEntity( activator ); + PostEvent( event, delay ); + + if ( thread.length() ) + { + // don't trigger the thread if we were triggered by the world touching us + if ( ( activator != world ) || ( ev->GetSource() != EV_FROM_CODE ) ) + { + event = new Event( EV_Trigger_StartThread ); + if ( activator ) + { + event->AddEntity( activator ); + } + PostEvent( event, delay ); + } + } + + if ( count > -1 ) + { + count--; + if ( count < 1 ) + { + // + // Don't allow it to trigger anymore + // + trigger_time = -1; + + // + // Make sure we wait until we're done triggering things before removing + // + if ( removable ) + { + PostEvent( EV_Remove, delay + FRAMETIME ); + } + } + } +} + +// +//============================== +// ActivateTargets +// +// "other" should be set to the entity that initiated the firing. +// +// Centerprints any message to the activator. +// +// Removes all entities with a targetname that match killtarget, +// so some events can remove other triggers. +// +// Search in targetname of all entities that match target +// and send EVENT_ACTIVATE to there event handler +//============================== +// +void Trigger::ActivateTargets( Event *ev ) +{ + Entity *other; + Entity *ent; + Event *event; + const char *name; + + other = ev->GetEntity( 1 ); + + if ( !other ) + other = world; + + if ( triggerActivated ) + { + // + // Entity triggered itself. Prevent an infinite loop + // + ev->Error( "Entity targeting itself--Targetname '%s'", TargetName() ); + return; + } + + triggerActivated = true; + activator = other; + + // + // print the message + // + if ( message.length() && other && ( other->isClient() || other->isSubclassOf( Camera ) ) ) + { + // HACK HACK HACK + // if it is a camera, pass in default player + if ( !other->isClient() ) + { + gi.centerprintf( &g_entities[ 0 ], CENTERPRINT_IMPORTANCE_NORMAL, message.c_str() ); + } + else + { + gi.centerprintf( other->edict, CENTERPRINT_IMPORTANCE_NORMAL, message.c_str() ); + } + if ( Noise().length() ) + { + other->Sound( noise.c_str(), CHAN_VOICE ); + } + } + + // + // kill the killtargets + // + name = KillTarget(); + if ( name && strcmp( name, "" ) ) + { + ent = NULL; + do + { + ent = G_FindTarget( ent, name ); + if ( !ent ) + { + break; + } + ent->PostEvent( EV_Remove, 0.0f ); + } + while ( 1 ); + } + + // + // fire targets + // + name = Target(); + if ( name && strcmp( name, "" ) ) + { + ent = NULL; + do + { + ent = G_FindTarget( ent, name ); + if ( !ent ) + { + break; + } + + event = new Event( EV_Activate ); + event->AddEntity( other ); + ent->ProcessEvent( event ); + } + while ( 1 ); + } + + triggerActivated = false; +} + +void Trigger::EventSetWait( Event *ev ) +{ + wait = ev->GetFloat( 1 ); +} + +void Trigger::EventSetDelay( Event *ev ) +{ + delay = ev->GetFloat( 1 ); +} + +void Trigger::EventSetKey( Event *ev ) +{ + key = ev->GetString( 1 ); +} + +void Trigger::EventSetThread( Event *ev ) +{ + thread = ev->GetString( 1 ); +} + +void Trigger::EventSetHealth( Event *ev ) +{ + health = ev->GetFloat( 1 ); + max_health = health; + if ( health ) + { + takedamage = DAMAGE_YES; + setSolidType( SOLID_BBOX ); + } + else + { + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_TRIGGER ); + } +} + +void Trigger::SetTakeDamage( bool cantakedamage ) +{ + if (cantakedamage) + { + takedamage = DAMAGE_YES ; + setSolidType( SOLID_BBOX ); + } + else + { + takedamage = DAMAGE_NO ; + setMoveType( MOVETYPE_NONE ); // no one knows why this is here + setSolidType( SOLID_TRIGGER ); + } +} + +void Trigger::EventSetCount( Event *ev ) +{ + count = ev->GetInteger( 1 ); +} + +void Trigger::EventSetMessage( Event *ev ) +{ + SetMessage( ev->GetString( 1 ) ); +} + +void Trigger::SetMessage( const char *text ) +{ + if ( text ) + { + message = str( text ); + } + else + { + message = ""; + } +} + +str &Trigger::Message( void ) +{ + return message; +} + +void Trigger::EventSetNoise( Event *ev ) +{ + SetNoise( ev->GetString( 1 ) ); +} + +void Trigger::SetNoise( const char *text ) +{ + if ( text ) + { + noise = str( text ); + // + // cache in the sound + // + CacheResource( noise.c_str(), this ); + } +} + +str &Trigger::Noise( void ) +{ + return noise; +} + +void Trigger::SetMultiFaceted( int newFacet ) +{ + multiFaceted = newFacet; +} + +void Trigger::SetEdgeTriggered( qboolean newEdge ) +{ + edgeTriggered = newEdge; +} + +int Trigger::GetMultiFaceted( void ) +{ + return multiFaceted; +} + +qboolean Trigger::GetEdgeTriggered( void ) +{ + return edgeTriggered; +} + +void Trigger::SetMultiFaceted( Event *ev ) +{ + SetMultiFaceted( ev->GetInteger( 1 ) ); +} + +void Trigger::SetEdgeTriggered( Event *ev ) +{ + SetEdgeTriggered( ev->GetBoolean( 1 ) ); +} + +void Trigger::SetTriggerable( Event *ev ) +{ + triggerable = true; +} + +void Trigger::SetNotTriggerable( Event *ev ) +{ + triggerable = false; +} + +void Trigger::CheckEntList(Event *ev) +{ + CancelEventsOfType(EV_Trigger_CheckEntList); + int entNum; + Entity *other = NULL; + + entNum = ev->GetInteger( 1 ); + other = G_GetEntity(entNum ); + + if ( !other ) + return; + + if (edgeTriggered && entList.NumObjects() > 0 ) + { + Event *checkEV; + checkEV = new Event(EV_Trigger_CheckEntList); + checkEV->AddInteger( entNum ); + + PostEvent( checkEV , CHECK_TIME ); + } + + Entity *ent; + Event *exitEvent; + + for ( int i = entList.NumObjects() ; i >= 1 ; i-- ) + { + entNum = entList.ObjectAt( i ); + ent = NULL; + ent = G_GetEntity( entNum ); + + if ( ent ) + { + if ( !IsEntityInBoundingBox( ent ) ) + { + entList.RemoveObjectAt( i ); + exitEvent = new Event ( EV_Trigger_TriggerExit ); + exitEvent->AddEntity( ent ); + PostEvent(exitEvent, .25); + } + } + } +} + +void Trigger::AddOtherToEntList(Entity *other) +{ + int entNum; + + if ( !other ) + return; + for ( int i = 1 ; i <= entList.NumObjects() ; i++ ) + { + entNum = entList.ObjectAt( i ); + if ( entNum == other->entnum ) + return; + } + + entList.AddObject( other->entnum ); +} + +qboolean Trigger::IsEntityInBoundingBox ( Entity *ent ) +{ + if ( ( ent->absmin[0] > absmax[0] ) || + ( ent->absmin[1] > absmax[1] ) || + ( ent->absmin[2] > absmax[2] ) || + ( ent->absmax[0] < absmin[0] ) || + ( ent->absmax[1] < absmin[1] ) || + ( ent->absmax[2] < absmin[2] ) ) + { + return false; + } + + return true; +} + + +CLASS_DECLARATION( Trigger, TouchField, NULL ) +{ + { &EV_Trigger_Effect, &TouchField::SendEvent }, + + { NULL, NULL } +}; + +TouchField::TouchField() +{ + ontouch = NULL; +} + +void TouchField::Setup( Entity *ownerentity, const Event &touchevent, const Vector &min, const Vector &max, int respondto ) +{ + assert( ownerentity ); + if ( !ownerentity ) + { + error( "Setup", "Null owner" ); + } + + owner = ownerentity; + if ( ontouch ) + { + delete ontouch; + } + ontouch = new Event( touchevent ); + setSize( min, max ); + + setContents( 0 ); + setSolidType( SOLID_TRIGGER ); + link(); + + this->respondto = respondto; +} + +void TouchField::SendEvent( Event *ev ) +{ + Event *event; + + // Check if our owner is still around + if ( owner ) + { + event = new Event( ontouch ); + event->AddEntity( ev->GetEntity( 1 ) ); + owner->PostEvent( event, delay ); + } + else + { + // Our owner is gone! It didn't delete us! + // Guess we're no longer needed, so remove ourself. + PostEvent( EV_Remove, 0.0f ); + } +} + +/*****************************************************************************/ +/*QUAKED trigger_once (1 0 0) ? NOTOUCH x NOT_PLAYERS MONSTERS PROJECTILES SCRIPTSLAVE + +Variable sized trigger. Triggers once, then removes itself. +You must set the key "target" to the name of another object in the +level that has a matching + +If "health" is set, the trigger must be killed to activate it. +If "delay" is set, the trigger waits some time after activating before firing. + +"targetname". If "health" is set, the trigger must be killed to activate. + +"thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. + +if "killtarget" is set, any objects that have a matching "target" will be +removed when the trigger is fired. + +if "angle" is set, the trigger will only fire when someone is facing the +direction of the angle. +"cone" the cone in which a directed trigger can be triggered (default 60 degrees) + +"key" The item needed to activate this. (default nothing) + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOTOUCH is set, trigger will not respond to touch +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) +If SCRIPTSLAVE is set, the trigger will respond to script slaves + +set "message" to text string + +******************************************************************************/ + +CLASS_DECLARATION( Trigger, TriggerOnce, "trigger_once" ) +{ + { NULL, NULL } +}; + +TriggerOnce::TriggerOnce() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + // + // no matter what, we only trigger once + // + count = 1; + respondto = spawnflags ^ TRIGGER_PLAYERS; + + // + // if it's not supposed to be touchable, clear the trigger + // + if ( spawnflags & 1 ) + { + setSolidType( SOLID_NOT ); + } +} + +/*****************************************************************************/ +/*QUAKED trigger_relay (1 0 0) (-8 -8 -8) (8 8 8) x x NOT_PLAYERS MONSTERS PROJECTILES + +This fixed size trigger cannot be touched, it can only be fired by other events. +It can contain killtargets, targets, delays, and messages. + +If NOT_PLAYERS is set, the trigger does not respond to events triggered by players +If MONSTERS is set, the trigger will respond to events triggered by monsters +If PROJECTILES is set, the trigger will respond to events triggered by projectiles (rockets, grenades, etc.) + +******************************************************************************/ + +CLASS_DECLARATION( Trigger, TriggerRelay, "trigger_relay" ) +{ + { &EV_Touch, NULL }, + { NULL, NULL } +}; + +TriggerRelay::TriggerRelay() +{ + setSolidType( SOLID_NOT ); +} + +/*****************************************************************************/ +/*QUAKED trigger_secret (1 0 0) ? NOTOUCH x NOT_PLAYERS MONSTERS PROJECTILES +Secret counter trigger. Automatically sets and increments script variables \ +level.total_secrets and level.found_secrets. + +set "message" to text string + +"key" The item needed to activate this. (default nothing) + +if "angle" is set, the trigger will only fire when someone is facing the +direction of the angle. +"cone" the cone in which a directed trigger can be triggered (default 60 degrees) + +"thread" name of thread to trigger. This can be in a different script file as well \ +by using the '::' notation. (defaults to "global/universal_script.scr::secret") + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOTOUCH is set, trigger will not respond to touch +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +******************************************************************************/ + +CLASS_DECLARATION( TriggerOnce, TriggerSecret, "trigger_secret" ) +{ + { &EV_Trigger_Effect, &TriggerSecret::FoundSecret }, + { NULL, NULL } +}; + +TriggerSecret::TriggerSecret() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + level.total_secrets++; + levelVars.SetVariable( "total_secrets", level.total_secrets ); + respondto = spawnflags ^ TRIGGER_PLAYERS; + + // set the thread to trigger when secrets are found + thread = "global/universal_script.scr::secret"; +} + +void TriggerSecret::FoundSecret( Event *ev ) +{ + // + // anything that causes the trigger to fire increments the number + // of secrets found. This way, if the level designer triggers the + // secret from the script, the player still gets credit for finding + // it. This is to prevent a secret from becoming undiscoverable. + // + level.found_secrets++; + levelVars.SetVariable( "found_secrets", level.found_secrets ); + + gi.centerprintf ( &g_entities[0], CENTERPRINT_IMPORTANCE_NORMAL, "$$FoundSecretArea$$" ); +} + +/*****************************************************************************/ +/*QUAKED trigger_setvariable (1 0 0) ? NOTOUCH LEVEL NOT_PLAYERS MONSTERS PROJECTILES + +Sets a variable specified by "variable" and "value". +Variable is assumed to be of the "global" variety unless LEVEL is set. +Variable sized trigger. Triggers once by default. +You must set the key "target" to the name of another object in the +level that has a matching + +"variable" - variable to set +"value" - value to set in variable, value can also be one of the following reserved\ +tokens. + - "increment" - add one to the variable + - "decrement" - subtract one from the variable + - "toggle" - if 1, then zero. If zero then 1. + +If "health" is set, the trigger must be killed to activate it. +If "delay" is set, the trigger waits some time after activating before firing. + +"targetname". If "health" is set, the trigger must be killed to activate. + +"thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. + +if "killtarget" is set, any objects that have a matching "target" will be +removed when the trigger is fired. + +if "angle" is set, the trigger will only fire when someone is facing the +direction of the angle. +"cone" the cone in which a directed trigger can be triggered (default 60 degrees) + +"key" The item needed to activate this. (default nothing) + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOTOUCH is set, trigger will not respond to touch +if LEVEL is set, variable will be a level variable otherwise it will be a game variable +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +set "message" to text string + +******************************************************************************/ + +#define LEVEL_VARIABLE ( 1 << 1 ) + +Event EV_TriggerSetVariable_SetVariable +( + "variable", + EV_SCRIPTONLY, + "s", + "variableName", + "Set the name of the variable to set" +); +Event EV_TriggerSetVariable_SetVariableValue +( + "value", + EV_SCRIPTONLY, + "s", + "variableValue", + "Set the value of the variable to set.\n" + "values can also use one of the following reserved words:\n" + " increment - increment the variable\n" + " decrement - decrement the variable\n" + " toggle - toggle the value of the variable" +); +CLASS_DECLARATION( Trigger, TriggerSetVariable, "trigger_setvariable" ) +{ + { &EV_Trigger_Effect, &TriggerSetVariable::SetVariable }, + { &EV_TriggerSetVariable_SetVariable, &TriggerSetVariable::SetVariableName }, + { &EV_TriggerSetVariable_SetVariableValue, &TriggerSetVariable::SetVariableValue }, + + { NULL, NULL } +}; + +TriggerSetVariable::TriggerSetVariable() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + // + // no matter what, we only trigger once + // + count = 1; + respondto = spawnflags ^ TRIGGER_PLAYERS; + + // + // if it's not supposed to be touchable, clear the trigger + // + if ( spawnflags & 1 ) + { + setSolidType( SOLID_NOT ); + } + variableName = "undefined"; + variableValue = "notset"; + variableType = VAR_NORMAL; +} + +void TriggerSetVariable::SetVariableName( Event *ev ) +{ + variableName = ev->GetString( 1 ); +} + +void TriggerSetVariable::SetVariableValue( Event *ev ) +{ + variableValue = ev->GetString( 1 ); + if ( variableValue == "increment" ) + { + variableType = VAR_INCREMENT; + } + else if ( variableValue == "decrement" ) + { + variableType = VAR_DECREMENT; + } + else if ( variableValue == "toggle" ) + { + variableType = VAR_TOGGLE; + } + else + { + variableType = VAR_NORMAL; + } +} + +void TriggerSetVariable::SetVariable( Event *ev ) +{ + ScriptVariable * var; + int value; + + if ( spawnflags & LEVEL_VARIABLE ) + { + var = levelVars.GetVariable( variableName ); + if ( !var ) + { + var = levelVars.SetVariable( variableName, 0 ); + } + } + else + { + var = gameVars.GetVariable( variableName ); + if ( !var ) + { + var = gameVars.SetVariable( variableName, 0 ); + } + } + + assert( var ); + value = var->intValue(); + + switch( variableType ) + { + case VAR_INCREMENT: + value++; + break; + case VAR_DECREMENT: + value--; + break; + case VAR_TOGGLE: + value = !value; + break; + } + + if ( variableType == VAR_NORMAL ) + { + var->setStringValue( variableValue ); + } + else + { + var->setIntValue( value ); + } +} + +/*****************************************************************************/ +/*QUAKED trigger_push (1 0 0) ? x x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES + +Pushes entities as if they were caught in a heavy wind. + +"speed" indicates the rate that entities are pushed (default 1000). + +"angle" indicates the direction the wind is blowing (-1 is up, -2 is down) + +"key" The item needed to activate this. (default nothing) + +"target" if target is set, then a velocity will be calculated based on speed + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not push players +If NOT_MONSTERS is set, the trigger will not push monsters +If NOT_PROJECTILES is set, the trigger will not push projectiles (rockets, grenades, etc.) + +******************************************************************************/ + +Event EV_TriggerPush_SetPushSpeed +( + "speed", + EV_DEFAULT, + "f", + "speed", + "Set the push speed of the TriggerPush" +); + +CLASS_DECLARATION( Trigger, TriggerPush, "trigger_push" ) +{ + { &EV_Trigger_Effect, &TriggerPush::Push }, + { &EV_SetAngle, &TriggerPush::SetPushDir }, + { &EV_TriggerPush_SetPushSpeed, &TriggerPush::SetPushSpeed }, + + { NULL, NULL } +}; + +void TriggerPush::Push( Event *ev ) +{ + Entity *other; + + other = ev->GetEntity( 1 ); + if ( other ) + { + const char * targ; + Entity *ent; + + targ = Target (); + if ( targ[ 0 ] ) + { + ent = G_FindTarget( NULL, Target() ); + if ( ent ) + { + other->velocity = G_CalculateImpulse + ( + other->origin, + ent->origin, + speed, + other->gravity + ); + } + } + else + { + float dot; + + // find out how much velocity we have in this direction + dot = triggerDir * other->velocity; + // subtract it out and add in our velocity + other->velocity += ( speed - dot ) * triggerDir; + } + + other->VelocityModified(); + movetype = MOVETYPE_TOSS; // Set movetype so actors can get pushed. + } +} + +void TriggerPush::SetPushDir( Event *ev ) +{ + float angle; + + angle = ev->GetFloat( 1 ); + // this is used, since we won't need elsewhere + triggerDir = G_GetMovedir( angle ); +} + + +void TriggerPush::SetPushSpeed( Event *ev ) +{ + speed = ev->GetFloat( 1 ); +} + +TriggerPush::TriggerPush() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + speed = 1000; + respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES ); +} + +/*****************************************************************************/ +/*QUAKED trigger_pushany (1 0 0) ? x x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES + +Pushes entities as if they were caught in a heavy wind. + +"speed" indicates the rate that entities are pushed (default 1000). +"angles" indicates the direction of the push +"key" The item needed to activate this. (default nothing) +"target" if target is set, then a velocity will be calculated based on speed + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not push players +If NOT_MONSTERS is set, the trigger will not push monsters +If NOT_PROJECTILES is set, the trigger will not push projectiles (rockets, grenades, etc.) + +******************************************************************************/ + +Event EV_TriggerPushAny_SetSpeed +( + "speed", + EV_DEFAULT, + "f", + "speed", + "Set the speed." +); + +CLASS_DECLARATION( Trigger, TriggerPushAny, "trigger_pushany" ) +{ + { &EV_TriggerPushAny_SetSpeed, &TriggerPushAny::SetSpeed }, + { &EV_Trigger_Effect, &TriggerPushAny::Push }, + + { NULL, NULL } +}; + +TriggerPushAny::TriggerPushAny() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + speed = 1000; + respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES ); +} + +void TriggerPushAny::Push( Event *ev ) +{ + Entity *other; + + other = ev->GetEntity( 1 ); + if ( other ) + { + Entity *ent; + + if ( target.length() ) + { + ent = G_FindTarget( NULL, target.c_str() ); + if ( ent ) + { + other->velocity = G_CalculateImpulse + ( + other->origin, + ent->origin, + speed, + other->gravity + ); + } + } + else + { + other->velocity = Vector( orientation[ 0 ] ) * speed; + } + other->VelocityModified(); + } +} + +void TriggerPushAny::SetSpeed( Event *ev ) +{ + speed = ev->GetFloat( 1 ); +} + +//================================================================================================ +// +// TriggerSound stuff +// +//================================================================================================ + +Event EV_TriggerPlaySound_SetVolume +( + "volume", + EV_DEFAULT, + "f", + "volume", + "Sets the volume." +); +Event EV_TriggerPlaySound_SetMinDist +( + "min_dist", + EV_DEFAULT, + "f", + "min_dist", + "Sets the minimum distance." +); +Event EV_TriggerPlaySound_SetChannel +( + "channel", + EV_DEFAULT, + "i", + "channel", + "Sets the sound channel to play on." +); + +CLASS_DECLARATION( Trigger, TriggerPlaySound, "play_sound_triggered" ) +{ + { &EV_Trigger_Effect, &TriggerPlaySound::ToggleSound }, + { &EV_TriggerPlaySound_SetVolume, &TriggerPlaySound::SetVolume }, + { &EV_TriggerPlaySound_SetMinDist, &TriggerPlaySound::SetMinDist }, + { &EV_TriggerPlaySound_SetChannel, &TriggerPlaySound::SetChannel }, + { &EV_Touch, NULL }, + + { NULL, NULL } +}; + +void TriggerPlaySound::ToggleSound( Event *ev ) +{ + if ( !state ) + { + // noise should already be initialized + if ( Noise().length() ) + { + if ( ambient || ( spawnflags & TOGGLESOUND ) ) + { + state = 1; + } + + if ( ambient ) + { + LoopSound( Noise().c_str(), volume, min_dist ); + } + else + { + Sound( Noise(), channel, volume, min_dist ); + } + } + } + else + { + state = 0; + if ( ambient ) + { + StopLoopSound(); + } + else + { + StopSound( channel ); + } + } +} + +void TriggerPlaySound::StartSound( void ) +{ + // turn the current one off + state = 1; + ToggleSound( NULL ); + + // start it up again + state = 0; + ToggleSound( NULL ); +} + +void TriggerPlaySound::SetVolume( float vol ) +{ + volume = vol; +} + +void TriggerPlaySound::SetVolume( Event *ev ) +{ + volume = ev->GetFloat( 1 ); +} + +void TriggerPlaySound::SetMinDist( float dist ) +{ + min_dist = dist; +} + +void TriggerPlaySound::SetMinDist( Event *ev ) +{ + min_dist = ev->GetFloat( 1 ); +} + +void TriggerPlaySound::SetChannel( Event *ev ) +{ + channel = ev->GetInteger( 1 ); +} + +TriggerPlaySound::TriggerPlaySound() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + // + // this is here so that it gets sent over at least once + // + PostEvent( EV_Show, EV_POSTSPAWN ); + + ambient = false; + volume = DEFAULT_VOL; + channel = CHAN_VOICE; + state = 0; + respondto = spawnflags ^ TRIGGER_PLAYERS; + min_dist = DEFAULT_MIN_DIST; + + if ( spawnflags & ( AMBIENT_ON | AMBIENT_OFF ) ) + { + ambient = true; + if ( spawnflags & AMBIENT_ON ) + { + PostEvent( EV_Trigger_Effect, EV_POSTSPAWN ); + } + } +} + +/*****************************************************************************/ +/*QUAKED sound_speaker (0 0.75 0.75) (-8 -8 -8) (8 8 8) AMBIENT-ON AMBIENT-OFF NOT_PLAYERS MONSTERS PROJECTILES TOGGLE + +play a sound when it is used + +AMBIENT-ON specifies an ambient sound that starts on +AMBIENT-OFF specifies an ambient sound that starts off +TOGGLE specifies that the speaker toggles on triggering + +if (AMBIENT-?) is not set, then the sound is sent over explicitly this creates more net traffic + +"volume" how loud 0-4 (1 default full volume, ambients do not respond to volume) +"noise" sound to play +"channel" channel on which to play sound\ +(0 auto, 1 weapon, 2 voice, 3 item, 4 body, 8 don't use PHS) (voice is default) +"key" The item needed to activate this. (default nothing) +"thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. + +Normal sounds play each time the target is used. + +Ambient Looped sounds have a volume 1, and the use function toggles it on/off. + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +******************************************************************************/ + +CLASS_DECLARATION( TriggerPlaySound, TriggerSpeaker, "sound_speaker" ) +{ + { &EV_Touch, NULL }, + { NULL, NULL } +}; + +TriggerSpeaker::TriggerSpeaker() +{ +} + +/*****************************************************************************/ +/*QUAKED sound_randomspeaker (0 0.75 0.75) (-8 -8 -8) (8 8 8) x x NOT_PLAYERS MONSTERS PROJECTILES x x + +play a sound at random times + +"mindelay" minimum delay between sound triggers (default 3) +"maxdelay" maximum delay between sound triggers (default 10) +"chance" chance that sound will play when fired (default 1) +"volume" how loud 0-4 (1 default full volume) +"noise" sound to play +"channel" channel on which to play sound\ +(0 auto, 1 weapon, 2 voice, 3 item, 4 body, 8 don't use PHS) (voice is default) +"key" The item needed to activate this. (default nothing) + +Normal sounds play each time the target is used. + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +******************************************************************************/ + +Event EV_TriggerRandomSpeaker_TriggerSound +( + "triggersound", + EV_SCRIPTONLY, + NULL, + NULL, + "Triggers the sound to play and schedules the next time to play." +); +Event EV_Trigger_SetMinDelay +( + "mindelay", + EV_SCRIPTONLY, + "f", + "min_delay", + "Sets the minimum time between playings." +); +Event EV_Trigger_SetMaxDelay +( + "maxdelay", + EV_SCRIPTONLY, + "f", + "max_delay", + "Sets the maximum time between playings." +); +Event EV_Trigger_SetChance +( + "chance", + EV_DEFAULT, + "f[0,1]", + "newChance", + "Sets the chance that the sound will play when triggered." +); + +CLASS_DECLARATION( TriggerSpeaker, RandomSpeaker, "sound_randomspeaker" ) +{ + { &EV_Trigger_Effect, &RandomSpeaker::TriggerSound }, + { &EV_Trigger_SetMinDelay, &RandomSpeaker::SetMinDelay }, + { &EV_Trigger_SetMaxDelay, &RandomSpeaker::SetMaxDelay }, + { &EV_Trigger_SetChance, &RandomSpeaker::SetChance }, + { &EV_Touch, NULL }, + + { NULL, NULL } +}; + +void RandomSpeaker::TriggerSound( Event *ev ) +{ + ScheduleSound(); + if ( G_Random( 1.0f ) <= chance ) + TriggerPlaySound::ToggleSound( ev ); +} + +void RandomSpeaker::ScheduleSound( void ) +{ + CancelEventsOfType( EV_Trigger_Effect ); + PostEvent( EV_Trigger_Effect, mindelay + G_Random( maxdelay-mindelay ) ); +} + +void RandomSpeaker::SetMinDelay( Event *ev ) +{ + mindelay = ev->GetFloat( 1 ); +} + +void RandomSpeaker::SetMaxDelay( Event *ev ) +{ + maxdelay = ev->GetFloat( 1 ); +} + +void RandomSpeaker::SetChance( Event *ev ) +{ + chance = ev->GetFloat( 1 ); +} + +void RandomSpeaker::SetMinDelay( float newMinDelay ) +{ + mindelay = newMinDelay; +} + +void RandomSpeaker::SetMaxDelay( float newMaxDelay ) +{ + maxdelay = newMaxDelay; +} + +void RandomSpeaker::SetChance( float newChance ) +{ + chance = newChance; +} + +RandomSpeaker::RandomSpeaker() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + ambient = false; + chance = 1.0f; + mindelay = 3; + maxdelay = 10; + + ScheduleSound(); +} + +/*****************************************************************************/ +/*QUAKED trigger_changelevel (1 0 0) ? NO_INTERMISSION x NOT_PLAYERS MONSTERS PROJECTILES + +When the player touches this, he gets sent to the map listed in the "map" variable. +Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission +spot and display stats. + +"spawnspot" name of the spawn location to start at in next map. +"key" The item needed to activate this. (default nothing) +"thread" This defaults to "LevelComplete" and should point to a thread that is called just +before the level ends. + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +******************************************************************************/ + +Event EV_TriggerChangeLevel_Map +( + "map", + EV_SCRIPTONLY, + "s", + "map_name", + "Sets the map to change to when triggered." +); +Event EV_TriggerChangeLevel_SpawnSpot +( + "spawnspot", + EV_SCRIPTONLY, + "s", + "spawn_spot", + "Sets the spawn spot to use." +); + +CLASS_DECLARATION( Trigger, TriggerChangeLevel, "trigger_changelevel" ) +{ + { &EV_Trigger_Effect, &TriggerChangeLevel::ChangeLevel }, + { &EV_TriggerChangeLevel_Map, &TriggerChangeLevel::SetMap }, + { &EV_TriggerChangeLevel_SpawnSpot, &TriggerChangeLevel::SetSpawnSpot }, + { &EV_Trigger_SetThread, &TriggerChangeLevel::SetThread }, + + { NULL, NULL } +}; + +TriggerChangeLevel::TriggerChangeLevel() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + // default level change thread + changethread = "LevelComplete"; + respondto = spawnflags ^ TRIGGER_PLAYERS; +} + +void TriggerChangeLevel::SetMap( Event *ev ) +{ + map = ev->GetString( 1 ); +} + +void TriggerChangeLevel::SetSpawnSpot( Event *ev ) +{ + spawnspot = ev->GetString( 1 ); +} + +void TriggerChangeLevel::SetThread( Event *ev ) +{ + // We have to handle calling the thread ourselves, so clear out Trigger's thread variable + thread = ""; + changethread = ev->GetString( 1 ); +} + +void TriggerChangeLevel::ChangeLevel( Event *ev ) +{ + Entity *other; + + other = ev->GetEntity( 1 ); + + if ( level.intermissiontime ) + { + // already activated + return; + } + + // if noexit, do a ton of damage to other + if ( multiplayerManager.inMultiplayer() && multiplayerManager.checkFlag( MP_FLAG_SAME_LEVEL ) && ( other != world ) ) + { + other->Damage( this, other, 10.0f * other->max_health, other->origin, vec_zero, vec_zero, 1000, 0, MOD_CRUSH ); + return; + } + + // tell the script that the player's not ready so that if we return to this map, + // we can do something about it. + Director.PlayerNotReady(); + + // + // make sure we execute the thread before changing + // + if ( changethread.length() ) + { + ExecuteThread( changethread , true , this ); + } + + if ( spawnspot.length() ) + { + G_BeginIntermission( ( map + "$" + spawnspot ).c_str() ); + } + else + { + G_BeginIntermission( map.c_str() ); + } +} + +const char *TriggerChangeLevel::Map( void ) +{ + return map.c_str(); +} + +const char *TriggerChangeLevel::SpawnSpot( void ) +{ + return spawnspot.c_str(); +} + +/*****************************************************************************/ +/*QUAKED trigger_use (1 0 0) ? VISIBLE x NOT_PLAYERS MONSTERS + +Activates targets when 'used' by an entity +"key" The item needed to activate this. (default nothing) +"thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters + +******************************************************************************/ + +CLASS_DECLARATION( Trigger, TriggerUse, "trigger_use" ) +{ + { &EV_Use, &TriggerUse::TriggerStuff }, + { &EV_Touch, NULL }, + + { NULL, NULL } +}; + +TriggerUse::TriggerUse() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + if ( spawnflags & VISIBLE ) + { + showModel(); + setMoveType( MOVETYPE_PUSH ); + setSolidType( SOLID_BSP ); + } + + respondto = ( spawnflags ^ TRIGGER_PLAYERS ) & ~TRIGGER_PROJECTILES; +} + +/*****************************************************************************/ +/*QUAKED trigger_useonce (1 0 0) ? VISIBLE x NOT_PLAYERS MONSTERS + +Activates targets when 'used' by an entity, but only once +"key" The item needed to activate this. (default nothing) +"thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters + +******************************************************************************/ + +CLASS_DECLARATION( TriggerUse, TriggerUseOnce, "trigger_useonce" ) +{ + { &EV_Touch, NULL }, + + { NULL, NULL } +}; + +TriggerUseOnce::TriggerUseOnce() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + // Only allow 1 use. + count = 1; + + respondto = ( spawnflags ^ TRIGGER_PLAYERS ) & ~TRIGGER_PROJECTILES; +} + +/*****************************************************************************/ +/*QUAKED trigger_hurt (1 0 0) ? x x NOT_PLAYERS NOT_MONSTERS PROJECTILES + +"damage" amount of damage to cause. (default 10) +"key" The item needed to activate this. (default nothing) +"damagetype" what kind of damage should be given to the player. (default CRUSH) + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not hurt players +If NOT_MONSTERS is set, the trigger does not hurt monsters +If PROJECTILES is set, the trigger will hurt projectiles (rockets, grenades, etc.) + +******************************************************************************/ + +Event EV_TriggerHurt_SetDamage +( + "damage", + EV_SCRIPTONLY, + "f", + "damage", + "Sets the amount of damage to do." +); +Event EV_TriggerHurt_SetDamageType +( + "damagetype", + EV_SCRIPTONLY, + "s", + "damageType", + "Sets the type of damage a TriggerHurt delivers." +); + +CLASS_DECLARATION( TriggerUse, TriggerHurt, "trigger_hurt" ) +{ + { &EV_Trigger_Effect, &TriggerHurt::Hurt }, + { &EV_TriggerHurt_SetDamage, &TriggerHurt::SetDamage }, + { &EV_SetGameplayDamage, &TriggerHurt::setDamage }, + { &EV_TriggerHurt_SetDamageType, &TriggerHurt::DamageType }, + { &EV_Touch, &Trigger::TriggerStuff }, + + { NULL, NULL } +}; + +TriggerHurt::TriggerHurt() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + damage = 10; + damage_type = MOD_CRUSH; + respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS ); +} + +//-------------------------------------------------------------- +// +// Name: setDamage +// Class: TriggerHurt +// +// Description: This function acts as a filter to the real function. +// It gets data from the database, and then passes it +// along to the original event. This is here as an attempt +// to sway people into using the database standard instead of +// hardcoded numbers. +// +// Parameters: Event *ev +// str -- The value keyword from the database (low, medium, high, etc). +// +// Returns: None +// +//-------------------------------------------------------------- +void TriggerHurt::setDamage( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasFormula("OffensiveDamage") ) + return; + + str damagestr = ev->GetString( 1 ); + float damagemod = 1.0f; + if ( gpm->getDefine(damagestr) != "" ) + damagemod = (float)atof(gpm->getDefine(damagestr)); + GameplayFormulaData fd(this, 0, 0, ""); + float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod); + Event *newev = new Event(EV_TriggerHurt_SetDamage); + newev->AddFloat(finaldamage); + ProcessEvent(newev); +} + + +void TriggerHurt::SetDamage( Event *ev ) +{ + damage = ev->GetFloat( 1 ); +} + +void TriggerHurt::Hurt( Event *ev ) +{ + Entity *other; + + other = ev->GetEntity( 1 ); + + if ( ( damage != 0 ) && !other->deadflag && !( other->flags & FL_GODMODE ) ) + { + other->Damage( this, world, damage, other->origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR|DAMAGE_NO_SKILL, damage_type ); + } +} + +/*****************************************************************************/ +/*QUAKED trigger_damagetargets (1 0 0) ? SOLID x NOT_PLAYERS NOT_MONSTERS PROJECTILES + +"damage" amount of damage to cause. If no damage is specified, objects\ +are damaged by the current health+1 + +"key" The item needed to activate this. (default nothing) + +if a trigger_damagetargets is shot at and the SOLID flag is set,\ +the damage is passed on to the targets + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not hurt players +If NOT_MONSTERS is set, the trigger does not hurt monsters +If PROJECTILES is set, the trigger will hurt projectiles (rockets, grenades, etc.) + +******************************************************************************/ + +Event EV_TriggerDamageTargets_SetDamage +( + "damage", + EV_SCRIPTONLY, + "f", + "damage", + "Sets the amount of damage to do." +); + +CLASS_DECLARATION( Trigger, TriggerDamageTargets, "trigger_damagetargets" ) +{ + { &EV_Trigger_ActivateTargets, &TriggerDamageTargets::DamageTargets }, + { &EV_TriggerDamageTargets_SetDamage, &TriggerDamageTargets::SetDamage }, + { &EV_SetGameplayDamage, &TriggerDamageTargets::setDamage }, + { &EV_Damage, &TriggerDamageTargets::PassDamage }, + { &EV_Touch, NULL }, + + { NULL, NULL } +}; + +TriggerDamageTargets::TriggerDamageTargets() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + damage = 0; + respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS ); + + if ( spawnflags & 1 ) + { + health = 60; + max_health = health; + takedamage = DAMAGE_YES; + setSolidType( SOLID_BBOX ); + } + else + { + takedamage = DAMAGE_NO; + setSolidType( SOLID_NOT ); + } +} + +//-------------------------------------------------------------- +// +// Name: setDamage +// Class: TriggerDamageTargets +// +// Description: This function acts as a filter to the real function. +// It gets data from the database, and then passes it +// along to the original event. This is here as an attempt +// to sway people into using the database standard instead of +// hardcoded numbers. +// +// Parameters: Event *ev +// str -- The value keyword from the database (low, medium, high, etc). +// +// Returns: None +// +//-------------------------------------------------------------- +void TriggerDamageTargets::setDamage( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + return; + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasFormula("OffensiveDamage") ) + return; + + str damagestr = ev->GetString( 1 ); + float damagemod = 1.0f; + if ( gpm->getDefine(damagestr) != "" ) + damagemod = (float)atof(gpm->getDefine(damagestr)); + GameplayFormulaData fd(this, 0, 0, ""); + float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod); + Event *newev = new Event(EV_TriggerDamageTargets_SetDamage); + newev->AddFloat((int)finaldamage); + ProcessEvent(newev); +} + +void TriggerDamageTargets::SetDamage( Event *ev ) +{ + damage = ev->GetInteger( 1 ); +} + +void TriggerDamageTargets::PassDamage( Event *ev ) +{ + Entity *attacker; + float dmg; + Entity *ent; + const char *name; + + dmg = ev->GetFloat( 1 ); + attacker = ev->GetEntity( 3 ); + + // + // damage the targets + // + name = Target(); + if ( name && strcmp( name, "" ) ) + { + ent = NULL; + do + { + ent = G_FindTarget( ent, name ); + if ( !ent ) + { + break; + } + + if ( !ent->deadflag && !( ent->flags & FL_GODMODE ) ) + { + ent->Damage( this, attacker, dmg, ent->origin, vec_zero, vec_zero, 0, 0, MOD_CRUSH ); + } + } + while ( 1 ); + } +} +// +//============================== +// DamageTargets +//============================== +// + +void TriggerDamageTargets::DamageTargets( Event *ev ) +{ + Entity *other; + Entity *ent; + const char *name; + + other = ev->GetEntity( 1 ); + + if ( triggerActivated ) + { + // + // Entity triggered itself. Prevent an infinite loop + // + ev->Error( "Entity targeting itself--Targetname '%s'", TargetName() ); + return; + } + + triggerActivated = true; + activator = other; + + // + // print the message + // + if ( message.length() && other && other->isClient() ) + { + gi.centerprintf( other->edict, CENTERPRINT_IMPORTANCE_NORMAL, message.c_str() ); + if ( Noise().length() ) + { + other->Sound( Noise().c_str(), CHAN_VOICE ); + } + } + + // + // damage the targets + // + name = Target(); + if ( name && strcmp( name, "" ) ) + { + ent = NULL; + do + { + ent = G_FindTarget( ent, name ); + if ( !ent ) + { + break; + } + + if ( !ent->deadflag && !( ent->flags & FL_GODMODE ) ) + { + if (damage) + ent->Damage( this, other, damage, ent->origin, vec_zero, vec_zero, 0, 0, MOD_CRUSH ); + else + ent->Damage( this, other, ent->health + 1.0f, ent->origin, vec_zero, vec_zero, 0, 0, MOD_CRUSH ); + } + } + while ( 1 ); + } + + triggerActivated = false; +} + +/*****************************************************************************/ +/*QUAKED trigger_camerause (1 0 0) ? VISIBLE x NOT_PLAYERS MONSTERS + +Activates 'targeted' camera when 'used' +If activated, toggles through cameras +"key" The item needed to activate this. (default nothing) +"thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters + +******************************************************************************/ + +CLASS_DECLARATION( TriggerUse, TriggerCameraUse, "trigger_camerause" ) +{ + { &EV_Use, &TriggerCameraUse::TriggerCamera }, + { &EV_Touch, NULL }, + + { NULL, NULL } +}; + +void TriggerCameraUse::TriggerCamera( Event *ev ) +{ + str camthread; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( other->isClient() ) + { + Player * client; + Entity * ent; + Camera * cam; + + client = ( Player * )other; + cam = client->CurrentCamera(); + if ( cam != NULL ) + { + str nextcam; + + nextcam = cam->NextCamera(); + if ( nextcam.length() ) + { + ent = G_FindTarget( NULL, nextcam.c_str() ); + + if ( ent ) + { + if ( ent->isSubclassOf( Camera ) ) + { + cam = (Camera *)ent; + camthread = cam->Thread(); + client->SetCamera( cam, CAMERA_SWITCHTIME ); + } + } + } + } + else + { + ent = G_FindTarget( NULL, Target() ); + if ( ent ) + { + if ( ent->isSubclassOf( Camera ) ) + { + cam = (Camera *)ent; + camthread = cam->Thread(); + client->SetCamera( cam, CAMERA_SWITCHTIME ); + } + else + { + warning( "TriggerCamera", "%s is not a camera", Target() ); + } + } + + } + if ( camthread.length() > 1 ) + { + if ( !ExecuteThread( camthread , true , this ) ) + { + warning( "TriggerCamera", "Null game script" ); + } + } + } +} + +/*****************************************************************************/ +/*QUAKED trigger_exit (1 0 0) ? + +When the player touches this, an exit icon will be displayed in his hud. +This is to inform him that he is near an exit. + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +******************************************************************************/ + +Event EV_TriggerExit_TurnExitOff +( + "_turnexitoff", + EV_CODEONLY, + NULL, + NULL, + "Internal event that turns the exit sign off." +); + +CLASS_DECLARATION( Trigger, TriggerExit, "trigger_exit" ) +{ + { &EV_Trigger_Effect, &TriggerExit::DisplayExitSign }, + { &EV_TriggerExit_TurnExitOff, &TriggerExit::TurnExitSignOff }, + + { NULL, NULL } +}; + +TriggerExit::TriggerExit() +{ + wait = 1; + respondto = TRIGGER_PLAYERS; +} + +void TriggerExit::TurnExitSignOff( Event *ev ) +{ + level.near_exit = false; +} + +void TriggerExit::DisplayExitSign( Event *ev ) +{ + level.near_exit = true; + + CancelEventsOfType( EV_TriggerExit_TurnExitOff ); + PostEvent( EV_TriggerExit_TurnExitOff, 1.1f ); +} + +/*****************************************************************************/ +/* trigger_box (0.5 0.5 0.5) ? x x NOT_PLAYERS MONSTERS PROJECTILES + +Variable sized repeatable trigger. Must be targeted at one or more entities. Made to +be spawned via script. + +If "health" is set, the trigger must be killed to activate each time. +If "delay" is set, the trigger waits some time after activating before firing. + +"thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. + +"wait" : Seconds between triggerings. (.2 default) +"cnt" how many times it can be triggered (infinite default) + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +set "message" to text string + +******************************************************************************/ + +Event EV_TriggerBox_SetMins +( + "mins", + EV_SCRIPTONLY, + "v", + "mins", + "Sets the minimum bounds of the trigger box." +); +Event EV_TriggerBox_SetMaxs +( + "maxs", + EV_SCRIPTONLY, + "v", + "maxs", + "Sets the maximum bounds of the trigger box." +); + +CLASS_DECLARATION( Trigger, TriggerBox, "trigger_box" ) +{ + { &EV_TriggerBox_SetMins, &TriggerBox::SetMins }, + { &EV_TriggerBox_SetMaxs, &TriggerBox::SetMaxs }, + + { NULL, NULL } +}; + +void TriggerBox::SetMins( Event *ev ) +{ + Vector org; + + mins = ev->GetVector( 1 ); + org = ( mins + maxs ) * 0.5f; + + setSize( mins - org, maxs - org ); + setOrigin( org ); +} + +void TriggerBox::SetMaxs( Event *ev ) +{ + Vector org; + + maxs = ev->GetVector( 1 ); + org = ( mins + maxs ) * 0.5f; + + setSize( mins - org, maxs - org ); + setOrigin( org ); +} + +/*****************************************************************************/ +/*QUAKED trigger_music (1 0 0) ? NORMAL ACTION NOT_PLAYERS MONSTERS PROJECTILES SUSPENSE MYSTERY SURPRISE + +Variable sized repeatable trigger to change the music mood. + +If "delay" is set, the trigger waits some time after activating before firing. +"current" can be used to set the current mood +"fallback" can be used to set the fallback mood +"altcurrent" can be used to set the current mood of the opposite face, if multiFaceted +"altfallback" can be used to set the fallback mood of the opposite face, if multiFaceted +"edgeTriggerable" trigger only fires when entering a trigger +"multiFaceted" if 1, then trigger is North/South separate triggerable\ +if 2, then trigger East/West separate triggerable + +"thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. +"wait" : Seconds between triggerings. (1.0 default) +"cnt" how many times it can be triggered (infinite default) +"oneshot" make this a one time trigger + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +NORMAL, ACTION, SUSPENSE, MYSTERY, and SURPRISE are the moods that can be triggered + +******************************************************************************/ + +Event EV_TriggerMusic_CurrentMood +( + "current", + EV_SCRIPTONLY, + "s", + "current_mood", + "Sets the current mood to use when triggered." +); +Event EV_TriggerMusic_FallbackMood +( + "fallback", + EV_SCRIPTONLY, + "s", + "fallback_mood", + "Sets the fallback mood to use when triggered." +); +Event EV_TriggerMusic_AltCurrentMood +( + "altcurrent", + EV_SCRIPTONLY, + "s", + "alternate_current_mood", + "Sets the alternate current mood to use when triggered." +); +Event EV_TriggerMusic_AltFallbackMood +( + "altfallback", + EV_SCRIPTONLY, + "s", + "alterante_fallback_mood", + "Sets the alternate fallback mood to use when triggered." +); +Event EV_TriggerMusic_OneShot +( + "oneshot", + EV_SCRIPTONLY, + NULL, + NULL, + "Make this a one time trigger." +); + +CLASS_DECLARATION( Trigger, TriggerMusic, "trigger_music" ) +{ + { &EV_TriggerMusic_CurrentMood, &TriggerMusic::SetCurrentMood }, + { &EV_TriggerMusic_FallbackMood, &TriggerMusic::SetFallbackMood }, + { &EV_TriggerMusic_AltCurrentMood, &TriggerMusic::SetAltCurrentMood }, + { &EV_TriggerMusic_AltFallbackMood, &TriggerMusic::SetAltFallbackMood }, + { &EV_TriggerMusic_OneShot, &TriggerMusic::SetOneShot }, + { &EV_Trigger_Effect, &TriggerMusic::ChangeMood }, + { &EV_Trigger_Effect_Alt, &TriggerMusic::AltChangeMood }, + + { NULL, NULL } +}; + +TriggerMusic::TriggerMusic() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + removable = false; + triggerActivated = false; + activator = NULL; + trigger_time = 0.0f; + edgeTriggered = true; + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_TRIGGER ); + + setContents( 0 ); + edict->svflags |= SVF_NOCLIENT; + + delay = 0; + wait = 1.0f; + health = 0; + max_health = 0; + + trigger_time = (float)0; + + SetOneShot( false ); + + noise = ""; + respondto = spawnflags ^ TRIGGER_PLAYERS; + + current = "normal"; + fallback = "normal"; + + altcurrent = "normal"; + altfallback = "normal"; + + // setup sound based on spawn flags + if ( spawnflags & 1 ) + current = "normal"; + else if ( spawnflags & 2 ) + current = "action"; + else if ( spawnflags & 32 ) + current = "suspense"; + else if ( spawnflags & 64 ) + current = "mystery"; + else if ( spawnflags & 128 ) + current = "surprise"; +} + +void TriggerMusic::SetMood( const str &crnt, const str &fback ) +{ + current = crnt; + fallback = fback; +} + +void TriggerMusic::SetAltMood( const str &crnt, const str &fback ) +{ + altcurrent = crnt; + altfallback = fback; +} + +void TriggerMusic::SetCurrentMood( Event *ev ) +{ + current = ev->GetString( 1 ); +} + +void TriggerMusic::SetFallbackMood( Event *ev ) +{ + fallback = ev->GetString( 1 ); +} + +void TriggerMusic::SetAltCurrentMood( Event *ev ) +{ + altcurrent = ev->GetString( 1 ); +} + +void TriggerMusic::SetAltFallbackMood( Event *ev ) +{ + altfallback = ev->GetString( 1 ); +} + +void TriggerMusic::ChangeMood( Event *ev ) +{ + ChangeMusic( current.c_str(), fallback.c_str(), false ); +} + +void TriggerMusic::AltChangeMood( Event *ev ) +{ + ChangeMusic( altcurrent.c_str(), altfallback.c_str(), false ); +} + +void TriggerMusic::SetOneShot( qboolean once ) +{ + trigger_time = 0.0f; + oneshot = once; + if ( oneshot ) + count = 1; + else + count = -1; +} + +void TriggerMusic::SetOneShot( Event *ev ) +{ + SetOneShot( true ); +} + +/*****************************************************************************/ +/*QUAKED trigger_reverb (1 0 0) ? x x NOT_PLAYERS MONSTERS PROJECTILES + +Variable sized repeatable trigger to change the reverb level in the game + +If "delay" is set, the trigger waits some time after activating before firing. +"reverbtype" what kind of reverb should be used +"reverblevel" how much of the reverb effect should be applied +"altreverbtype" what kind of reverb should be used +"altreverblevel" how much of the reverb effect should be applied +"edgeTriggerable" trigger only fires when entering a trigger +"multiFaceted" if 1, then trigger is North/South separate triggerable\ +if 2, then trigger East/West separate triggerable + +"thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. +"wait" : Seconds between triggerings. (1.0 default) +"cnt" how many times it can be triggered (infinite default) +"oneshot" make this a one time trigger + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +******************************************************************************/ + +Event EV_TriggerReverb_ReverbType +( + "reverbtype", + EV_SCRIPTONLY, + "i", + "reverbType", + "Sets the reverb type." +); +Event EV_TriggerReverb_ReverbLevel +( + "reverblevel", + EV_SCRIPTONLY, + "f", + "reverbLevel", + "Sets the reverb level to be used when triggered." +); +Event EV_TriggerReverb_AltReverbType +( + "altreverbtype", + EV_SCRIPTONLY, + "i", + "reverbType", + "Sets the reverb type." +); +Event EV_TriggerReverb_AltReverbLevel +( + "altreverblevel", + EV_SCRIPTONLY, + "f", + "reverbLevel", + "Sets the reverb level to be used when triggered." +); +Event EV_TriggerReverb_OneShot +( + "oneshot", + EV_SCRIPTONLY, + NULL, + NULL, + "Make this a one time trigger." +); + +CLASS_DECLARATION( Trigger, TriggerReverb, "trigger_music" ) +{ + { &EV_TriggerReverb_ReverbType, &TriggerReverb::SetReverbType }, + { &EV_TriggerReverb_ReverbLevel, &TriggerReverb::SetReverbLevel }, + { &EV_TriggerReverb_AltReverbType, &TriggerReverb::SetAltReverbType }, + { &EV_TriggerReverb_AltReverbLevel, &TriggerReverb::SetAltReverbLevel }, + { &EV_TriggerReverb_OneShot, &TriggerReverb::SetOneShot }, + { &EV_Trigger_Effect, &TriggerReverb::ChangeReverb }, + { &EV_Trigger_Effect_Alt, &TriggerReverb::AltChangeReverb }, + + { NULL, NULL } +}; + +TriggerReverb::TriggerReverb() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + removable = false; + triggerActivated = false; + activator = NULL; + trigger_time = (float)0; + edgeTriggered = true; + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_TRIGGER ); + + setContents( 0 ); + edict->svflags |= SVF_NOCLIENT; + + delay = 0; + wait = 1.0f; + health = 0; + max_health = 0; + + trigger_time = (float)0; + + SetOneShot( false ); + + noise = ""; + respondto = spawnflags ^ TRIGGER_PLAYERS; + + reverbtype = 0; + altreverbtype = 0; + reverblevel = 0.5f; + altreverblevel = 0.5f; +} + +void TriggerReverb::SetReverb( int type, float level ) +{ + reverbtype = type; + reverblevel = level; +} + +void TriggerReverb::SetAltReverb( int type, float level ) +{ + altreverbtype = type; + altreverblevel = level; +} + +void TriggerReverb::SetReverbType( Event *ev ) +{ + reverbtype = ev->GetInteger( 1 ); +} + +void TriggerReverb::SetReverbLevel( Event *ev ) +{ + reverblevel = ev->GetFloat( 1 ); +} + +void TriggerReverb::SetAltReverbType( Event *ev ) +{ + altreverbtype = ev->GetInteger( 1 ); +} + +void TriggerReverb::SetAltReverbLevel( Event *ev ) +{ + altreverblevel = ev->GetFloat( 1 ); +} + +void TriggerReverb::ChangeReverb( Event *ev ) +{ + Entity * other; + + other = ev->GetEntity( 1 ); + if ( other && other->isClient() ) + { + Player *client; + + client = ( Player * )other; + client->SetReverb( reverbtype, reverblevel ); + + if ( !multiplayerManager.inMultiplayer() ) + { + gi.DPrintf( "reverb set to %s at level %.2f\n", EAXMode_NumToName( reverbtype ), reverblevel ); + } + } +} + +void TriggerReverb::AltChangeReverb( Event *ev ) +{ + Entity * other; + + other = ev->GetEntity( 1 ); + if ( other && other->isClient() ) + { + Player *client; + + client = ( Player * )other; + client->SetReverb( altreverbtype, altreverblevel ); + + if ( !multiplayerManager.inMultiplayer() ) + { + gi.DPrintf( "reverb set to %s at level %.2f\n", EAXMode_NumToName( altreverbtype ), altreverblevel ); + } + } +} + +void TriggerReverb::SetOneShot( qboolean once ) +{ + trigger_time = 0.0f; + oneshot = once; + if ( oneshot ) + count = 1; + else + count = -1; +} + +void TriggerReverb::SetOneShot( Event *ev ) +{ + SetOneShot( true ); +} + +/*****************************************************************************/ +/*QUAKED trigger_pushobject (1 0 0) ? +Special trigger that can only be triggered by a push object. + +"triggername" if set, trigger only responds to objects with a targetname the same as triggername. +"cnt" how many times it can be triggered (default 1, use -1 for infinite) +******************************************************************************/ + +Event EV_TriggerByPushObject_TriggerName +( + "triggername", + EV_SCRIPTONLY, + "s", + "targetname_of_object", + "If set, trigger will only respond to objects with specified name." +); + +CLASS_DECLARATION( TriggerOnce, TriggerByPushObject, "trigger_pushobject" ) +{ + { &EV_TriggerByPushObject_TriggerName, &TriggerByPushObject::setTriggerName }, + + { NULL, NULL } +}; + +void TriggerByPushObject::setTriggerName( Event *event ) +{ + triggername = event->GetString( 1 ); +} + +qboolean TriggerByPushObject::respondTo( Entity *other ) +{ + if ( other->isSubclassOf( PushObject ) ) + { + if ( triggername.length() ) + { + return ( triggername == other->TargetName() ); + } + + return true; + } + + return false; +} + +Entity *TriggerByPushObject::getActivator( Entity *other ) +{ + Entity *owner; + + if ( other->isSubclassOf( PushObject ) ) + { + owner = ( ( PushObject * )other )->getOwner(); + + if ( owner ) + { + return owner; + } + } + + return other; +} + +/*****************************************************************************/ +/*QUAKED trigger_givepowerup (1 0 0) ? x x NOT_PLAYERS MONSTERS x + +Variable sized repeatable trigger to give a powerup to the player + +"oneshot" makes this triggerable only once +"powerupname" sets the name of the powerup to give to the player + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters + +******************************************************************************/ + +Event EV_TriggerGivePowerup_OneShot +( + "oneshot", + EV_SCRIPTONLY, + NULL, + NULL, + "Make this a one time trigger." +); +Event EV_TriggerGivePowerup_PowerupName +( + "powerupname", + EV_SCRIPTONLY, + "s", + "powerup_name", + "Specifies the powerup to give to the sentient." +); + +CLASS_DECLARATION( Trigger, TriggerGivePowerup, "trigger_givepowerup" ) +{ + { &EV_TriggerGivePowerup_OneShot, &TriggerGivePowerup::SetOneShot }, + { &EV_TriggerGivePowerup_PowerupName, &TriggerGivePowerup::SetPowerupName }, + { &EV_Trigger_Effect, &TriggerGivePowerup::GivePowerup }, + + { NULL, NULL } +}; + +TriggerGivePowerup::TriggerGivePowerup() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + removable = false; + triggerActivated = false; + activator = NULL; + trigger_time = (float)0; + edgeTriggered = false; + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_TRIGGER ); + + setContents( 0 ); + edict->svflags |= SVF_NOCLIENT; + + delay = 0; + wait = 1.0f; + health = 0; + max_health = 0; + + trigger_time = 0.0; + oneshot = false; + count = -1; + + noise = ""; + respondto = spawnflags ^ (TRIGGER_PLAYERS | TRIGGER_MONSTERS ); +} + +void TriggerGivePowerup::SetOneShot( Event *ev ) +{ + trigger_time = 0.0f; + oneshot = true; + count = 1; +} + +void TriggerGivePowerup::SetPowerupName( Event *ev ) +{ + powerup_name = ev->GetString( 1 ); +} + +void TriggerGivePowerup::GivePowerup( Event *ev ) +{ + Entity *other; + + other = ev->GetEntity( 1 ); + + if ( other->isSubclassOf( Sentient ) ) + { + Sentient *sent; + + sent = ( Sentient * )other; + + if ( powerup_name.length() && !sent->FindItem( powerup_name.c_str() ) ) + sent->giveItem( powerup_name ); + } +} + +/*****************************************************************************/ +/*QUAKED trigger_worktrigger (1 0 0) ? + +Trigger will be used to tell the AI how to do "work". When the AI activates +trigger, the thread you specifiy will execute. The AI will also play the +Animation you set in the trigger. + +"triggerable" turn trigger on +"nottriggerable" turn trigger off +"setworkanim" Sets the animation the Actor will use when "working" + + +******************************************************************************/ + +Event EV_TriggerSetWorkAnimation +( + "setworkanim", + EV_SCRIPTONLY, + "s", + "work_anim", + "Tells the actor to play this specified animation when using the trigger" +); +Event EV_TriggerSetWorkTime +( + "setworktime", + EV_SCRIPTONLY, + "f", + "work_time", + "sets how long to work at node ( -1 ) is infinite" +); +Event EV_TriggerAddWorker +( + "addworker", + EV_SCRIPTONLY, + "s", + "worker_name", + "adds a valid worker to the trigger" +); + +CLASS_DECLARATION( Trigger, WorkTrigger, "trigger_worktrigger" ) +{ + { &EV_Use, &WorkTrigger::TriggerStuff }, + { &EV_TriggerSetWorkAnimation, &WorkTrigger::SetAnimation }, + { &EV_TriggerSetWorkTime, &WorkTrigger::SetTime }, + { &EV_TriggerAddWorker, &WorkTrigger::AddWorker }, + + { NULL, NULL } +}; + +WorkTrigger::WorkTrigger() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + _time = 0.0f; + _reserved = false; + _currentEnt = 0; + + respondto = spawnflags ^ ( TRIGGER_MONSTERS ); +} + +void WorkTrigger::SetAnimation( Event *ev ) +{ + _animation = ev->GetString( 1 ); +} + +str WorkTrigger::GetAnimation() +{ + return _animation; +} + +void WorkTrigger::SetTime( Event *ev ) +{ + _time = ev->GetFloat( 1 ); +} + +float WorkTrigger::GetTime() +{ + return _time; +} + +void WorkTrigger::AddWorker( Event *ev ) +{ + str workerName; + str name; + + workerName = ev->GetString( 1 ); + + for ( int i = 1 ; i <= _allowedWorkers.NumObjects() ; i++ ) + { + name = _allowedWorkers.ObjectAt( i ); + if ( !Q_stricmp( name.c_str() , workerName.c_str() ) ) + return; + } + + _allowedWorkers.AddObject( workerName ); +} + +qboolean WorkTrigger::isAllowedToWork( const str &name , int entNum ) +{ + str workerName; + + if ( _reserved && entNum != _currentEnt ) + return false; + + if ( _allowedWorkers.NumObjects() < 1 ) + return true; + + + for ( int i = 1 ; i <= _allowedWorkers.NumObjects() ; i++ ) + { + workerName = _allowedWorkers.ObjectAt( i ); + if ( !Q_stricmp( name.c_str() , workerName.c_str() ) ) + return true; + } + + return false; +} + +qboolean WorkTrigger::isReserved() +{ + return _reserved; +} + +void WorkTrigger::Reserve(int entNum) +{ + _reserved = true; + _currentEnt = entNum; +} + +void WorkTrigger::Release() +{ + _reserved = false; + _currentEnt = 0; +} + +/*****************************************************************************/ +/*QUAKED trigger_levelinteraction (1 0 0) ? + +Trigger that will be damaged by the AI to fire off a "level_interaction" thread +for example, a boss that shoots crates and knocks them over onto players + +"triggerable" turn trigger on +"nottriggerable" turn trigger off +"health" should be set so that the trigger can take damage + +******************************************************************************/ +CLASS_DECLARATION( Trigger, LevelInteractionTrigger, "trigger_levelinteraction" ) +{ + { NULL, NULL } +}; + +LevelInteractionTrigger::LevelInteractionTrigger() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + respondto = spawnflags ^ ( TRIGGER_MONSTERS | TRIGGER_PROJECTILES ); +} + +qboolean LevelInteractionTrigger::respondTo( Entity *other ) +{ + if (other->isClient()) return false; + if (other->isSubclassOf( Actor)) return true; + + if (other->isSubclassOf( Projectile ) ) + { + Projectile* proj = (Projectile*)other; + Entity* ent = G_GetEntity(proj->owner); + + if (ent->isSubclassOf( Actor ) ) return true; + + } + + return false; +} + +/*****************************************************************************/ +/*QUAKED trigger_groupevent (1 0 0) ? x x NOT_PLAYERS MONSTERS PROJECTILES + +Variable sized repeatable trigger. Must be targeted at one or more entities. + +If "health" is set, the trigger must be killed to activate each time. +If "delay" is set, the trigger waits some time after activating before firing. + +"thread" name of thread to trigger. This can be in a different script file as well\ +by using the '::' notation. + +if "angle" is set, the trigger will only fire when someone is facing the +direction of the angle. +"cone" the cone in which a directed trigger can be triggered (default 60 degrees) + +"wait" : Seconds between triggerings. (.2 default) +"cnt" how many times it can be triggered (infinite default) + +"triggerable" turn trigger on +"nottriggerable" turn trigger off + +"setpassevent" Sets the event you wish to pass to the group. +"setgroupnumber" Sets the number of the group you wish to pass the event to. + +If NOT_PLAYERS is set, the trigger does not respond to players +If MONSTERS is set, the trigger will respond to monsters +If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.) + +set "message" to text string + +******************************************************************************/ + +CLASS_DECLARATION( Trigger, TriggerGroupEvent, "trigger_groupevent" ) +{ + { &EV_Trigger_SetPassEvent, &TriggerGroupEvent::SetPassEvent }, + { &EV_Trigger_SetGroupNumber, &TriggerGroupEvent::SetGroupNumber }, + { &EV_Trigger_ActivateTargets, &TriggerGroupEvent::PassEvent }, + { &EV_Activate, &TriggerGroupEvent::TriggerStuff }, + + { NULL, NULL } +}; + +TriggerGroupEvent::TriggerGroupEvent() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + groupNumber = 0; + respondto = spawnflags ^ TRIGGER_PLAYERS; +} + +void TriggerGroupEvent::TriggerStuff( Event *ev ) +{ + Trigger::TriggerStuff( ev ); +} + +void TriggerGroupEvent::SetPassEvent( Event *ev ) +{ + //Events coming from radiant like this one, seem to + //come as one string, regardless of how many parameters + //there are, so we're going to have to parse the string ourselves; + str eventString; + str character; + str parameter; + int len; + int i; + + eventString = ev->GetString( 1 ); + parameter = ""; + len = eventString.length(); + + + for ( i = 0 ; i < len ; i++ ) + { + character = eventString[i]; + + if ( character != " " ) + parameter += character; + else + { + passEvent.AddToken( parameter.c_str() ); + parameter = ""; + } + + } + + //Add The Last Parameter + if ( parameter.length() ) + passEvent.AddToken( parameter.c_str() ); + +} + +void TriggerGroupEvent::SetGroupNumber( Event *ev ) +{ + groupNumber = ev->GetInteger( 1 ); +} + +void TriggerGroupEvent::PassEvent( Event *ev ) +{ + //groupeventmanager->SendEvent( groupNumber, passEvent ); + + Event *newEvent; + newEvent = new Event ( passEvent.GetString( 1 ) ); + for ( int j=2; j<= passEvent.NumArgs() ; j++ ) + { + newEvent->AddToken( passEvent.GetToken( j ) ); + } + + groupcoordinator->SendEventToGroup( newEvent, groupNumber ); + +} + +/*****************************************************************************/ +/*QUAKED trigger_volume_callvolume (1 0 0) ? + +Allows you to specify a list of actors that are required to be inside this +volume before the thread fires. This is a good utility if you need to "herd" +a group of actors into one spot for an event to occur ( Such as Teammates and +Lifts ). When the player enters this trigger, a flag is set on all the actors +in the required list that notifies them that the player is in the volume. The AI +for those actors is then responsible for getting them into the volume. Once +all the required actors that are still in the level ( in case some were killed or +otherwise removed ) the specified thread will fire + + +******************************************************************************/ + +//============================================================================== +// TriggerCallVolume +//============================================================================== + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- + +CLASS_DECLARATION( Trigger, TriggerCallVolume, "trigger_volume_callvolume" ) +{ + { &EV_Touch, &TriggerCallVolume::TriggerStuff }, + { &EV_Trigger_SetRequiredEntity, &TriggerCallVolume::AddRequiredEntity }, + { &EV_Trigger_TriggerExit, &TriggerCallVolume::EntityLeftVolume }, + { &EV_Trigger_SetExitThread, &TriggerCallVolume::SetExitThread }, + { &EV_Trigger_CheckReady, &TriggerCallVolume::CheckReady }, + { &EV_Trigger_SetTriggerable, &TriggerCallVolume::SetTriggerable }, + { &EV_Trigger_SetNotTriggerable, &TriggerCallVolume::SetNotTriggerable }, + + { NULL, NULL } +}; + + +//-------------------------------------------------------------- +// Name: TriggerCallVolume() +// Class: TriggerCallVolume +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +TriggerCallVolume::TriggerCallVolume() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + respondto = spawnflags ^ (TRIGGER_PLAYERS | TRIGGER_MONSTERS ); + _ready = false; +} + +//-------------------------------------------------------------- +// Name: ~TriggerCallVolume() +// Class: TriggerCallVolume +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +TriggerCallVolume::~TriggerCallVolume() +{ + _requiredEntities.FreeObjectList(); +} + + +//-------------------------------------------------------------- +// Name: TriggerStuff() +// Class: TriggerCallVolume +// +// Description: Checks if the trigger is ready ( i.e. all available +// required entites are within the volume ) then +// calls the base class TriggerStuff() +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void TriggerCallVolume::TriggerStuff( Event *ev ) +{ + Entity *other; + Actor *actor; + + other = NULL; + actor = NULL; + + if (!CanTrigger()) return; + + if ((int)*ev == (int)EV_Damage) + { + other = ev->GetEntity(3); + } + else + { + other = ev->GetEntity( 1 ); + } + + assert( other != this ); + + // Always respond to activate messages from the world since they're probably from + // the "trigger" command + if ( !respondTo( other ) && !( ( other == world ) && ( ( int )*ev == ( int )EV_Activate ) ) && + ( !other || !other->isSubclassOf( Camera ) ) ) + { + return; + } + + if ( _ready ) + { + Trigger::TriggerStuff( ev ); + _ready = false; + return; + } + + //If we got here, we're still waiting on additional entities to arrive, so + //we'll repost our check event so that we can continue to monitor + Event *checkEvent; + checkEvent = new Event(EV_Trigger_CheckEntList); + checkEvent->AddInteger( other->entnum ); + PostEvent( checkEvent , CHECK_TIME ); + + int testEntNum; + for ( int i = 1 ; i <= entList.NumObjects() ; i++ ) + { + testEntNum = entList.ObjectAt( i ); + + if ( testEntNum == other->entnum && !_ready ) + return; + } + + AddOtherToEntList( other ); + + if ( other->isSubclassOf( Player ) ) + { + _checkForRequiredEnts(); + _notifyRequiredEnts(true); + + Event* checkEV; + checkEV = new Event(EV_Trigger_CheckReady); + PostEvent( checkEV , CHECK_TIME ); + } + + + if ( other->isSubclassOf( Actor ) && _isRequiredEnt( other ) ) + { + actor = (Actor*)other; + actor->SetActorFlag( ACTOR_FLAG_IN_CALL_VOLUME , true ); + + Event* checkEV; + checkEV = new Event(EV_Trigger_CheckReady); + PostEvent( checkEV , CHECK_TIME ); + } +} + + +//-------------------------------------------------------------- +// Name: AddRequiredEntity() +// Class: TriggerCallVolume +// +// Description: Adds the targetname of the entity to the required +// entity list +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void TriggerCallVolume::AddRequiredEntity( Event *ev ) +{ + str name; + str entName; + entName = ev->GetString( 1 ); + + // Check if name has already been added + for ( int i = 1; i < _requiredEntities.NumObjects() ; i++ ) + { + name = _requiredEntities.ObjectAt( i ); + + if ( !Q_stricmp( name.c_str() , entName.c_str() ) ) + return; + + } + + _requiredEntities.AddObject ( entName ); +} + +//-------------------------------------------------------------- +// Name: _getEntity() +// Class: TriggerCallVolume +// +// Description: Exchanges the targetname for an Entity Pointer +// +// Parameters: const str &name -- The targetname +// +// Returns: Entity* -- The entity with the specified targetname +//-------------------------------------------------------------- +Entity* TriggerCallVolume::_getEntity( const str &name ) +{ + Entity* ent_in_range; + gentity_t *ed; + + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + continue; + + + ent_in_range = g_entities[i].entity; + + if (!Q_stricmp(ent_in_range->targetname.c_str() , name.c_str() )) + return ent_in_range; + + } + + return NULL; +} + +//-------------------------------------------------------------- +// Name: EntityLeftVolume() +// Class: TriggerCallVolume +// +// Description: Clears the ACTOR_FLAG_IN_CALL_VOLUME flag on all +// the actors in the required list +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void TriggerCallVolume::EntityLeftVolume( Event *ev ) +{ + Entity *other; + Actor *actor; + Player *player; + + other = NULL; + other = ev->GetEntity( 1 ); + + if ( !other ) + return; + + if ( other->isSubclassOf( Player ) ) + { + _notifyRequiredEnts(false); + + // Null out the currentCallVolume on the Player + player = ( Player* )other; + player->SetCurrentCallVolume( TargetName() ); + + if ( _exitThread.length() ) + { + if ( !ExecuteThread( _exitThread , true , this ) ) + warning( "StartThread", "Null game script" ); + } + + int entNum; + for ( int i = entList.NumObjects() ; i > 0 ; i-- ) + { + entNum = entList.ObjectAt( i ); + if ( entNum == other->entnum ) + { + entList.RemoveObjectAt( i ); + } + + } + } + + if ( other->isSubclassOf( Actor ) ) + { + actor = (Actor*)other; + actor->SetActorFlag( ACTOR_FLAG_IN_CALL_VOLUME , false ); + } +} + +//-------------------------------------------------------------- +// Name: CheckReady() +// Class: TriggerCallVolume +// +// Description: Checks if all the required entities are within +// the volume +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void TriggerCallVolume::CheckReady( Event *ev ) +{ + str name; + Entity *ent; + Player *player; + + _ready = true; + + player = GetPlayer( 0 ); + + if ( !player ) + { + _ready = false; + return; + } + + if ( !IsEntityInBoundingBox( player ) ) + { + _ready = false; + return; + } + + for ( int i = 1; i <= _requiredEntities.NumObjects() ; i++ ) + { + name = _requiredEntities.ObjectAt( i ); + ent = _getEntity( name ); + + // If we have an entity and it's not in the bounding box + // we are false. If don't have an entity at all we are + // also false + if ( ent ) + { + if ( !IsEntityInBoundingBox( ent ) ) + _ready = false; + } + } +} + +//-------------------------------------------------------------- +// Name: _notifyRequiredEnts() +// Class: TriggerCallVolume +// +// Description: Sets the ACTOR_FLAG_PLAYER_IN_CALL_VOLUME flag +// on all the actors in the required list +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void TriggerCallVolume::_notifyRequiredEnts(bool inCallVolume ) +{ + str name; + Entity *ent; + Actor *actor; + + // Set the currentCallVolume on the Player + Player *player; + player = GetPlayer( 0 ); + player->SetCurrentCallVolume( TargetName() ); + + for ( int i = 1; i <= _requiredEntities.NumObjects() ; i++ ) + { + name = _requiredEntities.ObjectAt( i ); + ent = _getEntity( name ); + + if ( ent && ent->isSubclassOf(Actor ) ) + { + actor = (Actor*)ent; + actor->SetActorFlag(ACTOR_FLAG_PLAYER_IN_CALL_VOLUME , inCallVolume ); + } + } +} + +//-------------------------------------------------------------- +// Name: _checkForRequiredEnts() +// Class: TriggerCallVolume +// +// Description: Checks if we have any required actors available +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void TriggerCallVolume::_checkForRequiredEnts() +{ + str name; + Entity *ent; + qboolean haveEnts; + + haveEnts = false; + for ( int i = 1; i <= _requiredEntities.NumObjects() ; i++ ) + { + name = _requiredEntities.ObjectAt( i ); + ent = _getEntity( name ); + + if ( ent ) + { + haveEnts = true; + } + } + + if ( !haveEnts ) + _ready = true; +} + +//-------------------------------------------------------------- +// Name: _isRequiredEnt() +// Class: TriggerCallVolume +// +// Description: Checks if a entity is in the required list +// +// Parameters: Entity *other +// +// Returns: True or False +//-------------------------------------------------------------- +bool TriggerCallVolume::_isRequiredEnt(Entity *other ) +{ + str name; + Entity *ent; + + + for ( int i = 1; i <= _requiredEntities.NumObjects() ; i++ ) + { + name = _requiredEntities.ObjectAt( i ); + ent = _getEntity( name ); + + if ( ent ) + { + if ( !Q_stricmp(ent->targetname.c_str() , other->targetname.c_str() ) ) + return true; + } + } + + return false; +} + +/*****************************************************************************/ +/*QUAKED trigger_EntryAndExit (1 0 0) ? x x NOT_PLAYERS MONSTERS PROJECTILES + +Triggers entryThread when entity enters the trigger, calls thread while inside, +and calls exitThread when entity leaves the trigger. + +Key Value Pairs: +thread +entryThread +exitThread + +******************************************************************************/ + + +CLASS_DECLARATION( Trigger, TriggerEntryAndExit, "trigger_EntryAndExit" ) +{ + { &EV_Touch, &TriggerEntryAndExit::TriggerStuff }, + { &EV_Trigger_TriggerExit, &TriggerEntryAndExit::EntityLeftVolume }, + { &EV_Trigger_SetEntryThread, &TriggerEntryAndExit::SetEntryThread }, + { &EV_Trigger_SetExitThread, &TriggerEntryAndExit::SetExitThread }, + { &EV_Trigger_AltSetEntryThread, &TriggerEntryAndExit::SetEntryThread }, + { &EV_Trigger_AltSetExitThread, &TriggerEntryAndExit::SetExitThread }, + { &EV_Trigger_SetEnter, &TriggerEntryAndExit::EnterTrigger }, + { &EV_Trigger_EntityExit, &TriggerEntryAndExit::ExitTrigger }, + + // 1ST PLAYABLE HACK STUFF + { &EV_Trigger_Hack_SetTriggerParms, &TriggerEntryAndExit::HackSetParms }, + { &EV_Trigger_Hack_GetForceFieldNumber, &TriggerEntryAndExit::HackGetForceFieldTrigger }, + { &EV_Trigger_Hack_GetTriggerNumber, &TriggerEntryAndExit::HackGetTriggerNumber }, + + { NULL, NULL } +}; + +//-------------------------------------------------------------- +// Name: TriggerEntryAndExit() +// Class: TriggerEntryAndExit +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +TriggerEntryAndExit::TriggerEntryAndExit() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + // 1ST PLAYABLE HACK + _forcefieldtrigger = -1; + _triggernumber = -1; + //======================== + + respondto = spawnflags ^ TRIGGER_PLAYERS; + _entered = false; +} + +//-------------------------------------------------------------- +// Name: ~TriggerEntryAndExit() +// Class: TriggerEntryAndExit +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +TriggerEntryAndExit::~TriggerEntryAndExit() +{ +} + +//-------------------------------------------------------------- +// Name: TriggerStuff() +// Class: TriggerEntryAndExit +// +// Description: Overloaded TriggerStuff Function, handles custom +// stuff then calls the base class version +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void TriggerEntryAndExit::TriggerStuff( Event *ev ) +{ + Entity *other; + //Actor *actor; + Event *cancelEvent; + int ID; + + other = NULL; + //actor = NULL; + ID = GetGroupID(); + + if ((int)*ev == (int)EV_Damage) + { + other = ev->GetEntity(3); + } + else + { + other = ev->GetEntity( 1 ); + } + assert( other != this ); + + // Always respond to activate messages from the world since they're probably from + // the "trigger" command + if ( !respondTo( other ) && !( ( other == world ) && ( ( int )*ev == ( int )EV_Activate ) ) && + ( !other || !other->isSubclassOf( Camera ) ) ) + { + return; + } + + cancelEvent = new Event ( EV_Trigger_TriggerExit ); + groupcoordinator->GroupCancelEventsOfType( cancelEvent , ID ); + + if ( !_entered ) + { + Event *enteredEvent; + enteredEvent = new Event ( EV_Trigger_SetEnter ); + groupcoordinator->SendEventToGroup( enteredEvent , ID ); + if ( _entryThread.length() ) + { + if ( !ExecuteThread( _entryThread , true, this ) ) + { + warning( "StartThread", "Null game script" ); + } + } + + _entered = true; + } + + Trigger::TriggerStuff( ev ); + return; +} + +//-------------------------------------------------------------- +// Name: EntityLeftVolume() +// Class: TriggerEntryAndExit +// +// Description: Gets called when the trigger detects that an +// entity has left +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void TriggerEntryAndExit::EntityLeftVolume( Event *ev ) +{ + Event *exitEvent; + exitEvent = new Event( EV_Trigger_EntityExit ); + groupcoordinator->SendEventToGroup( exitEvent, GetGroupID() ); + + if ( _exitThread.length() ) + { + if ( !ExecuteThread( _exitThread , true , this ) ) + { + warning( "StartThread", "Null game script" ); + } + } + + _entered = false; +} + +//===================================================== +// 1ST PLAYABLE HACKES -- REMOVE ME, PLEASE +//===================================================== +void TriggerEntryAndExit::HackSetParms( Event *ev ) +{ + _forcefieldtrigger = ev->GetFloat( 1 ); + _triggernumber = ev->GetFloat( 2 ); +} + +void TriggerEntryAndExit::HackGetForceFieldTrigger( Event *ev ) +{ + ev->ReturnFloat( _forcefieldtrigger ); +} + +void TriggerEntryAndExit::HackGetTriggerNumber( Event *ev ) +{ + ev->ReturnFloat( _triggernumber ); +} + + + +//--------------------------------------------------------- +// T R I G G E R V O L U M E +//--------------------------------------------------------- +/*****************************************************************************/ +/*QUAKED trigger_volume (1 0 0) ? x x NOT_PLAYERS MONSTERS PROJECTILES + +Triggers entryThread when entity enters the trigger, calls thread while inside, +and calls exitThread when entity leaves the trigger. + +Key Value Pairs: +thread +entryThread +exitThread + +******************************************************************************/ + +CLASS_DECLARATION( Trigger, TriggerVolume, "trigger_volume" ) +{ + { &EV_Touch, NULL }, + { &EV_Contact, &TriggerVolume::HandleContactEvent }, + { &EV_LostContact, &TriggerVolume::HandleLostContactEvent }, + { &EV_Trigger_SetEntryThread, &TriggerVolume::HandleSetEntryThreadEvent }, + { &EV_Trigger_SetExitThread, &TriggerVolume::HandleSetExitThreadEvent }, + + { NULL, NULL } +}; + + + +//=============================================================== +// Name: HandleSetEntryThread +// Class: TriggerVolume +// +// Description: Handles the SetEntryThread event and sets the entry +// thread to the specified thread name. +// +// Parameters: Event* -- the event that triggered this +// +// Returns: None +// +//=============================================================== +void TriggerVolume::HandleSetEntryThreadEvent( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 1 ) + { + ev->Error("No entry threadname specified\n"); + return ; + } + + _entryThread = ev->GetString( 1 ); +} + + + +//=============================================================== +// Name: HandleSetExitThread +// Class: TriggerVolume +// +// Description: Handles the SetExitThread event and sets the exit +// thread to the specified thread name. +// +// Parameters: Event* -- the event that triggered this +// +// Returns: None +// +//=============================================================== +void TriggerVolume::HandleSetExitThreadEvent( Event *ev ) +{ + assert( ev ); + + if ( ev->NumArgs() != 1 ) + { + ev->Error("No exit threadname specified\n"); + return ; + } + + _exitThread = ev->GetString( 1 ); +} + + +//=============================================================== +// Name: HandleContactEvent +// Class: TriggerVolume +// +// Description: Handles an EV_Contact event. This function calls +// its entry_thread (if set) whenever an entity enters +// the event for the first time. +// +// Parameters: Event -- the event that triggered this call. The +// first argument must be an entity. +// +// Returns: None +// +//=============================================================== +void TriggerVolume::HandleContactEvent( Event *ev ) +{ + assert( ev ); + + + if (_entryThread.length()) + { + Entity *entityTouched = ev->GetEntity( 1 ); + ExecuteThread( _entryThread, true, entityTouched ); + return ; + } + + Trigger::TriggerStuff( ev ); +} + + +//=============================================================== +// Name: HandleTriggerExitEvent +// Class: TriggerVolume +// +// Description: Handles an EV_LostContact event. Called when we +// lose contact with an entity. This function calls +// an exit thread if set. +// +// Parameters: Event* -- the event that triggered this +// +// Returns: None +// +//=============================================================== +void TriggerVolume::HandleLostContactEvent( Event *ev ) +{ + if (_exitThread.length()) + { + Entity *entityLost = ev->GetEntity( 1 ); + ExecuteThread( _exitThread, true, entityLost ); + return ; + } + + Trigger::TriggerStuff( ev ); +} + +/*****************************************************************************/ +/*QUAKED trigger_ladder (1 0 0) ? x x NOT_PLAYERS MONSTERS + +Tells any entity that touches it that it is on a ladder + +Angle/Angles specifies the direction away from the ladder + +******************************************************************************/ + +CLASS_DECLARATION( Trigger, TriggerLadder, "trigger_ladder" ) +{ + { &EV_Touch, &TriggerLadder::handleTouchEvent }, + + { NULL, NULL } +}; + +void TriggerLadder::handleTouchEvent( Event *ev ) +{ + Player *player; + Entity *entity; + + if( !triggerable ) + return; + + entity = ev->GetEntity( 1 ); + + if ( !entity || !entity->isSubclassOf( Player ) ) + return; + + player = (Player *)entity; + + player->touchingLadder( this, triggerDir, absmax[2] ); +} diff --git a/dlls/game/trigger.h b/dlls/game/trigger.h new file mode 100644 index 0000000..c9f4bbb --- /dev/null +++ b/dlls/game/trigger.h @@ -0,0 +1,991 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/trigger.h $ +// $Revision:: 35 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Environment based triggers. +// + +#ifndef __TRIGGER_H__ +#define __TRIGGER_H__ + +#include "g_local.h" +#include "animate.h" +#include "groupcoordinator.hpp" + +class ScriptMaster; + +extern Event EV_Trigger_ActivateTargets; +extern Event EV_Trigger_SetWait; +extern Event EV_Trigger_SetDelay; +extern Event EV_Trigger_SetCount; +extern Event EV_Trigger_SetMessage; +extern Event EV_Trigger_SetNoise; +extern Event EV_Trigger_SetKey; +extern Event EV_Trigger_SetThread; +extern Event EV_Trigger_Effect; +extern Event EV_Trigger_StartThread; +extern Event EV_Trigger_SetKey; +extern Event EV_Trigger_SetTriggerable; +extern Event EV_Trigger_SetNotTriggerable; +extern Event EV_Trigger_SetDestructible; + + +#define TRIGGER_PLAYERS 4 +#define TRIGGER_MONSTERS 8 +#define TRIGGER_PROJECTILES 16 +#define TRIGGER_SCRIPTSLAVE 32 + +#define CHECK_TIME .5f + +class Trigger : public Entity + { + protected: + float wait; + float delay; + float trigger_time; + qboolean triggerActivated; + int count; + str noise; + str message; + str key; + str thread; + EntityPtr activator; + int respondto; + qboolean useTriggerDir; + float triggerCone; + Vector triggerDir; + float triggerDirYaw; + qboolean triggerable; + qboolean removable; // if count is 0, should this be removed? + qboolean edgeTriggered; // if true, trigger only triggers when entering trigger, not when standing in it + int multiFaceted; // if 0, it isn't. if 1 it is N/S oriented, if 2 it is E/W oriented + Container entList; + bool destructible ; + int instantdeath ; // set this to a MoD to have a trigger die instantly from a damage type + bool triggerondamage ; + bool triggerondeath ; + + // protected methods + void CheckEntList(Event *ev); + void AddOtherToEntList(Entity *other); + qboolean IsEntityInBoundingBox(Entity* ent); + bool CanTrigger(); + void SetTakeDamage(bool cantakedamage); + + public: + CLASS_PROTOTYPE( Trigger ); + + Trigger(); + virtual ~Trigger(); + + // override this to allow objects other than players, projectiles, and monsters to activate the trigger + virtual qboolean respondTo( Entity *other ); + + // override this to redirect messages to an entity other than the one who triggered it + virtual Entity *getActivator( Entity *other ); + + void SetModelEvent( Event *ev ); + void Touch( Event *ev ); + void EventSetWait( Event *ev ); + void EventSetDelay( Event *ev ); + void EventSetCount( Event *ev ); + void EventSetKey( Event *ev ); + void EventSetHealth( Event *ev ); + void EventSetDestructible( Event *ev ); + void EventSetInstantDeath( Event *ev ); + void EventSetTriggerOnDamage( Event *ev ); + void EventSetTriggerOnDeath( Event *ev ); + void EventSetThread( Event *ev ); + void EventGetLastActivatingEntity( Event *ev ); + void SetTriggerDir( Event *ev ); + void SetTriggerable( Event *ev ); + void SetNotTriggerable( Event *ev ); + void SetMultiFaceted( Event *ev ); + void SetEdgeTriggered( Event *ev ); + void SetTriggerCone( Event *ev ); + + void EventSetMessage( Event *ev ); + void SetMessage( const char *message ); + str &Message( void ); + + void EventSetNoise( Event *ev ); + void SetNoise( const char *text ); + str &Noise( void ); + + void setActivatingEntity( Entity *activatingEntity ); + + void SetTriggerDir( float angle ); + Vector GetTriggerDir( void ); + qboolean UsingTriggerDir( void ); + + void SetMultiFaceted( int newFacet ); + void SetEdgeTriggered( qboolean newEdge ); + + int GetMultiFaceted( void ); + qboolean GetEdgeTriggered( void ); + + void HandleDamage( Event *ev ); + void HandleKilled( Event *ev ); + + void StartThread( Event *ev ); + void TriggerStuff( Event *ev ); + void ActivateTargets( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void Trigger::Archive + ( + Archiver &arc + ) + { + Entity::Archive( arc ); + + arc.ArchiveFloat( &wait ); + arc.ArchiveFloat( &delay ); + arc.ArchiveFloat( &trigger_time ); + arc.ArchiveBoolean( &triggerActivated ); + arc.ArchiveInteger( &count ); + arc.ArchiveString( &noise ); + if ( arc.Loading() ) + { + SetNoise( noise.c_str() ); + } + arc.ArchiveString( &message ); + arc.ArchiveString( &key ); + arc.ArchiveString( &thread ); + arc.ArchiveSafePointer( &activator ); + arc.ArchiveInteger( &respondto ); + arc.ArchiveBoolean( &useTriggerDir ); + arc.ArchiveFloat( &triggerCone ); + arc.ArchiveVector( &triggerDir ); + arc.ArchiveFloat( &triggerDirYaw ); + arc.ArchiveBoolean( &triggerable ); + arc.ArchiveBoolean( &removable ); + arc.ArchiveBoolean( &edgeTriggered ); + arc.ArchiveInteger( &multiFaceted ); + + entList.Archive( arc ); + arc.ArchiveBool( &destructible ); + arc.ArchiveInteger( &instantdeath ); + arc.ArchiveBool( &triggerondamage ); + arc.ArchiveBool( &triggerondeath ); + } + +class LevelInteractionTrigger : public Trigger + { + private: + public: + CLASS_PROTOTYPE( LevelInteractionTrigger ); + + LevelInteractionTrigger(); + virtual qboolean respondTo( Entity *other ); + virtual void Archive( Archiver &arc ); + }; + +inline void LevelInteractionTrigger::Archive + ( + Archiver &arc + ) + + { + Trigger::Archive( arc ); + } + +class WorkTrigger; +typedef SafePtr WorkTriggerPtr; + +class WorkTrigger : public Trigger + { + private: + str _animation; + float _time; + qboolean _reserved; + int _currentEnt; + + Container _allowedWorkers; + + public: + CLASS_PROTOTYPE( WorkTrigger ); + + WorkTrigger(); + + void SetAnimation( Event *ev ); + str GetAnimation(); + + void SetTime( Event *ev ); + float GetTime(); + + void Reserve(int entNum ); + void Release(); + + void AddWorker( Event *ev ); + qboolean isAllowedToWork( const str &name , int entNum ); + qboolean isReserved(); + + + virtual void Archive( Archiver &arc ); + + }; + +inline void WorkTrigger::Archive ( Archiver &arc ) +{ + Trigger::Archive( arc ); + + arc.ArchiveString( &_animation ); + arc.ArchiveFloat( &_time ); + arc.ArchiveBoolean( &_reserved ); + arc.ArchiveInteger( &_currentEnt ); + + _allowedWorkers.Archive( arc ); +} + +class TouchField : public Trigger + { + private: + Event *ontouch; + EntityPtr owner; + + public: + CLASS_PROTOTYPE( TouchField ); + + TouchField(); + virtual void Setup( Entity *ownerentity, const Event &ontouch, const Vector &min, const Vector &max, int respondto ); + void SendEvent( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void TouchField::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.ArchiveEventPointer( &ontouch ); + arc.ArchiveSafePointer( &owner ); + } + +typedef SafePtr TouchFieldPtr; + +class TriggerOnce : public Trigger + { + public: + CLASS_PROTOTYPE( TriggerOnce ); + TriggerOnce(); + }; + +typedef enum + { + VAR_INCREMENT, + VAR_DECREMENT, + VAR_TOGGLE, + VAR_NORMAL + } variableType_t; + +class TriggerSetVariable : public Trigger + { + private: + str variableName; + str variableValue; + variableType_t variableType; + public: + CLASS_PROTOTYPE( TriggerSetVariable ); + + TriggerSetVariable(); + + void SetVariableName( Event * ev ); + void SetVariableValue( Event * ev ); + void SetVariable( Event * ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerSetVariable::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.ArchiveString( &variableName ); + arc.ArchiveString( &variableValue ); + ArchiveEnum( variableType, variableType_t ); + } + +class TriggerRelay : public Trigger + { + public: + CLASS_PROTOTYPE( TriggerRelay ); + + TriggerRelay(); + }; + +class TriggerSecret : public TriggerOnce + { + public: + CLASS_PROTOTYPE( TriggerSecret ); + + TriggerSecret(); + void FoundSecret( Event *ev ); + void Activate( Event *ev ); + }; + +class TriggerPush : public Trigger + { + protected: + float speed; + + public: + CLASS_PROTOTYPE( TriggerPush ); + + TriggerPush(); + void Push( Event *ev ); + void SetPushDir( Event *ev ); + void SetPushSpeed( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerPush::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.ArchiveFloat( &speed ); + } + +class TriggerPushAny : public Trigger + { + protected: + float speed; + + public: + CLASS_PROTOTYPE( TriggerPushAny ); + + TriggerPushAny(); + void Push( Event *ev ); + void SetSpeed( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerPushAny::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.ArchiveFloat( &speed ); + } + +#define AMBIENT_ON ( 1 << 0 ) +#define AMBIENT_OFF ( 1 << 1 ) +#define TOGGLESOUND ( 1 << 5 ) + +class TriggerPlaySound : public Trigger + { + protected: + friend class SoundManager; + + int state; + float min_dist; + float volume; + int channel; + qboolean ambient; + + public: + CLASS_PROTOTYPE( TriggerPlaySound ); + + TriggerPlaySound(); + void ToggleSound( Event *ev ); + void SetVolume( Event *ev ); + void SetMinDist( Event *ev ); + void SetChannel( Event *ev ); + + void StartSound( void ); + void SetVolume( float vol ); + void SetMinDist( float dist ); + + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerPlaySound::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.ArchiveInteger( &state ); + arc.ArchiveFloat( &min_dist ); + arc.ArchiveFloat( &volume ); + arc.ArchiveInteger( &channel ); + arc.ArchiveBoolean( &ambient ); + if ( arc.Loading() ) + { + // + // see if its a toggle sound, if it is, lets start its sound again + // + if ( spawnflags & TOGGLESOUND ) + { + // + // invert state so that final state will be right + // + state = !state; + PostEvent( EV_Trigger_Effect, EV_POSTSPAWN ); + } + } + } + +class TriggerSpeaker : public TriggerPlaySound + { + public: + CLASS_PROTOTYPE( TriggerSpeaker ); + + TriggerSpeaker(); + }; + +class RandomSpeaker : public TriggerSpeaker + { + protected: + friend class SoundManager; + + float chance; + float mindelay; + float maxdelay; + + public: + CLASS_PROTOTYPE( RandomSpeaker ); + + RandomSpeaker(); + void TriggerSound( Event *ev ); + void SetMinDelay( Event *ev ); + void SetMaxDelay( Event *ev ); + void SetChance( Event *ev ); + + void SetMinDelay( float value ); + void SetMaxDelay( float value ); + void SetChance( float value ); + void ScheduleSound( void ); + + virtual void Archive( Archiver &arc ); + }; + +inline void RandomSpeaker::Archive + ( + Archiver &arc + ) + { + TriggerSpeaker::Archive( arc ); + + arc.ArchiveFloat( &chance ); + arc.ArchiveFloat( &mindelay ); + arc.ArchiveFloat( &maxdelay ); + } + +class TriggerChangeLevel : public Trigger + { + protected: + str map; + str spawnspot; + str changethread; + + public: + CLASS_PROTOTYPE( TriggerChangeLevel ); + + TriggerChangeLevel(); + void SetMap( Event *ev ); + void SetSpawnSpot( Event *ev ); + void SetThread( Event *ev ); + void ChangeLevel( Event *ev ); + const char *Map( void ); + const char *SpawnSpot( void ); + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerChangeLevel::Archive + ( + Archiver &arc + ) + { + Trigger::Archive( arc ); + + arc.ArchiveString( &map ); + arc.ArchiveString( &spawnspot ); + arc.ArchiveString( &changethread ); + } + +class TriggerExit : public Trigger + { + public: + CLASS_PROTOTYPE( TriggerExit ); + + TriggerExit(); + void DisplayExitSign( Event *ev ); + void TurnExitSignOff( Event *ev ); + }; + + +class TriggerUse : public Trigger + { + public: + CLASS_PROTOTYPE( TriggerUse ); + + TriggerUse(); + }; + +class TriggerUseOnce : public TriggerUse + { + public: + CLASS_PROTOTYPE( TriggerUseOnce ); + + TriggerUseOnce(); + }; + +class TriggerHurt : public TriggerUse + { + protected: + float damage; + + void Hurt( Event *ev ); + void SetDamage( Event *ev ); + void setDamage( Event *ev ); // Gameplay version + + public: + CLASS_PROTOTYPE( TriggerHurt ); + + TriggerHurt(); + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerHurt::Archive + ( + Archiver &arc + ) + { + TriggerUse::Archive( arc ); + + arc.ArchiveFloat( &damage ); + } + +class TriggerDamageTargets : public Trigger + { + protected: + float damage; + + void DamageTargets( Event *ev ); + void SetDamage( Event *ev ); + void setDamage( Event *ev ); // Gameplay version + + public: + CLASS_PROTOTYPE( TriggerDamageTargets ); + + TriggerDamageTargets(); + void PassDamage( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerDamageTargets::Archive + ( + Archiver &arc + ) + + { + Trigger::Archive( arc ); + + arc.ArchiveFloat( &damage ); + } + +class TriggerCameraUse : public TriggerUse + { + public: + CLASS_PROTOTYPE( TriggerCameraUse ); + + void TriggerCamera( Event * ev ); + }; + +class TriggerBox : public Trigger + { + public: + CLASS_PROTOTYPE( TriggerBox ); + + void SetMins( Event *ev ); + void SetMaxs( Event *ev ); + }; + +class TriggerMusic : public Trigger + { + private: + friend class SoundManager; + + qboolean oneshot; + str current; + str fallback; + str altcurrent; + str altfallback; + public: + CLASS_PROTOTYPE( TriggerMusic ); + + TriggerMusic(); + void SetCurrentMood( Event *ev ); + void SetFallbackMood( Event *ev ); + void SetAltCurrentMood( Event *ev ); + void SetAltFallbackMood( Event *ev ); + void ChangeMood( Event *ev ); + void AltChangeMood( Event *ev ); + void SetOneShot( Event *ev ); + + void SetMood( const str &crnt, const str &fback ); + void SetAltMood( const str &crnt, const str &fback ); + void SetOneShot( qboolean once ); + + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerMusic::Archive + ( + Archiver &arc + ) + + { + Trigger::Archive( arc ); + + arc.ArchiveBoolean( &oneshot ); + arc.ArchiveString( ¤t ); + arc.ArchiveString( &fallback ); + arc.ArchiveString( &altcurrent ); + arc.ArchiveString( &altfallback ); + } + +class TriggerReverb : public Trigger + { + private: + friend class SoundManager; + + qboolean oneshot; + int reverbtype; + int altreverbtype; + float reverblevel; + float altreverblevel; + public: + CLASS_PROTOTYPE( TriggerReverb ); + + TriggerReverb(); + void SetReverbLevel( Event *ev ); + void SetReverbType( Event *ev ); + void SetAltReverbType( Event *ev ); + void SetAltReverbLevel( Event *ev ); + void ChangeReverb( Event *ev ); + void AltChangeReverb( Event *ev ); + void SetOneShot( Event *ev ); + + void SetReverb( int type, float level ); + void SetAltReverb( int type, float level ); + void SetOneShot( qboolean once ); + + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerReverb::Archive + ( + Archiver &arc + ) + + { + Trigger::Archive( arc ); + + arc.ArchiveBoolean( &oneshot ); + arc.ArchiveInteger( &reverbtype ); + arc.ArchiveInteger( &altreverbtype ); + arc.ArchiveFloat( &reverblevel ); + arc.ArchiveFloat( &altreverblevel ); + } + +class TriggerByPushObject : public TriggerOnce + { + private: + str triggername; + + void setTriggerName( Event *event ); + + public: + CLASS_PROTOTYPE( TriggerByPushObject ); + + virtual qboolean respondTo( Entity *other ); + virtual Entity *getActivator( Entity *other ); + + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerByPushObject::Archive + ( + Archiver &arc + ) + + { + TriggerOnce::Archive( arc ); + + arc.ArchiveString( &triggername ); + } + +class TriggerGivePowerup : public Trigger + { + private: + qboolean oneshot; + str powerup_name; + public: + CLASS_PROTOTYPE( TriggerGivePowerup ); + + TriggerGivePowerup(); + void SetOneShot( Event *ev ); + void SetPowerupName( Event *ev ); + void GivePowerup( Event *ev ); + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerGivePowerup::Archive + ( + Archiver &arc + ) + + { + Trigger::Archive( arc ); + + arc.ArchiveBoolean( &oneshot ); + arc.ArchiveString( &powerup_name ); + } + +class TriggerGroupEvent : public Trigger + { + private: + Event passEvent; + int groupNumber; + + public: + CLASS_PROTOTYPE( TriggerGroupEvent ); + + TriggerGroupEvent(); + void SetPassEvent( Event *ev ); + void SetGroupNumber( Event *ev ); + void PassEvent( Event *ev ); + void TriggerStuff( Event *ev ); + + virtual void Archive( Archiver &arc ); + + }; + +inline void TriggerGroupEvent::Archive( Archiver &arc ) + { + Trigger::Archive( arc ); + + arc.ArchiveEvent( &passEvent ); + arc.ArchiveInteger( &groupNumber ); + + } + + +//------------------------- CLASS ------------------------------ +// +// Name: TriggerCallVolume +// Base Class: Trigger +// +// Description: Allows the Level Designer to specify an arbitrary list +// of actors that are required to be within the volume +// before the specified trigger will fire. This can +// used for lifts that require teammates to be on before +// the lift should move. The volume is smart enough +// to recognize that not all actors in the list will be +// in the map, so it will fire the trigger when all +// available memembers of the list are in the volume +// +// It is also important to note that while the trigger +// will notify the actors in the list that the player +// is inside the volume, it will do nothing to get the +// actors in the volume itself. Helper Nodes and AI +// functionality will be required to utilize this +// volume as it is intended +// +// Method of Use: Level Editor +//-------------------------------------------------------------- +class TriggerCallVolume : public Trigger + { + private: + Container _requiredEntities; + qboolean _ready; + str _exitThread; + + protected: + Entity* _getEntity ( const str& name ); + + void _notifyRequiredEnts ( bool inCallVolume ); + void _checkForRequiredEnts (); + bool _isRequiredEnt ( Entity *other ); + + public: + CLASS_PROTOTYPE( TriggerCallVolume ); + + TriggerCallVolume(); + ~TriggerCallVolume(); + + void AddRequiredEntity ( Event *ev ); + void EntityLeftVolume ( Event *ev ); + void TriggerStuff ( Event *ev ); + void CheckReady ( Event *ev ); + void SetExitThread ( Event *ev ); + + virtual void Archive( Archiver &arc ); + + }; + +inline void TriggerCallVolume::Archive( Archiver &arc ) + { + Trigger::Archive( arc ); + _requiredEntities.Archive( arc ); + arc.ArchiveBoolean( &_ready ); + arc.ArchiveString( &_exitThread ); + } + +inline void TriggerCallVolume::SetExitThread( Event *ev ) +{ + _exitThread = ev->GetString( 1 ); +} + +class TriggerEntryAndExit : public Trigger + { + private: + str _entryThread; + str _exitThread; + qboolean _entered; + + //1ST PLAYABLE HACK + float _forcefieldtrigger; + float _triggernumber; + + public: + CLASS_PROTOTYPE( TriggerEntryAndExit ); + + TriggerEntryAndExit(); + ~TriggerEntryAndExit(); + + void EntityLeftVolume ( Event *ev ); + void TriggerStuff ( Event *ev ); + + void SetEntryThread ( Event *ev ); + void SetExitThread ( Event *ev ); + + void EnterTrigger ( Event *ev ); + void ExitTrigger ( Event *ev ); + + void EnterTrigger (); + void ExitTrigger (); + + // Hack stuff that needs to be removed + void HackSetParms( Event *ev ); + void HackGetForceFieldTrigger(Event *ev ); + void HackGetTriggerNumber(Event *ev ); + + virtual void Archive( Archiver &arc ); + }; + +inline void TriggerEntryAndExit::SetEntryThread( Event *ev ) +{ + _entryThread = ev->GetString( 1 ); +} + +inline void TriggerEntryAndExit::SetExitThread( Event *ev ) +{ + _exitThread = ev->GetString( 1 ); +} + +inline void TriggerEntryAndExit::EnterTrigger() +{ + _entered = true; +} + +inline void TriggerEntryAndExit::ExitTrigger() +{ + _entered = false; +} + +inline void TriggerEntryAndExit::EnterTrigger( Event *ev ) +{ + EnterTrigger(); +} + +inline void TriggerEntryAndExit::ExitTrigger( Event *ev ) +{ + ExitTrigger(); +} + +inline void TriggerEntryAndExit::Archive( Archiver &arc ) + { + Trigger::Archive( arc ); + arc.ArchiveString( &_entryThread ); + arc.ArchiveString( &_exitThread ); + arc.ArchiveBoolean( &_entered ); + + arc.ArchiveFloat( &_forcefieldtrigger ); + arc.ArchiveFloat( &_triggernumber ); + } + + + +//------------------------- CLASS ------------------------------ +// +// Name: TriggerVolume +// Base Class: Trigger +// +// Description: A TriggerVolume is a normal trigger that responds +// to both entrance (EV_Touch) and exit (EV_LostTouch) +// events. It is meant to be a base class for other +// triggers that use this functionality. +// +// Method of Use: Inheritance by other triggers +//-------------------------------------------------------------- +class TriggerVolume : public Trigger +{ +public: + CLASS_PROTOTYPE( TriggerVolume ); + ~TriggerVolume() { } + + void HandleTouchEvent( Event *ev ); + void HandleContactEvent( Event *ev ); + void HandleLostContactEvent( Event *ev ); + void HandleSetExitThreadEvent( Event *ev ); + void HandleSetEntryThreadEvent( Event *ev ); + + virtual void Archive( Archiver &arc ); + +private: + str _entryThread ; + str _exitThread ; +}; + +inline void TriggerVolume::Archive( Archiver &arc ) +{ + Trigger::Archive( arc ); + + arc.ArchiveString( &_entryThread ); + arc.ArchiveString( &_exitThread ); +} + +class TriggerLadder : public Trigger +{ +public: + CLASS_PROTOTYPE( TriggerLadder ); + + void handleTouchEvent( Event *ev ); +}; + +#endif /* trigger.h */ diff --git a/dlls/game/umap.h b/dlls/game/umap.h new file mode 100644 index 0000000..1bcc6cc --- /dev/null +++ b/dlls/game/umap.h @@ -0,0 +1,331 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/umap.h $ +// $Revision:: 2 $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1999 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Hash table based map class template. Maps text based keys to previously +// stored data. +// + +#ifndef __UMAP_H__ +#define __UMAP_H__ + +#include "str.h" +#include + +template< class Type > +class UMap : public Class + { + private: + struct hashnode_s + { + str key; + Type value; + hashnode_s *next; + + hashnode_s( const char *k, Type v, hashnode_s *n ) : key( k ), value( v ), next( n ) {}; + hashnode_s( const str &k, Type v, hashnode_s *n ) : key( k.c_str() ), value( v ), next( n ) {}; + }; + + hashnode_s **m_heads; + Type m_defaultvalue; + + unsigned m_tablesize; + unsigned m_numentries; + unsigned m_tablesizemask; + + unsigned getHash( const char *key ); + + public: + void Clear( void ); + unsigned getNumEntries( void ) const; + void DeleteDefaultValues ( void ); + + Type& operator[]( const char *key ); + Type& operator[]( const str &key ); + + const char *getKeyByIndex ( int index ) const; + Type& getValueByIndex ( unsigned index ); + + ~UMap(); + + UMap( Type defaultvalue, unsigned tablesize = 64 ) : m_defaultvalue( defaultvalue ), m_tablesize( tablesize ) + { + int i; + int bits; + + assert( m_tablesize > 0 ); + + m_heads = new hashnode_s *[ m_tablesize ]; + memset( m_heads, 0, sizeof( *m_heads ) * m_tablesize ); + + m_numentries = 0; + m_tablesizemask = 0; + + bits = 0; + for( i = 0; i < 32; i++ ) + { + if ( m_tablesize & ( 1 << i ) ) + { + bits++; + } + } + + if ( bits == 1 ) + { + m_tablesizemask = m_tablesize - 1; + } + }; + + UMap( UMap &map ) : m_defaultvalue( map.m_defaultvalue ), m_tablesize( map.m_tablesize ) + { + unsigned i; + hashnode_s *node; + hashnode_s **prev; + + assert( m_tablesize > 0 ); + + m_heads = new hashnode_s *[ m_tablesize ]; + m_numentries = map.m_numentries; + m_tablesizemask = map.m_tablesizemask; + + for( i = 0; i < m_tablesize; i++ ) + { + if ( !map.m_heads[ i ] ) + { + m_heads[ i ] = NULL; + continue; + } + + prev = &m_heads[ i ]; + for( node = map.m_heads[ i ]; node != NULL; node = node->next ) + { + *prev = new hashnode_s( node->key, node->value, NULL ); + prev = &( *prev )->next; + } + } + }; + }; + +template< class Type > +UMap::~UMap() + { + Clear(); + delete m_heads; + } + +template< class Type > +unsigned UMap::getHash + ( + const char *key + ) + + { + unsigned h; + const char *v; + + if ( m_tablesizemask ) + { + h = 0; + for( v = key; *v != '\0'; v++ ) + { + h = ( 64 * h + unsigned( *v ) ) & m_tablesizemask; + } + } + else + { + h = 0; + for( v = key; *v != '\0'; v++ ) + { + h = ( 64 * h + unsigned( *v ) ) % m_tablesize; + } + } + + return h; + } + +template< class Type > +Type& UMap::operator[] + ( + const char *key + ) + + { + hashnode_s **head; + hashnode_s *node; + unsigned hash; + + // FIXME + // if list was always sorted, could just insert new node as soon as we get + // node whose key is greater than the search key + hash = getHash( key ); + head = &m_heads[ hash ]; + if ( *head ) + { + for( node = *head; node != NULL; node = node->next ) + { + if ( node->key == key ) + { + return node->value; + } + } + } + + m_numentries++; + + *head = new hashnode_s( key, m_defaultvalue, *head ); + + return ( *head )->value; + } + +template< class Type > +const char * UMap::getKeyByIndex + ( + int index + ) const + + { + unsigned head; + + for ( head = 0; head < m_tablesize; head++ ) + { + hashnode_s *node; + + for ( node = m_heads[head]; node; node = node->next ) + { + if ( !index ) + return node->key.c_str (); + + index--; + } + } + + return NULL; + } + +template +Type &UMap::getValueByIndex + ( + unsigned index + ) + + { + unsigned head; + + ASSERT ( index < getNumEntries () ); + + for ( head = 0; head < m_tablesize; head++ ) + { + hashnode_s *node; + + for ( node = m_heads[head]; node; node = node->next ) + { + if ( !index ) + return node->value; + + index--; + } + } + + // Should never, EVER get here. + ASSERT ( 0 ); + return m_defaultvalue; + } + +template +inline void UMap::DeleteDefaultValues + ( + void + ) + + { + unsigned head; + + for ( head = 0; head < m_tablesize; head++ ) + { + hashnode_s *node; + + while ( m_heads[head] && m_heads[head]->value == m_defaultvalue ) + { + node = m_heads[head]; + m_heads[head] = node->next; + delete node; + m_numentries--; + } + + if ( !m_heads[head] ) + continue; + + for ( node = m_heads[head]; node->next; ) + { + hashnode_s *next = node->next; + + if ( next->value == m_defaultvalue ) + { + node->next = next->next; + delete next; + m_numentries--; + } + else + node = node->next; + } + } + } + +template< class Type > +inline Type& UMap::operator[] + ( + const str &key + ) + + { + return this->[ key.c_str() ]; + } + +template< class Type > +void UMap::Clear + ( + void + ) + + { + unsigned i; + hashnode_s *node; + hashnode_s *next; + + for( i = 0; i < m_tablesize; i++ ) + { + next = m_heads[ i ]; + while( next != NULL ) + { + node = next; + next = next->next; + delete node; + } + + m_heads[ i ] = NULL; + } + + m_numentries = 0; + } + +template< class Type > +inline unsigned UMap::getNumEntries + ( + void + ) const + + { + return m_numentries; + } + +#endif /* !__UMAP_H__ */ \ No newline at end of file diff --git a/dlls/game/useAlarm.cpp b/dlls/game/useAlarm.cpp new file mode 100644 index 0000000..3f29939 --- /dev/null +++ b/dlls/game/useAlarm.cpp @@ -0,0 +1,623 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/useAlarm.cpp $ +// $Revision:: 10 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CorridorCombatWithRangedWeapon Implementation +// +// PARAMETERS: +// str _movementAnim -- The animation to play while moving to the cover node +// +// ANIMATIONS: +// _movementAnim : PARAMETER +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "useAlarm.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, UseAlarm, NULL ) +{ + { &EV_Behavior_Args, &UseAlarm::SetArgs }, + { &EV_Behavior_AnimDone, &UseAlarm::AnimDone }, + { &EV_HelperNodeCommand, &UseAlarm::HandleNodeCommand }, + { NULL, NULL } +}; + +//-------------------------------------------------------------- +// Name: UseAlarm() +// Class: UseAlarm() +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +UseAlarm::UseAlarm() +{ + _movementAnimName = "walk"; + _maxDistance = 1024.0f; +} + +//-------------------------------------------------------------- +// Name: ~UseAlarm() +// Class: UseAlarm +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +UseAlarm::~UseAlarm() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: UseAlarm +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::SetArgs ( Event *ev) +{ + if (ev->NumArgs() == 1) + { + _movementAnimName = ev->GetString( 1 ); + } +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: UseAlarm +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::AnimDone( Event *ev ) +{ + switch( _state ) + { + case USE_ALARM_WAIT_ON_ANIM: + _animDone = true; + break; + } + +} + +//-------------------------------------------------------------- +// Name: HandleNodeCommand() +// Class: UseAlarm +// +// Description: Handles a command event from a helper node +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::HandleNodeCommand( Event *ev ) +{ +} + +//-------------------------------------------------------------- +// Name: Begin() +// Class: UseAlarm +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::Begin( Actor &self ) +{ + init( self ); +} + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: UseAlarm +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t UseAlarm::Evaluate ( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case USE_ALARM_FIND_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateFindNode(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( USE_ALARM_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( USE_ALARM_MOVE_TO_NODE ); + break; + + + //--------------------------------------------------------------------- + case USE_ALARM_MOVE_TO_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateMoveToNode(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( USE_ALARM_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( USE_ALARM_AT_NODE ); + + break; + + //--------------------------------------------------------------------- + case USE_ALARM_AT_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateAtNode(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( USE_ALARM_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( USE_ALARM_WAIT_ON_ANIM ); + + break; + + //--------------------------------------------------------------------- + case USE_ALARM_WAIT_ON_ANIM: + //--------------------------------------------------------------------- + stateResult = evaluateStateWaitOnAnim(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( USE_ALARM_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( USE_ALARM_ROTATE_TO_ENEMY ); + break; + + //--------------------------------------------------------------------- + case USE_ALARM_ROTATE_TO_ENEMY: + //--------------------------------------------------------------------- + stateResult = evaluateStateRotateToEnemy(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( USE_ALARM_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( USE_ALARM_SUCCESSFUL ); + + break; + + //--------------------------------------------------------------------- + case USE_ALARM_SUCCESSFUL: + //--------------------------------------------------------------------- + _node->RunExitThread(); + return BEHAVIOR_SUCCESS; + break; + + //--------------------------------------------------------------------- + case USE_ALARM_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + break; + + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: UseAlarm +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::End ( Actor &self ) +{ + if ( _node ) + { + _node->SetUser( NULL ); + } +} + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: UseAlarm +// +// Description: Transitions Internal State +// +// Parameters: workStates_t state +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::transitionToState ( useAlarmStates_t state ) +{ + switch ( state ) + { + case USE_ALARM_FIND_NODE: + setupStateFindNode(); + setInternalState( state , "USE_ALARM_FIND_NODE" ); + break; + + case USE_ALARM_MOVE_TO_NODE: + setupStateMoveToNode(); + setInternalState( state , "USE_ALARM_MOVE_TO_NODE" ); + break; + + case USE_ALARM_AT_NODE: + setupStateAtNode(); + setInternalState( state , "USE_ALARM_AT_NODE" ); + break; + + case USE_ALARM_WAIT_ON_ANIM: + setupStateWaitOnAnim(); + setInternalState( state , "WORK_ANIMATE_WAIT_ON_ANIM" ); + break; + + case USE_ALARM_ROTATE_TO_ENEMY: + setupStateRotateToEnemy(); + setInternalState( state, "USE_ALARM_ROTATE_TO_ENEMY" ); + break; + + case USE_ALARM_SUCCESSFUL: + setInternalState( state , "WORK_SUCCESSFUL" ); + break; + + case USE_ALARM_FAILED: + setInternalState( state , "WORK_FAILED" ); + break; + } + +} + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: UseAlarm +// +// Description: Sets internal state of the behavior +// +// Parameters: workStates_t state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::setInternalState ( useAlarmStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + + + +//-------------------------------------------------------------- +// Name: init() +// Class: UseAlarm +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::init( Actor &self ) +{ + _self = &self; + _animDone = false; + transitionToState( USE_ALARM_FIND_NODE ); +} + +//-------------------------------------------------------------- +// Name: think() +// Class: UseAlarm +// +// Description: Think function called every frame by evaluate +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::think() +{ +} + +//-------------------------------------------------------------- +// Name: setupStateFindNode() +// Class: UseAlarm +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::setupStateFindNode() +{ + str currentNodeCustomType; + if ( _self->currentHelperNode.node ) + currentNodeCustomType = _self->currentHelperNode.node->GetCustomType(); + + if ( currentNodeCustomType == "alarm" ) + _node = &(*_self->currentHelperNode.node); + else + _node = HelperNode::FindClosestHelperNode( *_self , "alarm" , _maxDistance ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateFindNode() +// Class: UseAlarm +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t UseAlarm::evaluateStateFindNode() +{ + if ( !_node ) + return BEHAVIOR_FAILED; + + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateFindNode() +// Class: UseAlarm +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::failureStateFindNode( const str& failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateMoveToNode() +// Class: UseAlarm +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::setupStateMoveToNode() +{ + _gotoHelperNode.SetNode( _node ); + _gotoHelperNode.SetMovementAnim ( _movementAnimName ); + _gotoHelperNode.Begin( *_self ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateMoveToNode() +// Class: UseAlarm +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t UseAlarm::evaluateStateMoveToNode() +{ + return _gotoHelperNode.Evaluate( *_self ); +} + +//-------------------------------------------------------------- +// Name: failureStateMoveToNode() +// Class: UseAlarm +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::failureStateMoveToNode( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateAtNode() +// Class: UseAlarm +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::setupStateAtNode() +{ + _self->SetAnim( "idle" ); + _self->RunThread( _node->GetEntryThread() ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateAtNode() +// Class: UseAlarm +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t UseAlarm::evaluateStateAtNode() +{ + Vector nodeAngles = _node->angles; + + _self->movementSubsystem->setAnimDir( nodeAngles ); + _self->setAngles( nodeAngles ); + + _node->SetUser( this ); + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateAtNode() +// Class: UseAlarm +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::failureStateAtNode( const str& failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateWaitOnAnim() +// Class: UseAlarm +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::setupStateWaitOnAnim() +{ + if( ! _self->SetAnim( _node->GetAnim() , EV_Actor_NotifyBehavior , legs ) ) + { + // setanim returns false if the node-specified anim doesn't exist, so + // set _animDone=true to avoid them just standing there. + _animDone = true; + } + else + { + // anim was set successfully, so let the behavior know it should wait for the + // anim to finish before continuing. + _animDone = false; + } +} + +//-------------------------------------------------------------- +// Name: evaluateStateWaitOnAnim() +// Class: UseAlarm +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t UseAlarm::evaluateStateWaitOnAnim() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateWaitOnAnim() +// Class: UseAlarm +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::failureStateWaitOnAnim( const str& failureReason ) +{ +} + +//-------------------------------------------------------------- +// Name: setupStateRotateToEnemy() +// Class: UseAlarm +// +// Description: Sets up state +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::setupStateRotateToEnemy() +{ + Entity *enemy = _self->enemyManager->GetCurrentEnemy(); + + if ( !enemy ) + return; + + _rotateToEntity.SetAnim( "idle" ); + _rotateToEntity.SetEntity( enemy ); + _rotateToEntity.SetTurnSpeed( 25.0f ); + _rotateToEntity.Begin( *_self ); + +} + +//-------------------------------------------------------------- +// Name: evaluateStateRotateToEnemy() +// Class: UseAlarm +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t UseAlarm::evaluateStateRotateToEnemy() +{ + return _rotateToEntity.Evaluate( *_self ); +} + +//-------------------------------------------------------------- +// Name: failureStateRotateToEnemy() +// Class: UseAlarm +// +// Description: Failure Handler for State +// +// Parameters: const str &failureReason +// +// Returns: None +//-------------------------------------------------------------- +void UseAlarm::failureStateRotateToEnemy( const str &failureReason ) +{ + +} + diff --git a/dlls/game/useAlarm.hpp b/dlls/game/useAlarm.hpp new file mode 100644 index 0000000..6002fec --- /dev/null +++ b/dlls/game/useAlarm.hpp @@ -0,0 +1,145 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/work.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CooridorCombatWithRangedWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class UseAlarm; + +#ifndef __USE_ALARM_HPP__ +#define __USE_ALARM_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" +#include "gotoHelperNode.hpp" + +//------------------------- CLASS ------------------------------ +// +// Name: UseAlarm +// Base Class: Behavior +// +// Description: Makes the Actor use Work Helper Nodes +// +// Method of Use: Statemachine or another behavior +//-------------------------------------------------------------- +class UseAlarm : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + USE_ALARM_FIND_NODE, + USE_ALARM_MOVE_TO_NODE, + USE_ALARM_AT_NODE, + USE_ALARM_WAIT_ON_ANIM, + USE_ALARM_ROTATE_TO_ENEMY, + USE_ALARM_SUCCESSFUL, + USE_ALARM_FAILED + } useAlarmStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: // Parameters + str _movementAnimName ; // anim to play to move to work node, default is "walk" + float _maxDistance; // maximum distance to look for node + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( useAlarmStates_t state ); + void setInternalState ( useAlarmStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + + void setupStateFindNode (); + BehaviorReturnCode_t evaluateStateFindNode (); + void failureStateFindNode ( const str& failureReason ); + + void setupStateMoveToNode (); + BehaviorReturnCode_t evaluateStateMoveToNode (); + void failureStateMoveToNode ( const str& failureReason ); + + void setupStateAtNode (); + BehaviorReturnCode_t evaluateStateAtNode (); + void failureStateAtNode ( const str& failureReason ); + + void setupStateWaitOnAnim (); + BehaviorReturnCode_t evaluateStateWaitOnAnim (); + void failureStateWaitOnAnim ( const str& failureReason ); + + void setupStateRotateToEnemy (); + BehaviorReturnCode_t evaluateStateRotateToEnemy (); + void failureStateRotateToEnemy ( const str& failureReason ); + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( UseAlarm ); + UseAlarm(); + ~UseAlarm(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void HandleNodeCommand ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + virtual void Archive ( Archiver &arc ); + + private: // Component Behaviors + GotoHelperNode _gotoHelperNode; + RotateToEntity _rotateToEntity; + + private: // Member Variables + HelperNodePtr _node; + unsigned int _state; + bool _animDone; + Actor* _self; + + }; + +inline void UseAlarm::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Parameters + arc.ArchiveString ( &_movementAnimName); + arc.ArchiveFloat ( &_maxDistance ); + + // Archive Components + arc.ArchiveObject ( &_gotoHelperNode ); + arc.ArchiveObject ( &_rotateToEntity ); + + + // Archive Member Vars + arc.ArchiveSafePointer ( &_node ); + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveBool ( &_animDone ); + arc.ArchiveObjectPointer ( ( Class ** )&_self ); +} + +#endif /* __USE_ALARM__ */ diff --git a/dlls/game/vector.h b/dlls/game/vector.h new file mode 100644 index 0000000..e7558ed --- /dev/null +++ b/dlls/game/vector.h @@ -0,0 +1,1078 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/vector.h $ +// $Revision:: 24 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// C++ implemention of a Vector object. Handles standard vector operations +// such as addition, subtraction, normalization, scaling, dot product, +// cross product, length, and decomposition into Euler angles. +// +// WARNING: This file is shared between game, cgame and possibly the user interface. +// It is instanced in each one of these directories because of the way that SourceSafe works. +// + +#ifndef __VECTOR_H__ +#define __VECTOR_H__ + +#ifdef GAME_DLL +#include "g_local.h" +#endif + +#include +#include + +#ifdef __Q_FABS__ +#define VECTOR_FABS Q_fabs +#else +#define VECTOR_FABS fabs +#endif + +class Vector +{ +public: + float x; + float y; + float z; + + Vector(); + Vector( const vec3_t src ); + Vector( const float x, const float y, const float z ); + explicit Vector( const char *text ); + + operator float * (); + operator float const * () const; + + float pitch( void ) const; + float yaw( void ) const; + float roll( void ) const; + float operator[]( const int index ) const; + float & operator[]( const int index ); + void copyTo( vec3_t vec ) const; + void setPitch( const float x ); + void setYaw( const float y ); + void setRoll( const float z ); + void setXYZ( const float x, const float y, const float z ); + const Vector & operator=( const Vector &a ); + const Vector & operator=( vec3_t a ); + friend Vector operator+( const Vector &a, const Vector &b ); + friend Vector operator+( vec3_t a, const Vector &b ); + friend Vector operator+( const Vector &a, vec3_t b ); + const Vector & operator+=( const Vector &a ); + const Vector & operator+=( vec3_t a ); + friend Vector operator-( const Vector &a, const Vector &b ); + friend Vector operator-( vec3_t a, const Vector &b ); + friend Vector operator-( const Vector &a, vec3_t b ); + const Vector & operator-=( const Vector &a ); + const Vector & operator-=( vec3_t a ); + friend Vector operator*( const Vector &a, const float b ); + friend Vector operator*( const float a, const Vector &b ); + friend float operator*( const Vector &a, const Vector &b ); + friend float operator*( vec3_t a, const Vector &b ); + friend float operator*( const Vector &a, vec3_t b ); + const Vector & operator*=( const float a ); + friend Vector operator/( const Vector &a, const float b ); + const Vector & operator/=( const float a ); + friend int operator==( const Vector &a, const Vector &b ); + friend int operator==( vec3_t a, const Vector &b ); + friend int operator==( const Vector &a, vec3_t b ); + friend int operator!=( const Vector &a, const Vector &b ); + friend int operator!=( vec3_t a, const Vector &b ); + friend int operator!=( const Vector &a, vec3_t b ); + int FuzzyEqual( const Vector &b, const float epsilon ) const; + int FuzzyEqual( vec3_t b, const float epsilon ) const; + const Vector & CrossProduct( const Vector &a, const Vector &b ); + const Vector & CrossProduct( vec3_t a, const Vector &b ); + const Vector & CrossProduct( const Vector &a, vec3_t b ); + float length( void ) const; + float lengthSquared( void ) const; + float lengthXY( void ) const; + float normalize( void ); + void EulerNormalize( void ); + void EulerNormalize360( void ); + static Vector Clamp( Vector &value, const Vector &min, const Vector &max ); + static Vector Cross( const Vector &vector1, const Vector &vector2 ); + static float Dot( const Vector &vector1, const Vector &vector2 ); + static float Dot( vec3_t a, const Vector &b ); + static float Dot( const Vector &a, vec3_t b ); + static float Distance( const Vector &vector1, const Vector &vector2 ); + static float DistanceSquared( const Vector &vector1, const Vector &vector2 ); + static float DistanceXY( const Vector &vector1, const Vector &vector2 ); + static Vector AnglesBetween( const Vector &vector1, const Vector &vector2 ); + static float AngleBetween( const Vector &vector1, const Vector &vector2 ); + static bool CloseEnough( const Vector &vector1, const Vector &vector2, const float epsilon = Vector::Epsilon()) ; + static bool SmallEnough( const Vector &vector, const float epsilon = Vector::Epsilon() ); + static float Epsilon( void ); + static Vector & Identity( void ); + Vector operator-( void ) const; + friend Vector fabs( const Vector &a ); + float toYaw( void ) const; + float toPitch( void ) const; + Vector toAngles( void ) const; + void AngleVectors( Vector *forward, Vector *left = NULL, Vector *up = NULL ) const; + friend Vector LerpVector( const Vector &w1, const Vector &w2, const float t ); + friend float MaxValue( const Vector &a ); +}; + +extern Vector vec_zero; + +inline float Vector::pitch( void ) const { return x; } +inline float Vector::yaw( void ) const { return y; } +inline float Vector::roll( void ) const { return z; } +inline void Vector::setPitch( float pitch ) { x = pitch; } +inline void Vector::setYaw( float yaw ) { y = yaw; } +inline void Vector::setRoll( float roll ) { z = roll; } + +inline void Vector::copyTo( vec3_t vec ) const +{ + vec[ 0 ] = x; + vec[ 1 ] = y; + vec[ 2 ] = z; +} + +inline float Vector::operator[]( const int index ) const +{ + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &x )[ index ]; +} + +inline float& Vector::operator[]( const int index ) +{ + assert( ( index >= 0 ) && ( index < 3 ) ); + return ( &x )[ index ]; +} + +inline void Vector::setXYZ( const float new_x, const float new_y,const float new_z ) +{ + x = new_x; + y = new_y; + z = new_z; +} + +inline Vector::Vector(): x( 0 ), y( 0 ), z( 0 ) +{ +} + +inline Vector::Vector( const vec3_t src ): x( src[0] ), y( src[1] ), z( src[2] ) +{ +} + +inline Vector::Vector( const float init_x, const float init_y, const float init_z ): x( init_x ), y( init_y ), z( init_z ) +{ +} + +inline Vector::Vector( const char *text ): x( 0 ), y( 0 ), z( 0 ) + +{ + if ( text ) + { + if ( text[0] == '"' ) + sscanf( text, "\"%f %f %f\"", &x, &y, &z ); + else + sscanf( text, "%f %f %f", &x, &y, &z ); + } +} + +inline Vector::operator float * ( void ) +{ + return &x; +} + +inline Vector::operator float const * ( void ) const +{ + return &x; +} + +inline const Vector & Vector::operator=( const Vector &a ) +{ + x = a.x; + y = a.y; + z = a.z; + + return *this; +} + +inline const Vector & Vector::operator=( vec3_t a ) +{ + x = a[ 0 ]; + y = a[ 1 ]; + z = a[ 2 ]; + + return *this; +} + +inline Vector operator+( const Vector &a, const Vector &b ) +{ + return Vector( a.x + b.x, a.y + b.y, a.z + b.z ); +} + +inline Vector operator+( vec3_t a, const Vector &b ) +{ + return Vector( a[ 0 ] + b.x, a[ 1 ] + b.y, a[ 2 ] + b.z ); +} + +inline Vector operator+( const Vector &a, vec3_t b ) +{ + return Vector( a.x + b[ 0 ], a.y + b[ 1 ], a.z + b[ 2 ] ); +} + +inline const Vector & Vector::operator+=( const Vector &a ) +{ + x += a.x; + y += a.y; + z += a.z; + + return *this; +} + +inline const Vector & Vector::operator+=( vec3_t a ) +{ + x += a[ 0 ]; + y += a[ 1 ]; + z += a[ 2 ]; + + return *this; +} + +inline Vector operator-( const Vector &a, const Vector &b ) +{ + return Vector( a.x - b.x, a.y - b.y, a.z - b.z ); +} + +inline Vector operator-( vec3_t a, const Vector &b ) +{ + return Vector( a[ 0 ] - b.x, a[ 1 ] - b.y, a[ 2 ] - b.z ); +} + +inline Vector operator-( const Vector &a, vec3_t b ) +{ + return Vector( a.x - b[ 0 ], a.y - b[ 1 ], a.z - b[ 2 ] ); +} + +inline const Vector & Vector::operator-=( const Vector &a ) +{ + x -= a.x; + y -= a.y; + z -= a.z; + + return *this; +} + +inline const Vector & Vector::operator-=( vec3_t a ) +{ + x -= a[ 0 ]; + y -= a[ 1 ]; + z -= a[ 2 ]; + + return *this; +} + +inline Vector operator*( const Vector &a, const float b ) +{ + return Vector( a.x * b, a.y * b, a.z * b ); +} + +inline Vector operator*( const float a, const Vector &b ) +{ + return b * a; +} + +inline float operator*( const Vector &a, const Vector &b ) +{ + return ( a.x * b.x ) + ( a.y * b.y ) + ( a.z * b.z ); +} + +inline float operator*( vec3_t a, const Vector &b ) +{ + return ( a[ 0 ] * b.x ) + ( a[ 1 ] * b.y ) + ( a[ 2 ] * b.z ); +} + +inline float operator*( const Vector &a, vec3_t b ) +{ + return ( a.x * b[ 0 ] ) + ( a.y * b[ 1 ] ) + ( a.z * b[ 2 ] ); +} + +inline const Vector& Vector::operator*=( const float a ) +{ + x *= a; + y *= a; + z *= a; + + return *this; +} + +inline Vector operator/( const Vector &a, const float b ) +{ + return Vector (a.x/b, a.y/b, a.z/b); +} + +inline const Vector & Vector::operator/=( const float a ) +{ + *this=*this/a; + return *this; +} + +inline int Vector::FuzzyEqual( const Vector &b, const float epsilon ) const +{ + return + ( + ( VECTOR_FABS( x - b.x ) < epsilon ) && + ( VECTOR_FABS( y - b.y ) < epsilon ) && + ( VECTOR_FABS( z - b.z ) < epsilon ) + ); +} + +inline int Vector::FuzzyEqual( vec3_t b, const float epsilon ) const +{ + return + ( + ( VECTOR_FABS( x - b[ 0 ] ) < epsilon ) && + ( VECTOR_FABS( y - b[ 1 ] ) < epsilon ) && + ( VECTOR_FABS( z - b[ 2 ] ) < epsilon ) + ); +} + +inline int operator==( const Vector &a, const Vector &b ) + +{ + return ( ( a.x == b.x ) && ( a.y == b.y ) && ( a.z == b.z ) ); +} + +inline int operator==( vec3_t a, const Vector &b ) +{ + return ( ( a[ 0 ] == b.x ) && ( a[ 1 ] == b.y ) && ( a[ 2 ] == b.z ) ); +} + +inline int operator==( const Vector &a, vec3_t b ) +{ + return ( ( a.x == b[ 0 ] ) && ( a.y == b[ 1 ] ) && ( a.z == b[ 2 ] ) ); +} + +inline int operator!=( const Vector &a, const Vector &b ) +{ + return ( ( a.x != b.x ) || ( a.y != b.y ) || ( a.z != b.z ) ); +} + +inline int operator!=( vec3_t a, const Vector &b ) +{ + return ( ( a[ 0 ] != b.x ) || ( a[ 1 ] != b.y ) || ( a[ 2 ] != b.z ) ); +} + +inline int operator!=( const Vector &a, vec3_t b ) +{ + return ( ( a.x != b[ 0 ] ) || ( a.y != b[ 1 ] ) || ( a.z != b[ 2 ] ) ); +} + +inline const Vector & Vector::CrossProduct( const Vector &a, const Vector &b ) +{ + x = ( a.y * b.z ) - ( a.z * b.y ); + y = ( a.z * b.x ) - ( a.x * b.z ); + z = ( a.x * b.y ) - ( a.y * b.x ); + + return *this; +} + +inline const Vector & Vector::CrossProduct( vec3_t a, const Vector &b ) +{ + x = ( a[ 1 ] * b.z ) - ( a[ 2 ] * b.y ); + y = ( a[ 2 ] * b.x ) - ( a[ 0 ] * b.z ); + z = ( a[ 0 ] * b.y ) - ( a[ 1 ] * b.x ); + + return *this; +} + +inline const Vector & Vector::CrossProduct( const Vector &a, vec3_t b ) +{ + x = ( a.y * b[ 2 ] ) - ( a.z * b[ 1 ] ); + y = ( a.z * b[ 0 ] ) - ( a.x * b[ 2 ] ); + z = ( a.x * b[ 1 ] ) - ( a.y * b[ 0 ] ); + + return *this; +} + +inline Vector Vector::Clamp( Vector &value, const Vector &minimum, const Vector &maximum ) +{ + Vector clamped(value); + for (int i=0; i<3; i++) + { + const float min = minimum[i]; + const float max = maximum[i]; + assert( min <= max ); + + if (clamped[i] < min) + { + clamped[i] = min; + } + else if (clamped[i] > max) + { + clamped[i] = max; + } + } + return clamped; +} +inline Vector Vector::Cross( const Vector &vector1, const Vector &vector2 ) +{ + const Vector result ( + ( vector1.y * vector2.z ) - ( vector1.z * vector2.y ), + ( vector1.z * vector2.x ) - ( vector1.x * vector2.z ), + ( vector1.x * vector2.y ) - ( vector1.y * vector2.x ) + ); + + return result; +} + + +inline float Vector::Dot( const Vector &vector1, const Vector &vector2 ) +{ + return vector1 * vector2; +} + +inline float Vector::Dot( vec3_t vector1, const Vector &vector2 ) +{ + return vector1 * vector2; +} + +inline float Vector::Dot( const Vector &vector1, vec3_t vector2 ) +{ + return vector1 * vector2; +} + +//---------------------------------------------------------------- +// Name: lengthSquared +// Class: Vector +// +// Description: Returns squared length of the vector +// +// Parameters: None +// +// Returns: float - squared length +//---------------------------------------------------------------- +inline float Vector::lengthSquared( void ) const +{ + return ( x * x ) + ( y * y ) + ( z * z ); +} + +inline float Vector::length( void ) const +{ + return sqrt( lengthSquared() ); +} + +//---------------------------------------------------------------- +// Name: lengthXY +// Class: Vector +// +// Description: Returns length of the vector (using only the x +// and y components +// +// Parameters: None +// +// Returns: float - length of the vector in the xy plane +//---------------------------------------------------------------- +inline float Vector::lengthXY( void ) const +{ + return sqrt(( x * x ) + ( y * y )); +} + +//---------------------------------------------------------------- +// Name: normalize +// Class: Vector +// +// Description: unitizes the vector +// +// Parameters: None +// +// Returns: float - length of the vector before the function +//---------------------------------------------------------------- +inline float Vector::normalize( void ) +{ + float length, ilength; + + length = this->length(); + if ( length ) + { + ilength = 1.0f / length; + x *= ilength; + y *= ilength; + z *= ilength; + } + + return length; +} + +//---------------------------------------------------------------- +// Name: EulerNormalize +// Class: Vector +// +// Description: forces each component of the vector into the +// range (-180, +180) by adding or subtracting 360 +// This is useful when the Vector is being used as +// EulerAngles to represent a rotational offset +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +inline void Vector::EulerNormalize( void ) +{ + x = AngleNormalize180( x ); + y = AngleNormalize180( y ); + z = AngleNormalize180( z ); +} + +//---------------------------------------------------------------- +// Name: EulerNormalize360 +// Class: Vector +// +// Description: forces each component of the vector into the +// range (0, +360) by adding or subtracting 360 +// This is useful when the Vector is being used as +// EulerAngles to represent a rotational direction +// +// Parameters: None +// +// Returns: None +//---------------------------------------------------------------- +inline void Vector::EulerNormalize360( void ) +{ + x = AngleNormalize360( x ); + y = AngleNormalize360( y ); + z = AngleNormalize360( z ); +} + +//---------------------------------------------------------------- +// Name: Epsilon +// Class: Vector +// +// Description: returns a standard 'small' value for the class +// +// Parameters: None +// +// Returns: float - the epsilon constant for the class +//---------------------------------------------------------------- +inline float Vector::Epsilon( void ) +{ + return 0.000000001f; +} + +//---------------------------------------------------------------- +// Name: Identity +// Class: Vector +// +// Description: returns the additive identity for the class +// +// Parameters: None +// +// Returns: Vector - the identity for the class +//---------------------------------------------------------------- +inline Vector & Vector::Identity(void) +{ + return vec_zero; +} + +//---------------------------------------------------------------- +// Name: Distance +// Class: Vector +// +// Description: returns the distance between two vectors +// +// Parameters: +// Vector - first vector +// Vector - second vector +// +// Returns: float - distance between the two vectors +//---------------------------------------------------------------- +inline float Vector::Distance(const Vector &vector1, const Vector &vector2) +{ + return (vector1 - vector2).length(); +} + +//---------------------------------------------------------------- +// Name: DistanceSquared +// Class: Vector +// +// Description: returns the squared distance between two vectors +// +// Parameters: +// Vector - first vector +// Vector - second vector +// +// Returns: float - distance between the two vectors squared +//---------------------------------------------------------------- +inline float Vector::DistanceSquared(const Vector &vector1, const Vector &vector2) +{ + return (vector1 - vector2).lengthSquared(); +} + +//---------------------------------------------------------------- +// Name: DistanceXY +// Class: Vector +// +// Description: returns the distance between two vectors in the +// xy plane +// +// Parameters: +// Vector - first vector +// Vector - second vector +// +// Returns: float - distance between the two vectors in the +// xy plane +//---------------------------------------------------------------- +inline float Vector::DistanceXY(const Vector &vector1, const Vector &vector2) +{ + return (vector1 - vector2).lengthXY(); +} + +inline Vector Vector::toAngles( void ) const +{ + float forward; + float yaw, pitch; + + if ( ( x == 0.0f ) && ( y == 0.0f ) ) + { + yaw = 0.0f; + if ( z > 0.0f ) + { + pitch = 90.0f; + } + else + { + pitch = 270.0f; + } + } + else + { + yaw = atan2( y, x ) * 180.0f / M_PI; + if ( yaw < 0.0f ) + { + yaw += 360.0f; + } + + forward = ( float )sqrt( x * x + y * y ); + pitch = atan2( z, forward ) * 180.0f / M_PI; + if ( pitch < 0.0f ) + { + pitch += 360.0f; + } + } + + return Vector( -pitch, yaw, 0.0f ); +} + +//---------------------------------------------------------------- +// Name: AnglesBetween +// Class: Vector +// +// Description: returns the smaller of the angles formed by the +// two vectors +// +// Parameters: +// Vector - first vector +// Vector - second vector +// +// Returns: Vector - angles between the vectors +//---------------------------------------------------------------- +inline Vector Vector::AnglesBetween(const Vector &vector1, const Vector &vector2) +{ + Vector unitVector1(vector1); + unitVector1.normalize(); + Vector unitVector2(vector2); + unitVector2.normalize(); + Vector angles(unitVector1.toAngles() - unitVector2.toAngles()); + angles.EulerNormalize(); + + return angles; +} + +//---------------------------------------------------------------- +// Name: AngleBetween +// Class: Vector +// +// Description: returns the smaller of the angles formed by the +// two vectors +// +// Parameters: +// Vector - first vector +// Vector - second vector +// +// Returns: float - angle between the vectors +//---------------------------------------------------------------- +inline float Vector::AngleBetween(const Vector &vector1, const Vector &vector2) +{ + Vector unitVector1(vector1); + unitVector1.normalize(); + Vector unitVector2(vector2); + unitVector2.normalize(); + + return acos( Vector::Dot( unitVector1, unitVector2 ) ); +} + +//---------------------------------------------------------------- +// Name: CloseEnough +// Class: Vector +// +// Description: tests to see if the two vectors are within +// 'epsilon' of each other +// +// Parameters: +// Vector - first vector +// Vector - second vector +// float - amount that each component of the +// vectors can be apart +// +// Returns: bool - the result of the test for closeness +//---------------------------------------------------------------- +inline bool Vector::CloseEnough(const Vector &vector1, const Vector &vector2, const float epsilon) +{ + return Distance(vector1, vector2) < epsilon; +} + +//---------------------------------------------------------------- +// Name: SmallEnough +// Class: Vector +// +// Description: tests to see if the vectors are within +// 'epsilon' of the origin +// +// Parameters: +// Vector - vector +// float - amount that each component of the +// vectors can be from the origin +// +// Returns: bool - the result of the test for smallness +//---------------------------------------------------------------- +inline bool Vector::SmallEnough(const Vector &vector, const float epsilon) +{ + return CloseEnough(vector, Vector::Identity(), epsilon); +} + +inline Vector Vector::operator-() const +{ + return Vector( -x, -y, -z ); +} + +inline Vector fabs( const Vector &a ) +{ + return Vector( VECTOR_FABS( a.x ), VECTOR_FABS( a.y ), VECTOR_FABS( a.z ) ); +} + +inline float MaxValue( const Vector &a ) +{ + float maxy; + float maxz; + float max; + + max = VECTOR_FABS( a.x ); + maxy = VECTOR_FABS( a.y ); + maxz = VECTOR_FABS( a.z ); + + if ( maxy > max ) + { + max = maxy; + } + if ( maxz > max ) + { + max = maxz; + } + return max; +} + +inline float Vector::toYaw( void ) const +{ + float yaw; + + if ( ( y == 0.0f ) && ( x == 0.0f ) ) + { + yaw = 0.0f; + } + else + { + yaw = ( float )( ( int )( atan2( y, x ) * 180.0f / M_PI ) ); + if ( yaw < 0.0f ) + { + yaw += 360.0f; + } + } + + return yaw; +} + +inline float Vector::toPitch( void ) const +{ + float forward; + float pitch; + + if ( ( x == 0.0f ) && ( y == 0.0f ) ) + { + if ( z > 0.0f ) + { + pitch = 90.0f; + } + else + { + pitch = 270.0f; + } + } + else + { + forward = ( float )sqrt( ( x * x ) + ( y * y ) ); + pitch = ( float )( ( int )( atan2( z, forward ) * 180.0f / M_PI ) ); + if ( pitch < 0.0f ) + { + pitch += 360.0f; + } + } + + return pitch; +} + +inline void Vector::AngleVectors( Vector *forward, Vector *left, Vector *up ) const +{ + float angle; + static float sr, sp, sy, cr, cp, cy; // static to help MS compiler fp bugs + + angle = yaw() * ( M_PI * 2.0f / 360.0f ); + sy = sin( angle ); + cy = cos( angle ); + + angle = pitch() * ( M_PI * 2.0f / 360.0f ); + sp = sin( angle ); + cp = cos( angle ); + + angle = roll() * ( M_PI * 2.0f / 360.0f ); + sr = sin( angle ); + cr = cos( angle ); + + if ( forward ) + { + forward->setXYZ( cp * cy, cp * sy, -sp ); + } + + if ( left ) + { + left->setXYZ( ( sr * sp * cy ) + ( cr * -sy ), (sr * sp * sy ) + ( cr * cy ), sr * cp ); + } + + if ( up ) + { + up->setXYZ( ( cr * sp * cy ) + ( -sr * -sy ), ( cr * sp * sy ) + ( -sr * cy ), cr * cp ); + } +} + + +#define LERP_DELTA 1e-6 +inline Vector LerpVector( const Vector &vector1, const Vector &vector2, const float t ) +{ + float omega, cosom, sinom, scale0, scale1; + + Vector w1( vector1 ); + Vector w2( vector2 ); + + w1.normalize(); + w2.normalize(); + + cosom = w1 * w2; + if ( ( 1.0f - cosom ) > LERP_DELTA ) + { + omega = acos( cosom ); + sinom = sin( omega ); + scale0 = sin( ( 1.0f - t ) * omega ) / sinom; + scale1 = sin( t * omega ) / sinom; + } + else + { + scale0 = 1.0f - t; + scale1 = t; + } + + return ( ( w1 * scale0 ) + ( w2 * scale1 ) ); +} + +class Quat + { + public: + float x; + float y; + float z; + float w; + + Quat(); + Quat( Vector angles ); + Quat( float scrMatrix[ 3 ][ 3 ] ); + Quat( const float x, const float y, const float z, const float w ); + + float * vec4( void ); + float operator[]( const int index ) const; + float & operator[]( const int index ); + void set( const float x, const float y, const float z, const float w ); + const Quat & operator=( const Quat &a ); + friend Quat operator+( const Quat &a, const Quat &b ); + const Quat & operator+=( const Quat &a ); + friend Quat operator-( const Quat &a, const Quat &b ); + const Quat & operator-=( const Quat &a ); + friend Quat operator*( const Quat &a, const float b ); + friend Quat operator*( const float a, const Quat &b ); + const Quat & operator*=( const float a ); + friend int operator==( const Quat &a, const Quat &b ); + friend int operator!=( const Quat &a, const Quat &b ); + float length( void ) const; + float lengthSquared( void ) const; + const Quat & normalize( void ); + Quat operator-() const; + Vector toAngles( void ); + }; + +inline Quat::Quat(): x( 0 ), y( 0 ), z( 0 ), w( 0 ) +{ +} + +inline Quat::Quat( Vector Angles ) +{ + EulerToQuat( Angles, this->vec4() ); +} + +inline Quat::Quat( float srcMatrix[ 3 ][ 3 ] ) +{ + MatToQuat( srcMatrix, this->vec4() ); +} + +inline Quat::Quat( const float init_x, const float init_y, const float init_z, const float init_w ): x( init_x ), y( init_y ), z( init_z ), w( init_w ) +{ +} + +inline float Quat::operator[]( const int index ) const +{ + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +inline float & Quat::operator[]( const int index) +{ + assert( ( index >= 0 ) && ( index < 4 ) ); + return ( &x )[ index ]; +} + +inline float *Quat::vec4( void ) +{ + return &x; +} + +inline void Quat::set( const float new_x, const float new_y, const float new_z, const float new_w ) +{ + x = new_x; + y = new_y; + z = new_z; + w = new_w; +} + + +inline const Quat & Quat::operator=( const Quat &a ) +{ + x = a.x; + y = a.y; + z = a.z; + w = a.w; + + return *this; +} + +inline Quat operator+( const Quat &a, const Quat &b ) +{ + return Quat( a.x + b.x, a.y + b.y , a.z + b.z, a.w + b.w ); +} + +inline const Quat & Quat::operator+=( const Quat &a ) +{ + *this = *this + a; + + return *this; +} + +inline Quat operator-( const Quat &a, const Quat &b ) +{ + return Quat( a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w ); +} + +inline const Quat & Quat::operator-=( const Quat &a ) +{ + *this = *this - a; + + return *this; +} + +inline Quat operator*( const Quat &a, const float b ) +{ + return Quat( a.x * b, a.y * b, a.z * b, a.w * b ); +} + +inline Quat operator*( const float a, const Quat &b ) +{ + return b * a; +} + +inline const Quat & Quat::operator*=( const float a ) +{ + *this = *this * a; + + return *this; +} + +inline int operator==( const Quat &a, const Quat &b ) +{ + return ( ( a.x == b.x ) && ( a.y == b.y ) && ( a.z == b.z ) && ( a.w == b.w ) ); +} + +inline int operator!=( const Quat &a, const Quat &b ) +{ + return ( ( a.x != b.x ) || ( a.y != b.y ) || ( a.z != b.z ) && ( a.w != b.w ) ); +} + +inline float Quat::length( void ) const +{ + float length; + + length = ( x * x ) + ( y * y ) + ( z * z ) + ( w * w ); + return sqrt( length ); +} + +inline const Quat & Quat::normalize( void ) +{ + float length, ilength; + + length = this->length(); + if ( length ) + { + ilength = 1.0f / length; + *this *= ilength; + } + + return *this; +} + +inline Quat Quat::operator-() const +{ + return Quat( -x, -y, -z, -w ); +} + +inline Vector Quat::toAngles( void ) +{ + float m[ 3 ][ 3 ]; + vec3_t angles; + + QuatToMat( this->vec4(), m ); + MatrixToEulerAngles( m, angles ); + return Vector( angles ); +} + + +#undef VECTOR_FABS + +#endif /* Vector.h */ diff --git a/dlls/game/vehicle.cpp b/dlls/game/vehicle.cpp new file mode 100644 index 0000000..0160053 --- /dev/null +++ b/dlls/game/vehicle.cpp @@ -0,0 +1,2578 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/vehicle.cpp $ +// $Revision:: 34 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Script controlled Vehicles. +// + +#include "_pch_cpp.h" +#include "scriptslave.h" +#include "vehicle.h" +#include "player.h" +#include "specialfx.h" +#include "explosion.h" +#include "earthquake.h" +#include "gibs.h" +#include "player.h" + +Event EV_Vehicle_Start +( + "start", + EV_DEFAULT, + NULL, + NULL, + "Initialize the vehicle." +); +Event EV_Vehicle_Enter +( + "enter", + EV_CODEONLY, + "eS", + "vehicle driver_anim", + "Called when someone gets into a vehicle." +); +Event EV_Vehicle_Exit +( + "exit", + EV_DEFAULT, + "e", + "vehicle", + "Called when driver gets out of the vehicle." +); +Event EV_Vehicle_Drivable +( + "drivable", + EV_DEFAULT, + NULL, + NULL, + "Make the vehicle drivable" +); +Event EV_Vehicle_UnDrivable +( + "undriveable", + EV_DEFAULT, + NULL, + NULL, + "Make the vehicle undrivable" +); +Event EV_Vehicle_Jumpable +( + "canjump", + EV_DEFAULT, + "b", + "jumpable", + "Sets whether or not the vehicle can jump" +); +Event EV_Vehicle_Lock +( + "lock", + EV_DEFAULT, + NULL, + NULL, + "Sets the vehicle to be locked" +); +Event EV_Vehicle_UnLock +( + "unlock", + EV_DEFAULT, + NULL, + NULL, + "Sets the vehicle to be unlocked" +); +Event EV_Vehicle_SeatAnglesOffset +( + "seatanglesoffset", + EV_DEFAULT, + "v", + "angles", + "Set the angles offset of the seat" +); +Event EV_Vehicle_SeatOffset +( + "seatoffset", + EV_DEFAULT, + "v", + "offset", + "Set the offset of the seat" +); +Event EV_Vehicle_DriverAnimation +( + "driveranim", + EV_DEFAULT, + "s", + "animation", + "Set the animation of the driver to use when the driver is in the vehicle" +); +Event EV_Vehicle_SetWeapon +( + "setweapon", + EV_DEFAULT, + "s", + "weaponname", + "Set the weapon for the vehicle" +); +Event EV_Vehicle_SetSpeed +( + "vehiclespeed", + EV_DEFAULT, + "f", + "speed", + "Set the speed of the vehicle" +); +Event EV_Vehicle_SetTurnRate +( + "turnrate", + EV_DEFAULT, + "f", + "rate", + "Set the turning rate of the vehicle" +); +Event EV_Vehicle_SteerInPlace +( + "steerinplace", + EV_DEFAULT, + NULL, + NULL, + "Set the vehicle to turn in place" +); +Event EV_Vehicle_ShowWeapon +( + "showweapon", + EV_DEFAULT, + NULL, + NULL, + "Set the weapon to be show in the view" +); +Event EV_Vehicle_RestrictPitch +( + "restrictpitch", + EV_DEFAULT, + "f", + "pitchDelta", + "The max and minimum pitch of the driver" +); +Event EV_Vehicle_RestrictRotation +( + "restrictrotation", + EV_DEFAULT, + "f", + "rotateDelta", + "The max and min rotation of the driver" +); +Event EV_Vehicle_NoPrediction +( + "noprediction", + EV_DEFAULT, + "b", + "bool", + "Turns no prediction on or off" +); + +Event EV_Vehicle_DisableInventory +( + "disableinventory", + EV_DEFAULT, + NULL, + NULL, + "Disables the inventory when the player uses this vehicle" +); + +extern Event EV_Player_DisableUseWeapon; +extern Event EV_Player_PutawayWeapon; + +CLASS_DECLARATION( ScriptModel, VehicleBase, NULL ) +{ + { NULL, NULL } +}; + +VehicleBase::VehicleBase() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + takedamage = DAMAGE_NO; + showModel(); + setSolidType( SOLID_NOT ); + setMoveType( MOVETYPE_NONE ); + setOrigin( GetLocalOrigin() + Vector( "0 0 30") ); + + // + // we want the bounds of this model auto-rotated + // + flags |= FL_ROTATEDBOUNDS; + + // + // rotate the mins and maxs for the model + // + setSize( mins, maxs ); + + vlink = NULL; + offset = Vector(0, 0, 0); + + PostEvent( EV_BecomeNonSolid, EV_POSTSPAWN ); +} + +CLASS_DECLARATION( VehicleBase, BackWheels, "script_wheelsback" ) +{ + { NULL, NULL } +}; + +CLASS_DECLARATION( VehicleBase, FrontWheels, "script_wheelsfront" ) +{ + { NULL, NULL } +}; + +CLASS_DECLARATION( VehicleBase, Vehicle, "script_vehicle" ) +{ + { &EV_Blocked, &Vehicle::VehicleBlocked }, + { &EV_Touch, &Vehicle::VehicleTouched }, + { &EV_Use, &Vehicle::DriverUse }, + { &EV_Vehicle_Start, &Vehicle::VehicleStart }, + { &EV_Vehicle_Drivable, &Vehicle::Drivable }, + { &EV_Vehicle_UnDrivable, &Vehicle::UnDrivable }, + { &EV_Vehicle_Jumpable, &Vehicle::Jumpable }, + { &EV_Vehicle_SeatAnglesOffset, &Vehicle::SeatAnglesOffset}, + { &EV_Vehicle_SeatOffset, &Vehicle::SeatOffset }, + { &EV_Vehicle_Lock, &Vehicle::Lock }, + { &EV_Vehicle_UnLock, &Vehicle::UnLock }, + { &EV_Vehicle_SetWeapon, &Vehicle::SetWeapon }, + { &EV_Vehicle_DriverAnimation, &Vehicle::DriverAnimation }, + { &EV_Vehicle_SetSpeed, &Vehicle::SetSpeed }, + { &EV_Vehicle_SetTurnRate, &Vehicle::SetTurnRate }, + { &EV_Vehicle_SteerInPlace, &Vehicle::SteerInPlace }, + { &EV_Vehicle_ShowWeapon, &Vehicle::ShowWeaponEvent }, + { &EV_Vehicle_RestrictPitch, &Vehicle::RestrictPitch }, + { &EV_Vehicle_RestrictRotation, &Vehicle::RestrictRotation}, + { &EV_Vehicle_NoPrediction, &Vehicle::SetNoPrediction }, + { &EV_Vehicle_DisableInventory, &Vehicle::DisableInventory}, + { NULL, NULL } +}; + +Vehicle::Vehicle() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + takedamage = DAMAGE_YES; + seatangles = vec_zero; + driveroffset = vec_zero; + seatoffset = vec_zero; + driver = 0; + lastdriver = 0; + currentspeed = 0; + turnangle = 0; + turnimpulse = 0; + moveimpulse = 0; + jumpimpulse = 0; + conesize = 75; + hasweapon = false; + locked = false; + steerinplace = false; + drivable = true; + jumpable = false; + showweapon = false; + flags |= FL_DIE_EXPLODE; + // touch triggers by default + flags |= FL_TOUCH_TRIGGERS; + gravity = 1; + mass = size.length() * 10.0f; + + health = 1000; + speed = 1200; + maxturnrate = 40.0f; + + usetime = 0.0f; + + maxtracedist = 0; + + + _restrictPitch = false; + _restrictYaw = false; + _noPrediction = false; + + _disableInventory = false; + + PostEvent( EV_Vehicle_Start, FRAMETIME ); +} + +void Vehicle::VehicleStart( Event *ev ) +{ + Entity *ent; + VehicleBase *last; + Vector drivemins, drivemaxs; + float max; + float width,height; + orientation_t orn; + + // become solid + setSolidType( SOLID_BBOX ); + + last = this; + + for( ent = G_NextEntity( NULL ); ent != NULL; ent = G_NextEntity( ent ) ) + { + if ( ( ent != this ) && ( ent->isSubclassOf( VehicleBase ) ) ) + { + if ( ( ent->absmax.x >= absmin.x ) && ( ent->absmax.y >= absmin.y ) && ( ent->absmax.z >= absmin.z ) && + ( ent->absmin.x <= absmax.x ) && ( ent->absmin.y <= absmax.y ) && ( ent->absmin.z <= absmax.z ) ) + { + last->vlink = ( VehicleBase * )ent; + last = ( VehicleBase * )ent; + last->offset = last->origin - origin; + last->offset = getLocalVector( last->offset ); + last->edict->s.scale *= edict->s.scale; + } + } + } + + last->vlink = NULL; + + // + // get the seat offset + // + if ( GetRawTag( "tag_rider", &orn ) ) + { + driveroffset = Vector( orn.origin ); + } + driveroffset += seatoffset * edict->s.scale; + + SetDriverAngles( angles + seatangles ); + + + + max_health = health; + + // + // calculate drive mins and maxs + // + max = 0; + if ( fabs( mins[ 0 ] ) > max ) + max = fabs( mins[ 0 ] ); + if ( fabs( maxs[ 0 ] ) > max ) + max = fabs( maxs[ 0 ] ); + if ( fabs( mins[ 1 ] ) > max ) + max = fabs( mins[ 1 ] ); + if ( fabs( maxs[ 1 ] ) > max ) + max = fabs( maxs[ 1 ] ); + drivemins = Vector( -max, -max, mins[ 2 ] ) * edict->s.scale; + drivemaxs = Vector( max, max, maxs[ 2 ] ) * edict->s.scale; + + width = maxs[ 1 ] - mins[ 1 ]; + height = maxs[ 0 ] - mins[ 0 ]; + + maxtracedist = height; + + Corners[ 0 ][ 0 ] = -( width / 4.0f ); + Corners[ 0 ][ 1 ] = ( height / 4.0f ); + Corners[ 0 ][ 2 ] = 0.0f; + + Corners[ 1 ][ 0 ] = ( width / 4.0f ); + Corners[ 1 ][ 1 ] = ( height / 4.0f ); + Corners[ 1 ][ 2 ] = 0.0f; + + Corners[ 2 ][ 0 ] = -( width / 4.0f ); + Corners[ 2 ][ 1 ] = -( height / 4.0f ); + Corners[ 2 ][ 2 ] = 0.0f; + + Corners[ 3 ][ 0 ] = ( width / 4.0f ); + Corners[ 3 ][ 1 ] = -( height / 4.0f ); + Corners[ 3 ][ 2 ] = 0.0f; + if ( drivable ) + { + // drop everything back to the floor + //droptofloor( 250.0f ); + Postthink(); + } + last_origin = origin; + setSize( drivemins, drivemaxs ); +} + +void Vehicle::Drivable( Event *ev ) +{ + setMoveType( MOVETYPE_NONE ); + drivable = true; +} + +void Vehicle::UnDrivable( Event *ev ) +{ + setMoveType( MOVETYPE_PUSH ); + drivable = false; +} + +void Vehicle::Jumpable( Event *ev ) +{ + jumpable = true; +} + +void Vehicle::Lock( Event *ev ) +{ + locked = true; +} + +void Vehicle::UnLock( Event *ev ) +{ + locked = false; +} + +void Vehicle::SteerInPlace( Event *ev ) +{ + steerinplace = true; +} + +void Vehicle::SeatAnglesOffset( Event *ev ) +{ + seatangles = ev->GetVector( 1 ); +} + +void Vehicle::SeatOffset( Event *ev ) +{ + seatoffset = ev->GetVector( 1 ); +} + +void Vehicle::SetWeapon( Event *ev ) +{ + showweapon = true; + hasweapon = true; + weaponName = ev->GetString( 1 ); +} + +void Vehicle::ShowWeaponEvent( Event *ev ) +{ + showweapon = true; +} + +//----------------------------------------------------- +// +// Name: RestrictPitch +// Class: Vehicle +// +// Description: Restricts the drivers pitch while in the vehicle. This calculates the pitchSeam. +// A seam is the location where the normalized angle range boundary. For example +// on a normalized range of -180, 180, the seam is located at -180/180. To solve the seam +// problem, the seam is always positioned directly (-180 degrees) behind the starting pitch. +// +// Parameters: event - event containing the pitch restrictions +// +// Returns: None +//----------------------------------------------------- +void Vehicle::RestrictPitch(Event* event) +{ + _startPitch = angles[PITCH]; + _pitchSeam = _startPitch - 180.0f; + + _minimumPitch = AngleNormalizeArbitrary( _startPitch - event->GetFloat(1), _pitchSeam); + _maximumPitch = AngleNormalizeArbitrary( _startPitch + event->GetFloat(1), _pitchSeam); + + _restrictPitch = true; +} + + +//----------------------------------------------------- +// +// Name: RestrictRotation +// Class: Vehicle +// +// Description: Restricts the drivers rotation while in the vehicle.This calculates the yawSeam. +// A seam is the location where the normalized angle range boundary. For example +// on a normalized range of -180, 180, the seam is located at -180/180. To solve the seam +// problem, the seam is always positioned directly (-180 degrees) behind the starting yaw. +// +// Parameters: event - the event containing the rotation restrictions +// +// Returns: None +//----------------------------------------------------- +void Vehicle::RestrictRotation(Event* event) +{ + _startYaw = angles[YAW]; + _yawSeam = _startYaw - 180.0f; + + _minimumYaw = AngleNormalizeArbitrary( _startYaw - event->GetFloat(1), _yawSeam); + _maximumYaw = AngleNormalizeArbitrary( _startYaw + event->GetFloat(1), _yawSeam); + + _restrictYaw = true; +} + + +//----------------------------------------------------- +// +// Name: SetNoPrediction +// Class: Vehicle +// +// Description: Turns the no prediction flag +// +// Parameters: event - the event that turns no prediction on or off +// +// Returns: None +//----------------------------------------------------- +void Vehicle::SetNoPrediction(Event* event) +{ + _noPrediction = event->GetBoolean(1); +} + + +void Vehicle::DisableInventory(Event* event) +{ + _disableInventory = true; +} + + +void Vehicle::DriverAnimation( Event *ev ) +{ + driveranim = ev->GetString( 1 ); +} + +qboolean Vehicle::HasWeapon( void ) +{ + return hasweapon; +} + +qboolean Vehicle::ShowWeapon( void ) +{ + return showweapon; +} + +void Vehicle::SetDriverAngles( const Vector &angles ) +{ + int i; + + if ( !driver ) + return; + + for( i = 0; i < 3; i++ ) + { + driver->client->ps.delta_angles[ i ] = ANGLE2SHORT( angles[ i ] - driver->client->cmd_angles[ i ] ); + } +} + +void Vehicle::HandleEvent( Event *ev ) +{ + +} + +/* +============= +CheckWater +============= +*/ +void Vehicle::CheckWater( void ) +{ + Vector point; + int cont; + int sample1; + int sample2; + VehicleBase *v; + + unlink(); + v = this; + while( v->vlink ) + { + v = v->vlink; + v->unlink(); + } + + if ( driver ) + { + driver->unlink(); + } + + // + // get waterlevel + // + waterlevel = 0; + watertype = 0; + + sample2 = maxs[ 2 ] - mins[ 2 ]; + sample1 = sample2 / 2; + + point = origin; + point[ 2 ] += mins[ 2 ]; + cont = gi.pointcontents( point, 0 ); + + if ( cont & MASK_WATER ) + { + watertype = cont; + waterlevel = 1; + point[ 2 ] = origin[ 2 ] + mins[ 2 ] + sample1; + cont = gi.pointcontents( point, 0 ); + if ( cont & MASK_WATER ) + { + waterlevel = 2; + point[ 2 ] = origin[ 2 ] + mins[ 2 ] + sample2; + cont = gi.pointcontents( point, 0 ); + if ( cont & MASK_WATER ) + { + waterlevel = 3; + } + } + } + + link(); + v = this; + while( v->vlink ) + { + v = v->vlink; + v->link(); + } + + if ( driver ) + { + driver->link(); + driver->waterlevel = waterlevel; + driver->watertype = watertype; + } +} + +/* +============= +WorldEffects +============= +*/ +void Vehicle::WorldEffects( void ) +{ + // + // Check for earthquakes + // + /* if ( groundentity && ( level.earthquake > level.time ) ) + { + velocity += Vector + ( + level.earthquake_magnitude * EARTHQUAKE_STRENGTH * G_CRandom(), + level.earthquake_magnitude * EARTHQUAKE_STRENGTH * G_CRandom(), + level.earthquake_magnitude * 1.5f * G_Random() + ); + } */ + + // + // check for lava + // + if ( watertype & CONTENTS_LAVA ) + { + Damage( world, world, 20.0f * waterlevel, origin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_LAVA ); + } +} + +void Vehicle::DriverUse( Event *ev ) +{ + Event *event; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( level.time < ( usetime + 1.0f ) ) + return; + else + usetime = level.time; + + if ( !other || !other->isSubclassOf( Sentient ) ) + { + return; + } + + if ( driver ) + { +// int height; +// int ang; + Vector angles; + Vector forward; + Vector pos; +// float ofs; +// trace_t trace; + + if ( other != driver ) + { + return; + } + + if ( locked ) + return; + /* + // + // place the driver on the ground + // + ofs = size.length() * 0.5f; + for ( height = 0; height < 100; height += 16 ) + { + for ( ang = 0; ang < 360; ang += 30 ) + { + angles[ 1 ] = driver->angles[ 1 ] + ang + 90.0f; + angles.AngleVectors( &forward, NULL, NULL ); + pos = origin + (forward * ofs); + pos[2] += height; + trace = G_Trace( pos, driver->mins, driver->maxs, pos, NULL, MASK_PLAYERSOLID, false, "Vehicle::DriverUse 1" ); + if ( !trace.startsolid && !trace.allsolid ) + { + Vector end; + + end = pos; + end[ 2 ] -= 128.0f; + trace = G_Trace( pos, driver->mins, driver->maxs, end, NULL, MASK_PLAYERSOLID, false, "Vehicle::DriverUse 2" ); + if ( trace.fraction < 1.0f ) + { + driver->setOrigin( pos ); + goto foundpos; + } + } + } + } + */ + +// return; + +//foundpos: + + turnimpulse = 0; + moveimpulse = 0; + jumpimpulse = 0; + + event = new Event( EV_Vehicle_Exit ); + event->AddEntity( this ); + driver->ProcessEvent( event ); + + if ( drivable ) + { + StopLoopSound(); + Sound( "snd_dooropen", CHAN_BODY ); + Sound( "snd_stop", CHAN_VOICE ); + driver->setSolidType( SOLID_BBOX ); + } + + driver->setOrigin(_oldOrigin); + + enableDriverInventory(); + + if( hasweapon ) + { + driver->DeactivateWeapon( WEAPON_DUAL ); + driver->takeItem( weaponName.c_str() ); + } + + driver = NULL; + + } + else + { + driver = ( Sentient * )other; + VectorCopy(driver->edict->client->ps.origin, _oldOrigin); + + lastdriver = driver; + + + if ( drivable ) + setMoveType( MOVETYPE_VEHICLE ); + + if ( hasweapon ) + { + driver->giveItem( weaponName.c_str() ); + driver->useWeapon(weaponName.c_str(), WEAPON_DUAL); + } + + disableDriverInventory(); + + if ( drivable ) + { + Sound( "snd_doorclose", CHAN_BODY ); + Sound( "snd_start", CHAN_VOICE ); + driver->setSolidType( SOLID_NOT ); + } + + event = new Event( EV_Vehicle_Enter ); + event->AddEntity( this ); + if ( driveranim.length() ) + event->AddString( driveranim ); + driver->ProcessEvent( event ); + + offset = other->origin - origin; + + flags |= FL_POSTTHINK; + SetDriverAngles( angles + seatangles ); + } +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Vehicle::disableDriverInventory( void ) +{ + if( _disableInventory == false ) + return; + + if(driver != 0 && driver->isSubclassOf(Player)) + { + ((Player*)driver.Pointer())->disableInventory(); + } +} + + +//----------------------------------------------------- +// +// Name: +// Class: +// +// Description: +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Vehicle::enableDriverInventory( void ) +{ + if( driver != 0 && driver->isSubclassOf(Player)) + { + ((Player*)driver.Pointer())->enableInventory(); + } +} + +qboolean Vehicle::Drive( usercmd_t *ucmd ) +{ + Vector i, j, k; + + if ( !driver || !driver->isClient() ) + { + return false; + } + + if ( !drivable ) + { + driver->client->ps.pm_flags |= PMF_FROZEN; + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + return false; + } + + if(_noPrediction ) + { + driver->client->ps.pm_flags |= PMF_NO_PREDICTION; + } + + moveimpulse = ( ( float )ucmd->forwardmove ) * 3.0f; + //turnimpulse = ( ( float )-ucmd->rightmove ) * 3.0f; + jumpimpulse = ( ( float )ucmd->upmove * gravity ) / 350.0f; + + ucmd->lean = 0; + + if ( ( jumpimpulse < 0.0f ) || ( !jumpable ) ) + jumpimpulse = 0.0f; + + turnimpulse = 10.0f * angledist( SHORT2ANGLE( ucmd->angles[ 1 ] ) - driver->client->cmd_angles[ 1 ] ); + + return true; +} + +void Vehicle::Postthink( void ) +{ + float turn; + Vector i, j, k; + Vector normalsum; + Vector temp; + Vector pitch; + Vector roll; + VehicleBase *v; + VehicleBase *last; + float drivespeed; + + + if ( drivable ) + { + currentspeed = moveimpulse / 4.0f; + + turnangle = turnangle * 0.25f + turnimpulse; + if ( turnangle > maxturnrate ) + { + turnangle = maxturnrate; + } + else if ( turnangle < -maxturnrate ) + { + turnangle = -maxturnrate; + } + else if ( fabs( turnangle ) < 2.0f ) + { + turnangle = 0; + } + + CalculateOrientation(); + + turn = turnangle * ( 1.0f / 200.0f ); + + if ( groundentity ) + { + float dot; + Vector newvel; + Vector flatvel; + + velocity[ 0 ] *= 0.925f; + velocity[ 1 ] *= 0.925f; + flatvel = Vector( orientation[ 0 ] ); + velocity += flatvel * currentspeed; + flatvel[ 2 ] = 0; + dot = velocity * flatvel; + if ( dot > speed ) + { + dot = speed; + } + else if ( dot < -speed ) + { + dot = -speed; + } + else if ( fabs( dot ) < 20.0f ) + { + dot = 0; + } + newvel = flatvel * dot; + velocity[ 0 ] = newvel[ 0 ]; + velocity[ 1 ] = newvel[ 1 ]; + velocity[ 2 ] += dot * jumpimpulse; + + avelocity *= 0.05f; + if ( steerinplace ) + { + if ( dot < 350.0f ) + dot = 350.0f; + avelocity.y += turn * dot; + } + else + { + avelocity.y += turn * dot; + } + } + else + { + avelocity *= 0.1f; + } + angles += avelocity * level.frametime; + setAngles( angles ); + } + + drivespeed = velocity * Vector( orientation[ 0 ] ); + + if ( drivable && driver ) + { + str sound_name; + + if ( currentspeed > 0.0f ) + sound_name = "snd_forward"; + else if ( currentspeed < 0.0f ) + sound_name = "snd_backward"; + else + sound_name = "snd_idle"; + + LoopSound( sound_name.c_str() ); + } + + i = Vector( orientation[ 0 ] ); + j = Vector( orientation[ 1 ] ); + k = Vector( orientation[ 2 ] ); + + if ( driver ) + { + Player * player; + + if ( driver->isSubclassOf ( Player ) ) + { + player = ( Player * )( Sentient * )driver; + player->setOrigin( origin + ( i * driveroffset[0] ) + ( j * driveroffset[1] ) + ( k * driveroffset[2] ) ); + if ( drivable ) + { + player->velocity = vec_zero; + Vector playerAngles = player->v_angle; + Vector viewAngles = angles; + + if( _restrictPitch ) + { + if(viewAngles[PITCH] > _maximumPitch) + viewAngles[PITCH] = _maximumPitch; + else if(viewAngles[PITCH] < _minimumPitch) + viewAngles[PITCH] = _minimumPitch; + } + + if( _restrictYaw ) + { + if(viewAngles[YAW] > _maximumYaw) + viewAngles[YAW] = _maximumYaw; + else if(viewAngles[YAW] <= _minimumYaw) + viewAngles[YAW] = _minimumYaw; + } + + angles = viewAngles; + playerAngles.y = angles.y; + playerAngles.z = angles.z; + player->SetViewAngles(playerAngles); + } + } + } + + last = this; + while( last->vlink ) + { + v = last->vlink; + v->setOrigin( origin + ( i * v->offset.x ) + ( j * v->offset.y ) + ( k * v->offset.z ) ); + v->avelocity = avelocity; + v->velocity = velocity; + v->angles[ ROLL ] = angles[ ROLL ]; + v->angles[ YAW ] = angles[ YAW ]; + v->angles[ PITCH ] = (float)( (int)( v->angles[ PITCH ] + (drivespeed/4) ) % 360 ); + + if ( v->isSubclassOf( FrontWheels ) ) + { + v->angles += Vector( 0.0f, turnangle, 0.0f ); + } + v->setAngles( v->angles ); + + last = v; + } + + //CheckWater(); + WorldEffects(); + + // save off last origin + last_origin = origin; + + if ( !driver && !velocity.length() && groundentity && !( watertype & CONTENTS_LAVA ) ) + { + flags &= ~FL_POSTTHINK; + if ( drivable ) + setMoveType( MOVETYPE_NONE ); + } +} + +///////////////////////////////////////////////////////// +// +// Name: CalculateOrientation() +// +// Description: Calculates the orientation of the vehicle +// by getting the normals of all the poly's underneath +// each corner of the vehicle's bounding box, this +// allows the vehicle to pitch appropriately +// +///////////////////////////////////////////////////////// + +void Vehicle::CalculateOrientation( void ) +{ + Vector temp, pitch, normalsum; + Vector i, j, k; + int numnormals; + trace_t trace; + + temp[ PITCH ] = 0; + temp[ YAW ] = angles[ YAW ]; + temp[ ROLL ] = 0; + temp.AngleVectors( &i, &j, &k ); + j = vec_zero - j; + + // + // figure out what our orientation is + // + + numnormals = 0; + for ( int index = 0; index < 4; index++ ) + { + Vector start, end; + Vector boxoffset; + + boxoffset = Corners[ index ]; + start = origin + ( i * boxoffset[0] ) + ( j * boxoffset[1] ) + ( k * boxoffset[2] ); + end = start; + end[ 2 ] -= maxtracedist; + trace = G_Trace( start, vec_zero, vec_zero, end, NULL, MASK_SOLID, false, "Vehicle::PostThink Corners" ); + + if ( ( trace.fraction > 0.0f ) && ( trace.fraction != 1.0f ) && !trace.startsolid ) + { + normalsum += Vector( trace.plane.normal ); + numnormals++; + } + } + + if ( numnormals > 1 ) + { + temp = normalsum * ( 1.0f/ numnormals ); + temp.normalize(); + i = temp.CrossProduct( temp, j ); + pitch = i; + + // determine pitch + angles[ 0 ] = -(pitch.toPitch()); + } +} + +void Vehicle::VehicleTouched( Event *ev ) +{ + Entity *other; + float speed; + Vector delta; + Vector dir; + + other = ev->GetEntity( 1 ); + if ( other == driver ) + { + return; + } + + if ( other == world ) + { + return; + } + + if ( drivable && !driver ) + { + return; + } + + delta = origin - last_origin; + speed = delta.length(); + if ( speed > 2 ) + { + Sound( "vehicle_crash", true ); + dir = delta * ( 1.0f / speed ); + other->Damage( this, lastdriver, speed * 8.0f, origin, dir, vec_zero, speed * 15, 0, MOD_VEHICLE ); + } + +} + +void Vehicle::VehicleBlocked( Event *ev ) +{ + return; + /* + Entity *other; + float speed; + float damage; + Vector delta; + Vector newvel; + Vector dir; + + if ( !velocity[0] && !velocity[1] ) + return; + + other = ev->GetEntity( 1 ); + if ( other == driver ) + { + return; + } + if ( other->isSubclassOf( VehicleBase ) ) + { + delta = other->origin - origin; + delta.normalize(); + + newvel = vec_zero - ( velocity) + ( other->velocity * 0.25 ); + if ( newvel * delta < 0 ) + { + velocity = newvel; + delta = velocity - other->velocity; + damage = delta.length()/4; + } + else + { + return; + } + } + else if ( ( velocity.length() < 350 ) ) + { + other->velocity += velocity*1.25f; + other->velocity[ 2 ] += 100; + damage = velocity.length()/4; + } + else + { + damage = other->health + 1000; + } + + // Gib 'em outright + speed = fabs( velocity.length() ); + dir = velocity * ( 1 / speed ); + other->Damage( this, lastdriver, damage, origin, dir, vec_zero, speed, 0, MOD_VEHICLE, -1, -1, 1.0f ); + */ +} + +Sentient *Vehicle::Driver( void ) +{ + return driver; +} + +qboolean Vehicle::IsDrivable( void ) +{ + return drivable; +} + +void Vehicle::SetSpeed( Event *ev ) +{ + speed = ev->GetFloat( 1 ); +} + +void Vehicle::SetTurnRate( Event *ev ) +{ + maxturnrate = ev->GetFloat( 1 ); +} + + +CLASS_DECLARATION( Vehicle, DrivableVehicle, "script_drivablevehicle" ) +{ + { &EV_Damage, &Entity::DamageEvent }, + { &EV_Killed, &DrivableVehicle::Killed }, + { NULL, NULL } +}; + +DrivableVehicle::DrivableVehicle() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + drivable = true; + flags |= FL_DIE_EXPLODE; +} + +void DrivableVehicle::Killed(Event *ev) +{ + Entity * ent; + Entity * attacker; + Vector dir; + Event * event; + const char * name; + VehicleBase *last; + + takedamage = DAMAGE_NO; + setSolidType( SOLID_NOT ); + hideModel(); + + attacker = ev->GetEntity( 1 ); + + // + // kill the driver + // + if ( driver ) + { + Vector dir; + SentientPtr sent; + Event * event; + + velocity = vec_zero; + sent = driver; + event = new Event( EV_Use ); + event->AddEntity( sent ); + ProcessEvent( event ); + dir = sent->origin - origin; + dir[ 2 ] += 64.0f; + dir.normalize(); + sent->Damage( this, this, sent->health * 2.0f, origin, dir, vec_zero, 50, 0, MOD_VEHICLE ); + } + + if (flags & FL_DIE_EXPLODE) + { + CreateExplosion( origin, 150.0f * edict->s.scale, this, this, this ); + } + + if (flags & FL_DIE_GIBS) + { + setSolidType( SOLID_NOT ); + hideModel(); + + CreateGibs( this, -150.0f, edict->s.scale, 3 ); + } + // + // kill all my wheels + // + last = this; + while( last->vlink ) + { + last->vlink->PostEvent( EV_Remove, 0.0f ); + last = last->vlink; + } + + + // + // kill the killtargets + // + name = KillTarget(); + if ( name && strcmp( name, "" ) ) + { + ent = NULL; + do + { + ent = G_FindTarget( ent, name ); + if ( !ent ) + { + break; + } + ent->PostEvent( EV_Remove, 0.0f ); + } + while ( 1 ); + } + + // + // fire targets + // + name = Target(); + if ( name && strcmp( name, "" ) ) + { + ent = NULL; + do + { + ent = G_FindTarget( ent, name ); + if ( !ent ) + { + break; + } + + event = new Event( EV_Activate ); + event->AddEntity( attacker ); + ent->ProcessEvent( event ); + } + while ( 1 ); + } + + PostEvent( EV_Remove, 0.0f ); +} + + +//=============================================================== +// Horse Vehicle Class +// New style of vehicle -- Allows the player to drive the vehicle +// but still mouselook and fire ( turret style ) +// different control configurations ( ie Strafe Only ) etc can +// be subclassed from movemode and plugged in easily +// +//=============================================================== + +Event EV_HorseVehicle_SetSpeed +( + "sethorsespeed", + EV_SCRIPTONLY, + "f", + "speed", + "Set the speed of the horse" +); +Event EV_HorseVehicle_SetMoveMode +( + "setmovemode", + EV_SCRIPTONLY, + "sF", + "mode angles", + "Mode ( standard , strafe, or locked ) and a Vector describing the view Angle )" +); +Event EV_HorseVehicle_SetForcedForward +( + "forceforwardspeed", + EV_SCRIPTONLY, + "f", + "speed", + "Forces the vehicle to move forward at the specified speed" +); +Event EV_Vehicle_AnimDone +( + "animdone", + EV_CODEONLY, + NULL, + NULL, + "animdoneevent" +); +Event EV_Driver_AnimDone +( + "driveranimdone", + EV_CODEONLY, + NULL, + NULL, + "driveranimdoneevent" +); + +CLASS_DECLARATION( Vehicle, HorseVehicle, "script_horsevehicle" ) +{ + { &EV_HorseVehicle_SetSpeed , &HorseVehicle::SetSpeed }, + { &EV_HorseVehicle_SetMoveMode , &HorseVehicle::SetVehicleMoveMode }, + { &EV_HorseVehicle_SetForcedForward, &HorseVehicle::SetForcedForwardSpeed }, + { &EV_Vehicle_AnimDone, &HorseVehicle::AnimDone }, + { &EV_Driver_AnimDone, &HorseVehicle::DriverAnimDone }, + + + //Move Mode Events + { &EV_FollowPath_SetWayPointName, &HorseVehicle::PassToMoveMode }, + { NULL, NULL } +}; + + +///////////////////////////////////////////////////////// +// +// Name: HorseVehicle() +// +// Description: Constructor +// +///////////////////////////////////////////////////////// + +HorseVehicle::HorseVehicle() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + drivable = true; + jumpable = true; + driver = NULL; + currentspeed = 300; + + _baseYaw = 0; + _driverYaw = 0; + _lastYaw = 0; + + _driverPitch = 0; + _lastPitch = 0; + + _forcedForwardSpeed = 0; + _jumpflag = false; + _jumped = false; + _jumpSpeed = 0; + + _ducked = false; + + _minYawThreshold = 90; + _maxYawThreshold = 90; + + _minPitchThreshold = 90; + _maxPitchThreshold = 90; + + _moveMode = NULL; + _SetMoveMode( "standard" ); + + _currentCrosshairMode = CROSSHAIR_MODE_STRAIGHT; + _newCrosshairMode = CROSSHAIR_MODE_STRAIGHT; + _jumpmode = JUMPMODE_DONE; + _duckmode = DUCKMODE_DONE; + _animDone = false; + _driverAnimDone = false; + + _ducked = false; + _duckheld = false; + + _DriverBBoxMaxs = vec_zero; + _DriverBBoxMins = vec_zero; + _originalBBoxMaxs = vec_zero; + _originalBBoxMins = vec_zero; + + //Create the Animate Object so that we can set the + //animation on the vehicle + if ( !animate ) + animate = new Animate; + +} + +HorseVehicle::~HorseVehicle() +{ + if ( _moveMode ) + { + delete _moveMode; + _moveMode = 0; + + } +} + +///////////////////////////////////////////////////////// +// +// Name: PassToMoveMode() +// +// Description: Hands off the event to the move mode for +// it to handle -- allows the vehicle to remain dumb in +// regards to movement specific events +// +///////////////////////////////////////////////////////// + +void HorseVehicle::PassToMoveMode( Event *ev ) +{ + + if ( _moveMode ) + _moveMode->HandleEvent( ev ); + +} + +///////////////////////////////////////////////////////// +// +// Name: Drive() +// +// Description: Called from Player->ClientThink +// Player passes it a ucmd pointer, which points to the +// latest input data from the user. Drive() uses this +// data to set up the movement impulses that help control +// how the vehicle moves +// +///////////////////////////////////////////////////////// + +qboolean HorseVehicle::Drive( usercmd_t *ucmd ) +{ + Vector i, j, k; + + if ( !driver || !driver->isClient() ) + return false; + + if ( !drivable ) + { + driver->client->ps.pm_flags |= PMF_FROZEN; + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + return false; + } + + //Turn off Client Prediction + //driver->client->ps.pm_flags |= PMF_NO_PREDICTION; + driver->client->ps.in_vehicle = true; + driver->client->ps.vehicleoffset[0] = origin[0]; + driver->client->ps.vehicleoffset[1] = origin[1]; + driver->client->ps.vehicleoffset[2] = origin[2] + 160.0f; + + driver->velocity = velocity; + + //jumpimpulse is used to calculate a jump value + //moveimpulse is used to calculate forward and back movement + //turnimpulse is used to calculate strafe movement + jumpimpulse = ( ( float )ucmd->upmove * gravity ) / 350.0f; + moveimpulse = ucmd->forwardmove; + turnimpulse = ucmd->rightmove; + + /* + if ( ( jumpimpulse < 0 ) || ( !jumpable ) ) + jumpimpulse = 0; + */ + + return true; +} + +///////////////////////////////////////////////////////// +// +// Name: Postthink() +// +// Description: Think Function ( called every frame ) +// Postthink is the master function for the vehicle. +// it delegates out movement, animation, and FX to +// subfunctions. +///////////////////////////////////////////////////////// + +void HorseVehicle::Postthink ( void ) +{ + + if ( drivable ) + { + CalculateOrientation(); + + _moveMode->Move( this ); + + if (driver) + { + _PlayMovementSound(); + + if ( !_jumpflag) + _AnimateVehicle("run"); + + if ( !_duckflag ) + _PositionDriverModel(); + + if ( groundentity && ( jumpimpulse > 0 ) && ( _jumpmode == JUMPMODE_DONE ) && ( _duckmode == DUCKMODE_DONE ) ) + _InitializeJump(); + + if ( _jumpflag ) + _HandleJump(); + + if ( groundentity && ( jumpimpulse < 0 ) && ( _duckmode == DUCKMODE_DONE ) && ( _jumpmode == JUMPMODE_DONE ) && !_duckflag ) + _InitializeDuck(); + + //Released the duck key + if ( _duckflag && ( jumpimpulse >= 0 ) ) + _ducked = false; + + if ( _duckflag ) + _HandleDuck(); + + } + + } + + //Do World Effects ( for earthquakes and the like ) + WorldEffects(); + + Vector flatvel; + if ( _forcedForwardSpeed ) + { + flatvel = Vector( orientation[ 0 ] ); + velocity += flatvel * _forcedForwardSpeed; + } + + + // Turn off think if we aren't being used + if ( !driver && !velocity.length() && groundentity && !( watertype & CONTENTS_LAVA ) ) + { + flags &= ~FL_POSTTHINK; + if ( drivable ) + setMoveType( MOVETYPE_NONE ); + } +} + +///////////////////////////////////////////////////////// +// +// Name: DriverUse() +// +// Description: Function is called when the player "uses" +// the vehicle. It handles putting the driver in and +// taking the driver out of the vehicle. +// +// Some sort of use-delay mechanism must be in place or +// it will be very difficult for the player to get on and +// off the vehicle, because the use events are stepping +// on each other +// +///////////////////////////////////////////////////////// + +void HorseVehicle::DriverUse( Event *ev ) +{ + + Event *event; + Entity *other; + + other = ev->GetEntity( 1 ); + if ( level.time < usetime + 1.0f ) + return; + else + usetime = level.time; + + if ( !other || !other->isSubclassOf( Sentient ) ) + { + return; + } + + if ( driver ) + { + int height; + int ang; + Vector angles; + Vector forward; + Vector pos; + float ofs; + trace_t trace; + + if ( other != driver ) + { + return; + } + + if ( locked ) + return; + + // + // place the driver on the ground + // + driver->detach(); + _AnimateDriver( "idle" ); + _AnimateVehicle( "idle" ); + driver->client->ps.in_vehicle = false; + ofs = size.length() * 0.5f; + for ( height = 0; height < 100; height += 16 ) + { + for ( ang = 0; ang < 360; ang += 30 ) + { + angles[ 1 ] = driver->angles[ 1 ] + ang + 90.0f; + angles.AngleVectors( &forward, NULL, NULL ); + pos = origin + (forward * ofs); + pos[2] += height; + trace = G_Trace( pos, driver->mins, driver->maxs, pos, NULL, MASK_PLAYERSOLID, false, "Vehicle::DriverUse 1" ); + if ( !trace.startsolid && !trace.allsolid ) + { + Vector end; + + end = pos; + end[ 2 ] -= 128.0f; + trace = G_Trace( pos, driver->mins, driver->maxs, end, NULL, MASK_PLAYERSOLID, false, "Vehicle::DriverUse 2" ); + if ( trace.fraction < 1.0f ) + { + driver->setOrigin( pos ); + goto foundpos; + } + } + } + } + return; + +foundpos: + + turnimpulse = 0; + moveimpulse = 0; + jumpimpulse = 0; + + event = new Event( EV_Vehicle_Exit ); + event->AddEntity( this ); + driver->ProcessEvent( event ); + if ( hasweapon ) + { + driver->takeItem( weaponName.c_str() ); + } + if ( drivable ) + { + StopLoopSound(); + Sound( "snd_dooropen", CHAN_BODY ); + Sound( "snd_stop", CHAN_VOICE ); + driver->setSolidType( SOLID_BBOX ); + } + + driver = NULL; + } + else + { + driver = ( Sentient * )other; + + lastdriver = driver; + + + if ( drivable ) + setMoveType( MOVETYPE_VEHICLE ); + + if ( drivable ) + { + Sound( "snd_doorclose", CHAN_BODY ); + Sound( "snd_start", CHAN_VOICE ); + driver->setSolidType( SOLID_NOT ); + } + + event = new Event( EV_Vehicle_Enter ); + event->AddEntity( this ); + if ( driveranim.length() ) + event->AddString( driveranim ); + driver->ProcessEvent( event ); + + offset = other->origin - origin; + + flags |= FL_POSTTHINK; + SetDriverAngles( angles + seatangles ); + + int tagnum = gi.Tag_NumForName( this->edict->s.modelindex, "tag_rider" ); + + + driver->attach(this->entnum , tagnum , false , seatoffset ); + + if ( driver->isSubclassOf( Player ) ) + { + Player* player; + player = ( Player * )( Sentient * )driver; + player->v_angle = angles; + + _lastYaw = player->client->cmd_angles[YAW]; + _DriverBBoxMaxs = player->maxs; + _DriverBBoxMins = player->mins; + } + + _driverYaw = angles[YAW]; + _driverPitch = angles[PITCH]; + + _originalBBoxMaxs = _DriverBBoxMaxs; + _originalBBoxMins = _DriverBBoxMins; + } +} + +///////////////////////////////////////////////////////// +// +// Name: _PlayMovementSound() +// +// Description: Plays a sound based on movement +// +///////////////////////////////////////////////////////// + +void HorseVehicle::_PlayMovementSound() +{ + str sound_name; + + if ( currentspeed > 0.0f ) + sound_name = "snd_forward"; + else if ( currentspeed < 0.0f ) + sound_name = "snd_backward"; + else + sound_name = "snd_idle"; + + LoopSound( sound_name.c_str() ); +} + +///////////////////////////////////////////////////////// +// +// Name: _AnimateVehicle() +// +// Description: Sets the animation on the vehicle +// +///////////////////////////////////////////////////////// + +void HorseVehicle::_AnimateVehicle(const str &anim , qboolean useEvent ) +{ + if ( animate ) + { + int anim_num = gi.Anim_Random ( this->edict->s.modelindex, anim.c_str() ); + if ( anim_num != -1 ) + { + if ( animate->CurrentAnim() != anim_num ) + { + animate->ClearLegsAnim(); + animate->ClearTorsoAnim(); + animate->NewAnim( anim_num ); + + if ( useEvent ) + { + Event *ev = new Event( EV_Vehicle_AnimDone ); + animate->SetAnimDoneEvent( ev ); + _animDone = false; + } + } + + } + } +} + +///////////////////////////////////////////////////////// +// +// Name: _AnimateDriver() +// +// Description: Sets the animation on the vehicle +// +///////////////////////////////////////////////////////// + +void HorseVehicle::_AnimateDriver(const str &anim , qboolean useEvent ) +{ + if ( !driver ) + return; + + Player *player; + player = ( Player * )( Sentient * )driver; + + + if ( player->animate ) + { + int anim_num = gi.Anim_Random ( player->edict->s.modelindex, anim.c_str() ); + if ( anim_num != -1 ) + { + if ( player->animate->CurrentAnim() != anim_num ) + { + player->animate->ClearLegsAnim(); + player->animate->ClearTorsoAnim(); + player->animate->NewAnim( anim_num ); + + if ( useEvent ) + { + Event *ev = new Event( EV_Driver_AnimDone ); + player->animate->SetAnimDoneEvent( ev ); + _driverAnimDone = false; + } + } + + } + } +} + + +///////////////////////////////////////////////////////// +// +// Name: _PositionDriverModel() +// +// Description: Places the driver model in the correct position +// +///////////////////////////////////////////////////////// + +void HorseVehicle::_PositionDriverModel() +{ + Vector i, j, k; + + i = Vector( orientation[ 0 ] ); + j = Vector( orientation[ 1 ] ); + k = Vector( orientation[ 2 ] ); + + if ( driver ) + { + Player *player; + player = ( Player * )( Sentient * )driver; + + if ( drivable ) + { + player->velocity = vec_zero; + player->setAngles( angles ); + + float delta; + delta = AngleDelta(_lastYaw , player->client->cmd_angles[YAW] ); + + + _driverYaw += ( delta * -1.0f ); + _lastYaw = player->client->cmd_angles[YAW]; + _newCrosshairMode = CROSSHAIR_MODE_STRAIGHT; + + if ( _driverYaw >= ( angles[YAW] + _maxYawThreshold ) ) + { + _driverYaw = angles[YAW] + _maxYawThreshold; + _newCrosshairMode = CROSSHAIR_MODE_RIGHT; + + } + + if ( _driverYaw <= ( angles[YAW] - _minYawThreshold ) ) + { + _driverYaw = angles[YAW] - _minYawThreshold; + _newCrosshairMode = CROSSHAIR_MODE_LEFT; + } + + delta = AngleDelta(_lastPitch , player->client->cmd_angles[PITCH] ); + + _driverPitch += ( delta * -1.0f ); + _lastPitch = player->client->cmd_angles[PITCH]; + + if ( _driverPitch > ( angles[PITCH] + _maxPitchThreshold ) ) + _driverPitch = angles[PITCH] + _maxPitchThreshold; + + if ( _driverPitch < ( angles[PITCH] - _minPitchThreshold ) ) + _driverPitch = angles[PITCH] - _minPitchThreshold; + + _SetCrossHairMode(); + player->v_angle = player->client->cmd_angles; + player->v_angle[YAW] = _driverYaw; + player->v_angle[PITCH] = _driverPitch; + + if ( !_duckflag ) + player->SetAnim( "ride" , legs ); + + } + } +} + +///////////////////////////////////////////////////////// +// +// Name: _SetSpeed() +// +// Description: Specifies the speed that the vehicle will move +// +///////////////////////////////////////////////////////// + +void HorseVehicle::_SetSpeed ( Event *ev ) +{ + currentspeed = ev->GetFloat( 1 ); +} + +///////////////////////////////////////////////////////// +// +// Name: SetVehicleMoveMode() +// +// Description: Script Interface to set the moveMode object +// +///////////////////////////////////////////////////////// + +void HorseVehicle::SetVehicleMoveMode( Event *ev ) +{ + str modeName = ev->GetString( 1 ); + _SetMoveMode( modeName ); +} + +///////////////////////////////////////////////////////// +// +// Name: SetForcedForwardSpeed() +// +// Description: Script interface for setting the +// _forcedForwardSpeed +// +///////////////////////////////////////////////////////// + +void HorseVehicle::SetForcedForwardSpeed( Event *ev ) +{ + _forcedForwardSpeed = ev->GetFloat( 1 ); +} + +///////////////////////////////////////////////////////// +// +// Name: _HandleJump() +// +// Description: Takes care of jumping +// +///////////////////////////////////////////////////////// + +void HorseVehicle::_HandleJump() +{ + + if ( groundentity && _jumped && ( _jumpmode == JUMPMODE_DONE ) ) + { + velocity[2] = 0; + _jumpflag = false; + _jumped = false; + _jumpSpeed = 0; + return; + + } + + switch ( _jumpmode ) + { + case JUMPMODE_START: + flags |= FL_FLY; + _jumpSpeed += 100.0f; + if ( _jumpSpeed > 600.0f ) + _jumpSpeed = 600.0f; + + velocity[2] = _jumpSpeed; + _AnimateVehicle ( "jump_to_rise" , true ); + + if ( _animDone ) + { + _jumpSpeed = 0; + _animDone = false; + _jumpmode = JUMPMODE_HOLD; + } + + break; + + case JUMPMODE_HOLD: + if ( level.time < _holdtime ) + { + _jumpSpeed += 10.0f; + velocity[2] = _jumpSpeed; + _AnimateVehicle("rise" , true); + } + else + { + _jumpSpeed = 0; + _animDone = false; + _jumpmode = JUMPMODE_LAND; + } + + break; + + case JUMPMODE_LAND: + _jumpSpeed -= 100.0f; + if ( _jumpSpeed < -600.0f ) + _jumpSpeed = -600.0f; + + velocity[2] = _jumpSpeed; + _AnimateVehicle( "rise_to_land" , true ); + + if ( _animDone ) + { + flags &= ~FL_FLY; + _jumpmode = JUMPMODE_DONE; + _AnimateVehicle( "run" ); + } + + break; + + + default: + //Should never get here, but you know... It's code. + break; + + } + + _jumped = true; + +} + +///////////////////////////////////////////////////////// +// +// Name: _InitializeDuck() +// +// Description: Starts Jump +// +///////////////////////////////////////////////////////// +void HorseVehicle::_InitializeDuck() +{ + if ( !driver ) + return; + + + _duckflag = true; + _duckmode = DUCKMODE_START; + _driverAnimDone = false; + _ducked = true; + + //Player *player; + //player = ( Player * )( Sentient * )driver; + + _DriverBBoxMaxs[2] -= 64.0f; +} + +///////////////////////////////////////////////////////// +// +// Name: _HandleDuck() +// +// Description: Starts Jump +// +///////////////////////////////////////////////////////// +void HorseVehicle::_HandleDuck() +{ + //Going to position torso Forward + //_driverYaw = 0; + //_lastYaw = 0; + + //_driverPitch = 0; + //_lastPitch = 0; + + if ( !driver ) + return; + + Player *player; + player = ( Player * )( Sentient * )driver; + player->v_angle = angles; + player->setAngles( angles ); + + _driverYaw = angles[YAW]; + _lastYaw = player->client->cmd_angles[YAW]; + + Vector tempMins; + Vector tempMaxs; + + switch ( _duckmode ) + { + case DUCKMODE_START: + _AnimateDriver("ride_to_duck" , true ); + + if ( _driverAnimDone ) + _duckmode = DUCKMODE_HOLD; + break; + + case DUCKMODE_HOLD: + if ( _ducked ) + _AnimateDriver("ride_duck_hold" , true ); + else + _duckmode = DUCKMODE_FINISH; + break; + + case DUCKMODE_FINISH: + _AnimateDriver("duck_to_ride" , true ); + if ( _driverAnimDone ) + _duckmode = DUCKMODE_DONE; + break; + + case DUCKMODE_DONE: + _AnimateDriver( "ride" ); + + tempMaxs[2] +=64.0f; + + player->setSize(_DriverBBoxMaxs, _DriverBBoxMaxs ); + _DriverBBoxMaxs = _originalBBoxMaxs; + _DriverBBoxMins = _originalBBoxMins; + _duckflag = false; + break; + } +} + +///////////////////////////////////////////////////////// +// +// Name: _InitializeJump() +// +// Description: Starts Jump +// +///////////////////////////////////////////////////////// + +void HorseVehicle::_InitializeJump() +{ + _jumptime = level.time + .25f; + _holdtime = _jumptime + .15f; + + _jumpflag = true; + _jumpmode = JUMPMODE_START; + _animDone = false; + +} + +///////////////////////////////////////////////////////// +// +// Name: _SetMoveMode() +// +// Description: _moveMode Factory +// +///////////////////////////////////////////////////////// + +void HorseVehicle::_SetMoveMode( const str &modeName ) +{ + if ( _moveMode ) + { + delete _moveMode; + _moveMode = 0; + } + + if ( !Q_stricmp(modeName.c_str() , "standard" ) ) + _moveMode = new HVMoveMode_Standard; + + else if ( !Q_stricmp(modeName.c_str() , "strafe" ) ) + _moveMode = new HVMoveMode_Strafe; + + else if ( !Q_stricmp(modeName.c_str() , "locked" ) ) + _moveMode = new HVMoveMode_Locked; + + else if ( !Q_stricmp(modeName.c_str() , "waypoint" ) ) + _moveMode = new HVMoveMode_FollowPath; +} + +void HorseVehicle::_SetCrossHairMode() +{ + if ( _newCrosshairMode == _currentCrosshairMode ) + return; + + switch ( _newCrosshairMode ) + { + case CROSSHAIR_MODE_STRAIGHT: + gi.cvar_set("cl_chright", "100" ); + gi.cvar_set("cl_chleft" , "-100" ); + _currentCrosshairMode = CROSSHAIR_MODE_STRAIGHT; + break; + + case CROSSHAIR_MODE_LEFT: + gi.cvar_set("cl_chright", "310" ); + gi.cvar_set("cl_chleft" , "-100" ); + _currentCrosshairMode = CROSSHAIR_MODE_LEFT; + break; + + case CROSSHAIR_MODE_RIGHT: + gi.cvar_set("cl_chright", "100" ); + gi.cvar_set("cl_chleft" , "-310" ); + _currentCrosshairMode = CROSSHAIR_MODE_RIGHT; + break; + + default: + return; + } + +} + +void HorseVehicle::AnimDone( Event *ev ) +{ + _animDone = true; +} + +void HorseVehicle::DriverAnimDone( Event *ev ) +{ + _driverAnimDone = true; +} + +void HorseVehicle::HandleEvent( Event *ev ) +{ + Event *new_event; + new_event = new Event( ev ); + ProcessEvent(new_event); +} + +void HorseVehicle::_SetBaseYaw() +{ + if ( !driver ) + return; + + + Player *player; + player = ( Player * )( Sentient * )driver; + _baseYaw = player->client->cmd_angles[YAW]; + _lastYaw = _baseYaw; + +} + +//=============================================================================== +// +// Vehicle Move Mode Classes: +// These classes act as movement handling strategies for vehicles. This way +// we can specify multiple different ways a single vehicle reacts, based on +// the context of the game. +// +//=============================================================================== +CLASS_DECLARATION( Listener, VehicleMoveMode, "MoveModeBaseClass" ) +{ + { NULL, NULL } +}; + +VehicleMoveMode::VehicleMoveMode() +{ + +} + +void VehicleMoveMode::Move( Vehicle *vehicle ) +{ + +} + +void VehicleMoveMode::HandleEvent( Event *ev ) +{ + +} + +//============================================================================== +// +// HVMoveMode_Standard +// +// Standard Move Strategy for the Horse Vehicle. This is the strategy that is +// instaniated by default +// +//=============================================================================== +HVMoveMode_Standard::HVMoveMode_Standard() +{ + +} + +void HVMoveMode_Standard::Move( Vehicle *base_vehicle ) +{ + HorseVehicle *vehicle; + vehicle = (HorseVehicle*)base_vehicle; + + Vector flatvel; + + //Zero Out our Velocity + vehicle->velocity = vec_zero; + + if ( vehicle->moveimpulse > 0.0f ) + { + if ( vehicle->currentspeed < 0.0f ) + vehicle->currentspeed *= -1.0f; + + flatvel = Vector( vehicle->orientation[ 0 ] ); + vehicle->velocity += flatvel * vehicle->currentspeed; + } + + if ( vehicle->moveimpulse < 0.0f ) + { + if ( vehicle->currentspeed > 0.0f ) + vehicle->currentspeed *= -1.0f; + + flatvel = Vector( vehicle->orientation[ 0 ] ); + vehicle->velocity += flatvel * vehicle->currentspeed; + } + + if ( vehicle->turnimpulse > 0.0f ) + { + if ( vehicle->currentspeed > 0.0f ) + vehicle->currentspeed *= -1.0f; + + flatvel = Vector( vehicle->orientation[ 1 ] ); + vehicle->velocity += flatvel * vehicle->currentspeed; + } + + if ( vehicle->turnimpulse < 0.0f ) + { + if ( vehicle->currentspeed < 0.0f ) + vehicle->currentspeed *= -1.0f; + + flatvel = Vector( vehicle->orientation[ 1 ] ); + vehicle->velocity += flatvel * vehicle->currentspeed; + } +} + +//============================================================================== +// +// HVMoveMode_Strafe +// +// The only movement allowed by the horse is strafing left and right +// +//=============================================================================== +HVMoveMode_Strafe::HVMoveMode_Strafe() +{ + +} + +void HVMoveMode_Strafe::Move( Vehicle *base_vehicle ) +{ + HorseVehicle *vehicle; + vehicle = (HorseVehicle*)base_vehicle; + + Vector flatvel; + + //Zero Out our Velocity + vehicle->velocity = vec_zero; + + if ( vehicle->turnimpulse > 0) + { + if ( vehicle->currentspeed > 0 ) + vehicle->currentspeed*=-1; + + flatvel = Vector( vehicle->orientation[ 1 ] ); + vehicle->velocity += flatvel * vehicle->currentspeed; + } + + if ( vehicle->turnimpulse < 0) + { + if ( vehicle->currentspeed < 0 ) + vehicle->currentspeed*=-1; + + flatvel = Vector( vehicle->orientation[ 1 ] ); + vehicle->velocity += flatvel * vehicle->currentspeed; + } +} + +//============================================================================== +// +// HVMoveMode_Locked +// +// A bit strange... Allows No Movement at all +// +//=============================================================================== +HVMoveMode_Locked::HVMoveMode_Locked() +{ + +} + +void HVMoveMode_Locked::Move( Vehicle *base_vehicle ) +{ + +} + +//============================================================================== +// +// HVMoveMode_FollowPath +// +// Makes the Vehicle Follow WayPointNodes -- There is currently NO collision +// avoidance here +// +//=============================================================================== +Event EV_FollowPath_SetWayPointName +( + "waypointname", + EV_SCRIPTONLY, + "s", + "waypoint_target_name", + "Set the waypoint node name to go to first" +); + +CLASS_DECLARATION( VehicleMoveMode, HVMoveMode_FollowPath, "FollowPath_MoveModeStrategy" ) +{ + { &EV_FollowPath_SetWayPointName, &HVMoveMode_FollowPath::SetWaypointName }, + { NULL, NULL } +}; + +HVMoveMode_FollowPath::HVMoveMode_FollowPath() +{ + _pathcompleted = false; + _currentWaypoint = NULL; +} + +void HVMoveMode_FollowPath::Move( Vehicle *base_vehicle ) +{ + HorseVehicle *vehicle; + vehicle = (HorseVehicle*)base_vehicle; + + + //Zero Out our Velocity + vehicle->velocity = vec_zero; + if ( vehicle->currentspeed < 0.0f ) + vehicle->currentspeed *= -1.0f; + + //First Check if we are at a way point; + Vector dest; + Vector check; + + if ( _pathcompleted ) + return; + + if (!_currentWaypoint) + { + _SetWayPoint( _currentWaypointName ); + if (!_currentWaypoint) + return; + } + + //Paranoia check, as it keeps crashing one particular box + if ( !_currentWaypoint ) + return; + + dest = _currentWaypoint->origin; + check = dest - vehicle->origin; + + // Take care of Z - Differences + check[2] = 0; + + // Check if we're close enough + if ( check.length() < 25.0f ) + { + // Run Our Thread + str waypointThread; + waypointThread = _currentWaypoint->GetThread(); + + if (waypointThread.length() ) + _RunThread( waypointThread ); + + // See if we have another point to go to + if ( _currentWaypoint->target.length() == 0 ) + { + vehicle->velocity = vec_zero; + _currentWaypoint = NULL; + _pathcompleted = true; + return; + } + + // Go To the Next Point + _currentWaypointName = _currentWaypoint->target; + _SetWayPoint( _currentWaypointName ); + + if (!_currentWaypoint) + return; + } + + if ( _currentWaypoint ) + { + vehicle->velocity = check; + vehicle->velocity.normalize(); + vehicle->velocity *= vehicle->currentspeed; + } +} + +void HVMoveMode_FollowPath::SetWaypointName( Event *ev ) +{ + _currentWaypointName = ev->GetString( 1 ); + _pathcompleted = false; +} + +void HVMoveMode_FollowPath::_SetWayPoint( const str& name ) +{ + Entity* ent_in_range; + gentity_t *ed; + + for ( int i = 0; i < MAX_GENTITIES; i++ ) + { + ed = &g_entities[i]; + + if ( !ed->inuse || !ed->entity ) + { + continue; + } + + + ent_in_range = g_entities[i].entity; + + if( ent_in_range->isSubclassOf( WayPointNode ) ) + { + if (!Q_stricmp(ent_in_range->targetname.c_str() , name.c_str() )) + { + _currentWaypoint = (WayPointNode*)ent_in_range; + return; + } + } + } + + _currentWaypoint = NULL; +} + +void HVMoveMode_FollowPath::_RunThread( const str &thread_name ) +{ + if ( thread_name.length() <= 0 ) + return; + + CThread *thread; + + thread = Director.CreateThread( thread_name ); + + if ( thread ) + thread->DelayedStart( 0.0f ); +} + +void HVMoveMode_FollowPath::HandleEvent( Event *ev ) +{ + Event *new_event; + new_event = new Event( ev ); + ProcessEvent(new_event); +} diff --git a/dlls/game/vehicle.h b/dlls/game/vehicle.h new file mode 100644 index 0000000..e68a142 --- /dev/null +++ b/dlls/game/vehicle.h @@ -0,0 +1,459 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/vehicle.h $ +// $Revision:: 22 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Script controlled vehicles. +// + +class Vehicle; +class HorseVehicle; + +#ifndef __VEHICLE_H__ +#define __VEHICLE_H__ + +#include "g_local.h" +#include "entity.h" +#include "sentient.h" +#include "scriptslave.h" +#include "waypoints.h" + +extern Event EV_Vehicle_Enter; +extern Event EV_Vehicle_Exit; +extern Event EV_Vehicle_Drivable; +extern Event EV_Vehicle_UnDrivable; +extern Event EV_Vehicle_Lock; +extern Event EV_Vehicle_UnLock; +extern Event EV_Vehicle_SeatAnglesOffset; +extern Event EV_Vehicle_SeatOffset; +extern Event EV_Vehicle_DriverAnimation; +extern Event EV_Vehicle_SetWeapon; +extern Event EV_Vehicle_ShowWeapon; +extern Event EV_Vehicle_SetSpeed; +extern Event EV_Vehicle_SetTurnRate; + +extern Event EV_FollowPath_SetWayPointName; + +typedef enum + { + CROSSHAIR_MODE_STRAIGHT, + CROSSHAIR_MODE_RIGHT, + CROSSHAIR_MODE_LEFT + } crosshairMode_t; + +typedef enum + { + JUMPMODE_START, + JUMPMODE_HOLD, + JUMPMODE_LAND, + JUMPMODE_DONE + } jumpMode_t; + +typedef enum + { + DUCKMODE_START, + DUCKMODE_HOLD, + DUCKMODE_FINISH, + DUCKMODE_DONE + } duckMode_t; + + +//================================================ +// VehicleMoveMode Classes +// +// The move mode classes provide a way for all new +// vehicles to easily change they're movement +// strategy +//================================================= +class VehicleMoveMode : public Listener + { + public: + CLASS_PROTOTYPE( VehicleMoveMode ); + VehicleMoveMode (); + + virtual void Move( Vehicle *vehicle ); + virtual void HandleEvent( Event *ev ); + }; + +class HVMoveMode_Standard : public VehicleMoveMode + { + public: + HVMoveMode_Standard (); + void Move( Vehicle *vehicle ); + }; + +class HVMoveMode_Strafe : public VehicleMoveMode + { + public: + HVMoveMode_Strafe (); + void Move( Vehicle *vehicle ); + }; + +class HVMoveMode_Locked : public VehicleMoveMode + { + public: + HVMoveMode_Locked (); + void Move( Vehicle *vehicle ); + + }; + +class HVMoveMode_FollowPath : public VehicleMoveMode + { + public: + CLASS_PROTOTYPE( HVMoveMode_FollowPath ); + + HVMoveMode_FollowPath (); + void Move( Vehicle *vehicle ); + void SetWaypointName ( Event *ev ); + void HandleEvent ( Event *ev ); + + private: + void _SetWayPoint( const str& name ); + void _RunThread( const str &thread_name ); + + private: + WayPointNode* _currentWaypoint; + str _currentWaypointName; + qboolean _pathcompleted; + + }; + + +//================================================ +// Old Vehicle Base class -- Needs to be changed +//================================================ +class VehicleBase : public ScriptModel + { + public: + VehicleBase *vlink; + Vector offset; + + CLASS_PROTOTYPE( VehicleBase ); + + VehicleBase(); + virtual void Archive( Archiver &arc ); + }; + +inline void VehicleBase::Archive + ( + Archiver &arc + ) + { + ScriptModel::Archive( arc ); + + arc.ArchiveObjectPointer( ( Class ** )&vlink ); + arc.ArchiveVector( &offset ); + } + + +/*QUAKED script_wheelsback (0 .5 .8) ? +*/ +class BackWheels : public VehicleBase + { + public: + CLASS_PROTOTYPE( BackWheels ); + }; + +/*QUAKED script_wheelsfront (0 .5 .8) ? +*/ +class FrontWheels : public VehicleBase + { + public: + CLASS_PROTOTYPE( FrontWheels ); + }; + + +class Vehicle : public VehicleBase + { + public: + float turnimpulse; + float pitchimpulse; + float moveimpulse; + float jumpimpulse; + float currentspeed; + + Vector _DriverBBoxMaxs; + Vector _DriverBBoxMins; + Vector _originalBBoxMaxs; + Vector _originalBBoxMins; + + void CalculateOrientation(); + + protected: + SentientPtr driver; + SentientPtr lastdriver; + float maxturnrate; + + float turnangle; + float pitchangle; + + float speed; + float conesize; + float maxtracedist; + str weaponName; + str driveranim; + Vector last_origin; + Vector seatangles; + Vector seatoffset; + Vector driveroffset; + Vector Corners[4]; + + qboolean drivable; + qboolean locked; + qboolean hasweapon; + qboolean showweapon; + qboolean steerinplace; + qboolean jumpable; + + qboolean _restrictPitch; + float _maximumPitch; + float _minimumPitch; + + qboolean _restrictYaw; + float _maximumYaw; + float _minimumYaw; + + float _startYaw; + float _startPitch; + float _yawSeam; + float _pitchSeam; + + qboolean _noPrediction; + + vec3_t _oldOrigin; + bool _oldWeaponName; + + bool _disableInventory; + + //Test stuff + float usetime; + + virtual void WorldEffects( void ); + virtual void CheckWater( void ); + virtual void DriverUse( Event *ev ); + virtual void VehicleStart( Event *ev ); + virtual void VehicleTouched( Event *ev ); + virtual void VehicleBlocked( Event *ev ); + virtual void Postthink( void ); + virtual void Drivable( Event *ev ); + virtual void UnDrivable( Event *ev ); + virtual void Jumpable( Event *ev ); + virtual void SeatAnglesOffset( Event *ev ); + virtual void SeatOffset( Event *ev ); + virtual void SetDriverAngles( const Vector &angles ); + virtual void Lock( Event *ev ); + virtual void UnLock( Event *ev ); + virtual void SetWeapon( Event *ev ); + virtual void ShowWeaponEvent( Event *ev ); + virtual void DriverAnimation( Event *ev ); + virtual void SetSpeed( Event *ev ); + virtual void SetTurnRate( Event *ev ); + virtual void SteerInPlace( Event *ev ); + + + public: + + CLASS_PROTOTYPE( Vehicle ); + + Vehicle(); + + virtual qboolean Drive( usercmd_t *ucmd ); + virtual qboolean HasWeapon( void ); + virtual qboolean ShowWeapon( void ); + Sentient *Driver( void ); + virtual qboolean IsDrivable( void ); + void disableDriverInventory( void ); + void enableDriverInventory( void ); + + virtual void Archive( Archiver &arc ); + virtual void HandleEvent( Event *ev ); + void RestrictPitch(Event* event); + void RestrictRotation(Event* event); + void SetNoPrediction(Event* event); + void DisableInventory(Event* event); + }; + +inline void Vehicle::Archive( Archiver &arc ) +{ + VehicleBase::Archive( arc ); + + arc.ArchiveFloat( &turnimpulse ); + arc.ArchiveFloat( &moveimpulse ); + arc.ArchiveFloat( &moveimpulse ); + arc.ArchiveFloat( &jumpimpulse ); + arc.ArchiveFloat( ¤tspeed ); + + arc.ArchiveVector( &_DriverBBoxMaxs ); + arc.ArchiveVector( &_DriverBBoxMins ); + arc.ArchiveVector( &_originalBBoxMaxs ); + arc.ArchiveVector( &_originalBBoxMins ); + + arc.ArchiveSafePointer( &driver ); + arc.ArchiveSafePointer( &lastdriver ); + + arc.ArchiveFloat( &maxturnrate ); + arc.ArchiveFloat( &turnangle ); + arc.ArchiveFloat( &pitchangle ); + + arc.ArchiveFloat( &speed ); + + arc.ArchiveFloat( &conesize ); + arc.ArchiveFloat( &maxtracedist ); + arc.ArchiveString( &weaponName ); + arc.ArchiveString( &driveranim ); + arc.ArchiveVector( &last_origin ); + arc.ArchiveVector( &seatangles ); + arc.ArchiveVector( &seatoffset ); + arc.ArchiveVector( &driveroffset ); + + arc.ArchiveVector( &Corners[ 0 ] ); + arc.ArchiveVector( &Corners[ 1 ] ); + arc.ArchiveVector( &Corners[ 2 ] ); + arc.ArchiveVector( &Corners[ 3 ] ); + + arc.ArchiveBoolean( &drivable ); + arc.ArchiveBoolean( &locked ); + arc.ArchiveBoolean( &hasweapon ); + arc.ArchiveBoolean( &showweapon ); + arc.ArchiveBoolean( &steerinplace ); + arc.ArchiveBoolean( &jumpable ); + + arc.ArchiveBoolean(&_restrictPitch); + arc.ArchiveFloat(&_maximumPitch); + arc.ArchiveFloat(&_minimumPitch); + + arc.ArchiveBoolean(&_restrictYaw); + arc.ArchiveFloat(&_maximumYaw); + arc.ArchiveFloat(&_minimumYaw); + + arc.ArchiveFloat( &_startYaw ); + arc.ArchiveFloat( &_startPitch ); + arc.ArchiveFloat( &_yawSeam ); + arc.ArchiveFloat( &_pitchSeam ); + + arc.ArchiveBoolean(&_noPrediction); + + arc.ArchiveFloat( &usetime ); + + arc.ArchiveBool(&_disableInventory); +} + +class DrivableVehicle : public Vehicle + { + public: + + CLASS_PROTOTYPE( DrivableVehicle ); + + DrivableVehicle(); + + virtual void Killed( Event *ev ); + }; + + +//================================================= +// Horse Vehicle +// A lot of this functionality needs to be redone +// in a cleaner vehicle base class +//================================================= + +class HorseVehicle : public DrivableVehicle + { + public: + + CLASS_PROTOTYPE( HorseVehicle ); + HorseVehicle(); + ~HorseVehicle(); + //Virtual Functions + qboolean Drive( usercmd_t *ucmd ); + void Postthink( void ); + void DriverUse( Event *ev ); + + //Other Functions + void SetVehicleMoveMode( Event *ev ); + void SetForcedForwardSpeed ( Event *ev ); + void SetWaypointName( Event *ev ); + void SetMinYawThreshold( Event *ev ); + void SetMaxYawThreshold( Event *ev ); + void SetMinPitchThreshold( Event *ev ); + void SetMaxPitchThreshold( Event *ev ); + void PassToMoveMode( Event *ev ); + void AnimDone( Event *ev ); + void DriverAnimDone ( Event *ev ); + void HandleEvent ( Event *ev ); + + + + private: + void _SetSpeed( Event *ev ); + + void _PlayMovementSound(); + void _AnimateVehicle(const str &anim, qboolean useEvent = false ); + void _AnimateDriver( const str &anim, qboolean useEvent = false ); + void _PositionDriverModel(); + void _InitializeJump(); + void _HandleJump(); + void _InitializeDuck(); + void _HandleDuck(); + void _SetMoveMode( const str &modeName ); + void _SetCrossHairMode(); + void _SetBaseYaw(); + + + + //Member Vars + private: + + float _baseYaw; + + float _driverYaw; + float _lastYaw; + float _minYawThreshold; + float _maxYawThreshold; + + float _driverPitch; + float _lastPitch; + float _minPitchThreshold; + float _maxPitchThreshold; + + float _forcedForwardSpeed; + float _jumptime; + + qboolean _jumpflag; + qboolean _jumped; + float _jumpSpeed; + + qboolean _animDone; + qboolean _driverAnimDone; + + float _holdtime; + jumpMode_t _jumpmode; + duckMode_t _duckmode; + + qboolean _duckflag; + qboolean _ducked; + qboolean _duckheld; + + crosshairMode_t _currentCrosshairMode; + crosshairMode_t _newCrosshairMode; + + + + VehicleMoveMode* _moveMode; + + }; + + + +typedef SafePtr VehiclePtr; + +#endif /* vehicle.h */ diff --git a/dlls/game/viewthing.cpp b/dlls/game/viewthing.cpp new file mode 100644 index 0000000..42a0ea1 --- /dev/null +++ b/dlls/game/viewthing.cpp @@ -0,0 +1,2604 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/viewthing.cpp $ +// $Revision:: 46 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Actor code for the Viewthing. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "viewthing.h" +#include "player.h" +#include "q_shared.h" +#include + +// These command vars are used with the viewset and viewlist commands +cvar_t *g_vt_droptoggle; // treated as a boolean. If true, we drop newly spawned things +cvar_t *g_vt_setFilename ; // contains the filename of the current .lst file (if any) +cvar_t *g_vt_modelIndex ; // Index specifying which model in the .lst file we are viewing +cvar_t *g_vt_numModels ; // Number of total models in the .lst file +cvar_t *g_vt_height ; // Z Height off 0 that we position spawned things +cvar_t *g_vt_scale ; // Scale of spawned things +cvar_t *g_vt_yaw ; // Current yaw value for spawned things +cvar_t *g_vt_roll ; // Current roll value for spawned things +cvar_t *g_vt_pitch ; // Current pitch value for spawned things +cvar_t *g_vt_xtrans ; // Current x translation for current viewthing +cvar_t *g_vt_ytrans ; // Current y translation for current viewthing +cvar_t *g_vt_ztrans ; // Current z translation for current viewthing +cvar_t *g_vt_makestatic ; // Toggle for controlling if spawned objects are "static" (static lighting) or not. +cvar_t *g_vt_usebox ; // When shooting objects, objects pull away from wall by their bounding box. Defaults true. +cvar_t *g_vt_viewcount ; // Holds number of spawned viewthings +cvar_t *g_vt_viewnumber ; // Holds position of current_viewthing amongst all spawned ones +cvar_t *g_vt_viewrandomize ; // If true (default is false) spawned things are affected by randomization settings (below) +cvar_t *g_vt_viewsrandomize; // If true randomize scale (default true) +cvar_t *g_vt_viewzrandomize; // If true randomize rotations about z axis (yaw) (default false) +cvar_t *g_vt_viewyrandomize; // If true randomize rotations about y axis (pitch) (default false) +cvar_t *g_vt_viewxrandomize; // If true randomize rotations about x axis (roll) (default false) +cvar_t *g_vt_viewminrandomize;// Min value for randomizing scale (default is 50%) +cvar_t *g_vt_viewmaxrandomize;// Max value for randomizing scale (default is 150%) +cvar_t *g_vt_viewbellrandomize;// Set to true if you want scale randomizatins to be on a bell curve (default true) + + +Event EV_ViewThing_Think +( + "viewthing_think", + EV_CODEONLY, + NULL, + NULL, + "Called every frame to process the view thing." +); +Event EV_ViewThing_ToggleAnimate +( + "viewanimate", + EV_CHEAT, + NULL, + NULL, + "Cycle through the animations modes of the current viewthing\n" + "No Animation\n" + "Animation with no motion\n" + "Animation with looping motion\n" + "Animation with motion\n" +); +Event EV_ViewThing_SetModel +( + "viewmodel", + EV_CHEAT, + "s", + "viewthingModel", + "Set the model of the current viewthing" +); +Event EV_ViewThing_NextFrame +( + "viewnext", + EV_CHEAT, + NULL, + NULL, + "Advance to the next frame of animation of the current viewthing" +); +Event EV_ViewThing_PrevFrame +( + "viewprev", + EV_CHEAT, + NULL, + NULL, + "Advance to the previous frame of animation of the current viewthing" +); +Event EV_ViewThing_NextAnim +( + "viewnextanim", + EV_CHEAT, + NULL, + NULL, + "Advance to the next animation of the current viewthing" +); +Event EV_ViewThing_PrevAnim +( + "viewprevanim", + EV_CHEAT, + NULL, + NULL, + "Advance to the previous animation of the current viewthing" +); +Event EV_ViewThing_SetFrame +( + "viewsetframe", + EV_CHEAT, + NULL, + NULL, + "Set the animation to a specific frame number." +); +Event EV_ViewThing_ScaleUp +( + "viewscaleup", + EV_CHEAT, + NULL, + NULL, + "Increase the scale of the current viewthing" +); +Event EV_ViewThing_ScaleDown +( + "viewscaledown", + EV_CHEAT, + NULL, + NULL, + "Decrease the scale of the current viewthing" +); +Event EV_ViewThing_SetScale +( + "viewscale", + EV_CHEAT, + "f", + "scale", + "Set the scale of the current viewthing" +); +Event EV_ViewThing_SetYaw +( + "viewyaw", + EV_CHEAT, + "f", + "yaw", + "Set the yaw of the current viewthing" +); +Event EV_ViewThing_SetPitch +( + "viewpitch", + EV_CHEAT, + "f", + "pitch", + "Set the pitch of the current viewthing" +); +Event EV_ViewThing_SetRoll +( + "viewroll", + EV_CHEAT, + "f", + "roll", + "Set the roll of the current viewthing" +); +Event EV_ViewThing_SetAngles +( + "viewangles", + EV_CHEAT, + "f[0,360]f[0,360]f[0,360]", + "pitch yaw roll", + "Set the angles of the current viewthing" +); +Event EV_ViewThing_TranslateX +( + "viewxtranslate", + EV_CHEAT, + "f", + "xtrans", + "Translate current viewthing in x direction" +); +Event EV_ViewThing_TranslateY +( + "viewytranslate", + EV_CHEAT, + "f", + "ytrans", + "Translate current viewthing in y direction" +); +Event EV_ViewThing_TranslateZ +( + "viewztranslate", + EV_CHEAT, + "f", + "ztrans", + "Translate current viewthing in z direction" +); +Event EV_ViewThing_Pull +( + "viewthingpull", + EV_CHEAT, + NULL, + NULL, + "Pulls the current viewthing to the camera" +); +Event EV_ViewThing_Spawn +( + "viewspawn", + EV_CHEAT, + "s", + "model", + "Create a viewthing with the specified model" +); +Event EV_ViewThing_SpawnFromTS +( + "viewspawnfromts", + EV_CHEAT, + "sS", + "reloadmodel spawnmodel", + "Create a viewthing from the model specified by the ToolServer" +); +Event EV_ViewThing_Next +( + "viewthingnext", + EV_CHEAT, + NULL, + NULL, + "Change the active viewthing to the next viewthing" +); +Event EV_ViewThing_Prev +( + "viewthingprev", + EV_CHEAT, + NULL, + NULL, + "Change the active viewthing to the previous viewthing" +); +Event EV_ViewThing_Attach +( + "viewattach", + EV_CHEAT, + "ss", + "tagname model", + "Attach a model the the specified tagname" +); +Event EV_ViewThing_Detach +( + "viewdetach", + EV_CHEAT, + NULL, + NULL, + "Detach the current viewthing from its parent" +); +Event EV_ViewThing_DetachAll +( + "viewdetachall", + EV_CHEAT, + NULL, + NULL, + "Detach all the models attached to the current viewthing" +); +Event EV_ViewThing_Delete +( + "viewdelete", + EV_CHEAT, + NULL, + NULL, + "Delete the current viewthing" +); +Event EV_ViewThing_SetStatic +( + "viewsetstatic", + EV_CHEAT, + "i", + "boolean", + "Makes the current viewthing a static (not dynamically lit) object" +); +Event EV_ViewThing_SetOrigin +( + "vieworigin", + EV_CHEAT, + "fff", + "x y z", + "Set the origin of the current viewthing" +); +Event EV_ViewThing_DeleteAll +( + "viewdeleteall", + EV_CHEAT, + NULL, + NULL, + "Delete all viewthings" +); +Event EV_ViewThing_LastFrame +( + "viewlastframe", + EV_CODEONLY, + NULL, + NULL, + "Called when the view things last animation frame is displayed." +); +Event EV_ViewThing_SaveOffSurfaces +( + "viewsavesurfaces", + EV_CODEONLY, + NULL, + NULL, + "Called after the model is spawned to save off the models original surfaces." +); +Event EV_ViewThing_PrintTime +( + "_viewthing_printtime", + EV_CODEONLY, + NULL, + NULL, + "Prints out the current level.time." +); +Event EV_ViewThing_SetAnim +( + "viewsetanim", + EV_CHEAT, + "f", + "animNum", + "Set the animation absolutely based off a floating point value" +); +Event EV_ViewThing_NextMorph +( + "viewnextmorph", + EV_CHEAT, + NULL, + NULL, + "Goes to the next morph for this model" +); +Event EV_ViewThing_PrevMorph +( + "viewprevmorph", + EV_CHEAT, + NULL, + NULL, + "Goes to the previous morph for this model" +); +Event EV_ViewThing_Morph +( + "viewmorph", + EV_CHEAT, + NULL, + NULL, + "Morphs the current morph" +); +Event EV_ViewThing_Unmorph +( + "viewunmorph", + EV_CHEAT, + NULL, + NULL, + "Unmorphs the current morph" +); +Event EV_ViewSet_Load +( + "viewsetload", + EV_CHEAT, + "s", + "filename", + "Loads the specified viewset" +); +Event EV_ViewSet_Save +( + "viewsetsave", + EV_CHEAT, + NULL, + NULL, + "Saves names and positions of currently spawned things data to disk" +); +Event EV_ViewThing_Copy +( + "viewcopy", + EV_CHEAT, + NULL, + NULL, + "Copies the current viewthing" +); +Event EV_ViewThing_Shoot +( + "viewshoot", + EV_CHEAT, + NULL, + NULL, + "Shoots current viewthing ahead and sticks it on the wall" +); +Event EV_ViewList_List +( + "viewlistlist", + EV_CHEAT, + NULL, + NULL, + "List all TIKIs in the current .lst set" +); +Event EV_ViewList_NextInSet +( + "viewlistnext", + EV_CHEAT, + NULL, + NULL, + "Delete current viewthing and load next view thing in view list" +); +Event EV_ViewList_PrevInSet +( + "viewlistprev", + EV_CHEAT, + NULL, + NULL, + "Delete current viewthing and load prev view thing in view list" +); +Event EV_ViewList_LiftModel +( + "viewlistliftmodel", + EV_CHEAT, + "f", + "z", + "Raise the origin (z) by specified amount" +); +Event EV_ViewList_ToggleDrop +( + "viewlisttoggledrop", + EV_CHEAT, + NULL, + NULL, + "Toggle whether objects are dropped upon spawning" +); +Event EV_ViewList_Jump +( + "viewlistjump", + EV_CHEAT, + "i", + "modelindex", + "Jumps to a specific model in a view set (.lst file) of models" +); +Event EV_ViewThing_ResetOrigin +( + "viewresetorigin", + EV_CHEAT, + NULL, + NULL, + "Sets the origin of current viewspawn to its current origin" +); +Event EV_ViewThing_XTrans +( + "viewtransx", + EV_CHEAT, + "i", + "offsetamount", + "translate current viewspawn in x direction by specified amount" +); +Event EV_ViewThing_YTrans +( + "viewtransy", + EV_CHEAT, + "i", + "offsetamount", + "translate current viewspawn in y direction by specified amount" +); +Event EV_ViewThing_ZTrans +( + "viewtransz", + EV_CHEAT, + "i", + "offsetamount", + "translate current viewspawn in z direction by specified amount" +); +Event EV_ViewThing_Offset +( + "viewoffset", + EV_CHEAT, + "fff", + "x y z", + "Offsets the origin of the current viewthing" +); +Event EV_ViewThing_ChangeRoll +( + "viewchangeroll", + EV_CHEAT, + "i", + "offsetamount", + "rotate current viewspawn around x axis by specified amount" +); +Event EV_ViewThing_ChangePitch +( + "viewchangepitch", + EV_CHEAT, + "i", + "offsetamount", + "rotate current viewspawn around y axis by specified amount" +); +Event EV_ViewThing_ChangeYaw +( + "viewchangeyaw", + EV_CHEAT, + "i", + "offsetamount", + "rotate current viewspawn around z axis by specified amount" +); +Event EV_ViewThing_Flash +( + "viewflash", + EV_CHEAT, + NULL, + NULL, + "Flashes the currently select viewthing" +); + +CLASS_DECLARATION( Listener, ViewMaster, NULL ) +{ + { &EV_ViewThing_Spawn, &ViewMaster::Spawn }, + { &EV_ViewThing_SpawnFromTS, &ViewMaster::SpawnFromTS }, + { &EV_ViewThing_Next, &ViewMaster::Next }, + { &EV_ViewThing_Prev, &ViewMaster::Prev }, + { &EV_ViewThing_SetModel, &ViewMaster::SetModelEvent }, + { &EV_ViewThing_DeleteAll, &ViewMaster::DeleteAll }, + { &EV_ViewThing_ToggleAnimate, &ViewMaster::PassEvent }, + { &EV_ViewThing_NextFrame, &ViewMaster::PassEvent }, + { &EV_ViewThing_PrevFrame, &ViewMaster::PassEvent }, + { &EV_ViewThing_NextAnim, &ViewMaster::PassEvent }, + { &EV_ViewThing_PrevAnim, &ViewMaster::PassEvent }, + { &EV_ViewThing_ScaleUp, &ViewMaster::PassEvent }, + { &EV_ViewThing_ScaleDown, &ViewMaster::PassEvent }, + { &EV_ViewThing_SetScale, &ViewMaster::PassEvent }, + { &EV_ViewThing_SetYaw, &ViewMaster::PassEvent }, + { &EV_ViewThing_SetPitch, &ViewMaster::PassEvent }, + { &EV_ViewThing_SetRoll, &ViewMaster::PassEvent }, + { &EV_ViewThing_SetAngles, &ViewMaster::PassEvent }, + { &EV_ViewThing_Attach, &ViewMaster::PassEvent }, + { &EV_ViewThing_Detach, &ViewMaster::PassEvent }, + { &EV_ViewThing_DetachAll, &ViewMaster::PassEvent }, + { &EV_ViewThing_SetStatic, &ViewMaster::PassEvent }, + { &EV_ViewThing_SetOrigin, &ViewMaster::PassEvent }, + { &EV_ViewThing_SetAnim, &ViewMaster::PassEvent }, + { &EV_ViewThing_NextMorph, &ViewMaster::PassEvent }, + { &EV_ViewThing_PrevMorph, &ViewMaster::PassEvent }, + { &EV_ViewThing_Morph, &ViewMaster::PassEvent }, + { &EV_ViewThing_Unmorph, &ViewMaster::PassEvent }, + { &EV_ViewThing_ChangeRoll, &ViewMaster::PassEvent }, + { &EV_ViewThing_ChangePitch, &ViewMaster::PassEvent }, + { &EV_ViewThing_ChangeYaw, &ViewMaster::PassEvent }, + { &EV_ViewThing_Flash, &ViewMaster::PassEvent }, + { &EV_ViewThing_SetFrame, &ViewMaster::PassEvent }, + { &EV_ViewSet_Load, &ViewMaster::LoadSet }, + { &EV_ViewSet_Save, &ViewMaster::Save }, + { &EV_ViewThing_Copy, &ViewMaster::Copy }, + { &EV_ViewThing_Shoot, &ViewMaster::Shoot }, + { &EV_ViewThing_Delete, &ViewMaster::Delete }, + { &EV_ViewThing_TranslateX, &ViewMaster::SetXTranslation }, + { &EV_ViewThing_TranslateY, &ViewMaster::SetYTranslation }, + { &EV_ViewThing_TranslateZ, &ViewMaster::SetZTranslation }, + { &EV_ViewList_PrevInSet, &ViewMaster::PrevModelInSet }, + { &EV_ViewList_NextInSet, &ViewMaster::NextModelInSet }, + { &EV_ViewList_LiftModel, &ViewMaster::SetZTranslation }, + { &EV_ViewList_ToggleDrop, &ViewMaster::ToggleDrop }, + { &EV_ViewList_Jump, &ViewMaster::JumpToModel }, + { &EV_ViewThing_Pull, &ViewMaster::PullToCamera }, + { &EV_ViewThing_ResetOrigin, &ViewMaster::ResetOrigin }, + { &EV_ViewThing_XTrans, &ViewMaster::XTranslate }, + { &EV_ViewThing_YTrans, &ViewMaster::YTranslate }, + { &EV_ViewThing_ZTrans, &ViewMaster::ZTranslate }, + { &EV_ViewThing_Offset, &ViewMaster::Offset }, + { NULL, NULL } +}; + +ViewMaster Viewmodel; + +ViewMaster::ViewMaster() +{ + current_viewthing = NULL; + _spawnAtLastItemsOrigin = false ; + _currentSetIdx = 0 ; + _numberOfSets = 0 ; + _numberOfModelsInSet = 0 ; +} + +ViewMaster::~ViewMaster() +{ + _setNamesArray.FreeObjectList(); + _modelNamesArray.FreeObjectList(); +} + +void ViewMaster::Init( void ) +{ + gi.AddCommand( "viewanimate" ); + gi.AddCommand( "viewmodel" ); + gi.AddCommand( "viewnext" ); + gi.AddCommand( "viewprev" ); + gi.AddCommand( "viewnextanim" ); + gi.AddCommand( "viewprevanim" ); + gi.AddCommand( "viewscaleup" ); + gi.AddCommand( "viewscaledown" ); + gi.AddCommand( "viewscale" ); + gi.AddCommand( "viewyaw" ); + gi.AddCommand( "viewpitch" ); + gi.AddCommand( "viewroll" ); + gi.AddCommand( "viewangles" ); + gi.AddCommand( "viewxtranslate" ); + gi.AddCommand( "viewytranslate" ); + gi.AddCommand( "viewztranslate" ); + gi.AddCommand( "viewoffset" ); + gi.AddCommand( "viewspawn" ); + gi.AddCommand( "viewspawnfromts"); + gi.AddCommand( "viewthingnext" ); + gi.AddCommand( "viewthingprev" ); + gi.AddCommand( "viewthingpull" ); + gi.AddCommand( "viewattach" ); + gi.AddCommand( "viewdetach" ); + gi.AddCommand( "viewdetachall" ); + gi.AddCommand( "viewdelete" ); + gi.AddCommand( "viewsetstatic" ); + gi.AddCommand( "vieworiginlift" ); + gi.AddCommand( "vieworigin" ); + gi.AddCommand( "viewdeleteall" ); + gi.AddCommand( "viewsetanim" ); + gi.AddCommand( "viewsetload" ); + gi.AddCommand( "viewsetnext" ); + gi.AddCommand( "viewsetprev" ); + gi.AddCommand( "viewsetsave" ); + gi.AddCommand( "viewcopy" ); + gi.AddCommand( "viewlistlist" ); + gi.AddCommand( "viewlistnext" ); + gi.AddCommand( "viewlistprev" ); + gi.AddCommand( "viewlistliftmodel" ); + gi.AddCommand( "viewlisttoggledrop" ); + gi.AddCommand( "viewlistjump" ); + gi.AddCommand( "viewresetorigin" ); + gi.AddCommand( "viewtransx" ); + gi.AddCommand( "viewtransy" ); + gi.AddCommand( "viewtransz" ); + gi.AddCommand( "viewflash" ); + gi.AddCommand( "viewsetframe" ); + + g_vt_droptoggle = gi.cvar( "g_vt_droptoggle", "1", 0 ); + g_vt_setFilename = gi.cvar( "g_vt_setFilename", "", 0 ); + g_vt_modelIndex = gi.cvar( "g_vt_modelIndex", "0", 0 ); + g_vt_height = gi.cvar( "g_vt_height", "0", 0 ); + g_vt_makestatic = gi.cvar( "g_vt_makestatic", "1", 0 ); + g_vt_scale = gi.cvar( "g_vt_scale", "1.0", 0 ); + g_vt_numModels = gi.cvar( "g_vt_numModels", "0", 0 ); + g_vt_yaw = gi.cvar( "g_vt_yaw", "0.0", 0 ); + g_vt_pitch = gi.cvar( "g_vt_pitch", "0.0", 0 ); + g_vt_roll = gi.cvar( "g_vt_roll", "0.0", 0 ); + g_vt_xtrans = gi.cvar( "g_vt_xtrans", "0.0", 0 ); + g_vt_ytrans = gi.cvar( "g_vt_ytrans", "0.0", 0 ); + g_vt_ztrans = gi.cvar( "g_vt_ztrans", "0.0", 0 ); + g_vt_usebox = gi.cvar( "g_vt_usebox", "1", 0 ); + g_vt_viewnumber = gi.cvar( "g_vt_viewnumber", "0", 0 ); + g_vt_viewcount = gi.cvar( "g_vt_viewcount", "0", 0 ); + g_vt_viewrandomize = gi.cvar( "g_vt_viewrandomize", "0", 0 ); + g_vt_viewsrandomize = gi.cvar( "g_vt_viewsrandomize", "1", 0 ); + g_vt_viewzrandomize = gi.cvar( "g_vt_viewzrandomize", "1", 0 ); + g_vt_viewyrandomize = gi.cvar( "g_vt_viewyrandomize", "0", 0 ); + g_vt_viewxrandomize = gi.cvar( "g_vt_viewxrandomize", "0", 0 ); + g_vt_viewminrandomize = gi.cvar( "g_vt_viewminrandomize", "25", 0 ); + g_vt_viewmaxrandomize = gi.cvar( "g_vt_viewmaxrandomize", "175", 0 ); + g_vt_viewbellrandomize = gi.cvar( "g_vt_viewminrandomize", "1", 0 ); +} + +void ViewMaster::Save( Event *ev ) +{ + // saves currently spawned things to disk + str buf; + str filename; + int count = 1 ; + + // get the name of the sound file from the world + filename = "maps/"; + filename += level.mapname; + for(int i = filename.length() - 1; i >= 0; i-- ) + { + if ( filename[ i ] == '.' ) + { + filename[ i ] = 0; + break; + } + } + + filename += "_ents.map"; + gi.cvar_set("g_vt_savefile", filename.c_str()); + gi.Printf( "Saving spawned things to '%s'...\n", filename.c_str() ); + + buf = ""; + buf += va( "//\n" ); + buf += va( "// Viewspawned entities file \"%s\".\n", filename.c_str()); + buf += va( "//\n" ); + + Viewthing *viewthingPtr = 0 ; + Entity *entityPtr = 0 ; + entityPtr = G_FindClass( entityPtr, "viewthing" ); + + while (entityPtr != 0) + { + viewthingPtr = (Viewthing*)entityPtr; + + str modelName = viewthingPtr->model ; + char* strPtr = (char*)modelName.c_str(); + char* endPtr = strPtr + (strlen(strPtr) - 1) ; + bool found = false ; + + for (int idx=strlen(strPtr); idx > 0; idx++) + { + if (*endPtr == '/') + { + endPtr++ ; + found = true ; + break ; + } + endPtr-- ; + } + + if (found) + { + strPtr = endPtr ; + } + + buf += va( "// Entity %d\n", count); + buf += va( "{\n"); + buf += va( "\"make_static\" \"%d\"\n", viewthingPtr->_static); + buf += va( "\"classname\" \"FROM_TIKI\"\n" ); + buf += va( "\"model\" \"%s\"\n", strPtr); + buf += va( "\"scale\" \"%f\"\n", viewthingPtr->edict->s.scale); + buf += va( "\"origin\" \"%.2f %.2f %.2f\"\n", viewthingPtr->origin.x, viewthingPtr->origin.y, viewthingPtr->origin.z ); + buf += va( "\"angles\" \"%.2f %.2f %.2f\"\n", viewthingPtr->angles.x, viewthingPtr->angles.y, viewthingPtr->angles.z ); + buf += va( "\"spawnflags\" \"1\"\n"); + buf += va( "}\n" ); + + entityPtr = G_FindClass( entityPtr, "viewthing"); + count++ ; + } + gi.FS_WriteFile( filename.c_str(), buf.c_str(), buf.length() + 1 ); + gi.Printf( "Done.\n" ); +} + +void ViewMaster::LoadSet( Event *ev ) +{ + // If we have stuff loaded, delete it all + if (_numberOfModelsInSet || _numberOfSets) + { + gi.cvar_set( "viewsetname", va( " ") ); + gi.cvar_set( "viewlistmodelname", va( " ") ); + gi.cvar_set( "g_vt_modelIndex", "0"); + gi.cvar_set( "g_vt_setFilename", ""); + + _currentSetIdx = 0 ; + _numberOfSets = 0 ; + _numberOfModelsInSet = 0 ; + _setNamesArray.FreeObjectList(); + _modelNamesArray.FreeObjectList(); + } + + str filename = ev->GetString(1); + _setNamesArray.AddObject(filename); + _numberOfSets++ ; + + // If we loaded any tiki filenames, display the first one + if (_numberOfSets) + { + _currentSetIdx = 1 ; + //str setFilename = _setNamesArray.ObjectAt(_currentSetIdx); + LoadModelsInSet(_setNamesArray.ObjectAt(_currentSetIdx)); + } +} + + +void ViewMaster::LoadModelsInSet( const str &setFileName ) +{ + char *buffer; + char *buf; + char com_token[MAX_STRING_CHARS]; + + if (_numberOfModelsInSet) + { + gi.cvar_set( "viewlistmodelname", va( " ") ); + gi.cvar_set( "g_vt_modelIndex", "0"); + _modelNamesArray.FreeObjectList(); + _numberOfModelsInSet = 0 ; + } + + // Read the viewlist.txt file. This file contains newline seperated list of .tik models + if ( gi.FS_ReadFile( setFileName, ( void ** )&buf, true ) != -1 ) + { + buffer = buf; + while ( 1 ) + { + // Pull out a token + strcpy( com_token, COM_ParseExt( &buffer, true ) ); + + // Quit when no more tokens + if (!com_token[0]) + break; + + // Check for single comment lines + if ((com_token[0] != '#')) { // # is a single line comment + const char *relative_pathname = NULL; + + str modelName = "models/" ; + + relative_pathname = strstr( com_token, "models/" ); + + if ( !relative_pathname ) + relative_pathname = strstr( com_token, "models\\" ); + + if ( relative_pathname ) + { + relative_pathname += strlen( "models/" ); + modelName += relative_pathname; + } + else + { + modelName += com_token; + } + + _modelNamesArray.AddObject(modelName); + _numberOfModelsInSet++ ; + } + + // Read in the rest of the line + while( 1 ) + { + strcpy( com_token, COM_ParseExt( &buffer, false ) ); + if (!com_token[0]) + break; + } + } + gi.FS_FreeFile( buf ); + + // If we loaded any tiki filenames, display the first one + if (_numberOfModelsInSet) + { + gi.cvar_set( "g_vt_modelIndex", "1"); + DisplayCurrentModelInSet(false) ; + } + + str setName = _setNamesArray.ObjectAt(_currentSetIdx); + gi.cvar_set( "viewsetname", va( "%s", setName.c_str()) ); + gi.cvar_set( "g_vt_numModels", va( "%d", _numberOfModelsInSet) ); + } +} + +void ViewMaster::DisplayCurrentModelInSet( bool spawnAtLastOrigin ) +{ + if ( (g_vt_modelIndex->integer <= 0) || (g_vt_modelIndex->integer > _numberOfModelsInSet) ) + { + gi.cvar_set( "viewlistmodelname", " " ); + gi.Printf( " No models in set or position out of range\n"); + return ; + } + + + // Get the string name of the view item + str modelName = _modelNamesArray.ObjectAt(g_vt_modelIndex->integer); + + // Create a new spawn event with item name to send to Spawn function + Event* spawnEvent = new Event( EV_ViewThing_Spawn); + spawnEvent->AddString(modelName); + _spawnAtLastItemsOrigin = spawnAtLastOrigin ; + Spawn(spawnEvent); + _spawnAtLastItemsOrigin = false ; + delete spawnEvent ; + spawnEvent = 0 ; + + gi.cvar_set( "viewlistnumitems", va( "%d", _numberOfModelsInSet ) ); + gi.cvar_set( "viewlistmodelname", modelName.c_str() ); + gi.cvar_set( "g_vt_scale", "1.0"); + gi.cvar_set( "g_vt_height", "0" ); +} + +//================================================================ +// Name: Copy +// Class: ViewMaster +// +// Description: Event function to spawn a duplicate of the current +// viewthing at the spot occupied by the player. +// +// The object should be a true copy, including rotation +// and scaling. But currently isn't. FIXME +// +// Parameters: Event* -- Not used +// +// Returns: None +// +//================================================================ +void ViewMaster::Copy( Event * ) +{ + DisplayCurrentModelInSet(false); + _updateViewthingCounts(); +} + +//================================================================ +// Name: Shoot +// Class: ViewMaster +// +// Description: Event function to spawn a duplicate of the current +// viewthing at the spot aimed by the player. The new +// viewthing is not copied in scale or rotation. +// +// Parameters: Event* -- Not used +// +// Returns: None +// +//================================================================ +void ViewMaster::Shoot( Event *ev ) +{ + int idx = g_vt_modelIndex->integer ; + if ( (idx <= 0) || (idx > _modelNamesArray.NumObjects()) ) return ; + + Vector forward ; + Vector right ; + Vector up ; + Vector view ; + Vector start; + Vector end; + Vector mins = vec_zero ; + Vector maxs = vec_zero ; + trace_t trace; + + // Check if we have a client + Player* player = (Player*)g_entities[ 0 ].entity; + if ( !player ) return; + + Viewthing* viewthing = 0 ; + if (current_viewthing && g_vt_usebox->integer) + { + viewthing = (Viewthing*) ( (Entity*)current_viewthing); + mins = viewthing->mins ; + maxs = viewthing->maxs ; + } + + view = player->GetVAngles(); + AngleVectors( view, forward, right, up ); + start = player->origin ; + start.z += player->viewheight ; + end = start + ( forward * 10000.0f ) ; + + trace = G_FullTrace( start, mins, maxs, end, player, MASK_SHOT, false, "ViewMaster::shoot" ); + if ( ( trace.fraction == 1.0f ) || trace.allsolid || !trace.ent ) + { + return ; + } + + if ( g_showbullettrace->integer ) + { + G_DebugLine( start, end, 1.0f, 1.0f, 1.0f, 1.0f ); + } + + Vector pos = Vector( trace.endpos ) + ( Vector( trace.plane.normal ) * 0.2f ); + + str modelName(_modelNamesArray.ObjectAt(g_vt_modelIndex->integer)); + spawnAtPosition(modelName, pos); + + if (current_viewthing) + { + Event* yawEvent = new Event(EV_ViewThing_SetYaw); + viewthing = (Viewthing*) ( (Entity*)current_viewthing); + Vector n(trace.plane.normal); + float yaw = n.toYaw(); + yawEvent->AddFloat(yaw); + viewthing->SetYawEvent(yawEvent); + delete yawEvent ; + yawEvent = 0 ; + + if (g_vt_viewrandomize->integer) + { + _randomizeViewthing(viewthing); + } + } +} + +//================================================================ +// Name: NextModelInSet +// Class: ViewMaster +// +// Description: Event function to switch current_viewthing to the +// next model in the set (if any). This will switch +// the current tiki model to be the next tiki model in +// the loaded .lst file. It does not change the number +// of total viewthings in the world. Wraps when at +// end of list. +// +// Parameters: Event* -- Not used +// +// Returns: None +// +//================================================================ +void ViewMaster::NextModelInSet( Event * ) +{ + if (!_numberOfModelsInSet) + { + gi.Printf(" No current set\n"); + return ; + } + + // Increment on to next model + int nextIndex = ( g_vt_modelIndex->integer >= _numberOfModelsInSet) ? 1 : (g_vt_modelIndex->integer + 1 ); + gi.cvar_set( "g_vt_modelIndex", va( "%d", nextIndex ) ); + DeleteCurrentViewthing(); + DisplayCurrentModelInSet(); +} + +//================================================================ +// Name: PrevModelInSet +// Class: ViewMaster +// +// Description: Event function to switch current_viewthing to the +// prev model in the set (if any). This will switch +// the current tiki model to be the prev tiki model in +// the loaded .lst file. It does not change the number +// of total viewthings in the world. Wraps when at +// start of list. +// +// Parameters: Event* -- Not used +// +// Returns: None +// +//================================================================ +void ViewMaster::PrevModelInSet( Event *ev ) +{ + if (!_numberOfModelsInSet) + { + gi.Printf(" No models in current set\n"); + return ; + } + + // Increment on to next model + int nextIndex = ( g_vt_modelIndex->integer <= 1) ? _numberOfModelsInSet : ( g_vt_modelIndex->integer - 1 ); + gi.cvar_set( "g_vt_modelIndex", va( "%d", nextIndex ) ); + DeleteCurrentViewthing(); + DisplayCurrentModelInSet() ; +} + +//================================================================ +// Name: JumpToModel +// Class: ViewMaster +// +// Description: Explicity function to jump to the tiki specified +// by number out of the total list of tiki's in the +// load .lst file. This enables random access thru +// the .lst instead of one at a time. +// +// Parameters: Event* -- first argument is position to jump to +// +// Returns: None +// +//================================================================ +void ViewMaster::JumpToModel( Event *ev ) +{ + int nextIndex = ev->GetInteger(1); + if ((nextIndex < 1) || (nextIndex > _numberOfModelsInSet)) + { + gi.Printf(" Illegal index for model in viewset list\n"); + return ; + } + + gi.cvar_set( "g_vt_modelIndex", va( "%d", nextIndex ) ); + DeleteCurrentViewthing(); + DisplayCurrentModelInSet(); +} + +void ViewMaster::SetXTranslation( Event *ev ) +{ + if (!current_viewthing) return ; + + Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + Event *originEvent = new Event(EV_ViewThing_SetOrigin); + + float y = viewthing->origin.y ; + float z = viewthing->origin.z ; + originEvent->AddFloat(_lastBaseOrigin.x + ev->GetFloat(1)); + originEvent->AddFloat(y); + originEvent->AddFloat(z); + viewthing->ChangeOrigin(originEvent); + delete originEvent ; + originEvent = 0 ; + + gi.cvar_set( "g_vt_xtrans", va("%0.2f", ev->GetFloat(1)) ); +} + +void ViewMaster::XTranslate( Event *ev ) +{ + if (!current_viewthing) return ; + + Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + Event *originEvent = new Event(EV_ViewThing_SetOrigin); + + originEvent->AddFloat(viewthing->origin.x + ev->GetFloat(1)); + originEvent->AddFloat(viewthing->origin.y); + originEvent->AddFloat(viewthing->origin.z); + + viewthing->ChangeOrigin(originEvent); + delete originEvent ; + originEvent = 0 ; + + _resetOrigin(); +} + +void ViewMaster::SetYTranslation( Event *ev ) +{ + if (!current_viewthing) return ; + + Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + Event *originEvent = new Event(EV_ViewThing_SetOrigin); + + float x = viewthing->origin.x ; + float z = viewthing->origin.z ; + originEvent->AddFloat(x); + originEvent->AddFloat(_lastBaseOrigin.y + ev->GetFloat(1)); + originEvent->AddFloat(z); + viewthing->ChangeOrigin(originEvent); + delete originEvent ; + originEvent = 0 ; + + gi.cvar_set( "g_vt_ytrans", va("%0.2f", ev->GetFloat(1)) ); +} + +void ViewMaster::YTranslate( Event *ev ) +{ + if (!current_viewthing) return ; + + Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + Event *originEvent = new Event(EV_ViewThing_SetOrigin); + + originEvent->AddFloat(viewthing->origin.x); + originEvent->AddFloat(viewthing->origin.y + ev->GetFloat(1)); + originEvent->AddFloat(viewthing->origin.z); + + viewthing->ChangeOrigin(originEvent); + delete originEvent ; + originEvent = 0 ; + + _resetOrigin(); +} + +void ViewMaster::SetZTranslation( Event *ev ) +{ + if (!current_viewthing) return ; + + Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + Event *originEvent = new Event(EV_ViewThing_SetOrigin); + + float x = viewthing->origin.x ; + float y = viewthing->origin.y ; + originEvent->AddFloat(x); + originEvent->AddFloat(y); + originEvent->AddFloat(_lastBaseOrigin.z + ev->GetFloat(1)); + viewthing->ChangeOrigin(originEvent); + delete originEvent ; + originEvent = 0 ; + + gi.cvar_set( "g_vt_ztrans", va("%0.2f", ev->GetFloat(1)) ); +} + +void ViewMaster::ZTranslate( Event *ev ) +{ + if (!current_viewthing) return ; + + Viewthing *viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + Event *originEvent = new Event(EV_ViewThing_SetOrigin); + + originEvent->AddFloat(viewthing->origin.x); + originEvent->AddFloat(viewthing->origin.y); + originEvent->AddFloat(viewthing->origin.z + ev->GetFloat(1)); + + viewthing->ChangeOrigin(originEvent); + delete originEvent ; + originEvent = 0 ; + + _resetOrigin(); +} + +void ViewMaster::Offset( Event *ev ) +{ + Viewthing *viewthing; + Event *new_event; + + if ( !current_viewthing ) + return; + + viewthing = ( Viewthing * )( Entity * )current_viewthing; + + new_event = new Event( EV_ViewThing_SetOrigin ); + + new_event->AddFloat( viewthing->origin.x + ev->GetFloat( 1 ) ); + new_event->AddFloat( viewthing->origin.y + ev->GetFloat( 2 ) ); + new_event->AddFloat( viewthing->origin.z + ev->GetFloat( 3 ) ); + + viewthing->ProcessEvent( new_event ); + + _resetOrigin(); +} + +//================================================================ +// Name: ResetOrigin +// Class: ViewMaster +// +// Description: Forces the _lastBaseOrigin to be the origin of the +// current_viewthing (if any). Calls _resetOrigin to +// do the work. +// +// This is useful because models can be translated off +// of _lastBaseOrigin and reset to it. This enables +// the _lastBaseOrigin to move to where ever the viewthing +// has been translated too so further relative movements are +// relative to this new position. +// +// Parameters: Event* -- Not used +// +// Returns: None +// +//================================================================ +void ViewMaster::ResetOrigin( Event* ) +{ + _resetOrigin(); +} + +//================================================================ +// Name: PullToCamera +// Class: ViewMaster +// +// Description: Moves the current_viewthing (if any) to the current +// player's (client[0]) position. Does not drop the +// object but instead explicitly calls _setToPosition +// to force the origin of the current_viewthing to be +// at player's positionn. +// +// Parameters: Event* -- Not used +// +// Returns: None +// +//================================================================ +void ViewMaster::PullToCamera( Event* ) +{ + if (!current_viewthing) return ; + + // Check if we have a client + Entity* clientEntity = g_entities[ 0 ].entity; + if (!clientEntity) return ; + + // Store this new camera position as being where items should spawn here on out + _lastBaseOrigin = clientEntity->origin ; + _setToPosition(clientEntity->origin); +} + +//================================================================ +// Name: ToggleDrop +// Class: ViewMaster +// +// Description: Toggles the g_vt_droptoggle cvar. When the +// cvar is true, objects are dropped upon spawning. +// +// Parameters: Event* -- Not used +// +// Returns: None +// +//================================================================ +void ViewMaster::ToggleDrop( Event * ) +{ + if (g_vt_droptoggle->integer) + { + gi.cvar_set("g_vt_droptoggle", "0"); + } + else + { + gi.cvar_set("g_vt_droptoggle", "1"); + } +} + +//================================================================ +// Name: Next +// Class: ViewMaster +// +// Description: Takes an event and moves to next spawned viewthing +// if there is one. Does not change current_viewthing +// if there isn't a next viewthing. +// +// Parameters: Event* -- Not used +// +// Returns: None +// +//================================================================ +void ViewMaster::Next( Event * ) +{ + _selectNext(); +} + +//================================================================ +// Name: Prev +// Class: ViewMaster +// +// Description: Responds to an event and moves to previous spawned +// viewthing if there is one. Does not change current_viewthing +// if there isn't a previous viewthing. +// +// Parameters: Event* -- Not used +// +// Returns: None +// +//================================================================ +void ViewMaster::Prev( Event * ) +{ + _selectPrevious(); +} + +//================================================================ +// Name: DeleteCurrentViewthing +// Class: ViewMaster +// +// Description: Support function that simply sends a EV_Remove event +// to the current viewthing if there is one. This will +// delete the current viewthing. +// +// Does not modify current_viewthing nor the count of +// viewthings because it is expected that the caller +// handles this. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void ViewMaster::DeleteCurrentViewthing() +{ + if (current_viewthing) + { + // delete all existing models + Viewthing *viewthing = (Viewthing*)( (Entity*) current_viewthing); + viewthing->PostEvent( EV_Remove, 0.0f ); + } +} + +//================================================================ +// Name: Delete +// Class: ViewMaster +// +// Description: Deletes the current_viewthing if there is one. Attempts +// to set the current_viewthing to the previous one in the +// list of viewthings. If it cannot do that, it sets it +// to the next one. If it cannot do that, it sets current_viewthing +// to 0 (NULL). +// +// Parameters: Event* -- Not used +// +// Returns: None +// +//================================================================ +void ViewMaster::Delete( Event * ) +{ + if (!current_viewthing) return ; + + // Save off the one to delete + Viewthing *viewthing = (Viewthing*)( (Entity*) current_viewthing); + + _selectPrevious(); // Try and select previous + + if (current_viewthing == viewthing) // If same as one we are deleting, there wasn't a previous + { + _selectNext(); // Try and select next + } + + if (current_viewthing == viewthing) // If same as one we are deleting, there wasn't a next + { + current_viewthing = 0 ; // set it to 0 + } + + // Delete the one we saved off + viewthing->PostEvent( EV_Remove, 0.0f ); + _updateViewthingCounts(-1); // we pass -1 here because there will be one less viewthing but it hasn't actually been deleted yet +} + +//================================================================ +// Name: DeleteAll +// Class: ViewMaster +// +// Description: Deletes all viewspawn things currently in memory. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void ViewMaster::DeleteAll( Event * ) +{ + Entity *next; + + for( next = G_FindClass( NULL, "viewthing" ); next != NULL; next = G_FindClass( next, "viewthing" ) ) + { + next->PostEvent( EV_Remove, 0.0f ); + } + + current_viewthing = NULL; +} + +//================================================================ +// Name: spawnAtPosition +// Class: ViewMaster +// +// Description: Spawns the specified tiki model at the specified +// position. The current_viewthing will point to this +// new viewthing if the spawn succeeds, 0 if it doesn't. +// +// Parameters: const str& -- name of tiki model ("models/eliza.tik") +// const Vector& -- Position to spawn model at +// +// Returns: None +// +//================================================================ +void ViewMaster::spawnAtPosition( const str& modelName, const Vector& pos ) +{ + // create a new viewthing + Viewthing *viewthing = new Viewthing; + current_viewthing = viewthing; + viewthing->baseorigin = pos ; + viewthing->setOrigin( viewthing->baseorigin ); + _lastBaseOrigin = viewthing->baseorigin ; + + if (g_vt_droptoggle->integer) + { + viewthing->droptofloor( 10000.0f ); + } + + viewthing->setAngles( vec_zero ); + + Event* event = new Event( EV_ViewThing_SetModel ); + event->AddString( modelName ); + viewthing->ProcessEvent( event ); + + if ( !gi.IsModel( viewthing->edict->s.modelindex ) ) + { + delete viewthing; + current_viewthing = 0 ; + return; + } + _updateViewthingCounts(); +} + +//================================================================ +// Name: SpawnFromTS +// Class: ViewMaster +// +// Description: Called by the ToolServer to viewspawn a model +// the ToolServer sends over dynamically. +// +// Parameters: Event *ev -- Point to event whose first string argument +// is name of tiki model to reload, the second string +// is the tiki to respawn. If the second string isn't +// specified, it respawns the first string +// +// Returns: None +// +//================================================================ +void ViewMaster::SpawnFromTS( Event *ev ) +{ + char *data = (char*)gi.ToolServerGetData(); + + if (!data) // No data? Just abort. + return; + + DeleteAll(NULL); // Overkill? + + char spawnmodel[256], reloadmodel[256]; + + strcpy(reloadmodel, "models/"); + strcat(reloadmodel, ev->GetString(1)); + + if ( ev->NumArgs() > 1 ) + { + // Already has models/ on it + strcpy(spawnmodel, ev->GetString(2)); + } + else + { + strcpy(spawnmodel,"models/"); + strcat(spawnmodel, ev->GetString(1)); + } + + gi.DPrintf("Reloading Model: %s...\n",reloadmodel); + gi.TikiLoadFromTS(reloadmodel, data); + // Setting this config string causes the client to reload the surfaces for this tiki + gi.setConfigstring( CS_DEBUGINFO, va( "reload %s %d", spawnmodel, gi.modelindex(reloadmodel) ) ); + + // Create a new spawn event with item name to send to Spawn function + Event* spawnEvent = new Event( EV_ViewThing_Spawn); + spawnEvent->AddString(spawnmodel); + Spawn(spawnEvent); + delete spawnEvent; + spawnEvent = 0 ; + + gi.setConfigstring( CS_DEBUGINFO, "" ); +} + +//================================================================ +// Name: Spawn +// Class: ViewMaster +// +// Description: Spawns the tiki model specified in the Event's +// first string argument. The current_viewthing points +// to the spawned object if the spawn succeeds, 0 +// if it doesn't. +// +// The spawned object is dropped straight down if +// the cvar g_vt_droptoggle is set to true (default). +// This will cause objects to spawn on the ground. +// +// Parameters: Event* -- Point to event whose first string argument +// is name of tiki model to spawn ("model/eliza.tik") +// +// Returns: None +// +//================================================================ +void ViewMaster::Spawn( Event *ev ) +{ + const char *mdl; + Viewthing *viewthing; + Entity *ent; + Event *event; + Vector forward; + Vector up; + Vector delta; + + mdl = ev->GetString( 1 ); + if ( !mdl || !mdl[ 0 ] ) + { + ev->Error( "Must specify a model name" ); + return; + } + + // Check if we have a client + ent = g_entities[ 0 ].entity; + assert( ent ); + + //----------------------------------------- + // This if block attempts to keep the current + // model index of a set up to date when an explicit + // viewspawn command is issued. If the model that's + // being spawn is within the set, the index in the + // set will be updated accordingly. + // + // If it isn't found in the set, we simply continue. + //----------------------------------------- + if (_numberOfModelsInSet) + { + str modelName(mdl); + int pos = g_vt_modelIndex->integer ; + if ((pos > 1) && (pos < _modelNamesArray.NumObjects()) && (_modelNamesArray.ObjectAt(pos) != modelName) ) + { + int pos = _modelNamesArray.IndexOfObject(modelName); + if (pos) + { + gi.cvar_set( "g_vt_modelIndex", va("%d", pos) ); + DeleteCurrentViewthing(); + DisplayCurrentModelInSet(); + return ; + } + } + } + + // create a new viewthin and set current_viewthing to it. + viewthing = new Viewthing; + current_viewthing = viewthing; + + //FIXME FIXME + ent->angles.AngleVectors( &forward, NULL, &up ); + + // If _spawnAtLastItemsOrigin is set, we will spawn on + if (_spawnAtLastItemsOrigin) + { + float upOffset = (g_vt_droptoggle->integer) ? 48 : 0 ; + viewthing->baseorigin = _lastBaseOrigin ; + viewthing->baseorigin += forward ; + viewthing->baseorigin += (up * upOffset); + } + else { + viewthing->baseorigin = ent->origin; + viewthing->baseorigin += ( forward * 48.0f ); + viewthing->baseorigin += ( up * 48.0f ); + } + + viewthing->setOrigin( viewthing->baseorigin ); + + // Added this so that guns and such can be prevented from falling into the floor. + // Controlled by viewmodeltoggledrop command. Default is true, so same as old functionality + if (g_vt_droptoggle->integer) + { + viewthing->droptofloor( 10000.0f ); + } + + viewthing->baseorigin = viewthing->origin; + _lastBaseOrigin = viewthing->baseorigin ; + + delta = ent->origin - viewthing->origin; + viewthing->setAngles( vec_zero ); + + event = new Event( EV_ViewThing_SetModel ); + event->AddString( mdl ); + viewthing->ProcessEvent( event ); + + if ( !gi.IsModel( viewthing->edict->s.modelindex ) ) + { + ev->Error( "model %s not found, viewmodel not spawned.", mdl ); + delete viewthing; + current_viewthing = NULL; + return; + } + + _updateViewthingCounts(); +} + +//================================================================ +// Name: SetModelEvent +// Class: ViewMaster +// +// Description: Sets the model for the current viewthing. Passes +// a SetModelEvent down to the current viewthing. The +// Event must contain the model name as its first string +// argument. +// +// Parameters: Event* -- First string argument contains model name +// +// Returns: None +// +//================================================================ +void ViewMaster::SetModelEvent( Event *ev ) +{ + const char *mdl; + char str[ 128 ]; + Event *event; + Viewthing *viewthing; + + mdl = ev->GetString( 1 ); + if ( !mdl || !mdl[ 0 ] ) + { + ev->Error( "Must specify a model name" ); + return; + } + + if ( !current_viewthing ) + { + // try to find one on the map + current_viewthing = G_FindClass( NULL, "viewthing" ); + if ( !current_viewthing ) + { + ev->Error( "No viewmodel" ); + return; + } + } + + viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + + // Prepend 'models/' to make things easier + str[ 0 ] = 0; + if ( ( mdl[ 1 ] != ':' ) && strnicmp( mdl, "models", 6 ) ) + { + strcpy( str, "models/" ); + } + strcat( str, mdl ); + + event = new Event( EV_ViewThing_SetModel ); + event->AddString( str ); + viewthing->ProcessEvent( event ); + viewthing->UpdateCvars(); +} + +//================================================================ +// Name: PassEvent +// Class: ViewMaster +// +// Description: Passes an event down to the current_viewthing if +// it is set. +// +// Parameters: Event* -- Event to pass +// +// Returns: None +// +//================================================================ +void ViewMaster::PassEvent( Event *ev ) +{ + Viewthing *viewthing; + Event *event; + + if ( !current_viewthing ) + { + ev->Error( "No viewmodel" ); + return; + } + + viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + if ( viewthing ) + { + event = new Event( ev ); + viewthing->ProcessEvent( event ); + } +} + + +//--------------------------------------------------------------------------- +// P R O T E C T E D M E T H O D S +//--------------------------------------------------------------------------- + + +//================================================================ +// Name: _resetOrigin +// Class: ViewMaster +// +// Description: Sets the origin for the current viewthing to be wherever +// it is at. This also sets the _lastBaseOrigin, which +// means further relative translations are based from here. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void ViewMaster::_resetOrigin() +{ + if (!current_viewthing) return ; + + Viewthing *viewthing = ( Viewthing*) ( (Entity*) current_viewthing); + _lastBaseOrigin = viewthing->origin ; + _setToPosition(_lastBaseOrigin); // just ensure we update cvars +} + +//================================================================ +// Name: _setToPosition +// Class: ViewMaster +// +// Description: Sets the Position of the current viewthing. +// +// Parameters: Vector& pos -- vector of position to set origin to +// +// Returns: None +// +//================================================================ +void ViewMaster::_setToPosition( const Vector& pos ) +{ + // Create a new event to pass to viewthing with new origin + Event* originEvent = new Event(EV_ViewThing_SetOrigin); + + originEvent->AddFloat(pos.x); + originEvent->AddFloat(pos.y); + originEvent->AddFloat(pos.z); + + // Pass to the viewthing and clean up + Viewthing *viewthing = (Viewthing*)( (Entity*)current_viewthing ); + viewthing->ChangeOrigin(originEvent); + delete originEvent ; + originEvent = 0 ; + + gi.cvar_set( "g_vt_xtrans", "0" ); + gi.cvar_set( "g_vt_ytrans", "0" ); + gi.cvar_set( "g_vt_ztrans", "0" ); +} + +//================================================================ +// Name: _selectNext +// Class: ViewMaster +// +// Description: Selects the next viewthing in the set of viewthings +// Selects none if there are no more viewthings. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void ViewMaster::_selectNext() +{ + Viewthing *viewthing; + Entity * ent; + + ent = G_FindClass( current_viewthing, "viewthing" ); + if ( ent ) + { + if (current_viewthing) + { + viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + viewthing->SetSelected( false ); + } + + current_viewthing = ent; + viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + _lastBaseOrigin = viewthing->origin ; + viewthing->UpdateCvars(); + viewthing->SetSelected( true ); + gi.Printf( "current viewthing model %s.\n", viewthing->model.c_str() ); + _updateViewthingCounts(); + } +} + +//================================================================ +// Name: _selectPrev +// Class: ViewMaster +// +// Description: Selects the previous viewthing in the set of viewthings. +// Selects none if there are no previous viewthings. +// +// Parameters: None +// +// Returns: None +// +//================================================================ +void ViewMaster::_selectPrevious() +{ + Viewthing *viewthing; + Entity *prev; + Entity *next; + + next = NULL; + do + { + prev = next; + next = G_FindClass( prev, "viewthing" ); + } + while( next != current_viewthing ); + + if ( prev ) + { + if (current_viewthing) + { + viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + viewthing->SetSelected( false ); + } + + current_viewthing = prev; + viewthing = ( Viewthing * )( ( Entity * )current_viewthing ); + _lastBaseOrigin = viewthing->origin ; + viewthing->UpdateCvars(); + viewthing->SetSelected(true); + gi.Printf( "current viewthing model %s.\n", viewthing->model.c_str() ); + _updateViewthingCounts(); + } +} + +//================================================================ +// Name: _updateViewthingCounts +// Class: ViewMaster +// +// Description: Sets the cvars g_vt_viewnumber and g_vt_viewcount +// appropriately after any change to the number of +// spawned viewthings in the world. When the loop +// finishes, g_vt_viewcount is the total number of +// spawned viewthings and g_vt_viewnumber is position +// in this list of the current_viewthing. +// +// The passed in parameter can be used for manipulating +// the final count. Because deletion of viewthings is +// event driven, when we know there is going to be a +// deletion the deletion hasn't actually occured yet. +// So it would show up in the count. Hence, we can pass +// in a -1 to offset the final count appropriately. +// Hack. +// +// Parameters: int countAdjustment -- offset final count (default is 0). +// +// Returns: None +// +//================================================================ +void ViewMaster::_updateViewthingCounts(int countAdjustment) +{ + Entity* next = G_FindClass( NULL, "viewthing"); + int numberOfViewthings = 0 ; + int currentViewthingIndex = 0 ; + + while (next) + { + numberOfViewthings++ ; + if (next == current_viewthing) + { + currentViewthingIndex = numberOfViewthings ; + } + next = G_FindClass( next, "viewthing"); + } + + gi.cvar_set( "g_vt_viewnumber", va("%d", currentViewthingIndex) ); + gi.cvar_set( "g_vt_viewcount", va("%d", numberOfViewthings + countAdjustment) ); +} + +//================================================================ +// Name: _randomizeViewthing +// Class: ViewMaster +// +// Description: Randomizes the scaling and rotation of the specified +// viewthing, based on various cvar settings. These +// randomizations can be turned on and off individually, +// but they are only applied if g_vt_viewrandomize is true. +// +// The bell curve randomization is done in the scaling section. +// It works like this: +// o Add two random values between 0 and 100 and divide total by 100. (Bell curve value--try with two dice) +// o Get half distance between desired low and high ((high-low) divided by 2) +// o Scale this half-distance by the bell curve value computed in step 1 (yields # from 0 to 2*half, but tends towards halfway) +// o Add scaled half to low value, giving bell curve between desired low and high +// +// Parameters: Viewthing* -- viewthing to randomize +// +// Returns: None +// +//================================================================ +void ViewMaster::_randomizeViewthing( Viewthing* viewthingPtr ) +{ + if (!viewthingPtr) + { + gi.Printf( "No viewthing to randomize\n"); + return ; + } + + if (g_vt_viewsrandomize->integer) + { + // Randomly scale + float die1 = G_Random( 100.0f ); + float die2 = G_Random( 100.0f ); + float scaleValue = (die1 + die2) / 100.0f ; + + float midpoint = ( (float)g_vt_viewmaxrandomize->integer - (float)g_vt_viewminrandomize->integer) / 2.0f ; + float adjustment = (float)g_vt_viewminrandomize->integer + (midpoint * scaleValue); + float scaleFactor = (adjustment / 100.0f); + + Event* scaleEvent = new Event(EV_ViewThing_SetScale); + scaleEvent->AddFloat(scaleFactor); + viewthingPtr->SetScaleEvent(scaleEvent); + delete scaleEvent ; + scaleEvent = 0 ; + } + + if (g_vt_viewzrandomize->integer) + { + // Randomly rotate around z-axis + Event* rotateEvent = new Event(EV_ViewThing_SetYaw); + rotateEvent->AddFloat(G_Random(360.0f)); + viewthingPtr->SetYawEvent(rotateEvent); + delete rotateEvent ; + rotateEvent = 0 ; + } + + if (g_vt_viewyrandomize->integer) + { + // Randomly rotate around z-axis + Event* rotateEvent = new Event(EV_ViewThing_SetPitch); + rotateEvent->AddFloat(G_Random(360.0f)); + viewthingPtr->SetPitchEvent(rotateEvent); + delete rotateEvent ; + rotateEvent = 0 ; + } + + if (g_vt_viewxrandomize->integer) + { + // Randomly rotate around z-axis + Event* rotateEvent = new Event(EV_ViewThing_SetRoll); + rotateEvent->AddFloat(G_Random(360.0f)); + viewthingPtr->SetRollEvent(rotateEvent); + delete rotateEvent ; + rotateEvent = 0 ; + } +} + +//------------------------------------------------------------------------ +// +// VIEWTHING +// +//------------------------------------------------------------------------ + +CLASS_DECLARATION( Entity, Viewthing, "viewthing" ) +{ + { &EV_ViewThing_Think, &Viewthing::ThinkEvent }, + { &EV_ViewThing_LastFrame, &Viewthing::LastFrameEvent }, + { &EV_ViewThing_ToggleAnimate, &Viewthing::ToggleAnimateEvent }, + { &EV_ViewThing_SetModel, &Viewthing::SetModelEvent }, + { &EV_ViewThing_NextFrame, &Viewthing::NextFrameEvent }, + { &EV_ViewThing_PrevFrame, &Viewthing::PrevFrameEvent }, + { &EV_ViewThing_NextAnim, &Viewthing::NextAnimEvent }, + { &EV_ViewThing_PrevAnim, &Viewthing::PrevAnimEvent }, + { &EV_ViewThing_ScaleUp, &Viewthing::ScaleUpEvent }, + { &EV_ViewThing_ScaleDown, &Viewthing::ScaleDownEvent }, + { &EV_ViewThing_SetScale, &Viewthing::SetScaleEvent }, + { &EV_ViewThing_SetYaw, &Viewthing::SetYawEvent }, + { &EV_ViewThing_SetPitch, &Viewthing::SetPitchEvent }, + { &EV_ViewThing_SetRoll, &Viewthing::SetRollEvent }, + { &EV_ViewThing_SetAngles, &Viewthing::SetAnglesEvent }, + { &EV_ViewThing_SetStatic, &Viewthing::SetStatic }, + { &EV_ViewThing_Attach, &Viewthing::AttachModel }, + { &EV_ViewThing_Detach, &Viewthing::Delete }, + { &EV_ViewThing_DetachAll, &Viewthing::DetachAll }, + { &EV_ViewThing_Delete, &Viewthing::Delete }, + { &EV_ViewThing_SetOrigin, &Viewthing::ChangeOrigin }, + { &EV_ViewThing_SaveOffSurfaces, &Viewthing::SaveSurfaces }, + { &EV_ViewThing_PrintTime, &Viewthing::PrintTime }, + { &EV_ViewThing_SetAnim, &Viewthing::SetAnim }, + { &EV_ViewThing_NextMorph, &Viewthing::NextMorph }, + { &EV_ViewThing_PrevMorph, &Viewthing::PrevMorph }, + { &EV_ViewThing_Morph, &Viewthing::Morph }, + { &EV_ViewThing_Unmorph, &Viewthing::Unmorph }, + { &EV_ViewThing_ChangeRoll, &Viewthing::ChangeRollEvent }, + { &EV_ViewThing_ChangePitch, &Viewthing::ChangePitchEvent }, + { &EV_ViewThing_ChangeYaw, &Viewthing::ChangeYawEvent }, + { &EV_ViewThing_Flash, &Viewthing::Flash }, + { &EV_ViewThing_SetFrame, &Viewthing::SetFrame }, + + { NULL, NULL } +}; + +Viewthing::Viewthing( void ) +{ + animate = new Animate( this ); + frame = 0; + animstate = 0; + _static = (g_vt_makestatic->integer != 0) ? true : false ; + _selected = false ; + _pulseCount = 0 ; + + setSolidType( SOLID_NOT ); + baseorigin = origin; + Viewmodel.current_viewthing = this; + edict->s.eType = ET_MODELANIM; + edict->s.renderfx |= RF_SHADOW; + edict->s.renderfx |= RF_SHADOW_PRECISE; + edict->s.renderfx |= RF_EXTRALIGHT; + + current_morph = 0; + + gi.cvar_set( "g_vt_scale", "1.0" ); + gi.cvar_set( "g_vt_roll", "0.0" ); + gi.cvar_set( "g_vt_pitch", "0.0" ); + gi.cvar_set( "g_vt_yaw", "0.0" ); + gi.cvar_set( "g_vt_xtrans", "0.0" ); + gi.cvar_set( "g_vt_ytrans", "0.0" ); + gi.cvar_set( "g_vt_ztrans", "0.0" ); + + + // save off surfaces once the model is spawned + PostEvent( EV_ViewThing_SaveOffSurfaces, FRAMETIME ); + + PostEvent( EV_ViewThing_Think, FRAMETIME ); +} + + +void Viewthing::SetSelected( qboolean state ) +{ + _selected = state ; + _pulseCount = 0 ; + + if (this->hidden()) + { + this->showModel(); + } +} + +void Viewthing::Flash( Event *ev ) +{ + SetSelected(true); +} + +void Viewthing::PrintTime( Event *ev ) +{ + gi.Printf( "ev current frame %d leveltime %.2f\n", ev->GetInteger( 1 ), level.time ); +} + +void Viewthing::UpdateCvars( qboolean quiet ) +{ + + gi.cvar_set( "viewmodelanim", animate->AnimName() ); + gi.cvar_set( "viewmodeltotaltime", va( "%.3f", animate->AnimTime()) ); + gi.cvar_set( "viewmodelanimframes",va( "%d", animate->NumFrames()) ); + gi.cvar_set( "viewmodelframe", va( "%d", CurrentFrame() ) ); + gi.cvar_set( "viewmodelname", model.c_str() ); + gi.cvar_set( "viewmodelscale", va( "%0.2f", edict->s.scale ) ); + gi.cvar_set( "currentviewthing", model.c_str() ); + gi.cvar_set( "viewthingorigin", va( "%0.2f,%0.2f,%0.2f", edict->s.origin[0],edict->s.origin[1],edict->s.origin[2] ) ); + gi.cvar_set( "viewmodelanimnum", va( "%.3f", ( float )CurrentAnim() / ( float )animate->NumAnims() ) ); + gi.cvar_set( "g_vt_scale", va( "%.2f", edict->s.scale) ); + gi.cvar_set( "g_vt_roll", va( "%.2f", (angles.z <= 180.0f) ? angles.z : (angles.z - 360.0f)) ); + gi.cvar_set( "g_vt_pitch", va( "%.2f", (angles.x <= 180.0f) ? angles.x : (angles.x - 360.0f)) ); + gi.cvar_set( "g_vt_yaw", va( "%.2f", (angles.y <= 180.0f) ? angles.y : (angles.y - 360.0f)) ); + gi.cvar_set( "g_vt_xtrans", va( "%.2f", (Viewmodel._lastBaseOrigin.x - baseorigin.x) ) ); + gi.cvar_set( "g_vt_ytrans", va( "%.2f", (Viewmodel._lastBaseOrigin.y - baseorigin.y) ) ); + gi.cvar_set( "g_vt_ztrans", va( "%.2f", (Viewmodel._lastBaseOrigin.z - baseorigin.z) ) ); + + if ( gi.Morph_NameForNum( edict->s.modelindex, current_morph ) ) + gi.cvar_set( "viewmorphname", gi.Morph_NameForNum( edict->s.modelindex, current_morph ) ); + else + gi.cvar_set( "viewmorphname", "" ); + + if ( !quiet ) + { + gi.Printf( "%s, frame %3d, scale %4.2f\n", animate->AnimName(), CurrentFrame(), edict->s.scale ); + } +} + + +void Viewthing::ThinkEvent( Event *ev ) +{ + static float startTime = 0 ; + + if (_selected && (_pulseCount < 20)) + { + if (startTime == 0.0f) + { + startTime = level.time ; + } + + if ( (level.time - startTime) > 0.15f) + { + if (this->hidden()) + { + this->showModel(); + } + else + { + this->hideModel(); + } + startTime = 0.0f ; + _pulseCount++ ; + } + } + + + int f; + if (animstate >= 2) + { + Vector forward; + Vector left; + Vector up; + Vector realmove; + + angles.AngleVectors( &forward, &left, &up ); + realmove = ( left * total_delta[1] ) + ( up * total_delta[2] ) + ( forward * total_delta[0] ); + setOrigin( baseorigin + realmove ); + gi.cvar_set( "viewthingorigin", va( "%0.2f,%0.2f,%0.2f", edict->s.origin[0],edict->s.origin[1],edict->s.origin[2] ) ); + } + PostEvent( EV_ViewThing_Think, FRAMETIME ); + if ( ( animstate > 0 ) && ( Viewmodel.current_viewthing == this ) ) + { + f = CurrentFrame(); + if ( f != lastframe ) + { + float time; + lastframe = f; + time = f * animate->AnimTime() / (float)animate->NumFrames(); + gi.Printf( "current frame %d time %.2f\n", f, time ); + gi.cvar_set( "viewmodeltime", va( "%.2f", time ) ); + gi.cvar_set( "viewmodelframe", va( "%d", f ) ); + gi.cvar_set( "viewmodelanim", animate->AnimName() ); + } + } +} + +void Viewthing::LastFrameEvent( Event *ev ) +{ + if ( animstate != 3 ) + { + total_delta = Vector(0, 0, 0); + } +} + +void Viewthing::ToggleAnimateEvent( Event *ev ) +{ + animstate = ( animstate + 1 ) % 4; + total_delta = Vector(0, 0, 0); + setOrigin( baseorigin ); + + // reset to a known state + switch( animstate ) + { + case 0: + animate->SetFrame( frame ); + gi.Printf( "Animation stopped.\n" ); + gi.cvar_set( "viewmodelanimmode", "Stopped" ); + break; + case 1: + animate->NewAnim( CurrentAnim() ); + gi.Printf( "Animation no motion.\n" ); + gi.cvar_set( "viewmodelanimmode", "No Motion" ); + break; + case 2: + animate->NewAnim( CurrentAnim(), EV_ViewThing_LastFrame ); + gi.Printf( "Animation with motion and looping.\n" ); + gi.cvar_set( "viewmodelanimmode", "Motion and Looping" ); + break; + case 3: + animate->NewAnim( CurrentAnim(), EV_ViewThing_LastFrame ); + gi.Printf( "Animation with motion no looping.\n" ); + gi.cvar_set( "viewmodelanimmode", "Motion and No Looping" ); + break; + } + UpdateCvars( true ); +} + +void Viewthing::SetStatic( Event *ev ) +{ + if (ev->NumArgs() == 1) + { + _static = (ev->GetInteger(1)) ? true : false ; + } + else + { + _static = (g_vt_makestatic->integer != 0) ? true : false ; + } +} + +void Viewthing::SetModelEvent( Event *ev ) +{ + str modelname; + + modelname = ev->GetString( 1 ); + + setModel( modelname ); + + if ( gi.IsModel( edict->s.modelindex ) ) + { + // Default the viewthing to an idle animation if the model has one + + if ( animate->HasAnim( "idle" ) ) + animate->RandomAnimate( "idle" ); + else + animate->NewAnim( 0 ); + + frame = 0; + animate->SetFrame( 0 ); + } + + UpdateCvars(); +} + +void Viewthing::NextFrameEvent( Event *ev ) +{ + int numframes; + + numframes = animate->NumFrames(); + if ( numframes ) + { + frame = (frame+1)%numframes; + animate->SetFrame( frame ); + animstate = 0; + UpdateCvars(); + } +} + +void Viewthing::PrevFrameEvent( Event *ev ) +{ + int numframes; + + numframes = animate->NumFrames(); + if ( numframes ) + { + frame = (frame-1)%numframes; + animate->SetFrame( frame ); + animstate = 0; + UpdateCvars(); + } +} + +void Viewthing::SetAnim( Event *ev ) +{ + int numanims, anim; + + numanims = animate->NumAnims(); + if ( numanims ) + { + // restore original surfaces + memcpy( edict->s.surfaces, origSurfaces, sizeof( origSurfaces ) ); + + anim = ev->GetFloat( 1 ) * numanims; + if ( anim >= numanims ) + anim = numanims - 1; + animate->NewAnim( anim % numanims ); + frame = 0; + animate->SetFrame( frame ); + animstate = 0; + UpdateCvars(); + } +} + +void Viewthing::SetAnim( int num ) +{ + int numanims; + + numanims = animate->NumAnims(); + if ( numanims ) + { + // restore original surfaces + memcpy( edict->s.surfaces, origSurfaces, sizeof( origSurfaces ) ); + + // if ( num >= numanims ) + // num = numanims - 1; + animate->NewAnim( num ); + animate->SetFrame( frame ); + //animstate = 0; + UpdateCvars(); + } +} + +void Viewthing::NextAnimEvent( Event *ev ) +{ + int numanims; + + numanims = animate->NumAnims(); + if ( numanims ) + { + // restore original surfaces + memcpy( edict->s.surfaces, origSurfaces, sizeof( origSurfaces ) ); + + animate->NewAnim( ( CurrentAnim() + 1 ) % numanims ); + frame = 0; + animate->SetFrame( frame ); + animstate = 0; + gi.Printf( "Animation stopped.\n" ); + gi.cvar_set( "viewmodelanimmode", "Stopped" ); + UpdateCvars(); + } +} + +void Viewthing::PrevAnimEvent( Event *ev ) +{ + int anim; + int numanims; + + numanims = animate->NumAnims(); + if ( numanims ) + { + // restore original surfaces + memcpy( edict->s.surfaces, origSurfaces, sizeof( origSurfaces ) ); + + anim = CurrentAnim() - 1; + while( anim < 0 ) + { + anim += numanims; + } + animate->NewAnim( anim ); + frame = 0; + animate->SetFrame( frame ); + animstate = 0; + gi.Printf( "Animation stopped.\n" ); + gi.cvar_set( "viewmodelanimmode", "Stopped" ); + UpdateCvars(); + } +} + +void Viewthing::ScaleUpEvent( Event *ev ) +{ + edict->s.scale += 0.05f; + UpdateCvars(); +} + +void Viewthing::ScaleDownEvent( Event *ev ) +{ + edict->s.scale -= 0.05f; + UpdateCvars(); +} + +void Viewthing::SetScaleEvent( Event *ev ) +{ + if ( ev->NumArgs() ) + { + float s = ev->GetFloat( 1 ); + edict->s.scale = s; + UpdateCvars(); + } +} + +void Viewthing::SetYawEvent( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + { + angles.setYaw( ev->GetFloat( 1 ) ); + setAngles( angles ); + UpdateCvars(); + } +} + +void Viewthing::SetPitchEvent( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + { + angles.setPitch( ev->GetFloat( 1 ) ); + setAngles( angles ); + UpdateCvars(); + } +} + +void Viewthing::SetRollEvent( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + { + angles.setRoll( ev->GetFloat( 1 ) ); + setAngles( angles ); + UpdateCvars(); + } +} + +void Viewthing::ChangeRollEvent( Event *ev ) +{ + if ( ev->NumArgs() > 0) + { + angles.setRoll(angles.roll() + ev->GetFloat( 1 ) ); + setAngles(angles); + UpdateCvars(); + } +} + +void Viewthing::ChangePitchEvent( Event *ev ) +{ + if ( ev->NumArgs() > 0) + { + angles.setPitch(angles.pitch() + ev->GetFloat( 1 ) ); + setAngles(angles); + UpdateCvars(); + } +} + +void Viewthing::ChangeYawEvent( Event *ev ) +{ + if ( ev->NumArgs() > 0) + { + angles.setYaw(angles.yaw() + ev->GetFloat( 1 ) ); + setAngles(angles); + UpdateCvars(); + } +} + +void Viewthing::SetAnglesEvent( Event *ev ) +{ + if ( ev->NumArgs() > 2 ) + { + angles.x = ev->GetFloat( 1 ); + angles.y = ev->GetFloat( 2 ); + angles.z = ev->GetFloat( 3 ); + setAngles( angles ); + } + gi.cvar_set( "g_vt_roll", va ("%0.2f", angles.x) ); + gi.cvar_set( "g_vt_pitch", va ("%0.2f", angles.y) ); + gi.cvar_set( "g_vt_yaw", va ("%0.2f", angles.z) ); + gi.Printf( "angles = %0.4f, %0.4f, %0.4f\n", angles.x, angles.y, angles.z ); +} + +//-------------------------------------------------------------- +// +// Name: AttachModel +// Class: Viewthing +// +// Description: Attaches a model to a viewspawned model +// +// Parameters: Event *ev +// string -- model name +// +// Returns: None +// +//-------------------------------------------------------------- +void Viewthing::AttachModel(Event *ev) +{ + Event *event = 0; + Viewthing *child = 0; + str mdl = ev->GetString(2); + + child = new Viewthing; + child->setModel( mdl.c_str() ); + + // Check to make sure it exists. + if ( !gi.IsModel( child->edict->s.modelindex ) ) + { + ev->Error( "attachmodel %s not found, attachmodel not spawned.", mdl.c_str() ); + delete child; + Viewmodel.ProcessEvent(EV_ViewThing_Prev); + return; + } + + // Attach the child + event = new Event( EV_Attach ); + event->AddEntity( this ); + event->AddString( ev->GetString( 1 ) ); + child->ProcessEvent( event ); +} + +void Viewthing::Delete( Event *ev ) +{ + Viewmodel.current_viewthing = NULL; + PostEvent( EV_Remove, 0.0f ); + Viewmodel.PostEvent( EV_ViewThing_Next, 0.0f ); +} + +void Viewthing::Delete() +{ + PostEvent( EV_Remove, 0.0f ); +} + +void Viewthing::DetachAll( Event *ev ) +{ + int i; + int num; + Entity * ent; + + if ( bind_info ) + { + num = bind_info->numchildren; + + for (i=0;ichildren[i]) + continue; + + ent = ( Entity * )G_GetEntity( bind_info->children[i] ); + ent->PostEvent( EV_Remove, 0.0f ); + num--; + + if (!num) + break; + } + } +} + +void Viewthing::ChangeOrigin( Event *ev ) +{ + if ( ev->NumArgs() ) + { + origin.x = ev->GetFloat( 1 ); + origin.y = ev->GetFloat( 2 ); + origin.z = ev->GetFloat( 3 ); + setOrigin( origin ); + baseorigin = origin; + gi.cvar_set( "g_vt_height", va ("%f", origin.z) ); + UpdateCvars(); + } + gi.Printf( "vieworigin = x%0.4f y%0.4f z%0.4f\n", origin.x, origin.y, origin.z ); +} + +void Viewthing::SaveSurfaces( Event *ev ) +{ + memcpy( origSurfaces, edict->s.surfaces, sizeof( origSurfaces ) ); +} + + +void Viewthing::NextMorph( Event *ev ) +{ + current_morph++; + + if ( current_morph >= gi.NumMorphs( edict->s.modelindex ) ) + current_morph = 0; + + if ( gi.Morph_NameForNum( edict->s.modelindex, current_morph ) ) + gi.Printf( "morph %s\n", gi.Morph_NameForNum( edict->s.modelindex, current_morph ) ); + + UpdateCvars( true ); +} + +void Viewthing::PrevMorph( Event *ev ) +{ + current_morph--; + + if ( current_morph < 0 ) + current_morph = gi.NumMorphs( edict->s.modelindex ) - 1; + + if ( gi.Morph_NameForNum( edict->s.modelindex, current_morph ) ) + gi.Printf( "morph %s\n", gi.Morph_NameForNum( edict->s.modelindex, current_morph ) ); + + UpdateCvars( true ); +} + +void Viewthing::Morph( Event *ev ) +{ + const char *morph_name; + + morph_name = gi.Morph_NameForNum( edict->s.modelindex, current_morph ); + + if ( morph_name ) + { + Event *new_event; + + new_event = new Event ( EV_Morph ); + new_event->AddString( morph_name ); + //new_event->AddFloat( 50 ); + ProcessEvent( new_event ); + } +} + +void Viewthing::Unmorph( Event *ev ) +{ + const char *morph_name; + + morph_name = gi.Morph_NameForNum( edict->s.modelindex, current_morph ); + + if ( morph_name ) + { + Event *new_event; + + new_event = new Event ( EV_Unmorph ); + new_event->AddString( morph_name ); + ProcessEvent( new_event ); + } +} + +void Viewthing::SetFrame( Event *ev ) +{ + int newframe; + + newframe = ev->GetInteger(1); + if ( newframe < animate->NumFrames() ) + { + animate->SetFrame( newframe ); + animstate = 0; + UpdateCvars(); + } +} diff --git a/dlls/game/viewthing.h b/dlls/game/viewthing.h new file mode 100644 index 0000000..662134a --- /dev/null +++ b/dlls/game/viewthing.h @@ -0,0 +1,187 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/viewthing.h $ +// $Revision:: 19 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Actor code for the viewthing. +// + +#ifndef __VIEWTHING_H__ +#define __VIEWTHING_H__ + +#include "animate.h" + +// forward declaration +class Viewthing ; + +class ViewMaster : public Listener + { + public: + CLASS_PROTOTYPE( ViewMaster ); + + EntityPtr current_viewthing; + Container _modelNamesArray ; + Container _setNamesArray ; + Vector _lastBaseOrigin ; + bool _spawnAtLastItemsOrigin ; + int _currentSetIdx ; + int _numberOfModelsInSet ; + int _numberOfSets ; + + ViewMaster(); + ~ViewMaster(); + + void LoadSet ( Event *ev ); + void NextModelInSet ( Event *ev ); + void PrevModelInSet ( Event *ev ); + void JumpToModel ( Event *ev ); + void PullToCamera ( Event *ev ); + void SetXTranslation( Event *ev ); + void SetYTranslation( Event *ev ); + void SetZTranslation( Event *ev ); + void XTranslate ( Event *ev ); + void YTranslate ( Event *ev ); + void ZTranslate ( Event *ev ); + void Offset ( Event *ev ); + void Copy ( Event *ev ); + void Save ( Event *ev ); + void Shoot ( Event *ev ); + void ResetOrigin ( Event *ev ); + void Delete ( Event *ev ); + + void LoadModelsInSet ( const str& setFilename); // Loads up list of tikis in .lst files + void spawnAtPosition(const str& modelName, const Vector& pos); + void DeleteCurrentViewthing(); + void DisplayCurrentModelInSet(bool spawnAtLastOrigin=true); + void ToggleDrop( Event *ev ); + void Spawn( Event *ev ); + void SpawnFromTS( Event *ev ); + + // These iterate through models already spawned + void Next( Event *ev ); + void Prev( Event *ev ); + + // Additional functions + void Init( void ); + void DeleteAll( Event *ev ); + void SetModelEvent( Event *ev ); + void PassEvent( Event *ev ); + virtual void Archive( Archiver &arc ); + + protected: + void _resetOrigin(); + void _setToPosition( const Vector& pos ); + void _selectPrevious(); + void _selectNext(); + void _updateViewthingCounts(int countAdjustment=0); // supports manipulating final count--see notes in .cpp + void _randomizeViewthing(Viewthing* viewthingPtr); // randomizes scale and/or rotation of this viewthing + }; + +inline void ViewMaster::Archive + ( + Archiver &arc + ) + + { + Listener::Archive( arc ); + + arc.ArchiveSafePointer( ¤t_viewthing ); + } + + +// The DLL Global ViewMaster singleton +extern ViewMaster Viewmodel; + + + +//----------------------------------------------------------- +// Class Viewthing +// A viewspawned tiki model in the world. This class +// provides a shell for viewing any tiki model in the +// game. The model can be animated and manipulated, but +// does not act like a normally spawned object (it doesn't +// execute scripts or AI. +//----------------------------------------------------------- +class Viewthing : public Entity + { + public: + CLASS_PROTOTYPE( Viewthing ); + + int animstate; + int frame; + int lastframe; + Vector baseorigin; + byte origSurfaces[MAX_MODEL_SURFACES]; + int current_morph; + qboolean _static ; + qboolean _selected ; + int _pulseCount ; + Viewthing(); + void UpdateCvars( qboolean quiet = false ); + void SetSelected( qboolean state = false ); + void SetAnim ( int num ); + void Delete(); + + void PrintTime ( Event *ev ); + void ThinkEvent ( Event *ev ); + void LastFrameEvent ( Event *ev ); + void ToggleAnimateEvent( Event *ev ); + void SetModelEvent ( Event *ev ); + void NextFrameEvent ( Event *ev ); + void PrevFrameEvent ( Event *ev ); + void NextAnimEvent ( Event *ev ); + void PrevAnimEvent ( Event *ev ); + void ScaleUpEvent ( Event *ev ); + void ScaleDownEvent ( Event *ev ); + void SetScaleEvent ( Event *ev ); + void SetYawEvent ( Event *ev ); + void SetPitchEvent ( Event *ev ); + void SetRollEvent ( Event *ev ); + void SetAnglesEvent ( Event *ev ); + void SetStatic ( Event *ev ); + void AttachModel ( Event *ev ); + void Delete ( Event *ev ); + void DetachAll ( Event *ev ); + void ChangeOrigin ( Event *ev ); + void SaveSurfaces ( Event *ev ); + void SetAnim ( Event *ev ); + void ChangeRollEvent ( Event *ev ); + void ChangePitchEvent ( Event *ev ); + void ChangeYawEvent ( Event *ev ); + void NextMorph ( Event *ev ); + void PrevMorph ( Event *ev ); + void Morph ( Event *ev ); + void Unmorph ( Event *ev ); + void Flash ( Event *ev ); + void SetFrame ( Event *ev ); + + virtual void Archive( Archiver &arc ); + }; + +inline void Viewthing::Archive + ( + Archiver &arc + ) + + { + Entity::Archive( arc ); + + arc.ArchiveInteger( &animstate ); + arc.ArchiveInteger( &frame ); + arc.ArchiveInteger( &lastframe ); + arc.ArchiveVector( &baseorigin ); + arc.ArchiveRaw( origSurfaces, sizeof( origSurfaces ) ); + arc.ArchiveInteger( ¤t_morph ); + } + +#endif /* viewthing.h */ diff --git a/dlls/game/watchEntity.cpp b/dlls/game/watchEntity.cpp new file mode 100644 index 0000000..38e5ece --- /dev/null +++ b/dlls/game/watchEntity.cpp @@ -0,0 +1,382 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/watchEntity.cpp $ +// $Revision:: 16 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Keeps the actor rotated to face the entity ( enemy by default ) for the specified +// amount of time ( or forever, if time is -1 ) +// +// PARAMETERS: +// _time -- The time to keep rotated +// _turnspeed -- How fast to rotate +// _anim -- Animation to play while rotating +// _ent -- Entity to watch ( Must be set by another behavior -- defaults to enemy ) +// _waitForAnim -- End behavior when the animation is complete +// +// ANIMATIONS: +// Rotation Anim -- Parameter +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "watchEntity.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, WatchEntity, NULL ) +{ + { &EV_Behavior_Args, &WatchEntity::SetArgs }, + { &EV_Behavior_AnimDone, &WatchEntity::AnimDone }, + { NULL, NULL } +}; + + +//-------------------------------------------------------------- +// Name: WatchEntity() +// Class: WatchEntity +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +WatchEntity::WatchEntity() +{ + _anim = ""; + _turnspeed = 10.0f; + _ent = NULL; + _waitForAnim = false; + _animDone = false; + _holdAnim = "idle"; +} + +//-------------------------------------------------------------- +// Name: ~WatchEntity() +// Class: WatchEntity +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +WatchEntity::~WatchEntity() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: WatchEntity +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void WatchEntity::SetArgs( Event *ev ) +{ + _time = ev->GetFloat( 1 ); + + if ( ev->NumArgs() > 1 ) + _turnspeed = ev->GetFloat( 2 ); + if ( ev->NumArgs() > 2 ) + _anim = ev->GetString( 3 ); + if ( ev->NumArgs() > 3 ) + _waitForAnim = ev->GetInteger( 4 ); + if ( ev->NumArgs() > 4 ) + _holdAnim = ev->GetString( 5 ); + + _forcePlayer = false; + if ( ev->NumArgs() > 5 ) + _forcePlayer = ev->GetBoolean( 6 ); + + +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: WatchEntity +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void WatchEntity::AnimDone( Event *ev ) +{ + _animDone = true; +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: WatchEntity +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void WatchEntity::Begin( Actor &self ) +{ + init( self ); +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: WatchEntity +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t WatchEntity::Evaluate( Actor &self ) +{ + + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case WATCH_HOLD: + //--------------------------------------------------------------------- + stateResult = evaluateStateHold(self); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WATCH_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WATCH_ROTATE ); + break; + + + //--------------------------------------------------------------------- + case WATCH_ROTATE: + //--------------------------------------------------------------------- + stateResult = evaluateStateRotate(self); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WATCH_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WATCH_HOLD ); + + break; + + //--------------------------------------------------------------------- + case WATCH_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + break; + + //--------------------------------------------------------------------- + case WATCH_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + break; + + } + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: WatchEntity +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void WatchEntity::End(Actor &self) +{ +// self.movementSubsystem->setTurnSpeed(_oldTurnSpeed); +} + +void WatchEntity::transitionToState( watchEntityStates_t state ) +{ + switch ( state ) + { + case WATCH_HOLD: + setupStateHold(); + setInternalState( state , "WATCH_HOLD" ); + break; + + case WATCH_ROTATE: + setupStateRotate(); + setInternalState( state , "WATCH_ROTATE" ); + break; + + case WATCH_SUCCESS: + setInternalState( state , "WATCH_SUCCESS" ); + break; + + case WATCH_FAILED: + setInternalState( state , "WATCH_FAILED" ); + break; + } +} + +void WatchEntity::setInternalState( watchEntityStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + +void WatchEntity::init( Actor &self ) +{ + SetSelf(&self); + + if ( _time > 0 ) + _time = level.time + _time; + + _oldTurnSpeed = self.movementSubsystem->getTurnSpeed(); + + //Check if we have an entity to watch + if ( !_ent ) + { + self.enemyManager->FindHighestHateEnemy(); + _ent = self.enemyManager->GetCurrentEnemy(); + + if ( !_ent ) + { + + if ( _forcePlayer ) + _ent = (Entity*)GetPlayer( 0 ); + + if ( !_ent ) + { + transitionToState(WATCH_FAILED); + return; + } + } + } + + transitionToState(WATCH_HOLD); +} + +void WatchEntity::think() +{ + if ( _time > 0 && level.time >= _time ) + transitionToState(WATCH_SUCCESS); + + if ( _waitForAnim && _animDone ) + transitionToState(WATCH_SUCCESS); +} + +void WatchEntity::setupStateRotate() +{ + _animDone = false; + GetSelf()->SetAnim( _anim , EV_Actor_NotifyBehavior , legs ); +} + +BehaviorReturnCode_t WatchEntity::evaluateStateRotate( Actor &self ) +{ + Vector entityPos, selfToEntity, dir; + + if ( !_ent ) + { + return BEHAVIOR_FAILED; + } + + entityPos = _ent->centroid; + entityPos.z = self.centroid.z; + + if ( self.IsEntityAlive( _ent ) ) + { + dir = self.movementSubsystem->getMoveDir(); + self.movementSubsystem->Accelerate( + self.movementSubsystem->SteerTowardsPoint( entityPos, vec_zero, dir, 1.0f) + ); + } + + // + // Here, again, I replaced the _ent->centroid with the z-value modified entityPos + // + selfToEntity = entityPos - self.centroid; + selfToEntity = selfToEntity.toAngles(); + + float yawDiff = selfToEntity[YAW] - self.angles[YAW]; + + yawDiff = AngleNormalize180(yawDiff); + if ( yawDiff > -1.5 && yawDiff < 1.5 ) + { + if ( _waitForAnim ) + { + if ( _animDone ) return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_SUCCESS; + } + + + return BEHAVIOR_EVALUATING; +} + +void WatchEntity::rotateFailed( Actor &self ) +{ +} + +void WatchEntity::setupStateHold() +{ + _animDone = false; + GetSelf()->SetAnim( _holdAnim , EV_Actor_NotifyBehavior , legs ); +} + +BehaviorReturnCode_t WatchEntity::evaluateStateHold( Actor &self ) +{ + Vector entityPos , selfToEntity; + if ( !_ent ) + { + return BEHAVIOR_FAILED; + } + + entityPos = _ent->centroid; + entityPos.z = self.centroid.z; + + // + // Here, again, I replaced the _ent->centroid with the z-value modified entityPos + // + selfToEntity = entityPos - self.centroid; + selfToEntity = selfToEntity.toAngles(); + + float yawDiff = selfToEntity[YAW] - self.angles[YAW]; + + if ( yawDiff > 5 || yawDiff < -5 ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +void WatchEntity::holdFailed( Actor &self ) +{ +} + diff --git a/dlls/game/watchEntity.hpp b/dlls/game/watchEntity.hpp new file mode 100644 index 0000000..b3a1e93 --- /dev/null +++ b/dlls/game/watchEntity.hpp @@ -0,0 +1,148 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/healGroupMember.hpp $ +// $Revision:: 1 $ +// $Author:: Sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// watchEntity Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class WatchEntity; + +#ifndef __WATCH_ENTITY_HPP__ +#define __WATCH_ENTITY_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" +#include "rotateToEntity.hpp" + +//------------------------- CLASS ------------------------------ +// +// Name: WatchEntity +// Base Class: Behavior +// +// Description: Will Rotate To, and Continue Rotating To +// an entity for a specified amount of time. +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class WatchEntity : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + WATCH_HOLD, + WATCH_ROTATE, + WATCH_SUCCESS, + WATCH_FAILED + } watchEntityStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + float _time; + float _turnspeed; + float _oldTurnSpeed; + str _anim; + EntityPtr _ent; + unsigned int _waitForAnim; + str _holdAnim; + bool _forcePlayer; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( watchEntityStates_t state ); + void setInternalState ( watchEntityStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + + + void setupStateRotate (); + BehaviorReturnCode_t evaluateStateRotate ( Actor &self ); + void rotateFailed ( Actor &self ); + + void setupStateHold (); + BehaviorReturnCode_t evaluateStateHold ( Actor &self ); + void holdFailed ( Actor &self ); + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( WatchEntity ); + + WatchEntity(); + ~WatchEntity(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + void SetEntity ( Entity *ent ); + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + unsigned int _state; + unsigned int _animDone; + + }; + +inline void WatchEntity::SetEntity( Entity *ent ) +{ + if ( ent ) + _ent = ent; +} + +inline void WatchEntity::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Parameters + arc.ArchiveFloat ( &_time ); + arc.ArchiveFloat ( &_turnspeed ); + arc.ArchiveFloat ( &_oldTurnSpeed ); + arc.ArchiveString ( &_anim ); + arc.ArchiveSafePointer ( &_ent ); + arc.ArchiveUnsigned ( &_waitForAnim ); + arc.ArchiveString ( &_holdAnim ); + arc.ArchiveBool ( &_forcePlayer ); + + // Archive Member Vars + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveUnsigned ( &_animDone ); +} + + + +#endif /* __WATCH_ENTITY_HPP__ */ + diff --git a/dlls/game/watchEntityEX.cpp b/dlls/game/watchEntityEX.cpp new file mode 100644 index 0000000..fe2b3f8 --- /dev/null +++ b/dlls/game/watchEntityEX.cpp @@ -0,0 +1,392 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/watchEntityEX.cpp $ +// $Revision:: 4 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Keeps the actor rotated to face the entity ( enemy by default ) for the specified +// amount of time ( or forever, if time is -1 ) +// +// PARAMETERS: +// _time -- The time to keep rotated +// _turnspeed -- How fast to rotate +// _anim -- Animation to play while rotating +// _ent -- Entity to watch ( Must be set by another behavior -- defaults to enemy ) +// +// ANIMATIONS: +// Rotation Anim -- Parameter +// +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "watchEntityEX.hpp" +#include + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, WatchEntityEX, NULL ) +{ + { &EV_Behavior_Args, &WatchEntityEX::SetArgs }, + { &EV_Behavior_AnimDone, &WatchEntityEX::AnimDone }, + { NULL, NULL } +}; + + +//-------------------------------------------------------------- +// Name: WatchEntityEX() +// Class: WatchEntityEX +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +WatchEntityEX::WatchEntityEX() +{ + _stance = ""; + _shuffleAnim = ""; + _torsoAnim = ""; + _ent = NULL; + _time = 0.0; + _turnspeed = 0.0; +} + +//-------------------------------------------------------------- +// Name: ~WatchEntityEX() +// Class: WatchEntityEX +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +WatchEntityEX::~WatchEntityEX() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: WatchEntityEX +// +// Description: Sets Arguments for this behavior +// +// Parameters: Event *ev -- Event holding the arguments +// +// Returns: None +//-------------------------------------------------------------- +void WatchEntityEX::SetArgs( Event *ev ) +{ + int numargs = ev->NumArgs(); + + if ( numargs > 0 ) _stance = ev->GetString( 1 ); + if ( numargs > 1 ) _torsoAnim = ev->GetString( 2 ); + if ( numargs > 2 ) _shuffleAnim = ev->GetString( 3 ); + if ( numargs > 3 ) _turnspeed = ev->GetFloat ( 4 ); + if ( numargs > 4 ) _time = ev->GetFloat ( 5 ); +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: WatchEntityEX +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void WatchEntityEX::AnimDone( Event *ev ) +{ +} + + + +//-------------------------------------------------------------- +// Name: Begin() +// Class: WatchEntityEX +// +// Description: Initializes the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void WatchEntityEX::Begin( Actor &self ) +{ + init( self ); +} + + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: WatchEntityEX +// +// Description: Evaluates the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: BehaviorReturnCode_t +//-------------------------------------------------------------- +BehaviorReturnCode_t WatchEntityEX::Evaluate( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case WATCH_ENTITY_EX_SETUP: + //--------------------------------------------------------------------- + stateResult = evaluateStateSetup(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WATCH_ENTITY_EX_ROTATE ); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WATCH_ENTITY_EX_FAILED ); + break; + + //--------------------------------------------------------------------- + case WATCH_ENTITY_EX_ROTATE: + //--------------------------------------------------------------------- + stateResult = evaluateStateRotate(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WATCH_ENTITY_EX_HOLD ); + + if ( stateResult == BEHAVIOR_FAILED) + transitionToState( WATCH_ENTITY_EX_FAILED ); + break; + + + //--------------------------------------------------------------------- + case WATCH_ENTITY_EX_HOLD: + //--------------------------------------------------------------------- + stateResult = evaluateStateHold(); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WATCH_ENTITY_EX_ROTATE ); + + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WATCH_ENTITY_EX_FAILED ); + break; + + + //--------------------------------------------------------------------- + case WATCH_ENTITY_EX_SUCCESS: + //--------------------------------------------------------------------- + return BEHAVIOR_SUCCESS; + + break; + + + //--------------------------------------------------------------------- + case WATCH_ENTITY_EX_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + + break; + + + } + + + return BEHAVIOR_EVALUATING; + +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: WatchEntityEX +// +// Description: Cleans Up the behavior +// +// Parameters: Actor &self -- The actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void WatchEntityEX::End(Actor &self) +{ +} + + +//-------------------------------------------------------------- +// Name: init() +// Class: WatchEntityEX +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void WatchEntityEX::init( Actor &self ) +{ + if ( _time > 0 ) + _time = level.time + _time; + + if ( !_ent ) + { + self.enemyManager->FindHighestHateEnemy(); + _ent = self.enemyManager->GetCurrentEnemy(); + } + + _stance = self.GetStateVar( _stance ); + + if ( !_stance.length() ) + _stance = "idle"; + + transitionToState( WATCH_ENTITY_EX_SETUP ); + +} + + +void WatchEntityEX::transitionToState( watchEntityStates_t state ) +{ + switch ( state ) + { + case WATCH_ENTITY_EX_SETUP: + setupStateSetup(); + setInternalState( state , "WATCH_ENTITY_EX_SETUP" ); + break; + + case WATCH_ENTITY_EX_ROTATE: + setupStateRotate(); + setInternalState( state , "WATCH_ENTITY_EX_ROTATE" ); + break; + + case WATCH_ENTITY_EX_HOLD: + setupStateHold(); + setInternalState( state , "WATCH_ENTITY_EX_HOLD" ); + break; + + case WATCH_ENTITY_EX_SUCCESS: + setInternalState( state , "WATCH_ENTITY_EX_SUCCESS" ); + break; + + case WATCH_ENTITY_EX_FAILED: + setInternalState( state , "WATCH_ENTITY_EX_FAILED" ); + break; + } +} + +void WatchEntityEX::setInternalState( watchEntityStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + + +void WatchEntityEX::think() +{ + if ( _time > 0 && level.time >= _time ) + transitionToState( WATCH_ENTITY_EX_SUCCESS ); +} + + + +void WatchEntityEX::setupStateSetup() +{ +} + +BehaviorReturnCode_t WatchEntityEX::evaluateStateSetup() +{ + if ( !_ent ) + { + failureStateSetup("No Entity To Rotate To" ); + return BEHAVIOR_FAILED; + } + + if ( _torsoAnim.length() ) + GetSelf()->SetAnim( _torsoAnim , NULL , torso ); + + return BEHAVIOR_SUCCESS; +} + +void WatchEntityEX::failureStateSetup( const str& failureReason ) +{ +} + + + +void WatchEntityEX::setupStateRotate() +{ + _rotateToEntity.SetEntity( _ent ); + _rotateToEntity.SetAnim( _shuffleAnim ); + _rotateToEntity.SetTurnSpeed( _turnspeed ); + + _rotateToEntity.Begin( *GetSelf() ); +} + +BehaviorReturnCode_t WatchEntityEX::evaluateStateRotate() +{ + BehaviorReturnCode_t result; + result = _rotateToEntity.Evaluate( *GetSelf() ); + + if ( result == BEHAVIOR_SUCCESS ) + { + _rotateToEntity.End( *GetSelf() ); + return BEHAVIOR_SUCCESS; + } + + if ( result != BEHAVIOR_EVALUATING ) + { + failureStateRotate( "Rotate Component Failed" ); + return BEHAVIOR_FAILED; + } + + return BEHAVIOR_EVALUATING; +} + +void WatchEntityEX::failureStateRotate( const str& failureReason ) +{ +} + + + +void WatchEntityEX::setupStateHold() +{ + GetSelf()->SetAnim( _stance , NULL , legs ); +} + +BehaviorReturnCode_t WatchEntityEX::evaluateStateHold() +{ + Vector selfToEntity = _ent->centroid - GetSelf()->centroid; + selfToEntity = selfToEntity.toAngles(); + + float yawDiff = selfToEntity[YAW] - GetSelf()->angles[YAW]; + + if ( yawDiff > -5.5 && yawDiff < 5.5 ) + return BEHAVIOR_EVALUATING; + + //if Not facing enemy + return BEHAVIOR_SUCCESS; +} + +void WatchEntityEX::failureStateHold( const str& failureReason ) +{ +} + + + + + diff --git a/dlls/game/watchEntityEX.hpp b/dlls/game/watchEntityEX.hpp new file mode 100644 index 0000000..d239b47 --- /dev/null +++ b/dlls/game/watchEntityEX.hpp @@ -0,0 +1,155 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/watchEntityEX.hpp $ +// $Revision:: 1 $ +// $Author:: Sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// watchEntityEX Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class WatchEntityEX; + +#ifndef __WATCH_ENTITY_EX_HPP__ +#define __WATCH_ENTITY_EX_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" +#include "rotateToEntity.hpp" + +//------------------------- CLASS ------------------------------ +// +// Name: WatchEntity +// Base Class: Behavior +// +// Description: Will Rotate To, and Continue Rotating To +// an entity for a specified amount of time. +// +// Method of Use: Called From State Machine +//-------------------------------------------------------------- +class WatchEntityEX : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + WATCH_ENTITY_EX_SETUP, + WATCH_ENTITY_EX_ROTATE, + WATCH_ENTITY_EX_HOLD, + WATCH_ENTITY_EX_SUCCESS, + WATCH_ENTITY_EX_FAILED, + } watchEntityStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: + str _stance; + str _shuffleAnim; + str _torsoAnim; + EntityPtr _ent; + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( watchEntityStates_t state ); + void setInternalState ( watchEntityStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + + void setupStateSetup (); + BehaviorReturnCode_t evaluateStateSetup (); + void failureStateSetup ( const str& failureReason ); + + void setupStateRotate (); + BehaviorReturnCode_t evaluateStateRotate (); + void failureStateRotate ( const str& failureReason ); + + void setupStateHold (); + BehaviorReturnCode_t evaluateStateHold (); + void failureStateHold ( const str& failureReason ); + + + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( WatchEntityEX ); + + WatchEntityEX(); + ~WatchEntityEX(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + void SetEntity ( Entity *ent ); + + virtual void Archive ( Archiver &arc ); + + //------------------------------------- + // Components + //------------------------------------- + private: + RotateToEntity _rotateToEntity; + + //------------------------------------- + // Member Variables + //------------------------------------- + private: + unsigned int _state; + float _time; + float _turnspeed; + + }; + +inline void WatchEntityEX::SetEntity( Entity *ent ) +{ + if ( ent ) + _ent = ent; +} + +inline void WatchEntityEX::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Parameters + arc.ArchiveString ( &_stance ); + arc.ArchiveString ( &_shuffleAnim ); + arc.ArchiveString ( &_torsoAnim ); + arc.ArchiveSafePointer ( &_ent ); + + // Archive Components + arc.ArchiveObject ( &_rotateToEntity ); + + // Archive Member Vars + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveFloat ( &_time ); + arc.ArchiveFloat ( &_turnspeed ); + +} + + + +#endif /* __WATCH_ENTITY_EX_HPP__ */ + diff --git a/dlls/game/waypoints.cpp b/dlls/game/waypoints.cpp new file mode 100644 index 0000000..f7eb0d2 --- /dev/null +++ b/dlls/game/waypoints.cpp @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/waypoints.cpp $ +// $Revision:: 12 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// +// Waypoint.cpp +// +// Implementation for Waypoint Class +// + +#include "_pch_cpp.h" +//#include "g_local.h" +#include "waypoints.h" + +Event EV_WayPoint_SetActorAnim +( + "setactoranim", + EV_SCRIPTONLY, + "s", + "anim_name", + "sets what primary anim to set an actor to, when it reaches this waypoint" +); +Event EV_WayPoint_SetThread +( + "setwaypointthread", + EV_SCRIPTONLY, + "s", + "thread_name", + "sets the script thread to be called when the actor reaches this waypoint" +); + +CLASS_DECLARATION( Entity , WayPointNode , "info_waypointnode_waypointnode" ) +{ + { &EV_WayPoint_SetActorAnim, &WayPointNode::SetActorAnim }, + { &EV_WayPoint_SetThread, &WayPointNode::SetWayPointThread }, + { NULL, NULL } +}; + +WayPointNode::WayPointNode() +{ + Thread = ""; + ActorAnim = ""; +} + +WayPointNode::~WayPointNode() +{ + +} + +// +// Accessors +// +const str &WayPointNode::GetThread() +{ + return Thread; +} + +const str &WayPointNode::GetActorAnim() +{ + return ActorAnim; +} + +void WayPointNode::SetActorAnim( const str &anim ) +{ + ActorAnim = anim; +} + +void WayPointNode::SetWayPointThread( const str &thread ) +{ + Thread = thread; +} +// +// Events +// +void WayPointNode::SetWayPointThread ( Event *ev ) +{ + Thread = ev->GetString( 1 ); +} + + +void WayPointNode::SetActorAnim ( Event *ev ) +{ + ActorAnim = ev->GetString( 1 ); +} + + +/*****************************************************************************/ +/*QUAKED info_waypointnode_waypointnode (1 0 1) (-12 -12 0) (12 12 12) +******************************************************************************/ + + +CLASS_DECLARATION( Entity , PatrolWayPointNode , "info_waypointnode_patrolwaypointnode" ) +{ + { &EV_WayPoint_SetActorAnim, &PatrolWayPointNode::SetActorAnim }, + { &EV_WayPoint_SetThread, &PatrolWayPointNode::SetWayPointThread }, + { NULL, NULL } +}; + + +PatrolWayPointNode::PatrolWayPointNode() +{ + SetWayPointThread(""); + SetActorAnim(""); +} + +PatrolWayPointNode::~PatrolWayPointNode() +{ + +} + +/*****************************************************************************/ +/*QUAKED info_waypointnode_patrolwaypointnode (0 0 1) (-12 -12 0) (12 12 12) +******************************************************************************/ + +CLASS_DECLARATION( Entity , CallVolumeWayPointNode , "info_waypointnode_callvolume" ) +{ + { &EV_WayPoint_SetActorAnim, &CallVolumeWayPointNode::SetActorAnim }, + { &EV_WayPoint_SetThread, &CallVolumeWayPointNode::SetWayPointThread }, + { NULL, NULL } +}; + + +CallVolumeWayPointNode::CallVolumeWayPointNode() +{ + SetWayPointThread(""); + SetActorAnim(""); +} + +CallVolumeWayPointNode::~CallVolumeWayPointNode() +{ + +} + +/*****************************************************************************/ +/*QUAKED info_waypointnode_callvolume (.5 .5 .5) (-12 -12 0) (12 12 12) +******************************************************************************/ + + + +CLASS_DECLARATION( Entity , PositionWayPointNode , "info_waypointnode_position" ) +{ + { &EV_WayPoint_SetActorAnim, &PositionWayPointNode::SetActorAnim }, + { &EV_WayPoint_SetThread, &PositionWayPointNode::SetWayPointThread }, + { NULL, NULL } +}; + + +PositionWayPointNode::PositionWayPointNode() +{ + SetWayPointThread(""); + SetActorAnim(""); + _reserved = false; +} + +PositionWayPointNode::~PositionWayPointNode() +{ + +} + +void PositionWayPointNode::Reserve( qboolean reserve ) +{ + _reserved = reserve; +} + +qboolean PositionWayPointNode::IsReserved() +{ + return _reserved; +} + +/*****************************************************************************/ +/*QUAKED info_waypointnode_position (.8 0 .8) (-12 -12 0) (12 12 12) +******************************************************************************/ diff --git a/dlls/game/waypoints.h b/dlls/game/waypoints.h new file mode 100644 index 0000000..1e72f78 --- /dev/null +++ b/dlls/game/waypoints.h @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/waypoints.h $ +// $Revision:: 9 $ +// $Author:: Steven $ +// $Date:: 10/13/03 8:54a $ +// +// Copyright (C) 2001 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: + +// +// Forward Declarations +// +class WayPointNode; + +#ifndef __WAYPOINTS_H__ +#define __WAYPOINTS_H__ + +#include "entity.h" + +typedef SafePtr WayPointNodePtr; + +class WayPointNode : public Entity + { + public: + CLASS_PROTOTYPE( WayPointNode ); + WayPointNode(); + ~WayPointNode(); + + //Events + virtual void SetWayPointThread ( Event *ev ); + virtual void SetActorAnim ( Event *ev ); + void SetTargetname ( Event *ev ); + void SetTarget ( Event *ev ); + + + virtual void SetWayPointThread ( const str &thread ); + virtual void SetActorAnim ( const str &thread ); + + virtual const str &GetThread(); + virtual const str &GetActorAnim(); + + virtual void Archive( Archiver &arc ); + + private: + str Thread; + str ActorAnim; + + }; + +inline void WayPointNode::Archive( Archiver &arc ) +{ + Entity::Archive( arc ); + + arc.ArchiveString( &Thread ); + arc.ArchiveString( &ActorAnim ); +} + +class PatrolWayPointNode : public WayPointNode + { + public: + CLASS_PROTOTYPE( PatrolWayPointNode ); + PatrolWayPointNode(); + ~PatrolWayPointNode(); + + }; + +class CallVolumeWayPointNode : public WayPointNode + { + public: + CLASS_PROTOTYPE( CallVolumeWayPointNode ); + CallVolumeWayPointNode(); + ~CallVolumeWayPointNode(); + }; + +class PositionWayPointNode : public WayPointNode + { + private: + qboolean _reserved; + + public: + CLASS_PROTOTYPE( PositionWayPointNode ); + PositionWayPointNode(); + ~PositionWayPointNode(); + + void Reserve( qboolean reserve ); + qboolean IsReserved(); + + virtual void Archive( Archiver &arc ); + }; + +inline void PositionWayPointNode::Archive( Archiver &arc ) +{ + WayPointNode::Archive( arc ); + + arc.ArchiveBoolean( &_reserved ); +} + +#endif /* __WAYPOINTS_H__ */ diff --git a/dlls/game/weapon.cpp b/dlls/game/weapon.cpp new file mode 100644 index 0000000..22f87da --- /dev/null +++ b/dlls/game/weapon.cpp @@ -0,0 +1,6018 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/weapon.cpp $ +// $Revision:: 263 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:43a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Source file for Weapon class. The weapon class is the base class for +// all weapons in the game. Any entity created from a class derived from the weapon +// class will be usable by any Sentient (players and monsters) as a weapon. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "item.h" +#include "weapon.h" +#include "scriptmaster.h" +#include "sentient.h" +#include "misc.h" +#include "specialfx.h" +#include "actor.h" +#include "weaputils.h" +#include "player.h" +#include "decals.h" +#include "mp_manager.hpp" +#include + +// for bot code +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +#include "ai_main.h" +extern bot_state_t *botstates[MAX_CLIENTS]; + +#define TargetIdleTime 0.2 + +Event EV_Weapon_Shoot +( + "shoot", + EV_DEFAULT, + "S", + "mode", + "Shoot the weapon" + ); +Event EV_Weapon_DoneRaising +( + "ready", + EV_TIKIONLY, + NULL, + NULL, + "Signals the end of the ready animation so the weapon can be used" + ); +Event EV_Weapon_DoneAnimating +( + "weapon_done_animate", + EV_CODEONLY, + NULL, + NULL, + "Signals the end of the ready animation so the weapon can be used" +); +Event EV_Weapon_DoneFiring +( + "donefiring", + EV_TIKIONLY, + NULL, + NULL, + "Signals the end of the fire animation" +); +Event EV_Weapon_Idle +( + "idle", + EV_DEFAULT, + NULL, + NULL, + "Puts the weapon into an idle state" +); +Event EV_Weapon_SecondaryUse +( + "secondaryuse", + EV_DEFAULT, + NULL, + NULL, + "Puts the weapon into its secondary mode of operation" +); +Event EV_Weapon_DoneReloading +( + "donereloading", + EV_CODEONLY, + NULL, + NULL, + "Signals the end of the reload animation" +); +Event EV_Weapon_DoneReloadingBurst +( + "doneReloadingBurst", + EV_CODEONLY, + NULL, + NULL, + "Signals the end of the reload animation for a burst" +); +Event EV_Weapon_SetAmmoClipSize +( + "clipsize", + EV_TIKIONLY, + "i", + "ammoClipSize", + "Set the amount of rounds a clip of the weapon holds" +); +Event EV_Weapon_SetAmmoInClip +( + "ammo_in_clip", + EV_TIKIONLY, + "i", + "ammoInClip", + "Set the amount of ammo in the clip" +); +Event EV_Weapon_ProcessModelCommands +( + "process_mdl_cmds", + EV_CODEONLY, + NULL, + NULL, + "Forces a processing of the init commands for the model" +); +Event EV_Weapon_SetMaxRange +( + "maxrange", + EV_TIKIONLY, + "f", + "maxRange", + "Set the maximum range of a weapon so the AI knows how to use it" +); +Event EV_Weapon_SetMinRange +( + "minrange", + EV_TIKIONLY, + "f", + "minRange", + "Set the minimum range of a weapon so the AI knows how to use it" +); +Event EV_Weapon_ActionIncrement +( + "actionincrement", + EV_TIKIONLY, + "i", + "actionLevelIncrement", + "Set the action level increment of the weapon.\n" + "When the weapon is fired, it will raise the action level by this amount" +); +Event EV_Weapon_NotDroppable +( + "notdroppable", + EV_TIKIONLY, + NULL, + NULL, + "Makes a weapon not droppable" +); +Event EV_Weapon_SetAimAnim +( + "setaimanim", + EV_TIKIONLY, + "si", + "aimAnimation aimFrame", + "Set the aim animation and frame for when a weapon fires" +); +Event EV_Weapon_SetFireType +( + "firetype", + EV_TIKIONLY, + "s", + "firingType", + "Set the firing type of the weapon (projectile or bullet)" +); +Event EV_Weapon_SetProjectile +( + "projectile", + EV_TIKIONLY, + "s", + "projectileModel", + "Set the model of the projectile that this weapon fires" +); +Event EV_Weapon_SetBulletDamage +( + "bulletdamage", + EV_TIKIONLY, + "f", + "bulletDamage", + "Set the damage that the bullet causes" +); +Event EV_Weapon_SetBulletKnockback +( + "bulletknockback", + EV_TIKIONLY, + "f", + "bulletKnockback", + "Set the knockback that the bullet causes" +); +Event EV_Weapon_SetBulletCount +( + "bulletcount", + EV_TIKIONLY, + "f", + "bulletCount", + "Set the number of bullets this weapon shoots when fired" +); +Event EV_Weapon_SetBulletRange +( + "bulletrange", + EV_TIKIONLY, + "f", + "bulletRange", + "Set the range of the bullets" +); +Event EV_Weapon_SetBulletSpread +( + "bulletspread", + EV_TIKIONLY, + "ffFFF", + "bulletSpreadX bulletSpreadY endSpreadX endSpreadY spreadTime", + "Set the max spread of the bullet in the x and y axis, optionally with a different end spread and time interval" +); +Event EV_Weapon_SetRange +( + "range", + EV_TIKIONLY, + "f", + "range", + "Set the range of the weapon" +); +Event EV_Weapon_Hand +( + "hand", + EV_TIKIONLY, + "s", + "weaponHand", + "Set the hand the weapon can be wielded in (lefthand, righthand, both, or any)" +); +Event EV_Weapon_Mode +( + "modeset", + EV_TIKIONLY, + "SSSSSSSSS", + "arg1 arg2 arg3 arg4 arg5 arg6 arg7 arg8 arg9", + "Set a value for a mode by passing commands through" +); +Event EV_Weapon_AmmoType +( + "ammotype", + EV_TIKIONLY, + "s", + "name", + "Set the type of ammo this weapon uses" +); +Event EV_Weapon_StartAmmo +( + "startammo", + EV_TIKIONLY, + "i", + "amount", + "Set the starting ammo of this weapon" +); +Event EV_Weapon_AmmoBoost +( + "ammoBoost", + EV_TIKIONLY, + "i", + "amount", + "Set the ammo given to a sentient that picks up this weapon but already had a weapon of this type." +); +Event EV_Weapon_AmmoRequired +( + "ammorequired", + EV_TIKIONLY, + "i", + "amount", + "Set the amount of ammo this weapon requires to fire" +); +Event EV_Weapon_MaxChargeTime +( + "maxchargetime", + EV_TIKIONLY, + "i", + "time", + "Set the maximum time the weapon may be charged up" +); +Event EV_Weapon_GiveStartingAmmo +( + "startingammotoowner", + EV_CODEONLY, + NULL, + NULL, + "Internal event used to give ammo to the owner of the weapon" +); +Event EV_Weapon_GiveAmmoBoost +( + "giveAmmoBoost", + EV_CODEONLY, + NULL, + NULL, + "Internal event used to give ammo boost to the player that just picked up this weapon." +); +Event EV_Weapon_AutoAim +( + "autoaim", + EV_TIKIONLY, + "FF", + "selection_angle lockon_angle", + "Turn on auto aiming for the weapon" +); +Event EV_Weapon_Crosshair +( + "crosshair", + EV_TIKIONLY, + "b", + "bool", + "Turn on/off the crosshair for this weapon" +); +Event EV_Weapon_TorsoAim +( + "torsoaim", + EV_DEFAULT, + "b", + "bool", + "Turn on/off the torsoaim for this weapon" +); +Event EV_Weapon_SetQuiet +( + "quiet", + EV_TIKIONLY, + NULL, + NULL, + "Makes the weapon make no noise." +); +Event EV_Weapon_SetLoopFire +( + "loopfire", + EV_TIKIONLY, + NULL, + NULL, + "Makes the weapon fire by looping the fire animation." +); +/* +Event EV_Weapon_FullAnimFire +( + "fullanimfire", + EV_DEFAULT, + NULL, + NULL, + "Makes the weapon play the full fire animation even if key is released" +); +*/ +Event EV_Weapon_LeftAttachToTag +( + "leftattachtotag", + EV_TIKIONLY, + "s", + "tagname", + "Set the name of the tag to attach this to it's owner when wielded in the left hand." +); +Event EV_Weapon_RightAttachToTag +( + "rightattachtotag", + EV_TIKIONLY, + "s", + "tagname", + "Set the name of the tag to attach this to it's owner when wielded in the right hand." +); +Event EV_Weapon_DualAttachToTag +( + "dualattachtotag", + EV_TIKIONLY, + "s", + "tagname", + "Set the name of the tag to attach this to it's owner when wielded dual handed." +); +Event EV_Weapon_HolsterTagLeft +( + "leftholstertag", + EV_TIKIONLY, + "s", + "tagname", + "Set the name of the tag to attach this to when the weapon is put away from the left hand." +); +Event EV_Weapon_HolsterTagRight +( + "rightholstertag", + EV_TIKIONLY, + "s", + "tagname", + "Set the name of the tag to attach this to when the weapon is put away from the right hand." +); +Event EV_Weapon_HolsterTagDual +( + "dualholstertag", + EV_TIKIONLY, + "s", + "tagname", + "Set the name of the tag to attach this to when the weapon is put away from dual handed" +); +Event EV_Weapon_LeftHolsterAngles +( + "leftholsterangles", + EV_TIKIONLY, + "v", + "angles", + "Set the angles of this weapon when it's attached to the left holster" +); +Event EV_Weapon_RightHolsterAngles +( + "rightholsterangles", + EV_TIKIONLY, + "v", + "angles", + "Set the angles of this weapon when it's attached to the right holster" +); +Event EV_Weapon_DualHolsterAngles +( + "dualholsterangles", + EV_TIKIONLY, + "v", + "angles", + "Set the angles of this weapon when it's attached to the dual holster" +); +Event EV_Weapon_HolsterScale +( + "holsterscale", + EV_TIKIONLY, + "f", + "scale", + "Set the scale of the weapon when it's attached to the holster" +); +Event EV_Weapon_WeildedScale +( + "weildedScale", + EV_TIKIONLY, + "f", + "scale", + "Sets the scale of the weapon when it's weilded" +); +Event EV_Weapon_AutoPutaway +( + "autoputaway", + EV_TIKIONLY, + "b", + "bool", + "Set the weapon to be automatically put away when out of ammo" +); +Event EV_Weapon_UseNoAmmo +( + "usenoammo", + EV_TIKIONLY, + "b", + "bool", + "Set the weapon to be able to be used when it's out of ammo" +); +Event EV_Weapon_SetMeansOfDeath +( + "meansofdeath", + EV_TIKIONLY, + "s", + "meansOfDeath", + "Set the meansOfDeath of the weapon." +); +Event EV_Weapon_SetWorldHitSpawn +( + "worldhitspawn", + EV_TIKIONLY, + "s", + "modelname", + "Set a model to be spawned when the weapon strikes the world." +); +Event EV_Weapon_MakeNoise +( + "makenoise", + EV_TIKIONLY, + "FB", + "noise_radius force", + "Makes the weapon make noise that actors can hear." +); +Event EV_Weapon_SetViewModel +( + "weaponviewmodel", + EV_TIKIONLY, + "s", + "filename", + "Set the view model name" +); +Event EV_Weapon_DonePutaway +( + "doneputaway", + EV_TIKIONLY, + NULL, + NULL, + "Called when the weapon is done with it's putaway anim" +); +Event EV_Weapon_SetRegenAmmo +( + "regenammo", + EV_TIKIONLY, + "ii", + "regenamount regentime", + "Specifics that a weapon regenerates ammo over time" +); +Event EV_Weapon_SetRegenOnlyWhenIdle +( + "regenonlywhenidle", + EV_TIKIONLY, + NULL, + NULL, + "Specifics that the weapon only regenerates ammo when idle, must still use regen ammo to set how much." +); +Event EV_Weapon_ChangeIdle +( + "changeidle", + EV_TIKIONLY, + NULL, + NULL, + "Changes the idle animation" +); +Event EV_Weapon_DrawBowStrain +( + "drawbowstrain", + EV_DEFAULT, + NULL, + NULL, + "Starts the bow draw strain animation" +); +Event EV_Weapon_AltDrawBowStrain +( + "altdrawbowstrain", + EV_DEFAULT, + NULL, + NULL, + "Starts the alternate bow draw strain animation" +); +Event EV_Weapon_SetAccuracy +( + "setaccuracy", + EV_TIKIONLY, + "sfffff", + "firemode stoppedac acchange walkac runac crouchac", + "Sets the accuracy of the weapon" +); +Event EV_Weapon_SetReticuleTime +( + "setreticuletime", + EV_TIKIONLY, + "f", + "reticuletime", + "Reticule time" +); +Event EV_Weapon_SetZoomFOV +( + "setzoomfov", + EV_TIKIONLY, + "f", + "zoomfov", + "Zoom FOV" +); +Event EV_Weapon_IncrementZoomFOV +( + "inczoomfov", + EV_TIKIONLY, + NULL, + NULL, + "Increments the zoom fov" +); +Event EV_Weapon_SetZoomStage +( + "setzoomstage", + EV_TIKIONLY, + "ff", + "fov1 fov2", + "Sets the fov stage" +); +Event EV_Weapon_SetStartZoom +( + "setstartzoom", + EV_TIKIONLY, + "f", + "startzoom", + "Sets the start zoom fov" +); +Event EV_Weapon_SetEndZoom +( + "setendzoom", + EV_TIKIONLY, + "f", + "endzoom", + "Sets the end zoom fov" +); +Event EV_Weapon_SetZoomTime +( + "setzoomtime", + EV_TIKIONLY, + "f", + "zoomtime", + "Sets the time it takes to zoom from startzoom to endzoom" +); +Event EV_Weapon_SetAimType +( + "setaim", + EV_DEFAULT, + "s", + "aimtype", + "Sets the aimtype of the weapon (changes according to player state)" +); +Event EV_Weapon_SetFireTimer +( + "setfiretimer", + EV_TIKIONLY, + "sf", + "mode mintime", + "Specifies the minimum time between shots" +); +Event EV_Weapon_UseSameClip +( + "usesameclip", + EV_TIKIONLY, + NULL, + NULL, + "Specifies that ammo comes out of the same clip in all fire modes" +); +Event EV_Weapon_SetMaxModes +( + "maxmode", + EV_TIKIONLY, + "s", + "maxmode", + "Overrides the maximum number of modes for this weapon (default is 2)" +); +Event EV_Weapon_SetSwitchMode +( + "switchmode", + EV_TIKIONLY, + NULL, + NULL, + "Specifies that this is a switch mode weapon (right button switches)" +); +Event EV_Weapon_DoneSwitching +( + "doneswitching", + EV_TIKIONLY, + NULL, + NULL, + "Signals the end of the switching to mode animation" +); +Event EV_Weapon_DoneSwitchToMiddle +( + "doneswitchtomiddle", + EV_TIKIONLY, + NULL, + NULL, + "Signals the end of the switching to 'neutral' animation" +); +Event EV_Weapon_TargetIdle +( + "targetidle", + EV_CODEONLY, + NULL, + NULL, + "This weapon have a specific on-target idle" +); +Event EV_Weapon_TargetIdleThink +( + "targetidlethink", + EV_DEFAULT, + NULL, + NULL, + "Think event to check for target on target idle weapons" +); +Event EV_Weapon_SetBurstMode +( + "burstmode", + EV_TIKIONLY, + "i", + "burstcount", + "Set this mode to be burst mode, that uses burstcount ammo" +); +Event EV_Weapon_StartFiring +( + "startfiring", + EV_TIKIONLY, + NULL, + NULL, + "Sets the time that the weapon starts firing (for spread computations)" +); +Event EV_Weapon_FinishedFiring +( + "finishedfiring", + EV_TIKIONLY, + "i", + "finished_firing", + "Sets if the gun is finished firing" +); +Event EV_Weapon_Zoom +( + "zoom", + EV_TIKIONLY, + NULL, + NULL, + "Zooms in" +); +Event EV_Weapon_EndZoom +( + "endZoom", + EV_TIKIONLY, + NULL, + NULL, + "Ends the zoom" +); +Event EV_Weapon_Rezoom +( + "rezoom", + EV_TIKIONLY, + NULL, + NULL, + "Rezooms to the last zoom fov" +); +Event EV_Weapon_SetTargetingSkin +( + "targetingskin", + EV_TIKIONLY, + "i", + "skinNum", + "Sets the skin number to use when targeting something" +); +Event EV_Weapon_SetShootingSkin +( + "shootingskin", + EV_TIKIONLY, + "i", + "skinNum", + "Sets the skin number to use when shooting" +); +Event EV_Weapon_SetFullAmmoSkin +( + "fullAmmoSkin", + EV_TIKIONLY, + "ii", + "skinNum modeNum", + "Sets the skin number to use when the weapon has full ammo" +); +Event EV_Weapon_SetMoveSpeedModifier +( + "moveSpeedModifier", + EV_DEFAULT, + "f", + "modifier", + "Sets the move speed modifier for this weapon (when not shooting)." +); +Event EV_Weapon_SetShootingMoveSpeedModifier +( + "shootingMoveSpeedModifier", + EV_DEFAULT, + "f", + "modifier", + "Sets the move speed modifier for this weapon while shooting." +); +Event EV_Weapon_UseActorAiming +( + "useactoraiming", + EV_DEFAULT, + "B", + "flag", + "This weapon aims with it's tag_barrel. Regardless of who is holding it." +); +Event EV_Weapon_ViewShake +( + "viewShake", + EV_DEFAULT, + "fF", + "viewShakeMagnitude viewShakeDuration", + "Sets the magnitude & duration of the view shaking while firing this weapon.\n" + "Duration defaults to .05 if not set" +); +Event EV_Weapon_AdvancedViewShake +( + "advancedViewShake", + EV_DEFAULT, + "vvFB", + "minViewShake maxViewShake viewShakeDuration override", + "Sets the min shake vector, max shake vector, & duration of the view shaking while firing this weapon.\n" + "Duration defaults to .05 if not set" +); +Event EV_Weapon_ClearViewShake +( + "clearViewShake", + EV_CODEONLY, + NULL, + NULL, + "Clears the current view shake." +); +Event EV_Weapon_ReduceViewShake +( + "reduceViewShake", + EV_CODEONLY, + NULL, + NULL, + "Reduces the current view shake." +); +Event EV_Weapon_SetPowerRating +( + "powerrating", + EV_DEFAULT, + "f", + "rating", + "Specifies how much damage is done per second by the weapon" +); +Event EV_Weapon_SetProjectileSpeed +( + "projectilespeed", + EV_DEFAULT, + "f", + "proj_speed", + "Specifies the speed of the projectile -- Used by AI" +); +Event EV_Weapon_SetProjectileDamage +( + "projectiledamage", + EV_DEFAULT, + "f", + "proj_damage", + "Specifies the damage of the projectile" +); +Event EV_Weapon_SetBurstModeDelay +( + "burstModeDelay", + EV_DEFAULT, + "f", + "burstModeDelay", + "Specifies the length of time to use between bursts (do not use this if you want to use the animation timing)" +); +Event EV_Weapon_SetArcProjectile +( + "arcprojectile", + EV_DEFAULT, + "b", + "arc_bool", + "Specifies for actors if the projectile should arc" +); +Event EV_Weapon_SetLowArcRange +( + "lowarcrange", + EV_DEFAULT, + "f", + "range", + "Specifies for actors the range at which to change from high trajectory to normal" +); + +Event EV_Weapon_SetPlayMissSound +( + "playmisssound", + EV_DEFAULT, + "b", + "playsound", + "Sets whether or not to play a miss sound( snd_ricochet ) at the point of impact" +); + +Event EV_Weapon_NoAmmoMode +( + "noAmmoMode", + EV_DEFAULT, + NULL, + NULL, + "Specifies that the current mode also has a no ammo mode." +); +Event EV_Weapon_NoDelay +( + "noDelay", + EV_DEFAULT, + NULL, + NULL, + "Specifies that this mode has no delay." +); +Event EV_Weapon_PauseRegen +( + "pauseRegen", + EV_DEFAULT, + NULL, + NULL, + "Pauses the regen of ammo for a little bit." +); +Event EV_Weapon_SetChargedModels +( + "chargedModels", + EV_DEFAULT, + "i", + "numChargedModels", + "Sets the number of charged models there are to use." +); +Event EV_Weapon_SetControllingProjectile +( + "controllingProjectile", + EV_DEFAULT, + "B", + "bool", + "Sets whether or not the weapon is currently controlling a projectile or not." +); +Event EV_Weapon_SetCanInterruptFiringState +( + "canInterruptFiringState", + EV_DEFAULT, + "B", + "bool", + "Sets whether or not the weapon can be interrupted during its firing state." +); +Event EV_Weapon_StartViewShake +( + "startViewShake", + EV_CODEONLY, + NULL, + NULL, + "Starts the weapons view shake." +); +Event EV_Weapon_SpreadAnimData +( + "spreadAnimData", + EV_DEFAULT, + "if", + "numAnims totalTime", + "Sets up the info for spread over time using different fire anims." +); +Event EV_Weapon_MaxViewShakeChange +( + "maxViewShakeChange", + EV_DEFAULT, + "f", + "maxViewChange", + "Sets the max view change for this weapon." +); +Event EV_Weapon_ControlParms +( + "controlProjParms", + EV_DEFAULT, + "ss", + "emitterName soundName", + "Sets the paramaters to use when controlling a projectile." +); +Event EV_Weapon_ProjectileControlHidden +( + "controlProjHidden", + EV_DEFAULT, + "b", + "hiddenBool", + "Sets whether or not the projectile control is currently hidden." +); + +Event EV_Weapon_MeleeParms +( + "meleeParms", + EV_DEFAULT, + "fff", + "width height length", + "Sets the parms of the melee attack." +); +Event EV_Weapon_FireOffset +( + "fireOffset", + EV_DEFAULT, + "v", + "fireOffset", + "Sets the offset where the weapon will fire from." +); +Event EV_Weapon_AutoReload +( + "autoReload", + EV_DEFAULT, + "b", + "autoReload", + "Specifies whether or not the weapon automatically reloads." +); +Event EV_Weapon_AllowAutoSwitch +( + "allowAutoSwitch", + EV_DEFAULT, + "b", + "allowAutoSwitch", + "Specifies whether or not the weapon automatically switches to another weapon when it's out of ammo." +); +Event EV_Weapon_ForceReload +( + "forceReload", + EV_DEFAULT, + NULL, + NULL, + "Forces a reload to occur." +); +Event EV_Weapon_NextSwitchTime +( + "nextSwitchTime", + EV_DEFAULT, + "f", + "time", + "Sets the next switch time." +); + +CLASS_DECLARATION( Item, Weapon, NULL ) +{ + { &EV_Item_Pickup, &Weapon::PickupWeapon }, + { &EV_Weapon_DoneRaising, &Weapon::DoneRaising }, + { &EV_Weapon_DoneFiring, &Weapon::DoneFiring }, + { &EV_Weapon_DoneAnimating, &Weapon::DoneAnimating }, + { &EV_Weapon_Idle, &Weapon::Idle }, + { &EV_BroadcastSound, &Weapon::WeaponSound }, + { &EV_Weapon_DoneReloading, &Weapon::DoneReloading }, + { &EV_Weapon_DoneReloadingBurst, &Weapon::DoneReloadingBurst }, + { &EV_Weapon_SetAmmoClipSize, &Weapon::SetAmmoClipSize }, + { &EV_Weapon_SetAmmoInClip, &Weapon::SetAmmoInClip }, + { &EV_Weapon_ProcessModelCommands, &Weapon::ProcessWeaponCommandsEvent }, + { &EV_Weapon_SetMaxRange, &Weapon::SetMaxRangeEvent }, + { &EV_Weapon_SetMinRange, &Weapon::SetMinRangeEvent }, + { &EV_Weapon_ActionIncrement, &Weapon::SetActionLevelIncrement }, + { &EV_Weapon_NotDroppable, &Weapon::NotDroppableEvent }, + { &EV_Weapon_SetAimAnim, &Weapon::SetAimAnim }, + { &EV_Weapon_Shoot, &Weapon::Shoot }, + { &EV_Weapon_SetFireType, &Weapon::SetFireType }, + { &EV_Weapon_DonePutaway, &Weapon::DonePutaway }, + { &EV_Weapon_SetProjectile, &Weapon::SetProjectile }, + { &EV_Weapon_SetBulletDamage, &Weapon::SetBulletDamage }, + { &EV_Weapon_SetBulletCount, &Weapon::SetBulletCount }, + { &EV_Weapon_SetBulletKnockback, &Weapon::SetBulletKnockback }, + { &EV_Weapon_SetBulletRange, &Weapon::SetBulletRange }, + { &EV_Weapon_SetRange, &Weapon::SetRange }, + { &EV_Weapon_SetBulletSpread, &Weapon::SetBulletSpread }, + { &EV_Weapon_Hand, &Weapon::SetHand }, + { &EV_Weapon_Mode, &Weapon::ModeSet }, + { &EV_Weapon_AmmoType, &Weapon::SetAmmoType }, + { &EV_Weapon_StartAmmo, &Weapon::SetStartAmmo }, + { &EV_Weapon_AmmoBoost, &Weapon::setAmmoBoost }, + { &EV_Weapon_AmmoRequired, &Weapon::SetAmmoRequired }, + { &EV_Weapon_MaxChargeTime, &Weapon::SetMaxChargeTime }, + { &EV_Weapon_GiveStartingAmmo, &Weapon::GiveStartingAmmo }, + { &EV_Weapon_GiveAmmoBoost, &Weapon::giveAmmoBoost }, + { &EV_Weapon_AutoAim, &Weapon::AutoAim }, + { &EV_Weapon_Crosshair, &Weapon::Crosshair }, + { &EV_Weapon_TorsoAim, &Weapon::TorsoAim }, + { &EV_Weapon_LeftAttachToTag, &Weapon::LeftAttachToTag }, + { &EV_Weapon_RightAttachToTag, &Weapon::RightAttachToTag }, + { &EV_Weapon_DualAttachToTag, &Weapon::DualAttachToTag }, + { &EV_Weapon_HolsterTagLeft, &Weapon::LeftHolsterAttachToTag }, + { &EV_Weapon_HolsterTagRight, &Weapon::RightHolsterAttachToTag }, + { &EV_Weapon_HolsterTagDual, &Weapon::DualHolsterAttachToTag }, + { &EV_Weapon_RightHolsterAngles, &Weapon::SetRightHolsterAngles }, + { &EV_Weapon_LeftHolsterAngles, &Weapon::SetLeftHolsterAngles }, + { &EV_Weapon_DualHolsterAngles, &Weapon::SetDualHolsterAngles }, + { &EV_Weapon_HolsterScale, &Weapon::SetHolsterScale }, + { &EV_Weapon_WeildedScale, &Weapon::setWeildedScale }, + { &EV_Weapon_SetQuiet, &Weapon::SetQuiet }, + { &EV_Weapon_SetLoopFire, &Weapon::SetLoopFire }, + // { &EV_Weapon_FullAnimFire, SetFullAnimFire }, + { &EV_Weapon_AutoPutaway, &Weapon::SetAutoPutaway }, + { &EV_Weapon_UseNoAmmo, &Weapon::SetUseNoAmmo }, + { &EV_Weapon_SetMeansOfDeath, &Weapon::SetMeansOfDeath }, + { &EV_Weapon_SetWorldHitSpawn, &Weapon::SetWorldHitSpawn }, + { &EV_Weapon_MakeNoise, &Weapon::MakeNoise }, + { &EV_Weapon_SetViewModel, &Weapon::SetViewModel }, + { &EV_Weapon_SetRegenAmmo, &Weapon::SetRegenAmmo }, + { &EV_Weapon_SetRegenOnlyWhenIdle, &Weapon::SetRegenOnlyWhenIdle }, + { &EV_Weapon_ChangeIdle, &Weapon::ChangeIdle }, + { &EV_Weapon_DrawBowStrain, &Weapon::DrawBowStrain }, + { &EV_Weapon_AltDrawBowStrain, &Weapon::AltDrawBowStrain }, + { &EV_Weapon_SetAccuracy, &Weapon::SetAccuracy }, + { &EV_Weapon_SetReticuleTime, &Weapon::SetReticuleTime }, + { &EV_Weapon_SetZoomFOV, &Weapon::SetZoomFOV }, + { &EV_Weapon_IncrementZoomFOV, &Weapon::IncrementZoom }, + { &EV_Weapon_SetZoomStage, &Weapon::setZoomStage }, + { &EV_Weapon_SetStartZoom, &Weapon::SetStartZoom }, + { &EV_Weapon_SetEndZoom, &Weapon::SetEndZoom }, + { &EV_Weapon_SetZoomTime, &Weapon::SetZoomTime }, + { &EV_Weapon_SetAimType, &Weapon::SetAimType }, + { &EV_Weapon_SetFireTimer, &Weapon::SetFireTimer }, + { &EV_Weapon_UseSameClip, &Weapon::UseSameClip }, + { &EV_Weapon_SetMaxModes, &Weapon::SetMaxModes }, + { &EV_Weapon_SetSwitchMode, &Weapon::SetSwitchMode }, + { &EV_Weapon_DoneSwitchToMiddle, &Weapon::DoneSwitchToMiddle }, + { &EV_Weapon_DoneSwitching, &Weapon::DoneSwitching }, + { &EV_Weapon_TargetIdle, &Weapon::TargetIdle }, + { &EV_Weapon_TargetIdleThink, &Weapon::TargetIdleThink }, + { &EV_Weapon_SetBurstMode, &Weapon::SetBurstMode }, + { &EV_Weapon_FinishedFiring, &Weapon::FinishedFiring }, + { &EV_Weapon_StartFiring, &Weapon::StartFiring }, + { &EV_Weapon_Zoom, &Weapon::Zoom }, + { &EV_Weapon_EndZoom, &Weapon::endZoom }, + { &EV_Weapon_Rezoom, &Weapon::rezoom }, + { &EV_Weapon_SetTargetingSkin, &Weapon::SetTargetingSkin }, + { &EV_Weapon_SetShootingSkin, &Weapon::SetShootingSkin }, + { &EV_Weapon_SetFullAmmoSkin, &Weapon::setFullAmmoSkin }, + { &EV_Weapon_SetMoveSpeedModifier, &Weapon::setMoveSpeedModifier }, + { &EV_Weapon_SetShootingMoveSpeedModifier, &Weapon::setShootingMoveSpeedModifier }, + { &EV_Weapon_UseActorAiming, &Weapon::UseActorAiming }, + { &EV_Weapon_SetPowerRating, &Weapon::SetPowerRating }, + { &EV_Weapon_SetProjectileSpeed, &Weapon::SetProjectileSpeed }, + { &EV_Weapon_SetProjectileDamage, &Weapon::SetProjectileDamage }, + { &EV_Weapon_ViewShake, &Weapon::setViewShakeInfo }, + { &EV_Weapon_AdvancedViewShake, &Weapon::setAdvancedViewShakeInfo }, + { &EV_Weapon_ReduceViewShake, &Weapon::reduceViewShake }, + { &EV_Weapon_ClearViewShake, &Weapon::clearViewShake }, + { &EV_ProcessGameplayData, &Weapon::processGameplayData }, + + { &EV_Weapon_SetArcProjectile, &Weapon::SetArcProjectile }, + { &EV_Weapon_SetLowArcRange, &Weapon::SetLowArcRange }, + + { &EV_Weapon_NoAmmoMode, &Weapon::noAmmoMode }, + { &EV_Weapon_NoDelay, &Weapon::setNoDelay }, + { &EV_Weapon_PauseRegen, &Weapon::pauseRegen }, + + { &EV_Weapon_SetBurstModeDelay, &Weapon::setBurstModeDelay }, + { &EV_Weapon_SetChargedModels, &Weapon::setChargedModels }, + { &EV_Weapon_SetControllingProjectile, &Weapon::setControllingProjectile }, + + { &EV_Weapon_SetCanInterruptFiringState, &Weapon::setCanInterruptFiringState }, + + { &EV_Weapon_StartViewShake, &Weapon::startViewShake }, + + { &EV_Weapon_SpreadAnimData, &Weapon::setSpreadAnimData }, + + { &EV_Weapon_MaxViewShakeChange, &Weapon::setMaxViewShakeChange }, + { &EV_Weapon_ControlParms, &Weapon::setControlParms }, + { &EV_Weapon_ProjectileControlHidden, &Weapon::setProjectileControlHidden }, + + { &EV_Weapon_MeleeParms, &Weapon::setMeleeParms }, + { &EV_Weapon_FireOffset, &Weapon::setFireOffset }, + + { &EV_Weapon_AutoReload, &Weapon::setAutoReload }, + { &EV_Weapon_AllowAutoSwitch, &Weapon::setAllowAutoSwitch }, + { &EV_Weapon_ForceReload, &Weapon::forceReload }, + { &EV_Weapon_SetPlayMissSound, &Weapon::SetPlayMissSound }, + + { &EV_Weapon_NextSwitchTime, &Weapon::setNextSwitchTime }, + + // These anim events are overridden from Entity + { &EV_Anim, &Weapon::PassToAnimate }, + { &EV_NewAnim, &Weapon::PassToAnimate }, + + { NULL, NULL } +}; + +//====================== +//Weapon::Weapon +//====================== +Weapon::Weapon() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + // Owner of the weapon + owner = NULL; + + // Starting rank of the weapon + rank = 0; + + // Amount of ammo required for weapon + INITIALIZE_WEAPONMODE_VAR( ammorequired, 0 ); + + // Starting ammo of the weapon + INITIALIZE_WEAPONMODE_VAR( startammo, 0 ); + + INITIALIZE_WEAPONMODE_VAR( _ammoBoost, 0 ); + + // Amount of ammo the clip can hold + INITIALIZE_WEAPONMODE_VAR( ammo_clip_size, 0 ); + + // Amount of ammo in clip + INITIALIZE_WEAPONMODE_VAR( ammo_in_clip, 0 ); + + // Amount of time to pass before broadcasting a weapon sound again + nextweaponsoundtime = 0; + + // The initial state of the weapon + weaponstate = WEAPON_HOLSTERED; + + // Is the weapon droppable when the owner is killed + notdroppable = false; + + // Aim animation for behavior of monsters + aimanim = -1; + aimframe = 0; + + // start off unattached + attached = false; + + // maximum effective firing distance (for autoaim) + maxrange = 1000; + + // minimum safe firing distance (for AI) + minrange = 0; + + // speed of the projectile (0 == infinite speed) + memset( projectilespeed, 0, sizeof( projectilespeed ) ); + + // default action_level_increment + INITIALIZE_WEAPONMODE_VAR( action_level_increment, 2 ); + + // Weapons don't move + setMoveType( MOVETYPE_NONE ); + + // What type of ammo this weapon fires + INITIALIZE_WEAPONMODE_VAR( firetype, (firetype_t)0 ); + + // Init the bullet specs + INITIALIZE_WEAPONMODE_VAR( bulletdamage, 0 ); + INITIALIZE_WEAPONMODE_VAR( bulletcount, 1 ); + INITIALIZE_WEAPONMODE_VAR( bulletrange, 1024 ); + INITIALIZE_WEAPONMODE_VAR( bulletknockback, 0 ); + INITIALIZE_WEAPONMODE_VAR( ammo_type, "" ); + INITIALIZE_WEAPONMODE_VAR( loopfire, false ); + //INITIALIZE_WEAPONMODE_VAR( fullanimfire, false ); + INITIALIZE_WEAPONMODE_VAR( burstmode, false); + burstcount = 0; + burstcountmax = 0; + + // Init the max amount of time a weapon may be charged (5 seconds) + INITIALIZE_WEAPONMODE_VAR( max_charge_time, 5 ); + charge_fraction = 1.0f; + + // Tag to attach this weapon to on its owner when used in the left hand and in the right hand + left_attachToTag = "tag_lhand"; + right_attachToTag = "tag_rhand"; + dual_attachToTag = "tag_weapon_dual"; + + // putaway is flagged true when the weapon should be put away by state machine + putaway = false; + + // Default to being able to use the weapon in any hand + hand = WEAPON_ANY; + + // This is used for setting mode functionality when initializing stuff + firemodeindex = FIRE_MODE1; + + // Name and index + setName( "Unnamed Weapon" ); + + // do better lighting on all weapons + edict->s.renderfx |= RF_EXTRALIGHT; + + // make all weapons RF_WEAPONMODELs + edict->s.renderfx |= RF_WEAPONMODEL; + + // Weapons do not auto aim automatically + autoAimTargetSelectionAngle = 0; + autoAimLockonAngle = 0; + // No crosshair visible + crosshair = false; + + // Don't torsoaim + torsoaim = false; + + // Weapons default to making noise + quiet = false; + next_noise_time = 0; + next_noammo_time = 0; + + // Used to keep track of last angles and scale before holstering + lastValid = false; + lastScale = 1.0f; + holsterScale = 1.0f; + _weildedScale = 0.5f; + + // Weapon will not be putaway by default when out of ammo + auto_putaway = false; + + // Weapon will be able to be used when it has no ammo + use_no_ammo = true; + + // Default accuracy value + for ( int i=0; is.renderfx |= RF_CHILDREN_DONT_INHERIT_ALPHA; + + _nextSwitchTime = 0.0f; +} + + +//====================== +//Weapon::Weapon +//====================== +Weapon::Weapon( const char *file ) +{ + // The tik file holds all the information available for a weapon + Weapon(); +} + +//====================== +//Weapon::~Weapon +//====================== +Weapon::~Weapon() +{ + DetachGun(); +} + +//====================== +//Weapon::GetRank +//====================== +int Weapon::GetRank( void ) +{ + return rank; +} + +//====================== +//Weapon::GetOrder +//====================== +int Weapon::GetOrder( void ) +{ + return order; +} + +//====================== +//Weapon::SetRank +//====================== +void Weapon::SetRank( int order, int rank ) +{ + this->order = order; + this->rank = rank; +} + +//====================== +//Weapon::SetAutoPutaway +//====================== +void Weapon::SetAutoPutaway( Event *ev ) +{ + auto_putaway = ev->GetBoolean( 1 ); +} + +//====================== +//Weapon::SetUseNoAmmo +//====================== +void Weapon::SetUseNoAmmo( Event *ev ) +{ + use_no_ammo = ev->GetBoolean( 1 ); +} + +//====================== +//Weapon::SetStartAmmo +//====================== +void Weapon::SetStartAmmo( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + startammo[firemodeindex] = ev->GetInteger( 1 ); +} + +void Weapon::setAmmoBoost( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + + _ammoBoost[ firemodeindex ] = ev->GetInteger( 1 ); +} + +//====================== +//Weapon::SetMaxChargeTime +//====================== +void Weapon::SetMaxChargeTime( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + max_charge_time[firemodeindex] = ev->GetFloat( 1 ); +} + +//====================== +//Weapon::SetAmmoRequired +//====================== +void Weapon::SetAmmoRequired( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + ammorequired[firemodeindex] = ev->GetInteger( 1 ); +} + +//====================== +//Weapon::GetStartAmmo +//====================== +int Weapon::GetStartAmmo( firemode_t mode ) +{ + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) + return startammo[mode]; + else + { + warning( "Weapon::GetStartAmmo", "Invalid mode %d\n", mode ); + return 0; + } +} + +int Weapon::getAmmoBoost( firemode_t mode ) +{ + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) + { + return _ammoBoost[ mode ]; + } + else + { + warning( "Weapon::getAmmoBoost", "Invalid mode %d\n", mode ); + return 0; + } +} + +//====================== +//Weapon::GetAmmoType +//====================== +str Weapon::GetAmmoType( firemode_t mode ) +{ + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) + return ammo_type[mode]; + else + { + warning( "Weapon::GetAmmoType", "Invalid mode %d\n", mode ); + return "UnknownAmmo"; + } +} + +//====================== +//Weapon::SetAmmoType +//====================== +void Weapon::SetAmmoType( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + + if ( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ) + ammo_type[firemodeindex] = ev->GetString( 1 ); + else + { + warning( "Weapon::SetAmmoType", "Invalid mode %d\n", firemodeindex ); + return; + } +} + +//====================== +//Weapon::SetAmmoAmount +//====================== +void Weapon::SetAmmoAmount( int amount, firemode_t mode ) +{ + firemode_t clipToUse; + + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( usesameclip ) + clipToUse = FIRE_MODE1; + else + clipToUse = mode; + + // If the clip can hold ammo, then set the amount in the clip to the specified amount + if ( ( clipToUse >= 0 ) && ( clipToUse < MAX_FIREMODES ) ) + { + if ( ammo_clip_size[clipToUse] ) + ammo_in_clip[clipToUse] = amount; + } + else + { + warning( "Weapon::SetAmmoAmount", "Invalid mode %d\n", clipToUse ); + return; + } +} + + +//----------------------------------------------------- +// +// Name: GetRequiredAmmo +// Class: Weapon +// +// Description: Retrieves the required amount of ammo to fire one round +// +// Parameters: mode - the firing mode. +// +// Returns: None +//----------------------------------------------------- +int Weapon::GetRequiredAmmo( firemode_t mode ) +{ + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + return ammorequired[mode]; +} + + +//====================== +//Weapon::GetClipSize +//====================== +int Weapon::GetClipSize( firemode_t mode ) +{ + firemode_t clipToUse; + + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( usesameclip ) + clipToUse = FIRE_MODE1; + else + clipToUse = mode; + + if ( ( clipToUse >= 0 ) && ( clipToUse < MAX_FIREMODES ) ) + return ammo_clip_size[clipToUse]; + else + { + warning( "Weapon::GetClipSize", "Invalid mode %d\n", clipToUse ); + return 0; + } +} + +//====================== +//Weapon::UseAmmo +//====================== +void Weapon::UseAmmo( int amount, firemode_t mode ) +{ + firemode_t clipToUse; + + if ( UnlimitedAmmo( mode ) ) + return; + + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( !( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) ) + warning( "Weapon::UseAmmo", "Invalid mode %d\n", mode ); + + if ( !HasAmmo( mode ) && hasNoAmmoMode( mode ) ) + { + return; + } + + // Determine which clip to use + + if ( usesameclip ) + clipToUse = FIRE_MODE1; + else + clipToUse = mode; + + // Remove ammo from the clip if it's available + + if ( ammo_clip_size[clipToUse] ) + { + ammo_in_clip[clipToUse] -= amount; + if (ammo_in_clip[clipToUse] < 0) + { + warning("UseAmmo","Used more ammo than in clip.\n"); + ammo_in_clip[clipToUse] = 0; + } + owner->AmmoAmountInClipChanged( ammo_type[mode], ammo_in_clip[clipToUse] ); + } + else + { + assert( owner ); + if ( owner && owner->isClient() && !UnlimitedAmmo( mode ) ) + { + // Remove ammo from the player's inventory + owner->UseAmmo( ammo_type[mode], ammorequired[mode] ); + } + } + + if ( burstmode[mode] ) + { + burstcount--; + } +} + +void Weapon::GetActorMuzzlePosition( Vector *position, Vector *forward, Vector *right, Vector *up, const char* tagname ) +{ + int tag_num; + + // Using tag_name for convience since it's referenced several times + str tag_name; + if ( !tagname ) + tag_name = "tag_barrel"; + else + tag_name = tagname; + + tag_num = gi.Tag_NumForName( edict->s.modelindex, tag_name ); + + if ( tag_num < 0 ) + gi.Error( ERR_FATAL, "Weapon::GetActorMuzzlePosition, invalid tag name." ); + + //Test Stuff + setOrigin(); + setAngles(); + + //GetTag( tag_num, &pos, &forward, &right, &up ); + + orientation_t weap_or, barrel_or, orn; + Vector pos; + vec3_t mat[3]={0,0,0,0,0,0,0,0,0}; + vec3_t orient[3]; + int i, mi, tagnum; + Sentient *owner; + owner = this->owner; + + // Get the owner's weapon orientation ( this is custom code and doesn't use the GetTag function + // because we need to use the saved off fire_frame and fire_animation indexes from the owner + mi = owner->edict->s.modelindex; + + tagnum = gi.Tag_NumForName( mi, current_attachToTag.c_str() ); + + + // Get the orientation based on the frame and anim stored off in the owner. + // This is to prevent weird timing with getting orientations on different frames of firing + // animations and the orientations will not be consistent. + + AnglesToAxis( owner->angles, owner->orientation ); + + orn = gi.Tag_OrientationEx( mi, + owner->CurrentAnim( legs ), + owner->CurrentFrame( legs ), + tagnum & TAG_MASK, + owner->edict->s.scale, + owner->edict->s.bone_tag, + owner->edict->s.bone_quat, + 0, + 0, + 1.0f, + ( owner->edict->s.anim & ANIM_BLEND ) != 0, + ( owner->edict->s.torso_anim & ANIM_BLEND ) != 0, + owner->CurrentAnim( torso ), + owner->CurrentFrame( torso ), + 0, + 0, + 1.0f + ); + + // Transform the weapon's orientation through the owner's orientation + // Player orientation is normally based on the player's view, but we need + // it to be based on the model's orientation, so we calculate it here. + AnglesToAxis( owner->angles, orient ); + VectorCopy( owner->origin, weap_or.origin ); + for ( i=0; i<3; i++ ) + { + VectorMA( weap_or.origin, orn.origin[i], orient[i], weap_or.origin ); + } + + MatrixMultiply( orn.axis, orient, weap_or.axis ); + + if ( !this->GetRawTag( tag_name, &barrel_or ) ) + { + pos = owner->centroid; + AnglesToAxis( owner->angles, mat ); + return; + } + + // Translate the barrel's orientation through the weapon's orientation + VectorCopy( weap_or.origin, pos ); + + for ( i = 0 ; i < 3 ; i++ ) + { + VectorMA( pos, barrel_or.origin[i], weap_or.axis[i], pos ); + } + + MatrixMultiply( barrel_or.axis, weap_or.axis, mat ); + + if ( position ) + *position = pos; + + if ( forward ) + *forward = mat[0]; + + if ( right ) + *right = mat[1]; + + if ( up ) + *up = mat[2]; +} + +//====================== +//Weapon::GetMuzzlePosition +//====================== +void Weapon::GetMuzzlePosition( Vector *position, Vector *forward, Vector *right, Vector *up ) +{ + Vector endpoint, vorg; + orientation_t barrel_or; + Vector f, r, u, viewWeapOrg, pview, pos; + Sentient *owner; + Player *player; + int tagnum; + + owner = this->owner; + assert( owner ); + + if ( owner->isSubclassOf( Player ) ) + player = ( Player * )owner; + else + { + warning("GetMuzzlePosition called from Weapon class for a non-player character.",NULL); + return; + } + + // Assign player based variables + + vorg = player->origin; + // Offset by the view since the origin is at the feet + vorg.z += player->client->ps.viewheight; + + pview = player->GetVAngles(); + +/* + vec3_t new_vieworg; + vec3_t left; + + if( player->client->ps.leanDelta != 0) + { + pview[2] -= player->client->ps.leanDelta / 2.0f; + + AngleVectors( pview, NULL, left, NULL ); + VectorMA( vorg, player->client->ps.leanDelta, left, new_vieworg ); + VectorCopy( new_vieworg, vorg ); + } +*/ + // Assign player based variables + + // Get our f, r, u vectors and set an endpoint + AngleVectors( pview, f, r, u ); + + // Create our point in worldspace. Lots of hardcoded crap here. + // This will only work correctly with the default FOV (since we don't have access any real numbers) + // We also don't have access to the znear value. + trace_t viewTrace; + player->GetViewTrace(viewTrace, MASK_SHOT); + endpoint = viewTrace.endpos; + Vector dir = endpoint - vorg; + float len = dir.length(); + dir.normalize(); + endpoint = vorg + dir * (len + 5.0f); + /*Vector p,s; + p.x = (float)chx * (-4.0f / 320.0f); + p.y = (float)chy * (-3.0f / 240.0f); + s = 4.0f * f + p.x * r + p.y * u; + s.normalize(); + endpoint = vorg + (s * 3000);*/ + + // Now we get the origin of the weapon that the CLIENT knows (first person) + viewWeapOrg = vorg; + //setScale(0.5f); // Client scales the weapon to half-size + //VectorMA( viewWeapOrg, -9.0f, r, viewWeapOrg ); + //VectorMA( viewWeapOrg, -7.5f, u, viewWeapOrg ); + + // Weapon is about at viewWeapOrg position, neglecting viewbob/pullback + + // Do a trace to get our endpoint + + trace_t trace; + + if ( !multiplayerManager.inMultiplayer() || multiplayerManager.fullCollision() ) + trace = G_FullTrace( vorg, vec_zero, vec_zero, endpoint, owner, MASK_SHOT, true, "Weapon::GetMuzzlePosition" ); + else + trace = G_Trace( vorg, vec_zero, vec_zero, endpoint, owner, MASK_SHOT, true, "Weapon::GetMuzzlePosition" ); + + // Get the barrel tag from the viewmodel + + tagnum = gi.Tag_NumForName( edict->s.viewmodelindex, "tag_barrel" ); + + if ( tagnum >= 0 ) + { + barrel_or = gi.Tag_Orientation( edict->s.viewmodelindex, 0, 0, tagnum & TAG_MASK, 1.0f, edict->s.bone_tag, edict->s.bone_quat ); + } + else + { + VectorClear( barrel_or.origin ); + } + + // Get the tag_barrel of the gun, otherwise use the owners centroid + /* if ( !this->GetRawTag( "tag_barrel", &barrel_or ) ) + { + warning( "Weapon::GetMuzzlePosition", "Could not find tag \"%s\"", current_attachToTag.c_str() ); + *position = owner->centroid; + } + else + *position = barrel_or.origin; */ + + // Translate the barrel's orientation through the weapon's orientation + + pos = viewWeapOrg; + + // Offset the fire position if the weapon tells us to + + if ( _fireOffset[ curmode ] != vec_zero ) + { + float viewOrientation[3][3]; + + AnglesToAxis( pview, viewOrientation ); + MatrixTransformVector( _fireOffset[ curmode ], viewOrientation, pos ); + pos += viewWeapOrg; + } + + // For now lets shoot from the viewpoint not the weapon + //VectorMA( pos, barrel_or.origin[0], f, pos ); + //VectorMA( pos, barrel_or.origin[1], r, pos ); + //VectorMA( pos, barrel_or.origin[2] - 1.0f, u, pos ); + + // Final barrel position + *position = pos; + + // Now set a NEW f,r,u vector based on changed needed to hit the center of the screen + Vector f2 = trace.endpos - *position; + AngleVectors( f2.toAngles(), f, r, u ); + + if ( forward ) + *forward = f; + if ( right ) + *right = r; + if ( up ) + *up = u; + + //G_DebugLine( *position, trace.endpos, 1,0,0,1 ); +} + + +//====================== +//Weapon::SetAmmoClipSize +//====================== +void Weapon::SetAmmoClipSize( Event * ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + ammo_clip_size[firemodeindex] = ev->GetInteger( 1 ); +} + +//====================== +//Weapon::SetAmmoInClip +//====================== +void Weapon::SetAmmoInClip( Event * ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + ammo_in_clip[firemodeindex] = ev->GetInteger( 1 ); +} + +//====================== +//Weapon::Shoot +//====================== +// This function is called from the weapon tiki file +// With a loopfire weapon, this function may be called +// multiple times without having called "Fire", so we need +// to also call UseAmmo here for loopfire weapons +void Weapon::Shoot( Event *ev ) +{ + Vector pos, forward, right, up, delta; + firemode_t mode = FIRE_MODE1; + firemode_t realmode = FIRE_MODE1; + firemode_t regenMode; + + if ( ev->NumArgs() > 0 ) + { + mode = WeaponModeNameToNum( ev->GetString( 1 ) ); + realmode = mode; + if ( mode == FIRE_ERROR ) + return; + } + + // Override the firemode if we're a switch weapon + if ( switchmode ) + mode = curmode; + else + curmode = mode; + + /* if ( next_fire_time[mode] > level.time ) + return; */ + + // Set the fire timer if we have one + if ( fire_timer[mode] > 0.0 ) + next_fire_time[mode] = fire_timer[mode] + level.time; + + // Set accuracy spread + //bulletspread[mode].x = 50.0f * accuracy[mode][aimtype]; + //bulletspread[mode].y = 50.0f * accuracy[mode][aimtype]; + + //if ( usesameclip ) + // mode = FIRE_MODE1; + + // If we are in loopfire, we need to keep checking ammo and using it up + if ( loopfire[mode] ) + { + if ( HasAmmo( mode ) || hasNoAmmoMode( mode ) ||UnlimitedAmmo( mode ) ) + { + // Use up the appropriate amount of ammo, it's already been checked that we have enough + UseAmmo( ammorequired[mode], mode ); + /* if ( mode != realmode ) + { + if ( burstmode[realmode] ) + burstcount -= ammorequired[realmode]; + } */ + if ( switchmode ) + mode = curmode; + } + else + { + if ( switchmode ) + mode = curmode; + ForceIdle(); + return; + } + } + + mode = realmode; + + if ( ( usesameclip ) || ( ammo_clip_size[ mode ] == 0 ) ) + regenMode = FIRE_MODE1; + else + regenMode = mode; + + if ( ( _regenOnlyWhenIdle[ regenMode ] ) && ( _regenAmount[ regenMode ] > 0 ) ) + { + _nextRegenTime[ regenMode ] = level.time + _regenTime[ regenMode ] + 0.5f; + } + + Sentient *owner; + owner = this->owner; + + // If I am owned by a player, I need to get the muzzle position, otherwise, I just + // care about my barrel tag + if ( useActorAiming || !owner->isSubclassOf( Player ) ) + { + /* + Actor *actorOwner; + actorOwner = (Actor*)owner; + + Vector gunPos, gunForward, gunRight, gunUp; + actorOwner->combatSubsystem->AimWeaponTag(actorOwner->enemyManager->GetCurrentEnemy() ); + actorOwner->combatSubsystem->GetGunPositionData( &gunPos, &gunForward, &gunRight, &gunUp ); + forward = gunForward; + */ + owner->shotsFiredThisVolley++; + GetActorMuzzlePosition( &pos , &forward, &right, &up ); + } + else if ( owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)owner; + + GetMuzzlePosition( &pos, &forward, &right, &up ); + if (g_aimviewangles->integer || ( player->client->ps.pm_type == PM_SECRET_MOVE_MODE ) ) // use viewangles to aim instead of player muzzle orientation thing + { + vec3_t fwd; + AngleVectors(owner->client->ps.viewangles,fwd,NULL,NULL); + forward = fwd; + } + // Apply spread + + if ( firetype[mode] != FT_BULLET ) + { + applySpread( &forward, &right, &up ); + } + } + + PostEvent( EV_Weapon_StartViewShake, level.frametime * 2 ); + + Vector testForward; + pos.AngleVectors( &testForward ); + + if ( firetype[mode] == FT_PROJECTILE ) + { + // Temporary + // This is bad bad bad + // + /*if ( owner->isSubclassOf(Actor) ) + { + Entity *target = NULL; + Actor *actor = NULL; + Vector actorToTarget; + + actor = (Actor*)owner; + + if ( actor ) + { + target = actor->enemyManager->GetCurrentEnemy(); + + if ( target ) + { + actorToTarget = target->centroid - actor->centroid; + actorToTarget.normalize(); + forward = actorToTarget; + } + } + + applySpread( pos, &forward, &right, &up ); + }*/ + + str projectileModelName; + + // Figure out the projectile name to shoot + + if ( _chargedModels[ mode ] ) + { + int modelIndexToUse; + + // Build the projectile name - projectileModel + # + .tik + + projectileModelName = projectileModel[mode]; + + modelIndexToUse = ( _chargedModels[ mode ] * charge_fraction ) + 1; + + if ( modelIndexToUse > _chargedModels[ mode ] ) + modelIndexToUse = _chargedModels[ mode ]; + + projectileModelName.CapLength( projectileModelName.length() - 4 ); + projectileModelName += modelIndexToUse; + projectileModelName += ".tik"; + } + else + { + projectileModelName = projectileModel[mode]; + } + + ProjectileAttack( pos, + forward, + owner, + projectileModelName, + charge_fraction + ); + } + else if ( firetype[mode] == FT_BULLET ) + { + Vector spread; + float range; + + if ( owner->isSubclassOf( Player ) ) + { + // Move the position to shoot from back a little ( 1 1/2 feet ) + + //pos = pos - forward * 24.0f; + + spread = getSpread(); + //range = bulletrange[mode] + 24.0f; + range = bulletrange[mode]; + } + else + { + spread = bulletspread[ mode ]; + range = bulletrange[mode]; + } + + BulletAttack( pos, + forward, + right, + up, + range, + bulletdamage[mode], + bulletknockback[mode], + 0, + GetMeansOfDeath( mode ), + spread, + bulletcount[mode], + owner, + this + ); + } + else if ( firetype[mode] == FT_EXPLOSION ) + { + if ( owner && owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)owner; + + player->shotFired(); + } + + ExplosionAttack( pos, owner, projectileModel[mode], forward, owner ); + } + // Apparently the special_projectile stuff is no longer used + /* else if ( firetype[mode] == FT_SPECIAL_PROJECTILE ) + { + this->SpecialFireProjectile( pos, + forward, + right, + up, + owner, + projectileModel[mode], + charge_fraction + ); + } */ + else if ( firetype[mode] == FT_MELEE ) // this is a weapon that fires like a sword + { + Vector melee_pos, melee_end; + Vector dir; + float damage; + meansOfDeath_t meansofdeath; + float knockback; + + if ( owner->isSubclassOf( Player ) ) + { + Vector forward; + + Player *player = (Player *)owner; + melee_pos = player->origin + Vector( 0.0f, 0.0f, player->client->ps.viewheight ); + + player->GetVAngles().AngleVectors( &forward ); + + melee_end = melee_pos + forward * _meleeLength[ mode ]; + } + else + { + melee_pos = owner->centroid; + melee_end = owner->centroid + Vector( owner->orientation[0] ) * _meleeLength[ mode ]; + } + + damage = bulletdamage[mode]; + knockback = bulletknockback[mode]; + + meansofdeath = GetMeansOfDeath( mode ); + + if ( owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)(Sentient *)owner; + + meansofdeath = player->changetMeansOfDeath( meansofdeath ); + damage = player->getDamageDone( damage, meansofdeath, true ); + + knockback = (knockback + player->GetPlayerKnockback()) * player->GetKnockbackMultiplier(); + } + + Containervictimlist; + + if ( MeleeAttack( melee_pos, melee_end, damage, owner, meansofdeath, _meleeWidth[ mode ], -_meleeHeight[ mode ], + _meleeHeight[ mode ], knockback, true, &victimlist ) ) + { + // Hit something + + Sound( "impact_flesh", CHAN_VOICE ); + } + else + { + // Didn't hit anything that took damage + + // Try to hit the world since we didn't do any damage to anything + trace_t trace = G_Trace( melee_pos, Vector( -8.0f, -8.0f, -8.0f ), Vector( 8.0f, 8.0f, 8.0f ), melee_end, owner, MASK_MELEE, false, "Weapon::Shoot" ); + + Entity *victim = G_GetEntity( trace.entityNum ); + + if ( victim && ( ( victim == world ) || ( victim->takedamage == DAMAGE_NO ) ) ) + { + vec3_t newangles; + vectoangles( trace.plane.normal, newangles ); + WorldHitSpawn( mode, trace.endpos, newangles, 0.1f ); + + str realname = this->GetRandomAlias( "impact_world" ); + if ( realname.length() > 1 ) + this->Sound( realname, CHAN_VOICE ); + } + } + } + + if ( !quiet ) + { + if ( next_noise_time <= level.time ) + { + BroadcastSound(); + next_noise_time = level.time + 1.0f; + } + } +} + +//====================== +//Weapon::SetAimAnim +//====================== +void Weapon::SetAimAnim( Event *ev ) +{ + str anim; + + anim = ev->GetString( 1 ); + aimanim = gi.Anim_NumForName( edict->s.modelindex, anim.c_str() ); + aimframe = ev->GetInteger( 2 ); +} + +//====================== +//Weapon::SetOwner +//====================== +void Weapon::SetOwner( Sentient *ent ) +{ + assert( ent ); + if ( !ent ) + { + // return to avoid any buggy behaviour + return; + } + + Item::SetOwner( ent ); + + setOrigin( vec_zero ); + setAngles( vec_zero ); +} + +//====================== +//Weapon::AmmoAvailable +//====================== +int Weapon::AmmoAvailable( firemode_t mode ) +{ + // Returns the amount of ammo the owner has that is available for use + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( !( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) ) + { + warning( "Weapon::AmmoAvailable", "Invalid mode %d\n", mode ); + return 0; + } + + // Make sure there is an owner before querying the amount of ammo + if ( owner ) + { + return owner->AmmoCount( ammo_type[mode] ); + } + else + { + warning( "Weapon::AmmoAvailable", "Weapon does not have an owner.\n" ); + return 0; + } +} + +//====================== +//Weapon::UnlimitedAmmo +//====================== +qboolean Weapon::UnlimitedAmmo( firemode_t mode ) +{ + if ( !owner ) + { + return false; + } + + if ( !ammorequired[mode] ) + return true; + + if ( !owner->isClient() || ( owner->flags & FL_GODMODE ) || multiplayerManager.checkFlag( MP_FLAG_INFINITE_AMMO ) ) + { + return true; + } + else if ( !stricmp( ammo_type[mode], "None" ) ) + { + return true; + } + + return false; +} + +//====================== +//Weapon::HasInvAmmo +//====================== +// Checks to see if we have any ammo in general +qboolean Weapon::HasInvAmmo( firemode_t mode ) +{ + if ( !ammorequired[mode] ) + return false; + + return ( AmmoAvailable( mode ) >= ammorequired[mode] ); +} + +//====================== +//Weapon::HasAmmo +//====================== +qboolean Weapon::HasAmmo( firemode_t mode, int numShots ) +{ + firemode_t clipToUse; + + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( !( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) ) + { + warning( "Weapon::HasAmmo", "Invalid mode %d\n", mode ); + return false; + } + + if ( !owner ) + { + return false; + } + + if ( UnlimitedAmmo( mode ) ) + { + return true; + } + + if ( burstmode[mode] && burstcount <= 0 ) + { + return false; + } + + if ( usesameclip ) + { + clipToUse = FIRE_MODE1; + } + else + { + clipToUse = mode; + } + + // If the weapon uses a clip, check for ammo in the right clip + if ( ammo_clip_size[clipToUse] ) + { + return HasAmmoInClip( mode, numShots ); + } + else // Otherwise check if ammo is available in general + { + if ( !ammorequired[mode] ) + return false; + + return ( AmmoAvailable( mode ) >= ammorequired[mode] * numShots ); + } +} + +//====================== +//Weapon::HasAmmoInClip +//====================== +qboolean Weapon::HasAmmoInClip( firemode_t mode, int numShots ) +{ + firemode_t clipToUse; + + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( !( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) ) + warning( "Weapon::HasAmmoInClip", "Invalid mode %d\n", mode ); + + if ( usesameclip ) + { + clipToUse = FIRE_MODE1; + } + else + { + clipToUse = mode; + } + + if ( ammo_clip_size[clipToUse] ) + { + if ( ammo_in_clip[clipToUse] >= ammorequired[mode] * numShots ) + { + return true; + } + } + return false; +} + +//====================== +//Weapon::ForceState +//====================== +void Weapon::ForceState( weaponstate_t state ) +{ + weaponstate = state; +} + +//====================== +//Weapon::ReadyToFire +//====================== +qboolean Weapon::ReadyToFire( firemode_t mode, qboolean playsound ) +{ + // Make sure the weapon is in a good state + + if ( _canInterruptFiringState ) + { + if ( weaponstate != WEAPON_READY && weaponstate != WEAPON_FIRING ) + return false; + } + else + { + if ( weaponstate != WEAPON_READY ) + return false; + } + + // If the weapon doesn't take ammo, we're always ready. + str ammotype = GetAmmoType( ( firemode_t )mode ); + if ( ammotype == "None" ) + return true; + + //if ( usesameclip ) + // mode = FIRE_MODE1; + + if ( HasAmmo( mode ) ) + { + return true; + } + + if ( hasNoAmmoMode( mode ) ) + { + return true; + } + + if ( playsound && ( level.time > next_noammo_time ) && ( AmmoAvailable( mode ) < ammorequired[mode] ) ) + { + Sound( "snd_noammo" ); + next_noammo_time = level.time + 0.25f; + + // If this is a bot, wipe out the rest of his useless ammo in the clip to prevent confusion + + if ( owner && owner->isSubclassOf( Player ) ) + { + /* if ( player->edict->svflags & SVF_BOT ) + { + ammo_in_clip[ mode ] = 0; + } + else */ + { + // Auto switch to phaser + + if ( shouldAutoSwitch( mode ) ) + { + autoSwitch(); + } + + } + } + } + + return false; +} + +//====================== +//Weapon::PutAway +//====================== +void Weapon::PutAway( void ) +{ + // int i; + // int original_ammo_count; + // int new_ammo_count; + + if ( owner && owner->isSubclassOf( Actor ) ) + { + Event *newEvent = new Event( EV_DisplayEffect ); + newEvent->AddString( "TransportOut" ); + newEvent->AddString( "FederationWeapons" ); + ProcessEvent( newEvent ); + } + + if ( _controllingProjectile ) + { + toggleProjectileControl(); + } + + // set the putaway flag to true, so the state machine know to put this weapon away + putaway = true; + + if ( !owner ) + return; + + Uninitialize(); + + // Give ammo back to owner + /* + for( i = 0 ; i < MAX_FIREMODES ; i++ ) + { + if ( ammo_in_clip[ i ] ) + { + // Add ammo to owner + original_ammo_count = owner->AmmoCount( ammo_type[ i ] ); + owner->GiveAmmo( ammo_type[ i ], ammo_in_clip[ i ] ); + new_ammo_count = owner->AmmoCount( ammo_type[ i ] ); + + // Subtract ammo from weapon + + ammo_in_clip[ i ] = ammo_in_clip[ i ] - (new_ammo_count - original_ammo_count); + + owner->AmmoAmountInClipChanged( ammo_type[ i ], ammo_in_clip[ i ] ); + } + }*/ +} + +//====================== +//Weapon::DetachFromOwner +//====================== +void Weapon::DetachFromOwner( void ) +{ + if ( edict->s.renderfx | RF_DONTDRAW ) + animate->StopAnimatingAtEnd(); + else + animate->StopAnimating(); + + DetachGun(); + weaponstate = WEAPON_HOLSTERED; + current_attachToTag = ""; + + Uninitialize(); +} + +//====================== +//Weapon::AttachToOwner +//====================== +void Weapon::AttachToOwner( weaponhand_t hand ) +{ + AttachGun( hand , false ); + + if ( targetidle ) + PostEvent(EV_Weapon_TargetIdleThink, TargetIdleTime); + + ForceIdle(); +} + +//====================== +//Weapon::AttachToHolster +//====================== +void Weapon::AttachToHolster( weaponhand_t hand ) +{ + AttachGun( hand, true ); + animate->RandomAnimate( "holster", EV_Weapon_Idle ); +} + +//====================== +//Weapon::Drop +//====================== +qboolean Weapon::Drop( void ) +{ + float radius; + Vector temp; + int i; + + if ( !owner ) + { + return false; + } + + if ( !IsDroppable() ) + { + return false; + } + + if ( multiplayerManager.inMultiplayer() && !multiplayerManager.checkRule( "weapon-candrop", true, NULL ) ) + { + return false; + } + + DetachGun(); + + // Get rid of special effect stuff + + CancelEventsOfType( EV_DisplayEffect ); + CancelEventsOfType( EV_FadeIn ); + clearCustomShader(); + setAlpha( 1.0f ); + edict->s.renderfx &= ~RF_FORCE_ALPHA_EFFECTS; + edict->s.renderfx &= ~RF_FORCE_ALPHA; + + if ( _controllingProjectile ) + { + toggleProjectileControl(); + } + + temp[ 1 ] = 64.0; + temp[ 2 ] = 48.0; + if ( owner ) + { + setOrigin( owner->origin + temp ); + } + else + { + setOrigin( origin + temp ); + } + + // hack to fix the bounds when the gun is dropped + // once dropped reset the rotated bounds + flags |= FL_ROTATEDBOUNDS; + + if ( ( mins == vec_zero ) && ( maxs == vec_zero ) ) + { + gi.CalculateBounds( edict->s.modelindex, edict->s.scale, mins, maxs ); + radius = ( mins - maxs ).length() * 2.0f; + mins.x = mins.y = -radius; + maxs.x = maxs.y = radius; + setSize( mins, maxs ); + } + + // stop animating + ForceIdle(); + animate->SetFrame( 0 ); + + // drop the weapon + PlaceItem(); + if ( owner ) + { + temp[ 0 ] = G_CRandom( 50.0f ); + temp[ 1 ] = G_CRandom( 50.0f ); + temp[ 2 ] = 250.0f; + velocity = owner->velocity * 0.5f + temp; + setAngles( owner->angles ); + } + + avelocity = Vector( 0.0f, G_CRandom( 360.0f ), 0.0f ); + + // Get rid of all of the ammo in the weapon + + for ( i = 0 ; i < MAX_FIREMODES ; i++ ) + { + ammo_in_clip[ i ] = 0; + } + + // FIXME - Make this work right if we need it + /* + if ( owner && owner->isClient() ) + { + spawnflags |= DROPPED_PLAYER_ITEM; + if ( ammo_clip_size ) + startammo = ammo_in_clip; + else + startammo = 0; + + // If owner is dead, put all his ammo of that type in the gun. + if ( owner->deadflag ) + { + startammo = AmmoAvailable(); + } + } + else + { + spawnflags |= DROPPED_ITEM; + if ( ammo_clip_size && ammo_in_clip ) + startammo = ammo_in_clip; + else + startammo >>= 2; + + if ( startammo == 0 ) + { + startammo = 1; + } + } + */ + + // Wait some time before the last owner can pickup this weapon + last_owner = owner; + last_owner_trigger_time = level.time + 2.5f; + + // Cancel reloading events + CancelEventsOfType( EV_Weapon_DoneReloading ); + CancelEventsOfType( EV_Weapon_DoneReloadingBurst ); + + // Remove this from the owner's item list + if ( owner ) + { + owner->RemoveItem( this ); + } + + owner = NULL; + + if ( multiplayerManager.inMultiplayer() && gi.Anim_NumForName( edict->s.modelindex, "idle_onground" ) >= 0 ) + { + animate->RandomAnimate( "idle_onground" ); + edict->s.eType = ET_MODELANIM; + } + + if ( multiplayerManager.inMultiplayer() ) + { + edict->s.renderfx |= RF_FULLBRIGHT; + } + + // Fade out dropped weapons, to keep down the clutter + PostEvent( EV_FadeOut, 30.0f ); + + Uninitialize(); + return true; +} + +//====================== +//Weapon::Charge +//====================== +void Weapon::Charge( firemode_t mode ) +{ +} + +//====================== +//Weapon::ReleaseFire +//====================== +void Weapon::ReleaseFire( firemode_t mode, float charge_time ) +{ + // Calculate and store off the charge fraction to use when the weapon actually shoots + + // Clamp to max_charge_time + if ( charge_time > max_charge_time[mode] ) + charge_fraction = 1.0f; + else + charge_fraction = charge_time / max_charge_time[mode]; + + // Call regular fire function + Fire( mode ); +} + +//====================== +//Weapon::Fire +//====================== +// This function is called from the state machine +// to tell the tiki which weapon animation to play +// The tiki file will most likely post the "shoot" event +// shortly after this function is called +void Weapon::Fire( firemode_t mode ) +{ + Event *done_event=NULL; + firemode_t realmode; + Vector pos; + //char anim[128]; + str fireAnimName; + + // Sanity check the mode + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( !( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) ) + warning( "Weapon::Fire", "Invalid mode %d\n", mode ); + + realmode = mode; + + // Override the firemode if we're a switch weapon + if ( switchmode ) + mode = curmode; + else + curmode = mode; + + if ( next_fire_time[mode] > level.time ) + return; + + // Set the fire timer if we have one + if ( fire_timer[mode] > 0.0f ) + next_fire_time[mode] = fire_timer[mode] + level.time; + + // If we are in loopfire mode, then we don't pass a DoneFiring event + if ( !loopfire[mode] ) + { + // The DoneFiring event requires to know the firing mode so save that off in the event + done_event = new Event( EV_Weapon_DoneFiring ); + done_event->AddInteger( mode ); + } + + /* + if ( fullanimfire[mode] ) + { + donefiring = false; + done_event = new Event ( EV_Weapon_DoneFiring ); + done_event->AddInteger( mode ); + } + */ + + // Set the state of the weapon to FIRING + weaponstate = WEAPON_FIRING; + + if ( shootingSkin ) + ChangeSkin( shootingSkin, true ); + + // Cancel any old done firing events + CancelEventsOfType( EV_Weapon_DoneFiring ); + + // Play the correct animation + if ( !switchmode ) + { + if ( mode == FIRE_MODE1 ) + fireAnimName = "fire"; + //animate->RandomAnimate( "fire", done_event ); + else if ( mode == FIRE_MODE2 ) + fireAnimName = "alternatefire"; + //animate->RandomAnimate( "alternatefire", done_event ); + } + else + { + //sprintf(anim,"fire_mode%d",(int)mode+1); + fireAnimName = "fire_mode"; + fireAnimName += (int)mode+1; + //if ( animate->HasAnim( anim ) ) + // animate->RandomAnimate( anim, done_event ); + } + + if ( world && world->isThingBroken( item_name.c_str() ) ) + { + fireAnimName += "_broken"; + animate->RandomAnimate( fireAnimName.c_str(), done_event ); + return; + } + else if ( !HasAmmo( mode ) && hasNoAmmoMode( mode ) ) + { + fireAnimName += "_noammo"; + } + else if ( _spreadTime[ mode ] && _spreadAnims[ mode ] ) + { + float firingTime; + int animNum; + + firingTime = level.time - startfiretime; + + animNum = 1 + ( firingTime / _spreadTime[ mode ] ) * ( _spreadAnims[ mode ] + 1 ); + + if ( animNum > _spreadAnims[ mode ] ) + animNum = _spreadAnims[ mode ]; + + fireAnimName += "_spread"; + fireAnimName += animNum; + } + + animate->RandomAnimate( fireAnimName.c_str(), done_event ); + + // Use up the appropriate amount of ammo, it's already been checked that we have enough + //if ( usesameclip ) + // mode = FIRE_MODE1; + + if ( !loopfire[mode] ) + { + if ( ammorequired[mode] != ammorequired[realmode] ) + UseAmmo(ammorequired[realmode], mode); + else + UseAmmo( ammorequired[mode], mode ); + if ( mode != realmode ) + { + if ( burstmode[realmode] ) + burstcount -= ammorequired[realmode]; + } + } +} + +//====================== +//Weapon::DetachGun +//====================== +void Weapon::DetachGun( void ) +{ + if ( attached ) + { + StopSound( CHAN_WEAPONIDLE ); + attached = false; + detach(); + hideModel(); + + clearDisplayEffects(); + + if ( lastValid ) + { + // Restore the last scale + + setScale( lastScale ); + lastValid = false; + } + } +} + +//====================== +//Weapon::AttachGun +//====================== +void Weapon::AttachGun( weaponhand_t hand, qboolean holstering ) +{ + int tag_num = 0; + + if ( !owner ) + { + current_attachToTag = ""; + return; + } + + if ( attached ) + { + DetachGun(); + } + + if ( holstering ) + { + // Save off these values if we are holstering the weapon. We will restore them when + // the users raises the weapons again. + lastAngles = this->angles; + lastScale = this->edict->s.scale; + lastValid = true; + } + else + { + lastScale = this->edict->s.scale; + lastValid = true; + + if ( owner->isSubclassOf( Player ) ) + { + if ( mp_bigGunMode->integer ) + setScale( _weildedScale * 2.0f ); + else + setScale( _weildedScale ); + } + } + /* else if ( lastValid ) + { + + // Restore the last + setScale( lastScale ); + setAngles( lastAngles ); + lastValid = false; + } */ + + + switch( hand ) + { + case WEAPON_LEFT: + if ( holstering ) + { + current_attachToTag = leftholster_attachToTag; + setAngles( leftHolsterAngles ); + setScale( holsterScale ); + } + else + { + current_attachToTag = left_attachToTag; + } + break; + case WEAPON_RIGHT: + case WEAPON_DUAL: + if ( holstering ) + { + current_attachToTag = rightholster_attachToTag; + setAngles( rightHolsterAngles ); + setScale( holsterScale ); + } + else + { + current_attachToTag = right_attachToTag; + } + break; + + default: + warning( "Weapon::AttachGun", "Invalid hand for attachment of weapon specified" ); + break; + } + + if ( !current_attachToTag.length() ) + return; + + tag_num = gi.Tag_NumForName( owner->edict->s.modelindex, this->current_attachToTag.c_str() ); + + NoLerpThisFrame(); + if ( tag_num >= 0 ) + { + attached = true; + + attach( owner->entnum, tag_num, false ); + + showModel(); + setOrigin(); + + if ( owner && owner->isSubclassOf( Actor ) ) + { + Event *newEvent = new Event( EV_DisplayEffect ); + newEvent->AddString( "TransportIn" ); + newEvent->AddString( "FederationWeapons" ); + ProcessEvent( newEvent ); + } + } + else + { + warning( "Weapon::AttachGun", "Attachment of weapon to tag \"%s\": Tag Not Found\n", this->current_attachToTag.c_str() ); + } +} + +//====================== +//Weapon::GiveStartingAmmo +//====================== +void Weapon::GiveStartingAmmo( Event *ev ) +{ + str ammotype; + int mode; + Entity *entityToGiveAmmo; + Sentient *sentient; + + if ( ev->NumArgs() > 0 ) + entityToGiveAmmo = ev->GetEntity( 1 ); + else + entityToGiveAmmo = owner; + + if ( !entityToGiveAmmo || !entityToGiveAmmo->isSubclassOf( Sentient ) ) + { + assert( entityToGiveAmmo ); + + warning( "Weapon::GiveStartingAmmo", "Could not give ammo\n" ); + return; + } + + sentient = reinterpret_cast(entityToGiveAmmo); + + // Give the player the starting ammo + + for ( mode=FIRE_MODE1; modeGiveAmmo( ammotype, this->GetStartAmmo( ( firemode_t )mode ), false ); + } + } +} + +void Weapon::giveAmmoBoost( Event *ev ) +{ + str ammotype; + int mode; + Entity *entityToGiveAmmo; + Sentient *sentient; + + + // Get the sentient to give the ammo boost to + + entityToGiveAmmo = ev->GetEntity( 1 ); + + if ( !entityToGiveAmmo || !entityToGiveAmmo->isSubclassOf( Sentient ) ) + { + assert( entityToGiveAmmo ); + + warning( "Weapon::GiveAmmoBoost", "Could not give ammo\n" ); + return; + } + + sentient = reinterpret_cast(entityToGiveAmmo); + + // Give the sentient the ammo boost + + for ( mode = FIRE_MODE1 ; mode < MAX_FIREMODES ; mode++ ) + { + ammotype = GetAmmoType( ( firemode_t )mode ); + + if ( ammotype.length() ) + { + sentient->GiveAmmo( ammotype, this->getAmmoBoost( ( firemode_t )mode ), true ); + } + } +} + +//====================== +//Weapon::PickupWeapon +//====================== +void Weapon::PickupWeapon( Event *ev ) +{ + Sentient *sen; + Entity *other; + Weapon *weapon; + qboolean hasweapon; + //qboolean giveammo[MAX_FIREMODES]; + //int mode; + + other = ev->GetEntity( 1 ); + + if ( !other ) + return; + + if ( !other->isSubclassOf( Sentient ) ) + { + return; + } + + sen = ( Sentient * )other; + + if ( !Pickupable( other ) ) + return; + + // If this is the last owner, check to see if he can pick it up + if ( ( sen == last_owner ) && ( level.time < last_owner_trigger_time ) ) + { + return; + } + + hasweapon = sen->HasItem( item_name ); + + + if ( hasweapon && !multiplayerManager.checkFlag( MP_FLAG_WEAPONS_STAY ) ) + { + Event *event; + + event = new Event( EV_Weapon_GiveAmmoBoost ); + event->AddEntity( other ); + ProcessEvent( event ); + + ItemPickup( other, false ); + } + else + { + weapon = ( Weapon * )ItemPickup( other ); + + if ( !weapon ) + { + // Item Pickup failed, so don't give ammo either. + return; + } + } + /* else + { + for ( mode = FIRE_MODE1; mode < MAX_FIREMODES; mode++ ) + { + giveammo[mode] = ( sen->isClient() && ammo_type[mode].length() && startammo[mode] ); + + if ( !giveammo[mode] ) + { + return; + } + + sen->GiveAmmo( ammo_type[mode].c_str(), startammo[mode] ); + } + } */ +} + +//====================== +//Weapon::ForceIdle +//====================== +void Weapon::ForceIdle( void ) +{ + // Force the weapon to the idle animation + weaponstate = WEAPON_READY; + + // Clear our fire timer + //INITIALIZE_WEAPONMODE_VAR(next_fire_time, 0.0); + + float r = G_Random(100.0f); + char anim[128]; + + + int animNum = gi.Anim_NumForName( edict->s.modelindex, "idle_onground" ); + if ( multiplayerManager.inMultiplayer() && animNum >= 0 && animate->CurrentAnim() == animNum ) + { + return; + } + + // If this is a switchmode weapon, we have a very different idle system + if ( switchmode ) + { + if ( r < 5.0f ) + { + sprintf(anim,"idle_rare_mode%d",(int)curmode+1); + if ( animate->HasAnim( anim ) ) + { + animate->RandomAnimate( anim ); + return; + } + } + + if ( r < 15.0f ) + { + sprintf(anim,"idle_uncommon_mode%d",(int)curmode+1); + if ( animate->HasAnim( anim ) ) + { + animate->RandomAnimate( anim ); + return; + } + } + + sprintf(anim,"idle_common_mode%d",(int)curmode+1); + if ( animate->HasAnim( anim ) ) + { + animate->RandomAnimate( anim ); + return; + } + + // We shouldn't get here if the anims are set up correctly + // for the switch mode weapon + animate->RandomAnimate("idle"); + return; + } + + if ( r < 5.0f && animate->HasAnim("idle_rare") ) + { + animate->RandomAnimate( "idle_rare" ); + return; + } + + if ( r < 15.0 && animate->HasAnim("idle_uncommon") ) + { + animate->RandomAnimate("idle_uncommon"); + return; + } + + if ( animate->HasAnim("idle_common") ) + { + animate->RandomAnimate( "idle_common" ); + return; + } + + // We shouldn't get here if the anims are set up correctly + animate->RandomAnimate("idle"); +} + +//====================== +//Weapon::DoneRaising +//====================== +void Weapon::DoneRaising( Event *ev ) +{ + Event *event; + + weaponstate = WEAPON_READY; + ForceIdle(); + + if ( !owner ) + { + PostEvent( EV_Remove, 0.0f ); + return; + } + + event = new Event( EV_Weapon_DoneReloading ); + event->AddInteger( FIRE_MODE1 ); + ProcessEvent( event ); + + event = new Event( EV_Weapon_DoneReloading ); + event->AddInteger( FIRE_MODE2 ); + ProcessEvent( event ); + + if ( targetidle ) + PostEvent(EV_Weapon_TargetIdleThink, TargetIdleTime); +} + +//---------------------------------------------------------------- +// Name: playAnim +// Class: Weapon +// +// Description: Plays the specified animation and marks its weapon state as animating +// +// Parameters: const str &animName - name of the animation to play +// bool animatingFlag - defaults to true +// +// Returns: none +//---------------------------------------------------------------- +void Weapon::playAnim( const str &animName, bool animatingFlag ) +{ + if ( animate->HasAnim( animName.c_str() ) ) + { + if ( animatingFlag ) + weaponstate = WEAPON_ANIMATING; + + SetAnim( animName, EV_Weapon_DoneAnimating ); + } +} + + + +//=============================================================== +// Name: animName +// Class: Weapon +// +// Description: Sets the weapons animation. +// +// Parameters: const str& -- +// +// Returns: None +// +//=============================================================== +void Weapon::SetAnim( const str &animName, Event *endevent, bodypart_t bodypart ) +{ + assert( animate ); + animate->RandomAnimate( animName.c_str(), endevent, bodypart ); + AddEffectsAnims(); +} + + +//=============================================================== +// Name: animName +// Class: Weapon +// +// Description: Sets the weapons animation. +// +// Parameters: const str& -- +// +// Returns: None +// +//=============================================================== +void Weapon::SetAnim( const str &animName, const Event &endevent, bodypart_t bodypart ) +{ + assert( animate ); + animate->RandomAnimate( animName.c_str(), endevent, bodypart ); + AddEffectsAnims(); +} + +//=============================================================== +// Name: PassToAnimate +// Class: Weapon +// +// Description: Passes the event to the animation object, and gives +// the weapon a chance to add additional special effects. +// If the animation object doesn't exist, creates it. +// +// Parameters: Event* -- an animation related event. +// +// Returns: None +// +//=============================================================== +void Weapon::PassToAnimate( Event *ev ) +{ + if ( !animate ) + { + animate = new Animate( this ); + } + Event *new_event = new Event( ev ); + animate->ProcessEvent( new_event ); + AddEffectsAnims(); +} + +//=============================================================== +// Name: AddEffectsAnims +// Class: Weapon +// +// Description: Adds the effect anims for a weapon. +// +// Parameters: None +// +// Returns: None +// +//=============================================================== +void Weapon::AddEffectsAnims( void ) +{ +} + +//====================== +//Weapon::DoneAnimating +//====================== +void Weapon::DoneAnimating( Event *ev ) +{ + weaponstate = WEAPON_READY; + + if ( !owner ) + PostEvent( EV_Remove, 0.0f ); + + return; +} + +//====================== +//Weapon::ClientFireDone +//====================== +void Weapon::ClientFireDone( void ) +{ + // This is called when the client's firing animation is done +} + +//====================== +//Weapon::DoneFiring +//====================== +void Weapon::DoneFiring( Event *ev ) +{ + /* + firemode_t mode = (firemode_t)ev->GetInteger( 1 ); + + + if ( fullanimfire[mode] ) + { + donefiring = true; + return; + } + */ + + if ( shootingSkin ) + ChangeSkin( shootingSkin, false ); + + // This is called when the weapon's firing animation is done + // If this weapon has a fire_stop animation, play it (tiki will force us back to idle) + if ( animate->HasAnim("fire_stop") ) + animate->RandomAnimate("fire_stop"); + else + ForceIdle(); + + // Check to see if the auto_putaway flag is set, and the weapon is out of ammo. If so, then putaway the + // weapon. + if ( + ( !HasAmmo( FIRE_MODE1 ) || ( !stricmp( ammo_type[ FIRE_MODE1 ], "None" ) ) ) && + ( !HasAmmo( FIRE_MODE2 ) || ( !stricmp( ammo_type[ FIRE_MODE2 ], "None" ) ) ) && + auto_putaway + ) + { + PutAway(); + } +} + +//====================== +//Weapon::DoneReloading +//====================== +void Weapon::DoneReloading( Event *ev ) +{ + int amount; + int amount_used; + firemode_t mode; + firemode_t clipToUse; + + // Get the mode from the passed in event + mode = (firemode_t)ev->GetInteger( 1 ); + + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( !( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) ) + warning( "Weapon::DoneReloading", "Invalid mode %d\n", mode ); + + /* if ( regenAmount > 0 ) + { + int tmpamount = 0; + Ammo *ammo; + ammo = owner->FindAmmoByName( ammo_type[mode] ); + if ( ammo ) + { + tmpamount = (ammo->getMaxAmount() / 2) - owner->AmmoCount( ammo_type[mode] ); + owner->GiveAmmo( ammo_type[ mode ], tmpamount); + ForceIdle(); + return; + } + } */ + + // If we need to do a REAL reload (not a burst reload) we skip the test + // altogether. + /* firemode_t realmode = mode; + if ( usesameclip ) + mode = FIRE_MODE1; + if ( !(ammo_clip_size[mode] && !ammo_in_clip[mode] && AmmoAvailable(mode)) ) + { + int tmp = 0; + if ( switchmode ) + tmp = burstmode[curmode]; + else + tmp = burstmode[realmode]; + + if ( tmp ) + { + int tmpamount = 0; + Ammo *ammo; + ammo = owner->FindAmmoByName( ammo_type[mode] ); + if ( ammo ) + { + tmpamount = owner->AmmoCount( ammo_type[mode] ); + + // Try the clip ammo if we have none in our main inventory + if ( tmpamount == 0 ) + tmpamount = ammo_in_clip[mode]; + + if ( tmpamount > burstcountmax ) + burstcount = burstcountmax; + else + burstcount = tmpamount; + + // We're out of ammo + if ( burstcount == 0 ) + burstcount = -1; + + ForceIdle(); + return; + } + } + } */ + + if ( usesameclip ) + clipToUse = FIRE_MODE1; + else + clipToUse = mode; + + // Calc the amount the clip should get + amount = ammo_clip_size[clipToUse] - ammo_in_clip[clipToUse]; + + assert( owner ); + if ( owner && owner->isClient() && !UnlimitedAmmo( mode ) ) + { + // use up the ammo from the player + amount_used = owner->UseAmmo( ammo_type[mode], amount ); + + // Stick it in the clip + if ( ammo_clip_size[clipToUse] ) + ammo_in_clip[clipToUse] = amount_used + ammo_in_clip[clipToUse]; + + assert( ammo_in_clip[clipToUse] <= ammo_clip_size[clipToUse] ); + if ( ammo_in_clip[clipToUse] > ammo_clip_size[clipToUse] ) + ammo_in_clip[clipToUse] = ammo_clip_size[clipToUse]; + + if ( burstmode[curmode] && ( ammo_in_clip[clipToUse] >= burstcountmax ) ) + burstcount = burstcountmax; + } + owner->AmmoAmountInClipChanged( ammo_type[mode], ammo_in_clip[clipToUse] ); + + ForceIdle(); +} + +void Weapon::DoneReloadingBurst( Event *ev ) +{ + //int amount; + //int amount_used; + firemode_t mode; + int inBurstMode; + firemode_t clipToUse; + + // Get the mode from the passed in event + mode = (firemode_t)ev->GetInteger( 1 ); + + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( !( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) ) + warning( "Weapon::DoneReloading", "Invalid mode %d\n", mode ); + + // If we need to do a REAL reload (not a burst reload) we skip the test + // altogether. + firemode_t realmode = mode; + //if ( usesameclip ) + // mode = FIRE_MODE1; + + // Make sure we are actually in burst mode + + inBurstMode = false; + + if ( switchmode ) + inBurstMode = burstmode[curmode]; + else + inBurstMode = burstmode[realmode]; + + if ( !inBurstMode ) + return; + + // Add to the burst count + + int tmpamount = 0; + Ammo *ammo; + + ammo = owner->FindAmmoByName( ammo_type[mode] ); + + if ( ammo ) + { + tmpamount = owner->AmmoCount( ammo_type[mode] ); + + // Try the clip ammo if we have none in our main inventory + + if ( usesameclip ) + clipToUse = FIRE_MODE1; + else + clipToUse = mode; + + if ( tmpamount == 0 ) + tmpamount = ammo_in_clip[clipToUse]; + + if ( tmpamount > burstcountmax ) + burstcount = burstcountmax; + else + burstcount = tmpamount; + + // We're out of ammo + + if ( burstcount == 0 ) + burstcount = -1; + + ForceIdle(); + return; + } +} + +qboolean Weapon::CheckReload( void ) +{ + return CheckReload( curmode ); +} + +//====================== +//Weapon::CheckReload - Checks to see if we NEED to reload the weapon +//====================== +qboolean Weapon::CheckReload( firemode_t mode ) +{ + int inBurstMode; + firemode_t clipToUse; + + // Check to see if the weapon needs to be reloaded + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( !( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) ) + { + warning( "Weapon::CheckReload", "Invalid mode %d\n", mode ); + return false; + } + + if ( !_autoReload ) + { + return false; + } + + if ( UnlimitedAmmo( mode ) ) + { + return false; + } + + if ( putaway ) + { + return false; + } + + if ( weaponstate != WEAPON_READY ) + return false; + + // If we're a burst mode weapon, see if we need to "reload" + + inBurstMode = false; + + if ( switchmode ) + inBurstMode = burstmode[curmode]; + else + inBurstMode = burstmode[mode]; + + firemode_t oldmode = mode; + + if ( inBurstMode && ( weaponstate != WEAPON_RELOADING ) && ( burstcount <= 0 ) ) + { + Event *doneReloadingEvent; + doneReloadingEvent = new Event( EV_Weapon_DoneReloadingBurst ); + doneReloadingEvent->AddInteger( oldmode ); + + if ( _burstModeDelay[ mode ] > 0.0f ) + { + animate->RandomAnimate( "reload_burst" ); + PostEvent( doneReloadingEvent, _burstModeDelay[ mode ] ); + } + else + { + animate->RandomAnimate( "reload_burst", doneReloadingEvent ); + } + + weaponstate = WEAPON_RELOADING; + return true; + } + + mode = oldmode; + + if ( usesameclip ) + clipToUse = FIRE_MODE1; + else + clipToUse = mode; + + if ( ammo_clip_size[clipToUse] && ( ammo_in_clip[clipToUse] < ammorequired[mode] ) && ( AmmoAvailable( mode ) >= ammorequired[mode] ) ) + { + Event *ev1; + + ev1 = new Event( EV_Weapon_DoneReloading ); + ev1->AddInteger( mode ); + + if ( animate->HasAnim( "reload_normal" ) && !multiplayerManager.skipWeaponReloads() ) + { + weaponstate = WEAPON_RELOADING; + animate->RandomAnimate( "reload_normal", ev1 ); + } + else + { + ProcessEvent( ev1 ); + } + return true; + } + + // We're a regenerating weapon out of ammo + /* if ( !AmmoAvailable(mode) && ( regenAmount > 0 ) ) + { + Event *ev1; + + ev1 = new Event( EV_Weapon_DoneReloading ); + ev1->AddInteger( mode ); + + if ( animate->HasAnim( "idle_special" ) ) + { + weaponstate = WEAPON_RELOADING; + animate->RandomAnimate( "idle_special", ev1 ); + } + else + { + ProcessEvent( ev1 ); + } + return true; + } */ + + return false; +} + +//====================== +//Weapon::ForceReload - Reload the clip if it's not full already +//====================== +qboolean Weapon::ForceReload( void ) +{ + // Only primary mode can reload + firemode_t mode = FIRE_MODE1; + firemode_t clipToUse; + + // Weapon has to be in the ready state + + if ( !canReload() ) + return false; + + // Make sure it's not put away + if ( putaway ) + return false; + + // We can't reload regenerating weapons + if ( _regenAmount[ FIRE_MODE1 ] > 0 ) + return false; + + if ( usesameclip ) + clipToUse = FIRE_MODE1; + else + clipToUse = mode; + + // If our clip is not full and we have ammo available... + // HACK: targetidle is currently only used on the TOW weapon, + // so instead of making an entirely new event JUST for this, we use it here. + if ( ( ( ammo_clip_size[clipToUse] != ammo_in_clip[clipToUse] ) && AmmoAvailable( mode ) ) || targetidle) + { + Event *ev1; + + ev1 = new Event( EV_Weapon_DoneReloading ); + ev1->AddInteger( mode ); + + if ( animate->HasAnim( "reload_normal" ) ) + { + weaponstate = WEAPON_RELOADING; + animate->RandomAnimate( "reload_normal", ev1 ); + } + else + { + ProcessEvent( ev1 ); + } + return true; + } + + return false; +} + +//====================== +//Weapon::Idle +//====================== +void Weapon::Idle( Event *ev ) +{ + ForceIdle(); +} + +//====================== +//Weapon::GetMaxRange +//====================== +float Weapon::GetMaxRange( void ) +{ + return maxrange; +} + +//====================== +//Weapon::GetMinRange +//====================== +float Weapon::GetMinRange( void ) +{ + return minrange; +} + +//====================== +//Weapon::SetMaxRangeEvent +//====================== +void Weapon::SetMaxRangeEvent( Event *ev ) +{ + maxrange = ev->GetFloat( 1 ); +} + +//====================== +//Weapon::SetMinRangeEvent +//====================== +void Weapon::SetMinRangeEvent( Event *ev ) +{ + minrange = ev->GetFloat( 1 ); +} + +//====================== +//Weapon::NotDroppableEvent +//====================== +void Weapon::NotDroppableEvent( Event *ev ) +{ + notdroppable = true; +} + +//====================== +//Weapon::SetMaxRange +//====================== +void Weapon::SetMaxRange( float val ) +{ + maxrange = val; +} + +//====================== +//Weapon::SetMinRange +//====================== +void Weapon::SetMinRange( float val ) +{ + minrange = val; +} + +//====================== +//Weapon::WeaponSound +//====================== +void Weapon::WeaponSound( Event *ev ) +{ + Event *e; + + // Broadcasting a sound can be time consuming. Only do it once in a while on really fast guns. + if ( nextweaponsoundtime > level.time ) + { + if ( owner ) + { + owner->BroadcastSound(SOUND_RADIUS, SOUNDTYPE_WEAPONFIRE);; + } + else + { + BroadcastSound(SOUND_RADIUS, SOUNDTYPE_WEAPONFIRE);; + } + return; + } + + if ( owner ) + { + e = new Event( ev ); + owner->ProcessEvent( e ); + } + else + { + Item::BroadcastSound(); + } + + // give us some breathing room + nextweaponsoundtime = level.time + 0.4f; +} + +//====================== +//Weapon::Removable +//====================== +qboolean Weapon::Removable( void ) +{ + if ( multiplayerManager.checkFlag( MP_FLAG_WEAPONS_STAY ) && Respawnable() ) + return false; + else + return true; +} + +//====================== +//Weapon::Pickupable +//====================== +qboolean Weapon::Pickupable( Entity *other ) +{ + Sentient *sen; + + if ( !other->isSubclassOf( Sentient ) ) + { + return false; + } + else if ( !other->isClient() ) + { + return false; + } + + if ( getSolidType() == SOLID_NOT ) + { + return NULL; + } + + if ( multiplayerManager.inMultiplayer() && other->isSubclassOf( Player ) ) + { + if ( !multiplayerManager.canPickup( (Player *)other, getMultiplayerItemType(), getName().c_str() ) ) + return false; + } + + sen = ( Sentient * )other; + + //FIXME + // This should be in player + + // If we have the weapon and weapons stay, then don't pick it up + if ( ( multiplayerManager.checkFlag( MP_FLAG_WEAPONS_STAY ) ) && Respawnable() ) + { + Weapon *weapon; + + weapon = ( Weapon * )sen->FindItem( getName() ); + + if ( weapon ) + return false; + } + + return true; +} + +//====================== +//Weapon::AutoChange +//====================== +qboolean Weapon::AutoChange( void ) +{ + return true; +} + +//====================== +//Weapon::ClipAmmo +//====================== +int Weapon::ClipAmmo( firemode_t mode ) +{ + firemode_t clipToUse; + + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( !( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) ) + warning( "Weapon::ClipAmmo", "Invalid mode %d\n", mode ); + + if ( usesameclip ) + clipToUse = FIRE_MODE1; + else + clipToUse = mode; + + if (ammo_clip_size[clipToUse]) + return ammo_in_clip[clipToUse]; + else + return -1; +} + +//====================== +//Weapon::ProcessWeaponCommandsEvent +//====================== +void Weapon::ProcessWeaponCommandsEvent( Event *ev ) +{ + int index; + + index = ev->GetInteger( 1 ); + ProcessInitCommands( index ); +} + +//====================== +//Weapon::SetActionLevelIncrement +//====================== +void Weapon::SetActionLevelIncrement( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + action_level_increment[firemodeindex] = ev->GetInteger( 1 ); +} + +//====================== +//Weapon::ActionLevelIncrement +//====================== +int Weapon::ActionLevelIncrement( firemode_t mode ) +{ + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) + return action_level_increment[mode]; + else + { + warning( "Weapon::ActionLevelIncrement", "Invalid mode %d\n", mode ); + return 0; + } +} + +//====================== +//Weapon::IsDroppable +//====================== +qboolean Weapon::IsDroppable( void ) +{ + if ( notdroppable ) + { + return false; + } + else + { + return true; + } +} + +//====================== +//Weapon::SetFireType +//====================== +void Weapon::SetFireType( Event *ev ) +{ + str ftype; + + ftype = ev->GetString( 1 ); + + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + + if ( !ftype.icmp( "projectile" ) ) + firetype[firemodeindex] = FT_PROJECTILE; + else if ( !ftype.icmp( "bullet" ) ) + firetype[firemodeindex] = FT_BULLET; + else if ( !ftype.icmp( "melee" ) ) + firetype[firemodeindex] = FT_MELEE; + else if ( !ftype.icmp( "special_projectile" ) ) + firetype[firemodeindex] = FT_SPECIAL_PROJECTILE; + else if ( !ftype.icmp( "none" ) ) + firetype[firemodeindex] = FT_NONE; + else if ( !ftype.icmp( "explosion" ) ) + firetype[firemodeindex] = FT_EXPLOSION; + else if ( !ftype.icmp( "triggerProjectile" ) ) + firetype[firemodeindex] = FT_TRIGGER_PROJECTILE; + else if ( !ftype.icmp( "controlProjectile" ) ) + firetype[firemodeindex] = FT_CONTROL_PROJECTILE; + else if ( !ftype.icmp( "controlZoom" ) ) + firetype[firemodeindex] = FT_CONTROL_ZOOM; + else + warning( "Weapon::SetFireType", "unknown firetype: %s\n", ftype.c_str() ); +} + +//====================== +//Weapon::GetFireType +//====================== +firetype_t Weapon::GetFireType( firemode_t mode ) +{ + return firetype[mode]; +} + +//====================== +//Weapon::SetProjectile +//====================== +void Weapon::SetProjectile( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + projectileModel[firemodeindex] = ev->GetString( 1 ); + CacheResource( projectileModel[firemodeindex].c_str(), this ); +} + +//====================== +//Weapon::SetBulletDamage +//====================== +void Weapon::SetBulletDamage( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + bulletdamage[firemodeindex] = ev->GetFloat( 1 ); +} + +//====================== +//Weapon::SetBulletKnockback +//====================== +void Weapon::SetBulletKnockback( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + bulletknockback[firemodeindex] = ev->GetFloat( 1 ); +} + +//====================== +//Weapon::SetBulletRange +//====================== +void Weapon::SetBulletRange( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + bulletrange[firemodeindex] = ev->GetFloat( 1 ); +} + +//====================== +//Weapon::SetRange +//====================== +void Weapon::SetRange( Event *ev ) +{ + SetBulletRange( ev ); +} + +//====================== +//Weapon::SetBulletCount +//====================== +void Weapon::SetBulletCount( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + bulletcount[firemodeindex] = ev->GetFloat( 1 ); +} + +void Weapon::SetBulletSpread( float spreadX , float spreadY ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + bulletspread[firemodeindex].x = spreadX; + bulletspread[firemodeindex].y = spreadY; +} + +//====================== +//Weapon::SetBulletSpread +//====================== +void Weapon::SetBulletSpread( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + bulletspread[firemodeindex].x = ev->GetFloat( 1 ); + if (ev->NumArgs() < 2) // this shouldn't happen, but there are assets that do it + bulletspread[firemodeindex].y = bulletspread[firemodeindex].x; + else + bulletspread[firemodeindex].y = ev->GetFloat( 2 ); + if (ev->NumArgs() == 5) // expanding/shrinking spread + { + endbulletspread[firemodeindex].x = ev->GetFloat(3); + endbulletspread[firemodeindex].y = ev->GetFloat(4); + endbulletspread[firemodeindex].z = ev->GetFloat(5); // time interval + } + else + { + endbulletspread[firemodeindex].z = 0; // make sure spread is constant if it's not variable (duh) + } +} + +//====================== +//Weapon::SetHand +//====================== +void Weapon::SetHand( Event *ev ) +{ + str side; + + side = ev->GetString( 1 ); + + if ( !stricmp( side.c_str(), "righthand" ) || !stricmp( side.c_str(), "right" ) ) + hand = WEAPON_RIGHT; + else if ( !stricmp( side.c_str(), "lefthand" ) || !stricmp( side.c_str(), "left" ) ) + hand = WEAPON_LEFT; + else if ( !stricmp( side.c_str(), "dualhand" ) || !stricmp( side.c_str(), "dual" ) ) + hand = WEAPON_DUAL; + else if ( !stricmp( side.c_str(), "any" ) ) + hand = WEAPON_ANY; + else + { + warning( "Weapon::SetHand", "Unknown side %s\n", side.c_str() ); + assert( 0 ); + } +} + +//====================== +//Weapon::ModeSet +//====================== +void Weapon::ModeSet( Event *ev ) +{ + int i; + str modestr; + + Event *modeev = new Event( ev->GetToken( 2 ) ); + + modestr = ev->GetToken( 1 ); + + firemodeindex = WeaponModeNameToNum(modestr); + + for( i=3; i<=ev->NumArgs(); i++ ) + { + modeev->AddToken( ev->GetToken( i ) ); + } + + ProcessEvent( modeev ); + firemodeindex = FIRE_MODE1; +} + +//==================== +//Weapon::AutoAim +//==================== +void Weapon::AutoAim( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + { + autoAimTargetSelectionAngle = ev->GetInteger( 1 ); + autoAimLockonAngle = ev->GetInteger( 2 ); + } + else + { + autoAimTargetSelectionAngle = 60; + autoAimLockonAngle = 15; + } +} + +//==================== +//Weapon::Crosshair +//==================== +void Weapon::Crosshair( Event *ev ) +{ + crosshair = ev->GetBoolean( 1 ); +} + +//==================== +//Weapon::TorsoAim +//==================== +void Weapon::TorsoAim( Event *ev ) +{ + torsoaim = ev->GetBoolean( 1 ); +} + +//==================== +//Weapon::LeftAttachToTag +//==================== +void Weapon::LeftAttachToTag( Event *ev ) +{ + left_attachToTag = ev->GetString( 1 ); +} + +//==================== +//Weapon::RightAttachToTag +//==================== +void Weapon::RightAttachToTag( Event *ev ) +{ + right_attachToTag = ev->GetString( 1 ); +} + +//==================== +//Weapon::DualAttachToTag +//==================== +void Weapon::DualAttachToTag( Event *ev ) +{ + dual_attachToTag = ev->GetString( 1 ); +} + +//==================== +//Weapon::LeftHolsterAttachToTag +//==================== +void Weapon::LeftHolsterAttachToTag( Event *ev ) +{ + leftholster_attachToTag = ev->GetString( 1 ); +} + +//==================== +//Weapon::RightHolsterAttachToTag +//==================== +void Weapon::RightHolsterAttachToTag( Event *ev ) +{ + rightholster_attachToTag = ev->GetString( 1 ); +} + +//==================== +//Weapon::DualHolsterAttachToTag +//==================== +void Weapon::DualHolsterAttachToTag( Event *ev ) +{ + dualholster_attachToTag = ev->GetString( 1 ); +} + +//==================== +//Weapon::SetLeftHolsterAngles +//==================== +void Weapon::SetLeftHolsterAngles( Event *ev ) +{ + leftHolsterAngles = ev->GetVector( 1 ); +} + +//==================== +//Weapon::SetRightHolsterAngles +//==================== +void Weapon::SetRightHolsterAngles( Event *ev ) +{ + rightHolsterAngles = ev->GetVector( 1 ); +} + +//==================== +//Weapon::SetDualHolsterAngles +//==================== +void Weapon::SetDualHolsterAngles( Event *ev ) +{ + dualHolsterAngles = ev->GetVector( 1 ); +} + +//==================== +//Weapon::SetHolsterScale +//==================== +void Weapon::SetHolsterScale( Event *ev ) +{ + holsterScale = ev->GetFloat( 1 ); +} + +void Weapon::setWeildedScale( Event *ev ) +{ + _weildedScale = ev->GetFloat( 1 ); +} + +//==================== +//Weapon::SetQuiet +//==================== +void Weapon::SetQuiet( Event *ev ) +{ + quiet = true; +} + +//==================== +//Weapon::SetLoopFire +//==================== +void Weapon::SetLoopFire( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + loopfire[firemodeindex] = true; +} +/* +void Weapon::SetFullAnimFire( Event *ev ) + { + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + fullanimfire[firemodeindex] = true; + } +*/ + +//====================== +//Weapon::SetMeansOfDeath +//====================== +void Weapon::SetMeansOfDeath( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + meansofdeath[firemodeindex] = (meansOfDeath_t )MOD_NameToNum( ev->GetString( 1 ) ); +} + +//====================== +//Weapon::GetMeansOfDeath +//====================== +meansOfDeath_t Weapon::GetMeansOfDeath( firemode_t mode ) +{ + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ) + return meansofdeath[mode]; + else + { + warning( "Weapon::GetMeansOfDeath", "Invalid mode %d\n", mode ); + return MOD_NONE; + } +} + +//====================== +//Weapon::SetAimTarget +//====================== +void Weapon::SetAimTarget( Entity *ent ) +{ + aim_target = ent; +} + +//====================== +//Weapon::WorldHitSpawn +//====================== +void Weapon::WorldHitSpawn( firemode_t mode, const Vector &origin, const Vector &angles, float life ) +{ + if ( !worldhitspawn[mode].length() ) + return; + + Entity::SpawnEffect(worldhitspawn[mode], origin, angles, life); +} + +//====================== +//Weapon::SetWorldHitSpawn +//====================== +void Weapon::SetWorldHitSpawn( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + worldhitspawn[firemodeindex] = ev->GetString( 1 ); +} + +void Weapon::MakeNoise( Event *ev ) +{ + float radius = 500.0f; + qboolean force = false; + + if ( ev->NumArgs() > 0 ) + radius = ev->GetFloat( 1 ); + + if ( ev->NumArgs() > 1 ) + force = ev->GetBoolean( 2 ); + + if ( attached && ( next_noise_time <= level.time || force ) ) + { + BroadcastSound( radius , SOUNDTYPE_WEAPONFIRE ); + next_noise_time = level.time + 1.0f; + } +} + +void Weapon::SetViewModel( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + { + viewmodel = ev->GetString( 1 ); + + gi.setviewmodel( edict, viewmodel ); + //setViewModel( viewmodel ); + } +} + +void Weapon::DonePutaway( Event *ev ) +{ + //int i; + + // Tell the state machine we're lowering the weapon + // so it will continue to do it's normal deactivate code + weaponstate = WEAPON_LOWERING; + + if ( zoomed ) + { + CancelEventsOfType( EV_Weapon_Zoom ); + ProcessEvent( EV_Weapon_Zoom ); + } + + if ( _controllingProjectile ) + { + toggleProjectileControl(); + } + + // No longer targeting anyone + + //SetTargetedEntity( NULL ); + + if ( targetidle ) + CancelEventsOfType( EV_Weapon_TargetIdleThink ); + + // Move ammo from weapon back to owner if possible + + /* if ( owner ) + { + for ( i = 0 ; i < MAX_FIREMODES ; i++ ) + { + if ( ammo_in_clip[ i ] ) + { + int maxAmount; + int amount; + + maxAmount = owner->MaxAmmoCount( ammo_type[ i ] ) - owner->AmmoCount( ammo_type[ i ] ); + + if ( maxAmount > ammo_in_clip[ i ] ) + amount = ammo_in_clip[ i ]; + else + amount = maxAmount; + + owner->GiveAmmo( ammo_type[ i ], amount, false ); + ammo_in_clip[ i ] -= amount; + owner->AmmoAmountInClipChanged( ammo_type[ i ], ammo_in_clip[ i ] ); + } + } + } */ +} + +void Weapon::SetRegenAmmo( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + { + _regenAmount[ firemodeindex ] = ev->GetInteger( 1 ); + + _regenTime[ firemodeindex ] = 1.0; // default time + } + + if ( ev->NumArgs() > 1 ) + { + _regenTime[ firemodeindex ] = ev->GetFloat( 2 ); + } + + turnThinkOn(); +} + +//---------------------------------------------------------------- +// Name: SetRegenOnlyWhenIdle +// Class: Weapon +// +// Description: Specifies that this weapon only regenerates ammo when it is idle (not shooting) +// +// Parameters: Event *ev +// +// Returns: none +//---------------------------------------------------------------- + +void Weapon::SetRegenOnlyWhenIdle( Event *ev ) +{ + _regenOnlyWhenIdle[ firemodeindex ] = true; +} + +void Weapon::ChangeIdle( Event *ev ) +{ + ForceIdle(); + + if ( !owner ) + { + return; + } +} + +void Weapon::DrawBowStrain( Event *ev ) +{ + if ( animate->HasAnim( "draw_strain" ) ) + animate->RandomAnimate( "draw_strain" ); +} + +void Weapon::AltDrawBowStrain( Event *ev ) +{ + if ( animate->HasAnim( "alternate_draw_strain" ) ) + animate->RandomAnimate( "alternate_draw_strain" ); +} + +void Weapon::SetAccuracy( Event *ev ) +{ + if ( ev->NumArgs() < 6 ) + { + gi.DPrintf("SetAccuracy: Too few parameters\n"); + return; + } + firemode_t mode = WeaponModeNameToNum( ev->GetToken( 1 ) ); + accuracy[mode][ACCURACY_STOPPED] = ev->GetFloat( 2 ); + accuracy[mode][ACCURACY_CHANGE] = ev->GetFloat( 3 ); + accuracy[mode][ACCURACY_WALK] = ev->GetFloat( 4 ); + accuracy[mode][ACCURACY_RUN] = ev->GetFloat( 5 ); + accuracy[mode][ACCURACY_CROUCH] = ev->GetFloat( 6 ); +} + +void Weapon::SetZoomFOV( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + { + zoomfov = ev->GetFloat( 1 ); + } +} + +void Weapon::SetReticuleTime( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + reticuletime = ev->GetFloat( 1 ); +} + +void Weapon::SetStartZoom( Event* ev ) +{ + if(ev == 0) + return; + + startzoom = ev->GetFloat(1); +} + +void Weapon::SetEndZoom( Event* ev ) +{ + if(ev == 0) + return; + + endzoom = ev->GetFloat(1); +} + +void Weapon::SetZoomTime( Event* ev ) +{ + if(ev == 0) + return; + + zoomtime = ev->GetFloat(1); +} + +void Weapon::SetAimType( Event *ev ) +{ + + str aimstr; + + Sentient *owner; + Player *player = NULL; + owner = this->owner; + assert( owner ); + if ( owner->isSubclassOf( Player ) ) + player = ( Player * )owner; + + if ( ev->NumArgs() > 0 ) + aimstr = ev->GetString( 1 ); + + // Crouching is a special case... you'll always have the "crouch" accuracy + // rating in your crouched, regardless of whether you're walking or not. + if ( player && player->GetCrouch() || ( aimstr == "crouch" ) ) + aimtype = ACCURACY_CROUCH; + else + { + if ( aimstr == "stopped" ) + aimtype = ACCURACY_STOPPED; + if ( aimstr == "walk" ) + aimtype = ACCURACY_WALK; + if ( aimstr == "run" ) + aimtype = ACCURACY_RUN; + if ( aimstr == "change" ) + aimtype = ACCURACY_CHANGE; + } + + if ( ( aimstr != "stopped" ) && ( aimstr != "walk" ) && ( aimstr != "run" ) && ( aimstr != "crouch" ) && ( aimstr != "change" ) ) + { + warning("SetAimType: Invalid aimtype, %s, defaulting to stopped",aimstr); + aimtype = ACCURACY_STOPPED; + } + + if ( g_showaccuracymod->integer ) + gi.DPrintf("Accuracy Mod: %s = %f\n", aimstr.c_str(), accuracy[curmode][aimtype] ); +} + +void Weapon::SetFireTimer( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + + fire_timer[firemodeindex] = ev->GetFloat( 1 ); +} + +void Weapon::UseSameClip( Event *ev ) +{ + usesameclip = true; +} + +qboolean Weapon::HasFullClip( void ) +{ + firemode_t mode = FIRE_MODE1; + firemode_t clipToUse; + + if ( usesameclip ) + clipToUse = FIRE_MODE1; + else + clipToUse = mode; + + if ( ammo_in_clip[clipToUse] == ammo_clip_size[clipToUse] ) + return true; + + return false; +} + +void Weapon::SetMaxModes( Event *ev ) +{ + if ( ev->NumArgs() < 1 ) + { + maxmode = FIRE_MODE2; + return; + } + + maxmode = WeaponModeNameToNum( ev->GetToken( 1 ) ); +} + +void Weapon::SetSwitchMode( Event *ev ) +{ + switchmode = true; +} + +// Target Idle Weapon functions +void Weapon::TargetIdle( Event *ev ) +{ + targetidle = true; +} + +void Weapon::TargetIdleThink( Event *ev ) +{ + CancelEventsOfType( EV_Weapon_TargetIdleThink ); + + // HACK sortof: If the weapon is playing it's put away animation, + // we don't do this event. For some reason a CancelEventsOfType + // call in the Putaway function failed to cancel this event. + if ( animate->GetName() == "putaway" ) + return; + + PostEvent( EV_Weapon_TargetIdleThink, TargetIdleTime ); + + Sentient *owner; + Player *player; + owner = this->owner; + assert( owner ); + + if ( owner->isSubclassOf( Player ) ) + { + player = ( Player * )owner; + Vector pos, forward, right, up, endpoint, vorg; + GetMuzzlePosition( &pos, &forward, &right, &up ); + vorg = player->origin; + vorg.z += player->viewheight; + + endpoint = vorg + (forward * 4000.0f); + trace_t trace = G_Trace( pos, vec_zero, vec_zero, endpoint, player, MASK_SHOT, true, "Weapon::TargetIdleThink" ); + if ( trace.ent && ( trace.entityNum != ENTITYNUM_WORLD ) && trace.ent->entity->isSubclassOf( Sentient )) + { + if ( !trace.ent->entity->deadflag ) + { + if ( !strstr(animate->GetName(), "idle_target") ) + animate->RandomAnimate( "idle_target" ); + targetidleflag = true; + } + return; + } + else + { + if ( targetidleflag ) + { + targetidleflag = false; + ForceIdle(); + } + } + + } +} +// **************************** + +// Weapon Mode switching functions +void Weapon::SwitchMode( void ) +{ + char anim[128]; + + Event *ev1; + ev1 = new Event( EV_Weapon_DoneSwitchToMiddle ); + + strcpy(anim,""); + sprintf(anim,"switch_mode%d_neutral",(int)curmode+1); + + if ( animate->HasAnim( anim ) ) + { + weaponstate = WEAPON_SWITCHINGMODE; + animate->RandomAnimate( anim, ev1 ); + } + else + { + ProcessEvent( ev1 ); + } +} + +void Weapon::DoneSwitchToMiddle( Event *ev ) +{ + int mode; + char anim[128]; + + Event *ev1; + ev1 = new Event( EV_Weapon_DoneSwitching ); + + if ( curmode == maxmode ) + mode = FIRE_MODE1; + else + mode = (int)curmode + 1; + + strcpy(anim,""); + sprintf(anim,"switch_mode%d",mode); + + if ( animate->HasAnim( anim ) ) + { + weaponstate = WEAPON_SWITCHINGMODE; + animate->RandomAnimate( anim, ev1 ); + } + else + { + ProcessEvent( ev1 ); + } +} + +void Weapon::DoneSwitching( Event *ev ) +{ + if ( curmode == maxmode ) + curmode = FIRE_MODE1; + else + curmode = (firemode_t)((int)curmode + 1); + + weaponstate = WEAPON_READY; + + // Clear our fire timer + INITIALIZE_WEAPONMODE_VAR(next_fire_time, 0.0); + + ForceIdle(); +} +// ************* + +void Weapon::SetBurstMode( Event *ev ) +{ + burstmode[firemodeindex] = true; + if ( ev->NumArgs() > 0 ) + burstcountmax = ev->GetInteger( 1 ); + else + burstcountmax = 10; // Default to 10 + + burstcount = burstcountmax; +} + +void Weapon::setBurstModeDelay( Event *ev ) +{ + _burstModeDelay[ firemodeindex ] = ev->GetFloat( 1 ); +} + +void Weapon::SetCHOffset(int chx, int chy) +{ + this->chx = chx; + this->chy = chy; +} + + + +// GetTargetedEntity +// +// Returns the entity that is targeted by the weapon. +// If there is not an entity, 0 is returned. +void Weapon::CheckForTargetedEntity( void ) +{ + +} + +void Weapon::SetRealViewOrigin( const Vector &rv ) +{ + realvieworg = rv; +} + +void Weapon::SetThirdPerson( qboolean tp ) +{ + thirdperson = tp; +} + +qboolean Weapon::IsDoneFiring() +{ + return donefiring; +} + +//---------------------------------------------------------------- +// Name: StartFiring +// Class: Weapon +// +// Description: Sets the start time used for bullet spread (for repeating weapons, this should be set in a prefire state) +// +// Parameters: Event *ev +// +// Returns: none +//---------------------------------------------------------------- +void Weapon::StartFiring( Event *ev ) +{ + startfiretime = level.time; +} + +void Weapon::FinishedFiring( Event *ev ) +{ + int oldDoneFiring; + + oldDoneFiring = donefiring; + + donefiring = ev->GetInteger( 1 ); + + if ( shootingSkin ) + ChangeSkin( shootingSkin, false ); + + if ( donefiring && !oldDoneFiring ) + { + CheckReload(); + } +} + +void Weapon::Zoom( Event *ev ) +{ + Player *player = NULL; + + if ( owner && owner->isSubclassOf( Player ) ) + player = (Player *)(Sentient *)owner; + + if ( !player ) + return; + + if ( zoomed ) + { + endZoom(); + } + else + { + zoomfov = startzoom; + startzoomtime = level.time; + zoomed = true; + + player->SetFov( zoomfov, true ); + player->client->ps.pm_flags |= PMF_ZOOM; + + } +} + +void Weapon::endZoom( Event *ev ) +{ + if ( zoomed ) + _lastZoomFov = zoomfov; + else + _lastZoomFov = sv_defaultFov->value; + + endZoom(); +} + +void Weapon::rezoom( Event *ev ) +{ + Player *player = NULL; + + if ( owner && owner->isSubclassOf( Player ) ) + player = (Player *)(Sentient *)owner; + + if ( !player ) + return; + + if ( _lastZoomFov < sv_defaultFov->value ) + { + zoomed = true; + zoomfov = _lastZoomFov; + + player->SetFov( zoomfov, true ); + player->client->ps.pm_flags |= PMF_ZOOM; + } +} + +void Weapon::endZoom( void ) +{ + Player *player = NULL; + float defaultFov; + + if ( owner && owner->isSubclassOf( Player ) ) + player = (Player *)(Sentient *)owner; + + if ( !player ) + return; + + defaultFov = player->getDefaultFov(); + + player->SetFov( defaultFov, true ); + + //remove the player client zoom flag + player->client->ps.pm_flags &= ~PMF_ZOOM; + + //gi.SendServerCommand(player->entnum, "stufftext \"ui_removehud zoomhud\"\n"); + player->removeHud( "zoomhud" ); + + _zoomStage = ZOOM_NORMAL_FOV; + zoomed = false; +} + +void Weapon::IncrementZoom( Event* ev ) +{ + + if(!zoomed) + return; + + Player *player = NULL; + + if ( owner && owner->isSubclassOf( Player ) ) + player = (Player *)(Sentient *)owner; + + float zoomPercentage = ((float)level.time - startzoomtime) / zoomtime; + zoomfov = ((endzoom - startzoom) * zoomPercentage) + startzoom; + if(zoomfov < endzoom) + zoomfov = endzoom; + + player->SetFov( zoomfov, true ); +} + + +//----------------------------------------------------- +// +// Name: zoomFov +// Class: Weapon +// +// Description: Changes the zoom fov from Normal FOV to FOV 1 to FOV 2 then back to normal fov +// +// Parameters: +// +// Returns: +//----------------------------------------------------- +void Weapon::setZoomStage( Event* ev ) +{ + changeZoomStage( ev->GetFloat( 1 ), ev->GetFloat( 2 ) ); +} + +void Weapon::changeZoomStage( float firstZoom, float secondZoom ) +{ + Player* player = NULL; + + if ( owner && owner->isSubclassOf( Player ) ) + player = (Player *)(Sentient *)owner; + + if(player == NULL) + return; + + // Move to next zoom stage + + switch( _zoomStage ) + { + case ZOOM_NORMAL_FOV: + _zoomStage = ZOOM_STAGE_1; + player->SetFov( firstZoom, true ); + player->addHud( "zoomhud" ); + break; + + case ZOOM_STAGE_1: + _zoomStage = ZOOM_STAGE_2; + player->SetFov( secondZoom, true ); + player->addHud( "zoomhud" ); + break; + + case ZOOM_STAGE_2: + _zoomStage = ZOOM_NORMAL_FOV; + endZoom(); + return; + } + + player->client->ps.pm_flags |= PMF_ZOOM; +} + + +void Weapon::SetTargetingSkin( Event *ev ) +{ + targetingSkin = ev->GetInteger( 1 ); +} + +void Weapon::SetShootingSkin( Event *ev ) +{ + shootingSkin = ev->GetInteger( 1 ); +} + +void Weapon::setFullAmmoSkin( Event *ev ) +{ + _fullAmmoSkin = ev->GetInteger( 1 ); + + _fullAmmoMode = WeaponModeNameToNum( ev->GetString( 2 ) ); + + turnThinkOn(); +} + + +void Weapon::ProcessTargetedEntity( EntityPtr entity ) +{ + + Player* player; + assert(owner); + + if ( !owner ) + return; + + player = (Player*) (Sentient*)owner; + if(player == 0) + return; + + if ( targetingSkin ) + { + ChangeSkin( targetingSkin, false ); + + if ( entity && entity->isSubclassOf( Actor ) ) + { + + Actor *actor = (Actor *)entity.Pointer(); + + if ( !actor->deadflag && ( actor->actortype == IS_ENEMY ) ) + ChangeSkin( targetingSkin, true ); + + } + } + + if(entity != 0) + { + entity->edict->s.eFlags |= EF_DISPLAY_DESC1; + } + +} + +//---------------------------------------------------------------- +// Name: setMoveSpeedModifier +// Class: Weapon +// +// Description: Sets the move speed modifier (when not firing) +// +// Parameters: Event *ev - float, specifies modifier +// +// Returns: none +//---------------------------------------------------------------- +void Weapon::Uninitialize(void) +{ + if( _zoomStage == ZOOM_STAGE_1 || _zoomStage == ZOOM_STAGE_2 ) + { + endZoom(); + } +} + + + +//---------------------------------------------------------------- +// Name: setMoveSpeedModifier +// Class: Weapon +// +// Description: Sets the move speed modifier (when not firing) +// +// Parameters: Event *ev - float, specifies modifier +// +// Returns: none +//---------------------------------------------------------------- +void Weapon::setMoveSpeedModifier( Event *ev ) +{ + defaultMoveSpeedModifier = ev->GetFloat( 1 ); +} + +//---------------------------------------------------------------- +// Name: setShootingMoveSpeedModifier +// Class: Weapon +// +// Description: Sets the move speed modified for a particular mode +// +// Parameters: Event *ev - float, specifies modifier +// +// Returns: none +//---------------------------------------------------------------- +void Weapon::setShootingMoveSpeedModifier( Event *ev ) +{ + shootingMoveSpeedModifier[ firemodeindex ] = ev->GetFloat( 1 ); +} + +//---------------------------------------------------------------- +// Name: getMoveSpeedModifier +// Class: Weapon +// +// Description: Gets the current move speed modifier +// +// Parameters: none +// +// Returns: float - the move speed modifier +//---------------------------------------------------------------- +float Weapon::getMoveSpeedModifier( void ) +{ + if ( weaponstate == WEAPON_FIRING ) + return shootingMoveSpeedModifier[ curmode ]; + else + return defaultMoveSpeedModifier; +} + +//---------------------------------------------------------------- +// Name: UseActorAiming +// Class: Weapon +// +// Description: Tells the weapon to use the tag_barrel for aiming, no matter who is holding it. +// +// Parameters: Event *ev +// +// Returns: None +//---------------------------------------------------------------- +void Weapon::UseActorAiming( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + useActorAiming = ev->GetBoolean( 1 ); + else + useActorAiming = true; +} + +//---------------------------------------------------------------- +// Name: updateViewShake +// Class: Weapon +// +// Description: Updates the view shake angles +// +// Parameters: none +// +// Returns: none +//---------------------------------------------------------------- +void Weapon::updateViewShake( void ) +{ + if ( _viewShakeMagnitude[ curmode ] > 0.0f ) + { + // Apply simple view shake + + viewShake[ PITCH ] = G_CRandom() * _viewShakeMagnitude[ curmode ]; + viewShake[ YAW ] = G_CRandom() * _viewShakeMagnitude[ curmode ]; + viewShake[ ROLL ] = G_CRandom() * _viewShakeMagnitude[ curmode ] * 1.5; + } + else if ( ( _viewMinShake[ curmode ] != vec_zero ) || ( _viewMaxShake[ curmode ] != vec_zero ) ) + { + Vector minShake; + Vector maxShake; + + // Apply advanced view shake + + minShake = _viewMinShake[ curmode ]; + maxShake = _viewMaxShake[ curmode ]; + + if ( _viewShakeOverride[ curmode ] ) + { + viewShake[ PITCH ] = G_Random() * ( maxShake[ PITCH ] - minShake[ PITCH ] ) + minShake[ PITCH ]; + viewShake[ YAW ] = G_Random() * ( maxShake[ YAW ] - minShake[ YAW ] ) + minShake[ YAW ]; + viewShake[ ROLL ] = G_Random() * ( maxShake[ ROLL ] - minShake[ ROLL ] ) + minShake[ ROLL ]; + } + else + { + viewShake[ PITCH ] += G_Random() * ( maxShake[ PITCH ] - minShake[ PITCH ] ) + minShake[ PITCH ]; + viewShake[ YAW ] += G_Random() * ( maxShake[ YAW ] - minShake[ YAW ] ) + minShake[ YAW ]; + viewShake[ ROLL ] += G_Random() * ( maxShake[ ROLL ] - minShake[ ROLL ] ) + minShake[ ROLL ]; + } + + for ( int i = 0 ; i < 3 ; i++ ) + { + if ( viewShake[ i ] > _maxViewShakeChange ) + viewShake[ i ] = _maxViewShakeChange; + else if ( viewShake[ i ] < -_maxViewShakeChange ) + viewShake[ i ] = -_maxViewShakeChange; + + } + } +} + +void Weapon::startViewShake( Event *ev ) +{ + startViewShake(); +} +void Weapon::startViewShake( void ) +{ + if ( ( _viewShakeMagnitude[ curmode ] > 0.0f ) || ( _viewMinShake[ curmode ] != vec_zero ) || ( _viewMaxShake[ curmode ] != vec_zero ) ) + { + //CancelEventsOfType( EV_Weapon_ClearViewShake ); + + updateViewShake(); + + //if ( _viewShakeDuration[ curmode ] > FRAMETIME ) + { + CancelEventsOfType( EV_Weapon_ReduceViewShake ); + PostEvent( EV_Weapon_ReduceViewShake, FRAMETIME ); + //PostEvent( EV_Weapon_ReduceViewShake, 0.25 ); + } + + //PostEvent( EV_Weapon_ClearViewShake, _viewShakeDuration[ curmode ] ); + } +} + +void Weapon::reduceViewShake( Event *ev ) +{ + int i; + bool repost; + + // Reduce the viewShake + + viewShake *= 0.75; + + // Determine if we should do reduce the viewShake next frame + + repost = false; + + for ( i = 0 ; i < 3 ; i++ ) + { + if ( ( viewShake[ i ] > 0.1f ) || ( viewShake[ i ] < -0.1f ) ) + { + repost = true; + } + } + + if ( repost ) + { + PostEvent( EV_Weapon_ReduceViewShake, FRAMETIME ); + } + else + { + viewShake = vec_zero; + } +} + +//---------------------------------------------------------------- +// Name: clearViewShake +// Class: Weapon +// +// Description: Clears the view shake angles +// +// Parameters: none +// +// Returns: none +//---------------------------------------------------------------- +void Weapon::clearViewShake( Event *ev ) +{ + viewShake = vec_zero; + CancelEventsOfType( EV_Weapon_ReduceViewShake ); +} + +//---------------------------------------------------------------- +// Name: getViewShake +// Class: Weapon +// +// Description: Gets the angles to shake the view +// +// Parameters: none +// +// Returns: Vector - angles to shake the view by +//---------------------------------------------------------------- +Vector Weapon::getViewShake( void ) +{ + return viewShake; +} + +//---------------------------------------------------------------- +// Name: setViewShakeInfo +// Class: Weapon +// +// Description: Sets the magnitude and duration that the view should shake when firing the weapon +// +// Parameters: Event *ev - float, specifies the magnitude +// float, specifies the duration (optional) +// +// Returns: none +//---------------------------------------------------------------- +void Weapon::setViewShakeInfo( Event *ev ) +{ + _viewShakeMagnitude[ firemodeindex ] = ev->GetFloat( 1 ); + + if ( ev->NumArgs() > 1 ) + { + _viewShakeDuration[ firemodeindex ] = ev->GetFloat( 2 ); + } + else + { + _viewShakeDuration[ firemodeindex ] = FRAMETIME; + } +} + +void Weapon::setAdvancedViewShakeInfo( Event *ev ) +{ + _viewMinShake[ firemodeindex ] = ev->GetVector( 1 ); + _viewMaxShake[ firemodeindex ] = ev->GetVector( 2 ); + + if ( ev->NumArgs() > 2 ) + { + _viewShakeDuration[ firemodeindex ] = ev->GetFloat( 3 ); + } + else + { + _viewShakeDuration[ firemodeindex ] = FRAMETIME; + } + + if ( ev->NumArgs() > 3 ) + { + _viewShakeOverride[ firemodeindex ] = ev->GetBoolean( 4 ); + } + else + { + _viewShakeOverride[ firemodeindex ] = false; + } +} + + +//-------------------------------------------------------------- +// Name: SetPowerRating() +// Class: Weapon +// +// Description: Calls SetPowerRating() +// +// Parameters: Event *ev -- Event with the rating +// +// Returns: None +//-------------------------------------------------------------- +void Weapon::SetPowerRating( Event *ev ) +{ + SetPowerRating( ev->GetFloat( 1 ) ); +} + +//-------------------------------------------------------------- +// Name: SetProjectileDamage() +// Class: Weapon +// +// Description: Calls SetProjectileDamage +// +// Parameters: Event *ev -- Event with the damage +// +// Returns: None +//-------------------------------------------------------------- +void Weapon::SetProjectileDamage( Event *ev ) +{ + SetProjectileDamage(ev->GetFloat( 1 ) ); +} + +//-------------------------------------------------------------- +// Name: SetProjectileSpeed() +// Class: Weapon +// +// Description: Calls SetProjectileSpeed +// +// Parameters: Event *ev -- Event with the speed of the projectile +// +// Returns: None +//-------------------------------------------------------------- +void Weapon::SetProjectileSpeed( Event *ev ) +{ + SetProjectileSpeed(ev->GetFloat( 1 ) ); +} + +//-------------------------------------------------------------- +// Name: AdvancedMeleeAttack +// Class: Weapon +// +// Description: Does some literal melee damage based on the weapon itself. +// Weapon angles (except Z) are taken into account, and we +// utilize the meleestart and meleeend events to apply damage +// properly to enemies in a single swipe. The melee trace goes +// between the two WEAPON tags passed in. +// +// Parameters: const char* tag1 -- The first tag to do the melee trace from +// const char* tag2 -- The second tag to do the melee trace from +// +// Returns: None +//-------------------------------------------------------------- +void Weapon::AdvancedMeleeAttack(const char* tag1, const char* tag2, bool criticalHit) +{ + Vector startpoint, endpoint; + Vector tagpos, tagpos2, vect; + float damage, knockback; + meansOfDeath_t meansofdeath; + + // New Melee Combat code + GetActorMuzzlePosition(&tagpos, NULL, NULL, NULL, tag1); // Get the position of the first tag + GetActorMuzzlePosition(&tagpos2, NULL, NULL, NULL, tag2); // Get the position of the second tag + startpoint = tagpos2; + vect = tagpos - tagpos2; + vect.z = 0; + vect.normalize(); + endpoint = startpoint + (vect*48); + + // Debug line + //G_DebugLine(startpoint, endpoint, 0.5, 1.0, 0.5, 1.0); + + damage = bulletdamage[FIRE_MODE1]; + knockback = bulletknockback[FIRE_MODE1]; + + meansofdeath = GetMeansOfDeath( FIRE_MODE1 ); + + if ( owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)(Sentient *)owner; + + meansofdeath = player->changetMeansOfDeath( meansofdeath ); + + damage = player->getDamageDone( damage, meansofdeath, true ); + + knockback = (knockback + player->GetPlayerKnockback()) * player->GetKnockbackMultiplier(); + } + + Sentient *owner; + Player *player; + owner = this->owner; + assert( owner ); + if ( owner->isSubclassOf( Player ) ) + { + player = ( Player * )owner; + if ( !player->in_melee_attack ) + meleeVictims.ClearObjectList(); + } + + if ( !MeleeAttack( startpoint, endpoint, damage, owner, meansofdeath, 15.0f, -45.0f, 45.0f, knockback, true, &meleeVictims, this, criticalHit ) ) + { + // Try to hit the world since we didn't do any damage to anything + trace_t trace = G_Trace( startpoint, Vector( -8.0f, -8.0f, -8.0f ), Vector( 8.0f, 8.0f, 8.0f ), endpoint, owner, MASK_MELEE, false, "Weapon::Shoot" ); + + Entity *victim = G_GetEntity( trace.entityNum ); + + if ( victim && ( ( victim == world ) || ( victim->takedamage == DAMAGE_NO ) ) ) + { + vec3_t newangles; + vectoangles( trace.plane.normal, newangles ); + WorldHitSpawn( FIRE_MODE1, trace.endpos, newangles, 0.1f ); + str realname = this->GetRandomAlias( "impact_world" ); + if ( realname.length() > 1 ) + this->Sound( realname, CHAN_VOICE ); + } + } + + if ( !quiet ) + { + if ( next_noise_time <= level.time ) + { + BroadcastSound(); + next_noise_time = level.time + 1.0f; + } + } +} + +//---------------------------------------------------------------- +// Name: applySpread +// Class: Weapon +// +// Description: Applies the weapons spread to the angles +// +// Parameters: const Vector &pos - position of the muzzle +// Vector *forward - forward vector of the muzzle +// Vector *right - right vector of the muzzle +// Vector *up - up vector of the muzzle +// +// Returns: none +//---------------------------------------------------------------- + +void Weapon::applySpread( Vector *forward, Vector *right, Vector *up ) +{ + Vector spread; + + + if (( !forward ) || (!up) || (!right)) + return; + + spread = getSpread(); + + // figure the new projected impact point based upon computed spread + *forward = ( *forward * bulletrange[curmode]) + + ( *right * G_CRandom( spread.x ) ) + + ( *up * G_CRandom( spread.y ) ); + + // after figuring spread location, re-normalize vectors + forward->normalize(); + *right = Vector::Cross(*forward,*up); + *up = Vector::Cross(*right,*forward); +} + +Vector Weapon::getSpread( void ) +{ + Vector spread; + + // compute the spread if it's time-variant + + if ( endbulletspread[ curmode ].z ) + { + float timemult = (level.time - startfiretime) / endbulletspread[curmode].z; + + if (timemult > 1) // cap it so we don't get silly spread + timemult = 1; + + spread.x = timemult * (endbulletspread[curmode].x - bulletspread[curmode].x) + bulletspread[curmode].x; + spread.y = timemult * (endbulletspread[curmode].y - bulletspread[curmode].y) + bulletspread[curmode].y; + } + else + { + spread.x = bulletspread[curmode].x; + spread.y = bulletspread[curmode].y; + } + + return spread; +} + + +//-------------------------------------------------------------- +// +// Name: processGameplayData +// Class: Weapon +// +// Description: Called usually from the tiki file after all other +// server side events are called. +// +// Parameters: Event *ev -- not used +// +// Returns: None +// +//-------------------------------------------------------------- +void Weapon::processGameplayData( Event *ev ) +{ + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( !gpm->hasObject(getArchetype()) ) + return; + + str objname = getArchetype(); + str useanim, usetype, usethread; + if ( gpm->hasProperty(objname, "useanim") ) + useanim = gpm->getStringValue(objname, "useanim"); + if ( gpm->hasProperty(objname, "usethread") ) + usethread = gpm->getStringValue(objname, "usethread"); + if ( gpm->hasProperty(objname + ".Pickup", "icon") ) + usetype = gpm->getStringValue(objname + ".Pickup", "icon"); + + // If any of these strings were set, add to our UseData object. + if ( useanim.length() || usethread.length() || usetype.length() ) + { + if ( !useData ) + useData = new UseData(); + + useData->setUseAnim(useanim); + useData->setUseThread(usethread); + useData->setUseType(usetype); + } +} + +float Weapon::RespawnTime( void ) +{ + if ( multiplayerManager.inMultiplayer() ) + return respawntime * multiplayerManager.getWeaponRespawnMultiplayer(); + else + return respawntime; +} + +void Weapon::SetArcProjectile( Event *ev ) +{ + _arcProjectile = ev->GetBoolean( 1 ); +} + +void Weapon::SetLowArcRange( Event *ev ) +{ + _lowArcRange = ev->GetFloat( 1 ); +} + +void Weapon::SetPlayMissSound( Event *ev ) +{ + _playMissSound = ev->GetBoolean( 1 ); +} + +void Weapon::noAmmoMode( Event * ) +{ + _noAmmoMode[ firemodeindex ] = true; +} + +bool Weapon::hasNoAmmoMode( firemode_t mode ) +{ + return _noAmmoMode[ mode ]; +} + +void Weapon::setNoDelay( Event *ev ) +{ + _noDelay[ firemodeindex ] = true; +} + +bool Weapon::isModeNoDelay( firemode_t mode ) +{ + return _noDelay[ mode ]; +} + +void Weapon::pauseRegen( Event *ev ) +{ + int i; + + for( i = 0 ; i < MAX_FIREMODES ; i++ ) + { + if ( ( _regenAmount[ i ] > 0 ) ) + { + _nextRegenTime[ i ] = level.time + _regenTime[ i ] + 0.5f; + } + } +} + +void Weapon::setChargedModels( Event *ev ) +{ + assert( ( firemodeindex >= 0 ) && ( firemodeindex < MAX_FIREMODES ) ); + _chargedModels[ firemodeindex ] = ev->GetInteger( 1 ); +} + +void Weapon::setControllingProjectile( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + { + _controllingProjectile = ev->GetBoolean( 1 ); + } + else + { + _controllingProjectile = true; + } +} + +bool Weapon::getControllingProjectile( void ) +{ + return _controllingProjectile; +} + +void Weapon::setCanInterruptFiringState( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + _canInterruptFiringState = ev->GetBoolean( 1 ); + else + _canInterruptFiringState = true; +} + +void Weapon::setSpreadAnimData( Event *ev ) +{ + _spreadAnims[ firemodeindex ] = ev->GetInteger( 1 ); + _spreadTime[ firemodeindex ] = ev->GetFloat( 2 ); +} + +bool Weapon::canReload( void ) +{ + if ( weaponstate != WEAPON_READY ) + return false; + + return true; +} + +int Weapon::getAmmoInClip( firemode_t mode ) +{ + firemode_t clipToUse; + + assert( ( mode >= 0 ) && ( mode < MAX_FIREMODES ) ); + + if ( usesameclip ) + clipToUse = FIRE_MODE1; + else + clipToUse = mode; + + if ( ( clipToUse >= 0 ) && ( clipToUse < MAX_FIREMODES ) ) + { + if ( ammo_clip_size[ clipToUse ] ) + { + return ammo_in_clip[ clipToUse ]; + } + } + + return 0; +} + +void Weapon::setMaxViewShakeChange( Event *ev ) +{ + _maxViewShakeChange = ev->GetFloat( 1 ); +} + +void Weapon::setControlParms( Event *ev ) +{ + _controlEmitterName = ev->GetString( 1 ); + _controlSoundName = ev->GetString( 2 ); +} + +void Weapon::toggleProjectileControl( void ) +{ + if ( _controllingProjectile ) + { + // No longer control projectile + + _controllingProjectile = false; + + // Turn off sound + + StopLoopSound(); + + // Turn off emitter + + clearCustomEmitter( _controlEmitterName ); + } + else + { + // Control projectile now + + _controllingProjectile = true; + + // Turn on sound + + LoopSound( _controlSoundName ); + + if ( !_controllingProjectileHidden ) + { + // Turn on emitter + + setCustomEmitter( _controlEmitterName ); + } + } +} + +void Weapon::setProjectileControlHidden( Event *ev ) +{ + _controllingProjectileHidden = ev->GetBoolean( 1 ); + + if ( _controllingProjectileHidden && _controllingProjectile ) + { + // Turn off emitter + + clearCustomEmitter( _controlEmitterName ); + } + else if ( !_controllingProjectileHidden && _controllingProjectile ) + { + // Turn on emitter + + setCustomEmitter( _controlEmitterName ); + } +} + +void Weapon::setMeleeParms( Event *ev ) +{ + _meleeWidth[ firemodeindex ] = ev->GetFloat( 1 ); + _meleeHeight[ firemodeindex ] = ev->GetFloat( 2 ); + _meleeLength[ firemodeindex ] = ev->GetFloat( 3 ); +} + +void Weapon::setFireOffset( Event *ev ) +{ + _fireOffset[ firemodeindex ] = ev->GetVector( 1 ); +} + +void Weapon::cacheStrings( void ) +{ + G_FindConfigstringIndex( va( "$$PickedUpThe$$ $$Weapon-%s$$\n", getName().c_str() ), CS_GENERAL_STRINGS, MAX_GENERAL_STRINGS, true ); +} + +void Weapon::setAutoReload( Event *ev ) +{ + _autoReload = ev->GetBoolean( 1 ); +} + +void Weapon::setAllowAutoSwitch( Event *ev ) +{ + _allowAutoSwitch = ev->GetBoolean( 1 ); +} + +void Weapon::forceReload( Event *ev ) +{ + ForceReload(); +} + +void Weapon::Think( void ) +{ + int i; + + for( i = 0 ; i < MAX_FIREMODES ; i++ ) + { + if ( owner && ( _regenAmount[ i ] > 0 ) && ( _nextRegenTime[ i ] < level.time ) ) + //if ( ( attached ) && ( _regenAmount[ i ] > 0 ) && ( _nextRegenTime[ i ] < level.time ) ) + { + owner->GiveAmmo( ammo_type[ i ], _regenAmount[ i ], false ); + + _nextRegenTime[ i ] = level.time + _regenTime[ i ]; + } + } + + if ( attached && _fullAmmoSkin ) + { + bool full = false; + firemode_t modeToCheck; + + if ( usesameclip ) + modeToCheck = FIRE_MODE1; + else + modeToCheck = _fullAmmoMode; + + if ( ammo_clip_size[ modeToCheck ] > 0 ) + { + if ( ammo_in_clip[ modeToCheck ] == ammo_clip_size[ modeToCheck ] ) + full = true; + } + else + { + if ( ( owner ) && ( owner->AmmoCount( ammo_type[ modeToCheck ] ) == owner->MaxAmmoCount( ammo_type[ modeToCheck ] ) ) ) + full = true; + } + + if ( full ) + { + ChangeSkin( _fullAmmoSkin, true ); + } + else + { + ChangeSkin( _fullAmmoSkin, false ); + } + } +} + +bool Weapon::shouldAutoSwitch( firemode_t mode ) +{ + Player *player; + int i; + firemode_t modeToCheck; + + + if ( !owner || !owner->isSubclassOf( Player ) ) + return false; + + player = (Player *)(Sentient *)owner; + + if ( HasAmmo( mode ) ) + return false; + + if ( !_allowAutoSwitch ) + return false; + + if ( player->edict->svflags & SVF_BOT ) + return true; + + if ( !player->getAutoSwitchWeapons() ) + return false; + + for ( i = 0 ; i < MAX_FIREMODES ; i++ ) + { + if ( usesameclip ) + modeToCheck = FIRE_MODE1; + else + modeToCheck = (firemode_t)i; + + if ( HasAmmo( modeToCheck ) ) + return false; + + if ( firetype[ i ] == FT_TRIGGER_PROJECTILE ) + { + if ( _nextSwitchTime > level.time ) + { + return false; + } + } + } + + return true; +} + +int Weapon::getWeaponPriority( void ) +{ + int priority; + str currentWeaponName; + + for ( priority = 0 ; priority < 14 ; priority++ ) + { + currentWeaponName = getWeaponByPriority( priority ); + + if ( currentWeaponName == getName() ) + return priority; + } + + return 100; +} + +str Weapon::getWeaponByPriority( int priority ) +{ + str weaponName; + + switch ( priority ) + { + case 0 : + weaponName = "PhotonBurst"; + break; + case 1 : + weaponName = "TetryonGatlingGun"; + break; + case 2 : + weaponName = "FederationSniperRifle"; + break; + case 3 : + weaponName = "RomulanRadGun"; + break; + case 4 : + weaponName = "AttrexianRifle"; + break; + case 5 : + weaponName = "BurstRifle"; + break; + case 6 : + weaponName = "GrenadeLauncher"; + break; + case 7 : + weaponName = "FieldAssaultRifle"; + break; + case 8 : + weaponName = "I-Mod"; + break; + case 9 : + weaponName = "CompressionRifle"; + break; + case 10 : + weaponName = "RomulanDisruptor"; + break; + case 11 : + weaponName = "DrullStaff"; + break; + case 12 : + weaponName = "Phaser-stx"; + break; + case 13 : + weaponName = "Phaser"; + break; + } + + return weaponName; +} + +void Weapon::autoSwitch() +{ + str weaponName; + Weapon *weapon; + Item *item; + int i; + Player *player; + int ammoAvailable; + + if ( !owner || !owner->isSubclassOf( Player ) ) + return; + + player = (Player *)(Sentient *)owner; + + for ( i = 0 ; i < 14 ; i++ ) + { + weaponName = getWeaponByPriority( i ); + + if ( player->HasItem( weaponName ) ) + { + item = player->FindItem( weaponName ); + + if ( item && item->isSubclassOf( Weapon ) ) + { + weapon = (Weapon *)item; + + ammoAvailable = weapon->getAmmoInClip( FIRE_MODE1 ) + weapon->AmmoAvailable( FIRE_MODE1 ); + + if ( ammoAvailable > weapon->ammorequired[ FIRE_MODE1 ] ) + { + Event *newEvent = new Event( EV_Player_UseItem ); + newEvent->AddString( weaponName ); + player->ProcessEvent( newEvent ); + + return; + } + } + } + } +} + +void Weapon::setNextSwitchTime( float time ) +{ + _nextSwitchTime = level.time + time; +} + +void Weapon::setNextSwitchTime( Event *ev ) +{ + setNextSwitchTime( ev->GetFloat( 1 ) ); +} diff --git a/dlls/game/weapon.h b/dlls/game/weapon.h new file mode 100644 index 0000000..3a6c80a --- /dev/null +++ b/dlls/game/weapon.h @@ -0,0 +1,919 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/weapon.h $ +// $Revision:: 121 $ +// $Author:: Steven $ +// $Date:: 10/13/03 9:43a $ +// +// Copyright (C) 1997 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Header file for Weapon class. The weapon class is the base class for +// all weapons in the game. Any entity created from a class derived from the weapon +// class will be usable by any Sentient (players and monsters) as a weapon. +// +class Weapon; + +#ifndef __WEAPON_H__ +#define __WEAPON_H__ + +#include "g_local.h" +#include "item.h" +#include "ammo.h" +#include "sentient.h" + +extern Event EV_Weapon_GiveStartingAmmo; +extern Event EV_Weapon_GiveAmmoBoost; + +typedef enum + { + FT_NONE, + FT_BULLET, + FT_PROJECTILE, + FT_MELEE, + FT_SPECIAL_PROJECTILE, + FT_EXPLOSION, + FT_TRIGGER_PROJECTILE, + FT_CONTROL_PROJECTILE, + FT_CONTROL_ZOOM + } firetype_t; + +typedef enum + { + WEAPON_ANIMATING, + WEAPON_READY, + WEAPON_FIRING, + WEAPON_LOWERING, + WEAPON_RAISING, + WEAPON_HOLSTERED, + WEAPON_RELOADING, + WEAPON_CHANGING, + WEAPON_SWITCHINGMODE + } weaponstate_t; + +typedef enum + { + ACCURACY_STOPPED, + ACCURACY_CHANGE, + ACCURACY_WALK, + ACCURACY_RUN, + ACCURACY_CROUCH, + MAX_ACCURACYTYPES + } weaponaccuracy_t; + + +typedef enum +{ + ZOOM_NORMAL_FOV = 0, + ZOOM_STAGE_1, + ZOOM_STAGE_2, + NUM_ZOOM_STAGES +} ZoomStage; + +//======================================== +// Bones used by weapon +//======================================== +typedef enum +{ + WEAPONBONE_BARREL_TAG +} weaponbones_t; + +#define INITIALIZE_WEAPONMODE_VAR(var,value) \ + { \ + int _ii; \ + for( _ii=0; _ii _weaponProperties ; +}; + + +class Weapon : public Item + { + protected: + friend class Player; + friend class WeaponDualWield; + + private: + qboolean attached; // Is this weapon attached to something? + float nextweaponsoundtime; // The next time this weapon should sound off + str current_attachToTag; // The current name of the tag to attach itself to on the owner + str left_attachToTag; // Tag to use when weapon is wielded in the left hand + str right_attachToTag; // ...right hand + str dual_attachToTag; // ...dual handed + str leftholster_attachToTag; // Tag to use when weapon is put away from left hand + str rightholster_attachToTag; // ...right hand + str dualholster_attachToTag; // ...dual handed + float lastScale; // Used for attaching to holster + Vector lastAngles; // Used for attaching to holster + qboolean lastValid; // Used for attaching to holster + qboolean auto_putaway; // Weapon will put itself away when out of ammo + qboolean use_no_ammo; // Weapon will be able to be used when it has no ammo + qboolean crosshair; // Whether or not to display a crosshair with this weapon + qboolean torsoaim; // Whether or not to torso aim with this weapon + qboolean special_move; // Allows special movement or not + EntityPtr aim_target; // Current target of the weapon + firemode_t curmode; // The current weapon mode + firemode_t maxmode; // The maximum valid mode for this weapon + qboolean switchmode; // Speicifies that this is a switch mode weapon + qboolean targetidleflag; // Flag to specify if we have just left a target idle + int chx; // x screen coord crosshair offset + int chy; // y screen coord crosshair offset + Vector realvieworg; // real view origin (third person camera loc) + qboolean thirdperson; // whether or not the player is in 3rd person + bool useActorAiming; + float _powerRating; // how much damage per second the weapon will do + float _projectileDamage; // how much the projectile launched from this weapon will do -- needs to match the damage in the projectile .tik file ( used by AI ) + float _projectileSpeed; // how fast the projectile launched from the weapon will move -- needs to match the damage in the projectile .tik file ( used by AI ) + bool _arcProjectile; // whether or not to arc the projectile + float _lowArcRange; // range at which the projectile will switch from high trajectory to normal + bool _playMissSound; //Play snd_ricochet if the bullet impact hits a non-damagable entity + + Container meleeVictims; // Melee victim list + + protected: + float maxrange; // maximum effective firing distance (for AI) + float minrange; // minimum safe firing distance (for AI) + str viewmodel; // the viewmodel of the weapon + weaponstate_t weaponstate; // current state of the weapon + int rank; // rank of the weapon (relative to other weapons) + int order; // The order of this weapon in the inventory + SentientPtr last_owner; // The last owner of the weapon + float last_owner_trigger_time; // The time when the last owner may re-pickup this weapon + qboolean notdroppable; // makes the weapon not able to be dropped + int aimanim; // The aim animation to use for this weapon (so it shoots straight) + int aimframe; // The aim frame to use for this weapon (so it shoots straight) + Vector leftHolsterAngles; // Angles to set the weapon to when it's holstered + Vector rightHolsterAngles; // Angles to set the weapon to when it's holstered + Vector dualHolsterAngles; // Angles to set the weapon to when it's holstered + float holsterScale; // Scale the weapon should be set to when it's holstered + float _weildedScale; + qboolean quiet; // Makes the weapon not alert actors + float next_noise_time; // next time weapon will alert actors + float next_noammo_time; // next time we can play out of ammo sound + int burstcount; // The current amount of burst ammo used + int burstcountmax; // Max amount of ammo for a burst + + // Each of these arrays is used to describe the properties of the weapon + // in its primary(index 0) and alternate(index 1) mode + + str ammo_type[MAX_FIREMODES]; // The type of ammo used + int ammorequired[MAX_FIREMODES]; // The amount of ammo required to fire this weapon + int startammo[MAX_FIREMODES]; // The starting amount of ammo when the weapon is picked up + int _ammoBoost[ MAX_FIREMODES ]; + str projectileModel[MAX_FIREMODES]; // The model of the projectile fired + float bulletdamage[MAX_FIREMODES]; // The amount of damate a single bullet causes + float bulletcount[MAX_FIREMODES]; // The number of bullets the weapon fires + float bulletrange[MAX_FIREMODES]; // The range of the bullet + float bulletknockback[MAX_FIREMODES]; // The amount of knockback a bullet causes + float projectilespeed[MAX_FIREMODES]; // The speed of the projectile fired + Vector bulletspread[MAX_FIREMODES]; // The amount of spread bullets can have + Vector endbulletspread[MAX_FIREMODES]; // The final bullet spread (if different from initial) and time interval (in .z) + firetype_t firetype[MAX_FIREMODES]; // The type of fire (projectile or bullet) + int ammo_clip_size[MAX_FIREMODES]; // The amount of rounds the clip can hold + int ammo_in_clip[MAX_FIREMODES]; // The current amount of ammo in the clip + float max_charge_time[MAX_FIREMODES]; // The max amount of time the weapon may be charged. + meansOfDeath_t meansofdeath[MAX_FIREMODES]; // The means of death for this mode + qboolean loopfire[MAX_FIREMODES]; // The weapon loopfires and will not idle when shooting + //qboolean fullanimfire[MAX_FIREMODES]; // The weapon will play full fire anim even if key is released + int action_level_increment[MAX_FIREMODES]; // Increments the action level everytime the weapon is fired + str worldhitspawn[MAX_FIREMODES]; // The models to spawn when the weapon strikes the world + float next_fire_time[MAX_FIREMODES]; // The next time the weapon can fire + float fire_timer[MAX_FIREMODES]; // The times for each fire mode + float accuracy[MAX_FIREMODES][MAX_ACCURACYTYPES]; // Accuracy values for this weapon + qboolean burstmode[MAX_FIREMODES]; // This mode is a burst fire mode + float _burstModeDelay[MAX_FIREMODES]; + float shootingMoveSpeedModifier[MAX_FIREMODES]; // Move speed modifier while shooting + + float _viewShakeMagnitude[MAX_FIREMODES]; + float _viewShakeDuration[MAX_FIREMODES]; + Vector _viewMinShake[MAX_FIREMODES]; + Vector _viewMaxShake[MAX_FIREMODES]; + bool _viewShakeOverride[ MAX_FIREMODES ]; + bool _noAmmoMode[MAX_FIREMODES]; + + bool _noDelay[MAX_FIREMODES]; + int _chargedModels[MAX_FIREMODES]; + + int _spreadAnims[ MAX_FIREMODES ]; + float _spreadTime[ MAX_FIREMODES ]; + + float _meleeWidth[ MAX_FIREMODES ]; + float _meleeHeight[ MAX_FIREMODES ]; + float _meleeLength[ MAX_FIREMODES ]; + + Vector _fireOffset[ MAX_FIREMODES ]; + + int _regenAmount[ MAX_FIREMODES ]; + float _regenTime[ MAX_FIREMODES ]; + bool _regenOnlyWhenIdle[ MAX_FIREMODES ]; + float _nextRegenTime[ MAX_FIREMODES ]; + + float _maxViewShakeChange; + + float startfiretime; // The time that the trigger was first depressed in an anim + int autoAimTargetSelectionAngle; // Whether or not the weapon will autoaim ( and the angle used to determine autoaiming) + int autoAimLockonAngle; // Angle within which a target will be locked on to, allowing + float charge_fraction; // Fraction of a charge up time + qboolean putaway; // This is set to true by the state system to signal a weapon to be putaway + firemode_t firemodeindex; // This is used as an internal index to indicate which mode to apply commands to + weaponhand_t hand; // which hand the weapon may be wielded in + + float reticuletime; // Time it takes the reticule to settle. + float zoomfov; // zoomfov for this weapon + float _lastZoomFov; + float startzoom; // the zoom fov to start at. + float endzoom; // the zoom fov to end at + float zoomtime; // the amount of time to go from start zoom to end zoom + float startzoomtime; // the start time of the zoom + int aimtype; // What accuracy modifiers we are using + qboolean usesameclip; // Use the same clip for both fire modes + float chargetime; // Current charge time + qboolean targetidle; // Weapon has a specific target idle anim + qboolean donefiring; // Whether or not the weapon is done firing; + + qboolean zoomed; + int targetingSkin; + int shootingSkin; + int _fullAmmoSkin; + firemode_t _fullAmmoMode; + float defaultMoveSpeedModifier; + + Vector viewShake; + + bool _controllingProjectile; + bool _controllingProjectileHidden; + str _controlEmitterName; + str _controlSoundName; + bool _canInterruptFiringState; + + ZoomStage _zoomStage; + + bool _autoReload; + + bool _allowAutoSwitch; + + float _nextSwitchTime; + + + void SetMaxRangeEvent( Event *ev ); + void SetMinRangeEvent( Event *ev ); + void SetSecondaryAmmo( const char *type, int amount, int startamount ); + virtual void DetachGun( void ); + virtual void AttachGun( weaponhand_t hand, qboolean holstering = false ); + void PickupWeapon( Event *ev ); + void DoneRaising( Event *ev ); + void DoneFiring( Event *ev ); + void DoneAnimating( Event *ev ); + void Idle( Event *ev ); + qboolean CheckReload( void ); + qboolean CheckReload( firemode_t mode ); + void DoneReloading( Event *ev ); + void DoneReloadingBurst( Event *ev ); + void SetAimAnim( Event *ev ); + virtual void Shoot( Event *ev ); + void ModeSet( Event *ev ); + void SetFireType( Event *ev ); + void SetProjectile( Event *ev ); + void SetBulletDamage( Event *ev ); + void SetBulletRange( Event *ev ); + void SetBulletKnockback( Event *ev ); + void SetBulletCount( Event *ev ); + void SetBulletSpread( Event *ev ); + void SetAutoPutaway( Event *ev ); + void SetRange( Event *ev ); + void SetSpecialMove( Event *ev ); + void SetUseNoAmmo( Event *ev ); + void LeftAttachToTag( Event *ev ); + void RightAttachToTag( Event *ev ); + void DualAttachToTag( Event *ev ); + void LeftHolsterAttachToTag( Event *ev ); + void RightHolsterAttachToTag( Event *ev ); + void DualHolsterAttachToTag( Event *ev ); + void SetLeftHolsterAngles( Event *ev ); + void SetRightHolsterAngles( Event *ev ); + void SetDualHolsterAngles( Event *ev ); + void SetHolsterScale( Event *ev ); + void setWeildedScale( Event *ev ); + void SetWorldHitSpawn( Event *ev ); + void SetViewModel( Event *ev ); + void DonePutaway( Event *ev ); + void SetRegenAmmo( Event *ev ); + void SetRegenOnlyWhenIdle( Event *ev ); + void ChangeIdle( Event *ev ); + void DrawBowStrain( Event *ev ); + void AltDrawBowStrain( Event *ev ); + void SetAccuracy( Event *ev ); + void SetReticuleTime( Event *ev ); + void SetZoomFOV( Event *ev ); + void IncrementZoom(Event *ev); + void setZoomStage( Event* ev ); + void changeZoomStage( float firstZoom, float secondZoom ); + + void SetStartZoom(Event *ev); + void SetEndZoom(Event *ev); + void SetZoomTime(Event *ev); + void SetAimType( Event *ev ); + void SetFireTimer( Event *ev ); + void UseSameClip( Event *ev ); + void SetMaxModes( Event *ev ); + void SetSwitchMode( Event *ev ); + void DoneSwitching( Event *ev ); + void DoneSwitchToMiddle( Event *ev ); + void TargetIdle( Event *ev ); + void TargetIdleThink( Event *ev ); + void SetBurstMode( Event *ev ); + void setBurstModeDelay( Event *ev ); + void UseActorAiming( Event *ev ); + void PassToAnimate( Event *ev ); + void SetArcProjectile( Event *ev ); + void SetLowArcRange( Event *ev ); + void SetPlayMissSound(Event *ev ); + virtual void processGameplayData( Event *ev ); + + public: + CLASS_PROTOTYPE( Weapon ); + + Weapon(); + Weapon( const char *file ); + ~Weapon(); + + bool shouldArcProjectile(); + bool shouldPlayMissSound(); + float GetLowArcRange(); + int GetRank( void ); + int GetOrder( void ); + void SetRank( int order, int rank ); + float GetMaxRange( void ); + float GetMinRange( void ); + inline qboolean GetPutaway( void ){ return putaway; }; + inline void SetPutAway( qboolean p ){ putaway = p; }; + void SetMaxRange( float val ); + void SetMinRange( float val ); + void SetHand( Event *ev ); + inline weaponhand_t GetHand( void ){ return hand; }; + void ForceIdle( void ); + void SetAmmoRequired( Event *ev ); + void SetStartAmmo( Event *ev ); + void setAmmoBoost( Event *ev ); + int GetStartAmmo( firemode_t mode ); + int getAmmoBoost( firemode_t mode ); + int GetMaxAmmo( firemode_t mode ); + str GetAmmoType( firemode_t mode ); + firetype_t GetFireType( firemode_t mode ); + void SetAmmoType( Event *ev ); + void SetAmmoAmount( int amount, firemode_t mode ); + void UseAmmo( int amount, firemode_t mode ); + void SetAmmoClipSize( Event *ev ); + void SetAmmoInClip( Event *ev ); + void SetModels( const char *world, const char *view ); + void SetOwner( Sentient *ent ); + void SetMaxChargeTime( Event *ev ); + void SetAnim( const str &animName, Event *endevent = NULL, bodypart_t part = legs ); + void SetAnim( const str &animName, const Event &endevent, bodypart_t part = legs ); + int AmmoAvailable( firemode_t mode ); + qboolean UnlimitedAmmo( firemode_t mode ); + qboolean HasAmmo( firemode_t mode, int numShots = 1 ); + + qboolean HasInvAmmo( firemode_t mode ); + qboolean HasAmmoInClip( firemode_t mode, int numShots = 1 ); + int GetClipSize( firemode_t mode ); + int GetRequiredAmmo( firemode_t mode ); + qboolean ReadyToFire( firemode_t mode, qboolean playsound = true ); + virtual void PutAway( void ); + qboolean Drop( void ); + void Fire( firemode_t mode ); + void Charge( firemode_t mode ); + void ReleaseFire( firemode_t mode, float chargetime ); + void ClientFireDone( void ); + qboolean Removable( void ); + qboolean Pickupable( Entity *other ); + void DetachFromOwner( void ); + virtual void AttachToOwner( weaponhand_t hand ); + void WeaponSound( Event *ev ); + void GetMuzzlePosition( Vector *position, Vector *forward = NULL, Vector *right = NULL, Vector *up = NULL ); + void GetActorMuzzlePosition( Vector *position, Vector *forward = NULL, Vector *right = NULL, Vector *up = NULL, const char* tagname = NULL); + qboolean AutoChange( void ); + int ClipAmmo( firemode_t mode ); + void ProcessWeaponCommandsEvent(Event *ev); + qboolean IsDroppable( void ); + int ActionLevelIncrement( firemode_t mode ); + void SetActionLevelIncrement( Event *ev ); + void ForceState( weaponstate_t state ); + void NotDroppableEvent( Event *ev ); + void GiveStartingAmmo( Event *ev ); + void giveAmmoBoost( Event *ev ); + void AutoAim( Event *ev ); + void Crosshair( Event *ev ); + void TorsoAim( Event *ev ); + void AttachToTag( Event *ev ); + void SetQuiet( Event *ev ); + void SetLoopFire( Event *ev ); + void AddEffectsAnims( void ); + //void SetFullAnimFire( Event *ev ); + + // Apparently the special_projectile stuff is no longer used + //inline virtual void SpecialFireProjectile( const Vector &pos, const Vector &forward, const Vector &right, const Vector &up, Entity *owner, str projectileModel, float charge_fraction ) {}; + void AttachToHolster( weaponhand_t hand ); + inline str GetCurrentAttachToTag( void ){ return current_attachToTag; }; + inline void SetCurrentAttachToTag( const str &s ){ current_attachToTag = s; }; + inline str GetLeftHolsterTag( void ){ return leftholster_attachToTag; }; + inline str GetRightHolsterTag( void ){ return rightholster_attachToTag; }; + inline str GetDualHolsterTag( void ){ return dualholster_attachToTag; }; + inline qboolean GetUseNoAmmo( void ){ return use_no_ammo; }; + void SetMeansOfDeath( Event *ev ); + meansOfDeath_t GetMeansOfDeath( firemode_t mode ); + void SetAimTarget( Entity * ); + void WorldHitSpawn( firemode_t mode, const Vector &org, const Vector &angles, float life ); + void MakeNoise( Event *ev ); + virtual void Archive( Archiver &arc ); + inline void SetAimType ( int aim ) { aimtype = aim; } + inline float GetFireTime ( firemode_t mode ) { return next_fire_time[mode]; } + inline qboolean GetSwitchMode() { return switchmode; } + inline firemode_t GetCurMode() { return curmode; } + qboolean ForceReload( ); + qboolean HasFullClip(); + void SwitchMode( ); + void SetCHOffset(int chx, int chy); + void CheckForTargetedEntity(); + Vector GetViewEndPoint(); + void SetRealViewOrigin( const Vector &rv ); + void SetThirdPerson( qboolean tp ); + qboolean IsDoneFiring(); + void StartFiring(Event *ev); + void FinishedFiring( Event *ev ); + void Zoom( Event *ev ); + void endZoom( Event *ev ); + void rezoom( Event *ev ); + void endZoom( void ); + virtual void ProcessTargetedEntity(EntityPtr entity); + virtual void Uninitialize(void); + void SetTargetingSkin( Event *ev ); + void SetShootingSkin( Event *ev ); + void setFullAmmoSkin( Event *ev ); + void setMoveSpeedModifier( Event *ev ); + void setShootingMoveSpeedModifier( Event *ev ); + float getMoveSpeedModifier( void ); + void playAnim( const str &animName, bool animatingFlag = true ); + + void setViewShakeInfo( Event *ev ); + void setAdvancedViewShakeInfo( Event *ev ); + void startViewShake( Event *ev ); + void startViewShake( void ); + void updateViewShake( void ); + void reduceViewShake( Event *ev ); + void clearViewShake( Event *ev ); + Vector getViewShake( void ); + + void SetPowerRating( Event *ev ); + void SetPowerRating( float damage ); + + void SetProjectileSpeed( Event *ev ); + void SetProjectileSpeed( float speed ); + void SetProjectileDamage( Event *ev ); + void SetProjectileDamage( float damage ); + void SetBulletSpread( float spreadX , float spreadY ); + + float GetPowerRating(); + float GetBulletSpreadX(firemode_t idx); + float GetRange(firemode_t idx); + float GetProjectileDamage(); + float GetProjectileSpeed(); + + + void ClearMeleeVictims() { meleeVictims.ClearObjectList(); } + void AdvancedMeleeAttack(const char* tag1, const char* tag2, bool critical=false); + float GetBulletDamage(firemode_t idx = FIRE_MODE1); + + Vector getSpread( void ); + void applySpread( Vector *forward, Vector *right, Vector *up ); + + // Need a way to trick the weapon into thinking it's attached so + // that it will drop properly from the player. + void setAttached(bool flag) { attached = flag; } + + /* virtual */ float RespawnTime( void ); + + void noAmmoMode( Event *ev ); + bool hasNoAmmoMode( firemode_t mode ); + + void setNoDelay( Event *ev ); + bool isModeNoDelay( firemode_t mode ); + + void pauseRegen( Event *ev ); + void setChargedModels( Event *ev ); + + void setControllingProjectile( Event *ev ); + bool getControllingProjectile( void ); + + void setCanInterruptFiringState( Event *ev ); + + void setSpreadAnimData( Event *ev ); + + bool canReload( void ); + + int getAmmoInClip( firemode_t mode ); + + void setMaxViewShakeChange( Event *ev ); + + void setControlParms( Event *ev ); + void toggleProjectileControl( void ); + void setProjectileControlHidden( Event *ev ); + + void setMeleeParms( Event *ev ); + + void setFireOffset( Event *ev ); + + /* virtual */ void cacheStrings( void ); + + void setAutoReload( Event *ev ); + void setAllowAutoSwitch( Event *ev ); + void forceReload( Event *ev ); + + /* virtual */ void Think( void ); + + bool shouldAutoSwitch( firemode_t mode ); + + virtual int getStat( int statNum ) { return 0; } + + void autoSwitch(); + + str getWeaponByPriority( int priority ); + int getWeaponPriority(); + + void setNextSwitchTime( Event *ev ); + void setNextSwitchTime( float time ); + + /* virtual */ void ArchivePersistantData( Archiver &arc ); + }; + +inline bool Weapon::shouldArcProjectile() +{ + return _arcProjectile; +} + +inline float Weapon::GetLowArcRange() +{ + return _lowArcRange; +} + +inline bool Weapon::shouldPlayMissSound() +{ + return _playMissSound; +} +inline float Weapon::GetBulletDamage(firemode_t idx) +{ + return bulletdamage[idx]; +} + +inline void Weapon::SetPowerRating( float damage ) +{ + _powerRating = damage; +} + +inline float Weapon::GetPowerRating() +{ + if ( world && world->isThingBroken( item_name.c_str() ) ) + return 0.0f; + + return _powerRating; +} + +inline void Weapon::SetProjectileSpeed( float speed ) +{ + _projectileSpeed = speed; +} + +inline float Weapon::GetProjectileSpeed() +{ + return _projectileSpeed; +} + +inline void Weapon::SetProjectileDamage( float damage ) +{ + _projectileDamage = damage; +} + +inline float Weapon::GetProjectileDamage() +{ + return _projectileDamage; +} + +inline float Weapon::GetBulletSpreadX( firemode_t idx ) +{ + return bulletspread[idx].x; +} + +inline float Weapon::GetRange( firemode_t idx ) +{ + return bulletrange[idx]; +} + +inline void Weapon::Archive( Archiver &arc ) +{ + int i,j; + + Item::Archive( arc ); + + arc.ArchiveBoolean( &attached ); + + arc.ArchiveFloat( &nextweaponsoundtime ); + arc.ArchiveString( ¤t_attachToTag ); + + arc.ArchiveString( &left_attachToTag ); + arc.ArchiveString( &right_attachToTag ); + arc.ArchiveString( &dual_attachToTag ); + arc.ArchiveString( &leftholster_attachToTag ); + arc.ArchiveString( &rightholster_attachToTag ); + arc.ArchiveString( &dualholster_attachToTag ); + + arc.ArchiveFloat( &lastScale ); + arc.ArchiveVector( &lastAngles ); + arc.ArchiveBoolean( &lastValid ); + arc.ArchiveBoolean( &auto_putaway ); + arc.ArchiveBoolean( &use_no_ammo ); + arc.ArchiveBoolean( &crosshair ); + arc.ArchiveBoolean( &torsoaim ); + + arc.ArchiveBoolean( &special_move ); + arc.ArchiveSafePointer( &aim_target ); + + ArchiveEnum( curmode, firemode_t ); + ArchiveEnum( maxmode, firemode_t ); + + arc.ArchiveBoolean( &switchmode ); + arc.ArchiveBoolean( &targetidleflag ); + + arc.ArchiveInteger( &chx ); + arc.ArchiveInteger( &chy ); + + arc.ArchiveVector( &realvieworg ); + arc.ArchiveBoolean( &thirdperson ); + + arc.ArchiveBool( &useActorAiming ); + + arc.ArchiveFloat( &_powerRating ); + arc.ArchiveFloat( &_projectileDamage ); + arc.ArchiveFloat( &_projectileSpeed ); + + arc.ArchiveBool( &_arcProjectile ); + arc.ArchiveFloat( &_lowArcRange ); + arc.ArchiveBool( &_playMissSound ); + + int numEntries; + if ( arc.Saving() ) + { + numEntries = meleeVictims.NumObjects(); + arc.ArchiveInteger( &numEntries ); + + EntityPtr eptr; + for ( int i = 1 ; i <= numEntries ; i++ ) + { + eptr = meleeVictims.ObjectAt( i ); + arc.ArchiveSafePointer( &eptr ); + } + } + else + { + EntityPtr eptr; + EntityPtr *eptrptr; + arc.ArchiveInteger( &numEntries ); + + meleeVictims.Resize( numEntries ); + + for ( int i = 1 ; i <= numEntries ; i++ ) + { + meleeVictims.AddObject( eptr ); + eptrptr = &meleeVictims.ObjectAt( i ); + arc.ArchiveSafePointer( eptrptr ); + } + } + + arc.ArchiveFloat( &maxrange ); + arc.ArchiveFloat( &minrange ); + arc.ArchiveString( &viewmodel ); + + if ( arc.Loading() ) + gi.setviewmodel( edict, viewmodel.c_str() ); + + ArchiveEnum( weaponstate, weaponstate_t ); + arc.ArchiveInteger( &rank ); + arc.ArchiveInteger( &order ); + + arc.ArchiveSafePointer( &last_owner ); + arc.ArchiveFloat( &last_owner_trigger_time ); + arc.ArchiveBoolean( ¬droppable ); + arc.ArchiveInteger( &aimanim ); + arc.ArchiveInteger( &aimframe ); + arc.ArchiveVector( &leftHolsterAngles ); + arc.ArchiveVector( &rightHolsterAngles ); + arc.ArchiveVector( &dualHolsterAngles ); + arc.ArchiveFloat( &holsterScale ); + arc.ArchiveFloat( &_weildedScale ); + arc.ArchiveBoolean( &quiet ); + arc.ArchiveFloat( &next_noise_time ); + arc.ArchiveFloat( &next_noammo_time ); + + arc.ArchiveInteger( &burstcount ); + arc.ArchiveInteger( &burstcountmax ); + + + for ( i=0;i WeaponPtr; + +#endif /* weapon.h */ diff --git a/dlls/game/weaputils.cpp b/dlls/game/weaputils.cpp new file mode 100644 index 0000000..3b85e9c --- /dev/null +++ b/dlls/game/weaputils.cpp @@ -0,0 +1,2755 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/weaputils.cpp $ +// $Revision:: 136 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// General Weapon Utility Functions + +#include "_pch_cpp.h" +//#include "g_local.h" +#include "weaputils.h" +#include "specialfx.h" +#include "sentient.h" +#include "actor.h" +#include "decals.h" +#include "weapon.h" +#include "player.h" +#include "mp_manager.hpp" +#include + +void FlashPlayers +( + const Vector &org, + float r, + float g, + float b, + float a, + float minradius, + float radius, + float minTime, + float time, + int type +); + +qboolean MeleeAttack +( + const Vector &pos, + const Vector &original_end, + float damage, + Entity *attacker, + meansOfDeath_t means_of_death, + float attack_width, + float attack_min_height, + float attack_max_height, + float knockback, + qboolean hit_dead, + Container*victimlist, + Weapon *weapon, + bool critical +) +{ + trace_t trace; + Entity *victim; + Vector dir; + float world_dist; + Vector new_pos; + Entity *skip_ent; + qboolean hit_something = false; + Vector mins; + Vector maxs; + Container potential_victimlist; + int i; + int num_traces; + Vector end; + + end = original_end; + + /* if ( attack_width == 0 ) + attack_width = 15; + if ( attack_min_height == 0 ) + attack_min_height = -15; + if ( attack_max_height == 0 ) + attack_max_height = 15; */ + + if ( attacker && attacker->isSubclassOf( Player ) && ( means_of_death != MOD_FIRE ) && ( means_of_death != MOD_ON_FIRE ) ) + { + Player *player = (Player *)attacker; + player->shotFired(); + } + + // See how far the world is away + + dir = end - pos; + world_dist = dir.length(); + + new_pos = pos; + + skip_ent = attacker; + + num_traces = 0; + + while( new_pos != end ) + { + trace = G_Trace( pos, vec_zero, vec_zero, end, skip_ent, MASK_SOLID, false, "MeleeAttack - World test" ); + + num_traces++; + + if ( trace.fraction < 1.0f ) + { + if ( attacker->isSubclassOf( Actor ) ) + { + Actor *act = (Actor*)attacker; + + if ( trace.entityNum == ENTITYNUM_WORLD ) + act->SetActorFlag ( "meleehitworld" , true ); + else + act->SetActorFlag ( "meleehitworld" , false ); + } + + if ( ( trace.entityNum == ENTITYNUM_WORLD ) || ( trace.ent && trace.ent->entity && !trace.ent->entity->takedamage ) ) + { + dir = trace.endpos - pos; + world_dist = dir.length(); + break; + } + else + { + // Make sure we don't go backwards any in our trace + if ( ( Vector( new_pos - pos ).length() + 0.001f ) >= Vector( trace.endpos - pos ).length() ) + break; + + if ( num_traces > 10 ) + { + // We have done too many traces, stop here + dir = trace.endpos - pos; + world_dist = dir.length(); + break; + } + + new_pos = trace.endpos; + + if ( trace.ent ) + skip_ent = trace.ent->entity; + } + } + else + { + break; + } + } + + // Find things hit + + dir = end - pos; + dir.normalize(); + end = pos + ( dir * world_dist ); + + victim = NULL; + + mins = Vector( -attack_width, -attack_width, attack_min_height ); + maxs = Vector( attack_width, attack_width, attack_max_height ); + + if ( g_showbullettrace->integer ) + { + G_DebugBBox(pos, mins, maxs, 1.0f, 0.0f, 0.0f, 1.0f ); + } + + G_TraceEntities( pos, mins, maxs, end, &potential_victimlist, MASK_MELEE ); + + for( i = 1 ; i <= potential_victimlist.NumObjects() ; i++ ) + { + victim = potential_victimlist.ObjectAt( i ); + + if ( victimlist && victimlist->ObjectInList(victim) ) + continue; + + if ( victim && victim->takedamage && ( victim != attacker ) ) + { + dir = end - pos; + dir.normalize(); + + if ( dir == vec_zero ) + { + dir = victim->centroid - pos; + dir.normalize(); + } + + if ( victim->isSubclassOf( Sentient ) && ( victim->health > 0.0f ) ) + hit_something = true; + + + if ( ( victim->health > 0 ) || hit_dead ) + { + if ( victimlist && victim->isSubclassOf( Sentient ) && ( victim->health > 0 ) ) + victimlist->AddObject( victim ); + + // Override damage if we're using the GameplayManager system + // Comes from From -- + // Actor::MeleeAttack + // Player::AdvancedMeleeAttack + // + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( weapon && gpm->hasObject(weapon->getArchetype()) ) + { + str attacktype = ""; + if ( attacker->isSubclassOf( Player ) ) + { + Player *player = (Player*)attacker; + attacktype = player->getAttackType(); + } + GameplayFormulaData fd(attacker, 0, weapon, attacktype); + switch ( weapon->GetCurMode() ) + { + case FIRE_MODE1: + if ( gpm->hasFormula("DamageMode1") ) + damage = gpm->calculate("DamageMode1", fd); + break; + case FIRE_MODE2: + if ( gpm->hasFormula("DamageMode2") ) + damage = gpm->calculate("DamageMode2", fd); + break; + } + if ( critical && !victim->deadflag ) + { + damage *= 2.0f ; + str snd = gpm->getStringValue( "Criticals.Use", "wav"); + if ( snd.length() ) + { + int channel = CHAN_BODY; + float volume = -1.0f; + float mindist = -1.0f; + if ( gpm->hasProperty( "Criticals.Use","channel") ) + channel = (int)gpm->getFloatValue( "Criticals.Use", "channel"); + if ( gpm->hasProperty( "Criticals.Use","volume") ) + volume = (int)gpm->getFloatValue( "Criticals.Use", "volume"); + if ( gpm->hasProperty( "Criticals.Use","mindist") ) + mindist = (int)gpm->getFloatValue( "Criticals.Use", "mindist"); + weapon->Sound(snd, channel, volume, mindist); + } + + str effect = gpm->getStringValue("Criticals.Use", "tikifx"); + if ( effect.length() ) + { + Vector pos, pos2, fxpos; + weapon->GetActorMuzzlePosition(&pos, NULL, NULL, NULL, "tag_swipe1"); + weapon->GetActorMuzzlePosition(&pos2, NULL, NULL, NULL, "tag_swipe2"); + fxpos = (pos + pos2) / 2.0f; // Spark is halfway between the two points + weapon->SpawnEffect(effect, fxpos, Vector(0.0f,0.0f,0.0f), 2.0f); + } + } + + if ( gpm->hasFormula("Knockback") ) + knockback = gpm->calculate("Knockback", fd); + } + + Vector painDir; + + painDir = victim->centroid - pos; + painDir.normalize(); + + victim->Damage( attacker, attacker, damage, pos, painDir, vec_zero, knockback, 0, means_of_death, -1, -1, weapon ); + } + } + } + + if ( hit_something && attacker && attacker->isSubclassOf( Player ) && ( means_of_death != MOD_FIRE ) && ( means_of_death != MOD_ON_FIRE ) ) + { + Player *player = (Player *)attacker; + player->shotHit(); + } + + return hit_something; +} + +Event EV_Projectile_Speed +( + "speed", + EV_DEFAULT, + "f", + "projectileSpeed", + "set the speed of the projectile" +); +Event EV_Projectile_MinSpeed +( + "minspeed", + EV_TIKIONLY, + "f", + "minspeed", + "set the minimum speed of the projectile (this is for charge up weapons)" +); +Event EV_Projectile_ChargeSpeed +( + "chargespeed", + EV_TIKIONLY, + NULL, + NULL, + "set the projectile's speed to be determined by the charge time" +); +Event EV_Projectile_Damage +( + "hitdamage", + EV_TIKIONLY, + "f", + "projectileHitDamage", + "set the damage a projectile does when it hits something" +); +Event EV_Projectile_Life +( + "life", + EV_DEFAULT, + "f", + "projectileLife", + "set the life of the projectile" +); +Event EV_Projectile_MinLife +( + "minlife", + EV_TIKIONLY, + "f", + "minProjectileLife", + "set the minimum life of the projectile (this is for charge up weapons)" +); +Event EV_Projectile_ChargeLife +( + "chargelife", + EV_TIKIONLY, + NULL, + NULL, + "set the projectile's life to be determined by the charge time" +); +Event EV_Projectile_Knockback +( + "knockback", + EV_TIKIONLY, + "f", + "projectileKnockback", + "set the knockback of the projectile when it hits something" +); +Event EV_Projectile_DLight +( + "dlight", + EV_TIKIONLY, + "ffff", + "color_red color_green color_blue intensity", + "set the color and intensity of the dynamic light on the projectile" +); +Event EV_Projectile_Avelocity +( + "avelocity", + EV_TIKIONLY, + "SFSFSF", + "[random|crandom] yaw [random|crandom] pitch [random|crandom] roll", + "set the angular velocity of the projectile" +); +Event EV_Projectile_MeansOfDeath +( + "meansofdeath", + EV_TIKIONLY, + "s", + "meansOfDeath", + "set the meansOfDeath of the projectile" +); +Event EV_Projectile_BeamCommand +( + "beam", + EV_TIKIONLY, + "sSSSSSS", + "command arg1 arg2 arg3 arg4 arg5 arg6", + "send a command to the beam of this projectile" +); +Event EV_Projectile_UpdateBeam +( + "updatebeam", + EV_CODEONLY, + NULL, + NULL, + "Update the attached beam" +); +Event EV_Projectile_SetQuietExpire +( + "setquietexpire", + EV_TIKIONLY, + NULL, + NULL, + "If projectile times out without dying, make it not play explosion tiki" +); +Event EV_Projectile_BounceFactor +( + "bouncefactor", + EV_TIKIONLY, + "f", + "bounceFactor", + "set the amount of velocity retained when a projectile bounces" +); +Event EV_Projectile_BounceTouch +( + "bouncetouch", + EV_TIKIONLY, + "s", + "bouncetype", + "Make the projectile bounce when it hits a non-damageable solid" +); +Event EV_Projectile_BounceSound +( + "bouncesound", + EV_DEFAULT, + NULL, + NULL, + "Set the name of the sound that is played when the projectile bounces" +); +Event EV_Projectile_Explode +( + "explode", + EV_DEFAULT, + NULL, + NULL, + "Make the projectile explode" +); +Event EV_Projectile_ImpactMarkShader +( + "impactmarkshader", + EV_TIKIONLY, + "s", + "shader", + "Set the impact mark of the shader" +); +Event EV_Projectile_ImpactMarkRadius +( + "impactmarkradius", + EV_TIKIONLY, + "f", + "radius", + "Set the radius of the impact mark" +); +Event EV_Projectile_ImpactMarkOrientation +( + "impactmarkorientation", + EV_TIKIONLY, + "f", + "degrees", + "Set the orientation of the impact mark" +); +Event EV_Projectile_SetExplosionModel +( + "explosionmodel", + EV_TIKIONLY, + "s", + "modelname", + "Set the modelname of the explosion to be spawned" +); +Event EV_Projectile_SetAddVelocity +( + "addvelocity", + EV_TIKIONLY, + "fff", + "velocity_x velocity_y velocity_z", + "Set a velocity to be added to the projectile when it is created" +); +Event EV_Projectile_AddOwnerVelocity +( + "addownervelocity", + EV_TIKIONLY, + "b", + "bool", + "Set whether or not the owner's velocity is added to the projectile's velocity" +); +Event EV_Projectile_HeatSeek +( + "heatseek", + EV_TIKIONLY, + NULL, + NULL, + "Make the projectile heat seek" +); +Event EV_Projectile_Drunk +( + "drunk", + EV_TIKIONLY, + "BF", + "drunkFlag angleModifier", + "Make the projectile drunk" +); +Event EV_Projectile_SetCanHitOwner +( + "canhitowner", + EV_TIKIONLY, + NULL, + NULL, + "Make the projectile be able to hit its owner" +); +Event EV_Projectile_ClearOwner +( + "clearowner", + EV_CODEONLY, + NULL, + NULL, + "Make the projectile be able to hit its owner now" +); +Event EV_Projectile_RemoveWhenStopped +( + "removewhenstopped", + EV_TIKIONLY, + NULL, + NULL, + "Make the projectile get removed when it stops" +); +Event EV_Projectile_SetOwnerControl +( + "ownercontrol", + EV_TIKIONLY, + NULL, + NULL, + "Make the projectile's angles controlled by the player" +); +Event EV_Projectile_SetOwnerControlLaser +( + "ownerControlLaser", + EV_TIKIONLY, + "B", + "bool", + "Make's the projectile's angles controlled by the player (laser method)" +); +Event EV_Projectile_SetControlTurnSpeed +( + "controlTurnSpeed", + EV_TIKIONLY, + "f", + "controlTurnSpeed", + "Sets the max turn speed of a controlled projectile" +); +Event EV_Projectile_StickOnTouch +( + "stickontouch", + EV_TIKIONLY, + "B", + "stick_on_touch", + "sets whether or not the projectile sticks on contact" +); +Event EV_Projectile_TriggerDetonate +( + "triggerdetonate", + EV_TIKIONLY, + "B", + "bool", + "Make the projectile explode when triggered." +); +Event EV_Projectile_AngleThink +( + "anglethink", + EV_TIKIONLY, + NULL, + NULL, + "Make the projectile update it's angles in flight" +); +Event EV_Projectile_ScaleByCharge +( + "scaleByCharge", + EV_TIKIONLY, + "ff", + "minScale maxScale", + "Makes the projectile size scaled by the charge." +); +Event EV_Projectile_ScaleExplosion +( + "scaleExplosion", + EV_TIKIONLY, + NULL, + NULL, + "Make the explosion scale with the projectile." +); + +Event EV_Projectile_NotifyActors +( + "notifyactors", + EV_TIKIONLY, + NULL, + NULL, + "Notify's active actors when close" +); +Event EV_Projectile_NotShootable +( + "proj_NotShootable", + EV_TIKIONLY, + NULL, + NULL, + "Specifies that this projectile is not shootable and can't hit shootable only stuff." +); +Event EV_Projectile_HitsProjectiles +( + "proj_HitsProjectiles", + EV_TIKIONLY, + "b", + "hitsProjectilesBool", + "Specifies whether or not this projectile will hit other projectiles." +); +Event EV_Projectile_MinOnGroundTime +( + "minOnGroundTime", + EV_TIKIONLY, + "f", + "minOnGroundTime", + "Specifies how long the projectile has to stay on the ground until it changes it's animation." +); + +CLASS_DECLARATION( Entity, Projectile, NULL ) +{ + { &EV_Touch, &Projectile::Touch }, + { &EV_Projectile_Speed, &Projectile::SetSpeed }, + { &EV_Projectile_MinSpeed, &Projectile::SetMinSpeed }, + { &EV_Projectile_ChargeSpeed, &Projectile::SetChargeSpeed }, + { &EV_Projectile_Damage, &Projectile::SetDamage }, + { &EV_Projectile_Life, &Projectile::SetLife }, + { &EV_Projectile_MinLife, &Projectile::SetMinLife }, + { &EV_Projectile_ChargeLife, &Projectile::SetChargeLife }, + { &EV_Projectile_Knockback, &Projectile::SetKnockback }, + { &EV_Projectile_DLight, &Projectile::SetDLight }, + { &EV_Projectile_Avelocity, &Projectile::SetAvelocity }, + { &EV_Projectile_MeansOfDeath, &Projectile::SetMeansOfDeath }, + { &EV_Projectile_SetQuietExpire, &Projectile::SetQuietExpire }, + { &EV_Projectile_BounceFactor, &Projectile::SetBounceFactor }, + { &EV_Projectile_BounceTouch, &Projectile::SetBounceTouch }, + { &EV_Projectile_BounceSound, &Projectile::SetBounceSound }, + { &EV_Projectile_BeamCommand, &Projectile::BeamCommand }, + { &EV_Projectile_UpdateBeam, &Projectile::UpdateBeam }, + { &EV_Projectile_Explode, &Projectile::Explode }, + { &EV_Projectile_ImpactMarkShader, &Projectile::SetImpactMarkShader }, + { &EV_Projectile_ImpactMarkRadius, &Projectile::SetImpactMarkRadius }, + { &EV_Projectile_ImpactMarkOrientation, &Projectile::SetImpactMarkOrientation }, + { &EV_Projectile_SetExplosionModel, &Projectile::SetExplosionModel }, + { &EV_Projectile_SetAddVelocity, &Projectile::SetAddVelocity }, + { &EV_Projectile_AddOwnerVelocity, &Projectile::AddOwnerVelocity }, + { &EV_Projectile_HeatSeek, &Projectile::HeatSeek }, + { &EV_Projectile_Drunk, &Projectile::Drunk }, + { &EV_Projectile_SetCanHitOwner, &Projectile::SetCanHitOwner }, + { &EV_Projectile_ClearOwner, &Projectile::ClearOwner }, + { &EV_Projectile_RemoveWhenStopped, &Projectile::RemoveWhenStopped }, + { &EV_Killed, &Projectile::destroyed }, + { &EV_Stop, &Projectile::Stopped }, + { &EV_Projectile_SetOwnerControl, &Projectile::SetOwnerControl }, + { &EV_Projectile_SetOwnerControlLaser, &Projectile::setOwnerControlLaser }, + { &EV_Projectile_SetControlTurnSpeed, &Projectile::setControlTurnSpeed }, + { &EV_Projectile_StickOnTouch, &Projectile::StickOnTouch }, + { &EV_Projectile_TriggerDetonate, &Projectile::TriggerDetonate }, + { &EV_Projectile_AngleThink, &Projectile::AngleThink }, + { &EV_Projectile_ScaleByCharge, &Projectile::setScaleByCharge }, + { &EV_Projectile_ScaleExplosion, &Projectile::setScaleExplosion }, + { &EV_Projectile_NotifyActors, &Projectile::setNotifyActors }, + { &EV_Projectile_NotShootable, &Projectile::setNotShootable }, + { &EV_Projectile_HitsProjectiles, &Projectile::setHitsProjectiles }, + { &EV_Projectile_MinOnGroundTime, &Projectile::setMinOnGroundTime }, + { NULL, NULL } +}; + +//-------------------------------------------------------------- +// Name: Projectile() +// Class: Projectile +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +Projectile::Projectile() +{ + animate = new Animate( this ); + + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + m_beam = NULL; + speed = 0; + minspeed = 0; + bouncefactor = 0; + damage = 0; + life = 5; + knockback = 0; + dlight_radius = 0; + dlight_color = Vector( 1.0f, 1.0f, 1.0f ); + avelocity = Vector( 0.0f, 0.0f, 0.0f ); + mins = Vector( -1.0f, -1.0f, -1.0f ); + maxs = Vector( 1.0f, 1.0f, 1.0f ); + meansofdeath = (meansOfDeath_t )0; + projFlags = 0; + gravity = 0; + impactmarkradius = 10; + charge_fraction = 1.0f; + target = NULL; + addownervelocity = true; + drunk = false; + can_hit_owner = false; + remove_when_stopped = false; + stick_on_touch = false; + takedamage = DAMAGE_NO; + ownercontrol = false; + _ownerControlLaser = false; + _ownerControlUsed = false; + _controlTurnSpeed = 20.0f; + firstTimeOwnerControl = true; + triggerdetonate = false; + drunkAngleModifier = 1.0; + _notifyActors = false; + + _scaleByCharge = false; + _scaleExplosion = false; + + _minScaleFromCharge = 1.0f; + _maxScaleFromCharge = 1.0f; + + // make this shootable but non-solid on the client + setContents( CONTENTS_SHOOTABLE_ONLY ); + setFullTrace( true ); + // + // touch triggers by default + // + flags |= FL_TOUCH_TRIGGERS; + + startTime = level.time; + + _notShootable = false; + _hitsProjectiles = true; + + _minOnGroundTime = 0.0f; + _onGround = false; + _startOnGroundTime = 0.0f; + + minlife = 0.0f; + + _heatSeek = false; + + _damagedSomething = false; +} + +//-------------------------------------------------------------- +// Name: ~Projectile() +// Class: Projectile +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +Projectile::~Projectile() +{ + detach(); +} + +float Projectile::ResolveMinimumDistance( Entity *potential_target, float currmin ) +{ + float currdist; + float dot; + Vector angle; + Vector delta; + Vector norm; + float sine = 0.4f; + + delta = potential_target->centroid - this->origin; + + norm = delta; + norm.normalize(); + + // Test if the target is in front of the missile + dot = norm * orientation[ 0 ]; + if ( dot < 0.0f ) + { + return currmin; + } + + // Test if we're within the rocket's viewcone (45 degree cone) + dot = norm * orientation[ 1 ]; + if ( fabs( dot ) > sine ) + { + return currmin; + } + + dot = norm * orientation[ 2 ]; + if ( fabs( dot ) > sine ) + { + return currmin; + } + + currdist = delta.length(); + if ( currdist < currmin ) + { + currmin = currdist; + target = potential_target; + } + + return currmin; +} + +float Projectile::AdjustAngle( float maxadjust, float currangle, float targetangle ) +{ + float dangle; + float magangle; + + dangle = currangle - targetangle; + + if ( dangle ) + { + magangle = ( float )fabs( dangle ); + + while( magangle >= 360.0f ) + { + magangle -= 360.0f; + } + + if ( magangle < maxadjust ) + { + currangle = targetangle; + } + else + { + if ( magangle > 180.0f ) + { + maxadjust = -maxadjust; + } + if ( dangle > 0 ) + { + maxadjust = -maxadjust; + } + currangle += maxadjust; + } + } + + while( currangle >= 360.0f ) + { + currangle -= 360.0f; + } + + while( currangle < 0.0f ) + { + currangle += 360.0f; + } + + return currangle; +} + +void Projectile::Drunk( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + drunk = ev->GetBoolean( 1 ); + else + drunk = true; + + if ( ev->NumArgs() > 1 ) + drunkAngleModifier = ev->GetFloat( 2 ); + + if ( drunk ) + { + turnThinkOn(); + } +} + +void Projectile::TriggerDetonate( Event *ev ) +{ + if( ev->NumArgs() > 0 ) + triggerdetonate = ev->GetBoolean(1); + else + triggerdetonate = true; + + if ( triggerdetonate ) + { + turnThinkOn(); + } +} + +void Projectile::SetOwnerControl( Event *ev ) +{ + ownercontrol = ev->GetBoolean( 1 ); + + if ( ownercontrol ) + { + turnThinkOn(); + } +} + +void Projectile::setOwnerControlLaser( Event *ev ) +{ + if ( ev->NumArgs() > 0 ) + { + _ownerControlLaser = ev->GetBoolean( 1 ); + } + else + { + _ownerControlLaser = true; + } +} + +void Projectile::setControlTurnSpeed( Event *ev ) +{ + _controlTurnSpeed = ev->GetFloat( 1 ); +} + +void Projectile::HeatSeek( Event *ev ) +{ + float mindist; + Entity *ent; + trace_t trace; + Vector delta; + Vector v; + int n; + int i; + + _heatSeek = true; + + if ( ( !target ) || ( target == world ) ) + { + mindist = 8192.0f; + + n = SentientList.NumObjects(); + for( i = 1; i <= n; i++ ) + { + ent = SentientList.ObjectAt( i ); + if ( ent->entnum == owner ) + { + continue; + } + + if ( ( ( ent->takedamage != DAMAGE_AIM ) || ( ent->health <= 0.0f ) ) && !( edict->svflags & SVF_MONSTER ) ) + { + continue; + } + + trace = G_Trace( this->origin, vec_zero, vec_zero, ent->centroid, this, MASK_SHOT, false, "DrunkMissile::HeatSeek" ); + if ( ( trace.fraction != 1.0f ) && ( trace.ent != ent->edict ) ) + { + continue; + } + + mindist = ResolveMinimumDistance( ent, mindist ); + } + } + else + { + float angspeed; + + delta = target->centroid - this->origin; + v = delta.toAngles(); + + angspeed = 5.0f; + angles.x = AdjustAngle( angspeed, angles.x, v.x ); + angles.y = AdjustAngle( angspeed, angles.y, v.y ); + angles.z = AdjustAngle( angspeed, angles.z, v.z ); + + setAngles( angles ); + velocity = Vector( orientation[ 0 ] ) * speed; + } + PostEvent( EV_Projectile_HeatSeek, 0.1f ); + //turnThinkOn(); +} + +void Projectile::AngleThink( Event *ev ) +{ + turnThinkOn(); +} + +void Projectile::NotifyActors() +{ + Actor *act; + Vector selfToActor; + float distanceToActor; + Event *projCloseEvent; + //First loop through the awake list + for( int i = 1; i <= ActiveList.NumObjects(); i++ ) + { + act = ActiveList.ObjectAt( i ); + selfToActor = act->origin - origin; + distanceToActor = selfToActor.length(); + if ( distanceToActor < 192 ) + { + projCloseEvent = new Event(EV_Actor_ProjectileClose); + projCloseEvent->AddEntity( G_GetEntity(owner) ); + act->ProcessEvent(projCloseEvent); + } + } + +} + +void Projectile::Think( void ) +{ + Vector end; + Vector f, r, u; + Vector delta; + Entity *own; + Player *player; + + + if ( _notifyActors ) + { + NotifyActors(); + } + + if ( drunk ) + { + angles += Vector( G_CRandom( drunkAngleModifier ), G_CRandom( drunkAngleModifier ), 0.0f ); + velocity = Vector( orientation[ 0 ] ) * speed; + } + + if ( !_onGround ) + { + setAngles( velocity.toAngles() ); + } + + // Determine if the projectile is on the ground (if needed) + + if ( ( _minOnGroundTime > 0.0f ) && !_onGround ) + { + trace_t trace = G_Trace( origin, vec_zero, vec_zero, origin - Vector( 0, 0, maxs[2] - mins[2] ) , this, MASK_PROJECTILE, false, "Projectile::Think" ); + + if ( trace.ent && trace.ent->entity && trace.ent->entity == world ) + { + // Projectile is on the ground + + if ( _startOnGroundTime == 0.0f ) + { + _startOnGroundTime = level.time; + } + + if ( _startOnGroundTime + _minOnGroundTime < level.time ) + { + _onGround = true; + + if ( animate && animate->HasAnim( "onground" ) ) + { + animate->RandomAnimate( "onground" ); + } + + velocity = vec_zero; + } + } + else + { + _startOnGroundTime = 0.0f; + } + } + + if ( this->owner != ENTITYNUM_NONE ) + { + own = G_GetEntity(owner); + if ( own && own->isSubclassOf( Player ) ) + { + player = (Player *)own; + + if ( ownercontrol ) + { + if ( firstTimeOwnerControl && !_ownerControlLaser ) + { + setAngles( player->GetVAngles() ); + prevPlayerAngles = player->GetVAngles(); + firstTimeOwnerControl = false; + //towcam = new Camera; + //towcam->setOrigin( origin ); + //towcam->setAngles( player->GetVAngles() ); + //towcam->FollowEntity((Entity *)this, 25.0, MASK_SOLID, (Entity *)this); + //player->SetCamera(towcam, 0); + } + else + { + if ( _ownerControlLaser ) + { + Vector pos; + Vector dir; + Vector forward; + Weapon *weapon; + Vector goalAngles; + + weapon = player->GetActiveWeapon( WEAPON_DUAL ); + + if ( weapon && weapon->getControllingProjectile() ) + { + if ( !_ownerControlUsed ) + { + _ownerControlUsed = true; + + if ( animate && animate->HasAnim( "controlled" ) ) + { + animate->RandomAnimate( "controlled" ); + } + } + + weapon->GetMuzzlePosition( &pos, &forward, NULL, NULL ); + + trace_t trace = G_Trace( pos, vec_zero, vec_zero, pos + 2000 * forward, NULL, MASK_SOLID, false, "Projectile::Think" ); + + dir = trace.endpos - origin; + goalAngles = dir.toAngles(); + + delta = goalAngles - angles; + + delta.EulerNormalize(); + + // Don't let the projectile try to turn all of the way around + + if ( ( delta[ YAW ] < -135.0f ) || ( delta[ YAW ] > 135.0f ) || + ( delta[ PITCH ] < -135.0f ) || ( delta[ PITCH ] > 135.0f ) ) + { + delta[ YAW ] = 0.0f; + delta[ PITCH ] = 0.0f; + } + + delta.EulerNormalize(); + + if ( delta[ YAW ] < -_controlTurnSpeed ) + delta[ YAW ] = -_controlTurnSpeed; + if ( delta[ YAW ] > _controlTurnSpeed ) + delta[ YAW ] = _controlTurnSpeed; + if ( delta[ PITCH ] < -_controlTurnSpeed ) + delta[ PITCH ] = -_controlTurnSpeed; + if ( delta[ PITCH ] > _controlTurnSpeed ) + delta[ PITCH ] = _controlTurnSpeed; + + setAngles( angles + delta ); + } + } + else + { + delta = (player->GetVAngles() - prevPlayerAngles); + prevPlayerAngles = player->GetVAngles(); + + if ( player->isButtonDown( BUTTON_ATTACKRIGHT ) ) + { + setAngles( angles + delta ); + } + } + } + AngleVectors( angles, f, r, u ); + orientation[0][0] = f.x; orientation[0][1] = f.y; orientation[0][2] = f.z; + orientation[1][0] = r.x; orientation[1][1] = r.y; orientation[1][2] = r.z; + orientation[2][0] = u.x; orientation[2][1] = u.y; orientation[2][2] = u.z; + } + + if ( triggerdetonate ) + { + if ( ( player->getHealth() <= 0.0f ) || ( player->deadflag != DEAD_NO ) ) + { + PostEvent( EV_Remove, 0.0f ); + } + else if ( player->GetProjDetonate() ) + { + Event *newEvent = new Event( EV_Player_Weapon ); + newEvent->AddString( "dual" ); + newEvent->AddString( "nextSwitchTime" ); + newEvent->AddFloat( 0.0f ); + player->ProcessEvent( newEvent ); + + if ( level.time > startTime + minlife ) + ProcessEvent( EV_Projectile_Explode ); + else + PostEvent( EV_Projectile_Explode, startTime + minlife - level.time ); + } + + } + } + } + else + setAngles( angles ); + + if ( ownercontrol ) + { + velocity = Vector( orientation[ 0 ] ) * speed; + } +} + +void Projectile::AddOwnerVelocity( Event *ev ) +{ + addownervelocity = ev->GetBoolean( 1 ); +} + +void Projectile::SetAddVelocity( Event *ev ) +{ + addvelocity.x = ev->GetFloat( 1 ); + addvelocity.y = ev->GetFloat( 2 ); + addvelocity.z = ev->GetFloat( 3 ); +} + +void Projectile::SetExplosionModel( Event *ev ) +{ + explosionmodel = ev->GetString( 1 ); + + CacheResource( explosionmodel, this ); +} + +void Projectile::SetImpactMarkShader( Event *ev ) +{ + impactmarkshader = ev->GetString( 1 ); +} + +void Projectile::SetImpactMarkRadius( Event *ev ) +{ + impactmarkradius = ev->GetFloat( 1 ); +} + +void Projectile::SetImpactMarkOrientation( Event *ev ) +{ + impactmarkorientation = ev->GetString( 1 ); +} + +void Projectile::Explode( Event *ev ) +{ + Entity *owner; + Entity *ignoreEnt=NULL; + + if ( ev->NumArgs() == 1 ) + ignoreEnt = ev->GetEntity( 1 ); + + // Get the owner of this projectile + owner = G_GetEntity( this->owner ); + + // If the owner's not here, make the world the owner + if ( !owner ) + owner = world; + + takedamage = DAMAGE_NO; + + if (! ((level.time - startTime >= life-FRAMETIME) && (projFlags & P_QUIET_EXPIRE)) ) // if projectile didn't expire by timeout + { // or isn't set to quiet_expire (FRAMETIME required due to float roundoff errors) + // Spawn an explosion model + if ( explosionmodel.length() ) + { + // Move the projectile back off the surface a bit so we can see + // explosion effects. + Vector dir, v; + float scale; + + v = velocity; + v.normalize(); + dir = v; + //v = origin - ( v * 36 ); + + if ( _scaleExplosion ) + scale = edict->s.scale; + else + scale = 1.0f; + + //ExplosionAttack( v, owner, explosionModleToUse, dir, ignoreEnt, scale ); + ExplosionAttack( centroid, owner, explosionmodel, dir, ignoreEnt, scale ); + } + } + + CancelEventsOfType( EV_Projectile_UpdateBeam ); + + // Kill the beam + if ( m_beam ) + { + m_beam->ProcessEvent( EV_Remove ); + m_beam = NULL; + } + + // Remove the projectile + + PostEvent( EV_Remove, 0.0f ); +} + +//---------------------------------------------------------------- +// Name: SetQuietExpire +// Class: Projectile +// +// Description: sets projectile to not spawn explosionmodel if it times out +// +// Parameters: None +// +// Returns: void +//---------------------------------------------------------------- +void Projectile::SetQuietExpire( Event *ev ) +{ + projFlags |= P_QUIET_EXPIRE; // set projectile to not spawn explosionmodel if it times out +} + +void Projectile::SetBounceFactor( Event *ev ) +{ + bouncefactor = ev->GetFloat(1); +} + +void Projectile::SetBounceTouch( Event *ev ) +{ + // if arg1 is "all", make projectile bounce off *everything* + if (ev->NumArgs() > 0) + { + str t = ev->GetString( 1 ); + // add more tests here if we want them (for p_bounce_actor, p_bounce_player, etc) + if (!t.icmp("all")) + { + projFlags |= P_BOUNCE_ALL; + } + } // "bouncetouch all" should fall through and set P_BOUNCE_TOUCH and movetype anyway + + projFlags |= P_BOUNCE_TOUCH; + setMoveType( MOVETYPE_BOUNCE ); +} + +void Projectile::BeamCommand( Event *ev ) +{ + int i; + + if ( !m_beam ) + { + m_beam = new FuncBeam; + + m_beam->setOrigin( this->origin ); + m_beam->Ghost( NULL ); + } + + Event *beamev = new Event( ev->GetToken( 1 ) ); + + for( i=2; i<=ev->NumArgs(); i++ ) + { + beamev->AddToken( ev->GetToken( i ) ); + } + + m_beam->ProcessEvent( beamev ); + PostEvent( EV_Projectile_UpdateBeam, level.frametime ); +} + +void Projectile::UpdateBeam( Event *ev ) +{ + if ( m_beam ) + { + m_beam->setOrigin( this->origin ); + PostEvent( EV_Projectile_UpdateBeam, level.frametime ); + } +} + +void Projectile::SetBounceSound( Event *ev ) +{ + bouncesound = ev->GetString( 1 ); +} + +void Projectile::SetChargeLife( Event *ev ) +{ + projFlags |= P_CHARGE_LIFE; +} + +void Projectile::SetMinLife( Event *ev ) +{ + minlife = ev->GetFloat( 1 ); + projFlags |= P_CHARGE_LIFE; +} + +void Projectile::SetLife( Event *ev ) +{ + life = ev->GetFloat( 1 ); +} + +void Projectile::SetSpeed( Event *ev ) +{ + speed = ev->GetFloat( 1 ); +} + +void Projectile::SetMinSpeed( Event *ev ) +{ + minspeed = ev->GetFloat( 1 ); + projFlags |= P_CHARGE_SPEED; +} + +void Projectile::SetChargeSpeed( Event *ev ) +{ + projFlags |= P_CHARGE_SPEED; +} + +void Projectile::SetAvelocity( Event *ev ) +{ + int i=1; + int j=0; + str vel; + + if ( ev->NumArgs() < 3 ) + { + warning( "ClientGameCommandManager::SetAngularVelocity", "Expecting at least 3 args for command randvel" ); + } + + while ( j < 3 ) + { + vel = ev->GetString( i++ ); + if ( vel == "crandom" ) + { + avelocity[j++] = ev->GetFloat( i++ ) * crandom(); + } + else if ( vel == "random" ) + { + avelocity[j++] = ev->GetFloat( i++ ) * random(); + } + else + { + avelocity[j++] = (float)atof( vel.c_str() ); + } + } +} + +void Projectile::SetDamage( Event *ev ) +{ + damage = ev->GetFloat( 1 ); +} + +void Projectile::SetKnockback( Event *ev ) +{ + knockback = ev->GetFloat( 1 ); +} + +void Projectile::SetDLight( Event *ev ) +{ + dlight_color[0] = ev->GetFloat( 1 ); + dlight_color[1] = ev->GetFloat( 2 ); + dlight_color[2] = ev->GetFloat( 3 ); + dlight_radius = ev->GetFloat( 4 ); +} + +void Projectile::SetMeansOfDeath( Event *ev ) +{ + meansofdeath = (meansOfDeath_t )MOD_NameToNum( ev->GetString( 1 ) ); +} + +void Projectile::DoDecal( void ) +{ + if ( impactmarkshader.length() ) + { + Decal *decal = new Decal; + decal->setShader( impactmarkshader ); + decal->setOrigin( level.impact_trace.endpos ); + decal->setDirection( level.impact_trace.plane.normal ); + decal->setOrientation( impactmarkorientation ); + decal->setRadius( impactmarkradius ); + } +} + +void Projectile::Touch( Event *ev ) +{ + // Other is what got hit + Entity *other = ev->GetEntity( 1 ); + assert( other ); + + // *really* bouncy projectile + if ( projFlags & P_BOUNCE_ALL ) + { + if (bouncefactor) + velocity *= bouncefactor; + return; + } + + // Don't touch teleporters + if ( other->isSubclassOf( Teleporter ) ) + { + return; + } + + // Can't hit yourself with a projectile + if ( other->entnum == edict->ownerNum ) + { + return; + } + + // Remove it if we hit the sky + if ( HitSky() ) + { + PostEvent( EV_Remove, 0.0f ); + return; + } + + if ( can_hit_owner ) + { + ProcessEvent( EV_Projectile_ClearOwner ); + } + + // Bouncy Projectile + if ( ( projFlags & P_BOUNCE_TOUCH ) && !other->takedamage ) + //( !other->takedamage || other->health <= 0 ) ) + { + Actor *act = (Actor *)other; + + if ( other->isSubclassOf(Actor) ) + { + if ( act->GetActorFlag( ACTOR_FLAG_BOUNCE_OFF ) && (act->Immune( meansofdeath ) || act->takedamage == DAMAGE_NO )) + { + // Remove the owner so it can be hit after the bounce + //this->owner = ENTITYNUM_NONE; + //edict->ownerNum = ENTITYNUM_NONE; + edict->ownerNum = other->entnum; + if ( bouncesound.length() ) + Sound( bouncesound, CHAN_BODY ); + + //Bounce The Projectile + velocity *= act->bounce_off_velocity; + + // Tell the actor something just bounced off of it + + Event *event = new Event( EV_Actor_BounceOff ); + event->AddVector( origin ); + act->PostEvent( event, 0.0f ); + return; + } + } + + if (bouncefactor) + velocity *= bouncefactor; + + // Play a bouncy sound + if ( !_onGround && bouncesound.length() ) + { + Sound( bouncesound, CHAN_BODY ); + } + BroadcastSound(); + return; + } + + if ( stick_on_touch && other->projectilesCanStickToMe ) + { + turnThinkOff(); + Vector forward; + angles.AngleVectors(&forward); + forward.normalize(); + + Vector endOfTrace = ( forward * 1000.0f ) + origin; + if ( other->entnum == ENTITYNUM_WORLD ) + { + if ( animate && animate->HasAnim( "stuck" ) ) + { + animate->RandomAnimate( "stuck" ); + } + + trace_t trace = G_Trace( origin, vec_zero, vec_zero, endOfTrace, NULL, MASK_PROJECTILE, false, "ProjectileTouch" ); + + if( trace.ent && trace.ent->entity && trace.ent->entity == world ) + { + origin.x = trace.endpos[0]; + origin.y = trace.endpos[1]; + origin.z = trace.endpos[2]; + setOrigin(origin); + } + + setMoveType( MOVETYPE_NONE ); + + CancelEventsOfType( EV_Projectile_AngleThink ); + velocity = Vector(0, 0, 0); + return; + } + else + { + if ( animate && animate->HasAnim( "stuck_body" ) ) + { + animate->RandomAnimate( "stuck_body" ); + } + + const trace_t trace = G_FullTrace( origin, vec_zero, vec_zero, endOfTrace, this, MASK_PROJECTILE, false, "ProjectileTouch" ); + + if( trace.ent && trace.ent->entity ) + { + const Vector targetDimensions( Vector( trace.ent->maxs ) - Vector( trace.ent->mins ) ); + + const Vector translation( + ( G_Random( 0.24f ) - 0.12f ) * targetDimensions.x, // Lots of magic numbers that generally makes the + ( G_Random( 0.24f ) - 0.12f ) * targetDimensions.y, // projectile stick in the target's torso + ( G_Random( 0.12f ) + 0.12f ) * targetDimensions.z + ); + + const float yawOffset = angles.yaw() - trace.ent->entity->angles.yaw(); + const float pitchOffset = angles.pitch() - trace.ent->entity->angles.pitch(); + const Vector rotation( pitchOffset, yawOffset, 0 ); + + if ( !attach( trace.ent->entity->entnum, -1, false, translation, rotation ) ) + { + PostEvent( EV_Remove, 0.0f ); + + } + } + + setMoveType( MOVETYPE_NONE ); + CancelEventsOfType( EV_Projectile_AngleThink ); + velocity = Vector(0, 0, 0); + } + } + else + { + // See if we should bounce off object + if ( other->isSubclassOf( Actor ) ) + { + Actor *act = (Actor *)other; + + if ( act->GetActorFlag( ACTOR_FLAG_BOUNCE_OFF ) && (act->Immune( meansofdeath ) || act->takedamage == DAMAGE_NO )) + { + // Remove the owner so it can be hit after the bounce + //this->owner = ENTITYNUM_NONE; + //edict->ownerNum = ENTITYNUM_NONE; + edict->ownerNum = other->entnum; + if ( bouncesound.length() ) + Sound( bouncesound, CHAN_BODY ); + + //Bounce The Projectile + velocity *= act->bounce_off_velocity; + + // Tell the actor something just bounced off of it + + Event *event = new Event( EV_Actor_BounceOff ); + event->AddVector( origin ); + act->PostEvent( event, 0.0f ); + return; + } + } + + // Remove the projectile + PostEvent( EV_Remove, 0.0f ); + // Call the explosion event + Event *explEv; + explEv = new Event( EV_Projectile_Explode ); + explEv->AddEntity( other ); + ProcessEvent( explEv ); + } + + + // Make the projectile not solid + setSolidType( SOLID_NOT ); + setMoveType( MOVETYPE_NONE ); + //hideModel(); + + // Do a decal + DoDecal(); + + // Get the owner of this projectile + Entity *myOwner = G_GetEntity( owner ); + + // If the owner's not here, make the world the owner + if ( !myOwner ) + myOwner = world; + + + str realname = GetRandomAlias( "impact_world" ); + if ( other->isSubclassOf( Sentient ) ) + realname = GetRandomAlias( "impact_flesh" ); + + if ( realname.length() > 1 ) + Sound( realname, CHAN_VOICE ); + + + // + // Override damage if we're using the GameplayManager system + // + + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + if ( gpm->hasObject(getArchetype()) ) + { + str attacktype = ""; + if ( myOwner->isSubclassOf( Player ) ) + { + Player *player = (Player*)myOwner; + attacktype = player->getAttackType(); + } + GameplayFormulaData fd(myOwner, 0, this, attacktype); + if ( gpm->hasFormula("OffensiveDamage") ) + damage = GameplayManager::getTheGameplayManager()->calculate("OffensiveDamage", fd); + if ( gpm->hasFormula("Knockback") ) + knockback = GameplayManager::getTheGameplayManager()->calculate("Knockback", fd); + } + + + // Damage the thing that got hit + if (other->takedamage) + { + if ( myOwner && myOwner->isSubclassOf( Player ) ) + { + Player *player = (Player *)myOwner; + + damage = player->getDamageDone( damage, meansofdeath, false ); + } + + other->Damage( this, + myOwner, + damage, + origin, + velocity, + level.impact_trace.plane.normal, + knockback, + 0, + meansofdeath, + level.impact_trace.intersect.surface, + level.impact_trace.intersect.bone + ); + } + + BroadcastSound(); +} + +void Projectile::SetCanHitOwner( Event *ev ) +{ + can_hit_owner = true; +} + +void Projectile::ClearOwner( Event *ev ) +{ + //this->owner = ENTITYNUM_NONE; + edict->ownerNum = ENTITYNUM_NONE; +} + +void Projectile::RemoveWhenStopped( Event *ev ) +{ + remove_when_stopped = true; +} + +void Projectile::StickOnTouch( Event *ev ) +{ + if(ev->NumArgs() > 0 ) + stick_on_touch = ev->GetBoolean(1); + else + stick_on_touch = true; +} + +void Projectile::Stopped( Event *ev ) +{ + if ( remove_when_stopped ) + PostEvent( EV_Remove, 0.0f ); +} + +void Projectile::setScaleByCharge( Event *ev ) +{ + _scaleByCharge = true; + + _minScaleFromCharge = ev->GetFloat( 1 ); + _maxScaleFromCharge = ev->GetFloat( 2 ); +} + +void Projectile::setScaleExplosion( Event *ev ) +{ + _scaleExplosion = true; +} + +void Projectile::setNotifyActors( Event *ev ) +{ + _notifyActors = ev->GetBoolean( 1 ); + + if ( _notifyActors ) + { + turnThinkOn(); + } +} + +void Projectile::setNotShootable( Event *ev ) +{ + _notShootable = true; +} + +void Projectile::setHitsProjectiles( Event *ev ) +{ + _hitsProjectiles = ev->GetBoolean( 1 ); +} + +Entity *Projectile::getOwner( void ) +{ + return G_GetEntity( owner ); +} + +void Projectile::setMinOnGroundTime( Event *ev ) +{ + _minOnGroundTime = ev->GetFloat( 1 ); + + turnThinkOn(); +} + +void Projectile::didDamage( void ) +{ + if ( !_damagedSomething ) + { + Entity *owner = getOwner(); + + _damagedSomething = true; + + if ( owner && owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)owner; + + player->shotHit(); + } + } +} + +void Projectile::destroyed( Event *ev ) +{ + Entity *shooter; + + shooter = ev->GetEntity( 1 ); + + if ( shooter ) + { + owner = shooter->entnum; + edict->ownerNum = shooter->entnum; + } + + ProcessEvent( EV_Projectile_Explode ); +} + +Event EV_Explosion_Radius +( + "radius", + EV_DEFAULT, + "f", + "projectileRadius", + "set the radius for the explosion" +); +Event EV_Explosion_ConstantDamage +( + "constantdamage", + EV_TIKIONLY, + NULL, + NULL, + "Makes the explosion do constant damage over the radius" +); +Event EV_Explosion_DamageEveryFrame +( + "damageeveryframe", + EV_TIKIONLY, + NULL, + NULL, + "Makes the explosion damage every frame" +); +Event EV_Explosion_DamageAgain +( + "damageagain", + EV_CODEONLY, + NULL, + NULL, + "This event is generated each frame if explosion is set to damage each frame" +); +Event EV_Explosion_Flash +( + "flash", + EV_TIKIONLY, + "fffffff", + "mintime time color_red color_green color_blue minradius radius", + "Flash player screens" +); +Event EV_Explosion_RadiusDamage +( + "radiusdamage", + EV_TIKIONLY, + "f", + "radiusDamage", + "set the radius damage an explosion does" +); + +CLASS_DECLARATION( Projectile, Explosion, NULL ) +{ + { &EV_Explosion_Radius, &Explosion::SetRadius }, + { &EV_Explosion_ConstantDamage, &Explosion::SetConstantDamage }, + { &EV_Explosion_DamageEveryFrame, &Explosion::SetDamageEveryFrame }, + { &EV_Explosion_DamageAgain, &Explosion::DamageAgain }, + { &EV_Explosion_Flash, &Explosion::SetFlash }, + { &EV_Explosion_RadiusDamage, &Explosion::SetRadiusDamage }, + { &EV_Explosion_RadiusDamage, &Explosion::SetRadiusDamage }, + + { NULL, NULL } +}; + +Explosion::Explosion() +{ + if ( LoadingSavegame ) + { + // Archive function will setup all necessary data + return; + } + + radius = 0; + radius_damage = 0.0f; + constant_damage = false; + damage_every_frame = false; + flash_mintime = 0; + flash_time = 0; + flash_type = 0; + + mins = Vector( -1.0f, -1.0f, -1.0f ); + maxs = Vector( 1.0f, 1.0f, 1.0f ); +} + +void Explosion::SetFlash( Event *ev ) +{ + flash_mintime = ev->GetFloat( 1 ); + flash_time = ev->GetFloat( 2 ); + flash_r = ev->GetFloat( 3 ); + flash_g = ev->GetFloat( 4 ); + flash_b = ev->GetFloat( 5 ); + flash_a = ev->GetFloat( 6 ); + flash_minradius = ev->GetFloat( 7 ); + flash_radius = ev->GetFloat( 8 ); + flash_type = 0; + + if ( ev->NumArgs() > 8 ) + { + str t = ev->GetString( 9 ); + + if ( !t.icmp( "addblend" ) ) + flash_type = 1; + else if ( !t.icmp( "alphablend" ) ) + flash_type = 0; + } +} + +void Explosion::SetRadius( Event *ev ) +{ + radius = ev->GetFloat( 1 ); +} + +void Explosion::SetRadiusDamage( Event *ev ) +{ + radius_damage = ev->GetFloat( 1 ); +} + +void Explosion::SetConstantDamage( Event *ev ) +{ + constant_damage = true; +} + +void Explosion::SetDamageEveryFrame( Event *ev ) +{ + damage_every_frame = true; +} + +void Explosion::DamageAgain( Event *ev ) +{ + Entity *owner_ent; + + owner_ent = G_GetEntity( owner ); + RadiusDamage( this, owner_ent, radius_damage, NULL, meansofdeath, radius, knockback, constant_damage ); + + PostEvent( EV_Explosion_DamageAgain, FRAMETIME ); +} + +Projectile *ProjectileAttack( const Vector &start, Vector &dir, Entity *owner, const str &projectileModel, float fraction, + float real_speed ) +{ + Event *ev; + Projectile *proj=NULL; + float newspeed,newlife; + SpawnArgs args; + Entity *obj; + float dot=0; + + if ( !projectileModel.length() ) + { + gi.WDPrintf( "ProjectileAttack : No model specified for ProjectileAttack" ); + return NULL; + } + + args.setArg( "model", projectileModel ); + obj = args.Spawn(); + + if ( !obj ) + { + gi.WDPrintf( "projectile model '%s' not found\n", projectileModel.c_str() ); + return NULL; + } + + if ( obj->isSubclassOf( Projectile ) ) + proj = ( Projectile * )obj; + else + gi.WDPrintf( "%s is not of class projectile\n", projectileModel.c_str() ); + + if ( !proj ) + return NULL; + + if ( owner && owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)owner; + player->shotFired(); + } + + // Create a new projectile entity and set it off + proj->setModel( projectileModel ); + proj->setMoveType( MOVETYPE_BOUNCE ); + proj->CancelEventsOfType( EV_ProcessInitCommands ); + proj->ProcessInitCommands( proj->edict->s.modelindex ); + proj->owner = owner->entnum; + proj->edict->ownerNum = owner->entnum; + + proj->angles = dir.toAngles(); + proj->charge_fraction = fraction; + + if ( proj->_scaleByCharge ) + { + proj->edict->s.scale = proj->_minScaleFromCharge + ( proj->_maxScaleFromCharge - proj->_minScaleFromCharge ) * fraction; + + proj->damage *= proj->edict->s.scale; + } + + if ( proj->projFlags & P_CHARGE_SPEED ) + { + newspeed = (proj->speed - proj->minspeed ) * fraction + proj->minspeed; + } + else + { + newspeed = proj->speed; + } + + if ( real_speed ) + newspeed = real_speed; + + if ( proj->addownervelocity ) + { + dot = DotProduct( owner->velocity, dir ); + if ( dot < 0.0f ) + dot = 0; + } + + Player *player = NULL; + Vector newAngles = proj->angles; + Vector newVelocity = dir * ( newspeed + dot ); + newVelocity += proj->addvelocity; + + if ( owner->isSubclassOf( Player ) ) + { + player = static_cast(owner); + if ( player->GetTarget() && player->GetTargetEnemyLocked() ) + { + Vector anglesToTarget; + if ( player->GetProjectileLaunchAngles( anglesToTarget, start, newspeed, proj->gravity ) ) + { + newAngles = anglesToTarget; + anglesToTarget.AngleVectors( &newVelocity ); + newVelocity *= newspeed; + } + else + { + anglesToTarget = Vector::Identity(); + } + } + + if ( multiplayerManager.inMultiplayer() ) + multiplayerManager.playerFired( player ); + } + + + Actor *actor = NULL; + if ( owner->isSubclassOf( Actor ) ) + { + actor = static_cast(owner); + + if ( actor->combatSubsystem->shouldArcProjectile() ) + { + Vector anglesToTarget; + Entity *enemy = actor->enemyManager->GetCurrentEnemy(); + + if ( enemy ) + { + Vector ActorToTarget = actor->centroid - enemy->centroid; + float distToTarget = ActorToTarget.length(); + + if ( distToTarget > actor->combatSubsystem->GetLowArcRange() ) + { + if ( actor->combatSubsystem->GetProjectileLaunchAngles( anglesToTarget, start, newspeed, proj->gravity , true ) ) + { + newAngles = anglesToTarget; + anglesToTarget.AngleVectors( &newVelocity ); + newVelocity *= newspeed; + } + else + { + anglesToTarget = Vector::Identity(); + } + } + else + { + if ( actor->combatSubsystem->GetProjectileLaunchAngles( anglesToTarget, start, newspeed, proj->gravity , false ) ) + { + newAngles = anglesToTarget; + anglesToTarget.AngleVectors( &newVelocity ); + newVelocity *= newspeed; + } + else + { + anglesToTarget = Vector::Identity(); + } + } + } + } + } + + proj->velocity = newVelocity; + proj->setAngles( newAngles ); + + if ( proj->_notShootable ) + { + proj->setSolidType( SOLID_BBOX ); + proj->setContents( 0 ); + proj->edict->clipmask = MASK_PROJECTILE_NOTSHOOTABLE; + } + else + { + proj->setSolidType( SOLID_BBOX ); + + if ( proj->_hitsProjectiles ) + proj->edict->clipmask = MASK_PROJECTILE; + else + proj->edict->clipmask = MASK_PROJECTILE_NOTSHOOTABLE; + } + + proj->setSize( proj->mins, proj->maxs ); + proj->setOrigin( start ); + //proj->origin.copyTo( proj->edict->s.origin2 ); + + if ( proj->m_beam ) + { + proj->m_beam->setOrigin( start ); + proj->m_beam->origin.copyTo( proj->m_beam->edict->s.origin2 ); + } + + if ( proj->dlight_radius ) + { + G_SetConstantLight( &proj->edict->s.constantLight, + &proj->dlight_color[0], + &proj->dlight_color[1], + &proj->dlight_color[2], + &proj->dlight_radius + ); + } + + // Calc the life of the projectile + if ( proj->projFlags & P_CHARGE_LIFE ) + { + newlife = proj->life * fraction; + + if ( newlife < proj->minlife ) + newlife = proj->minlife; + } + else + { + newlife = proj->life; + } + + // Remove the projectile after it's life expires + ev = new Event( EV_Projectile_Explode ); + proj->PostEvent( ev, newlife ); + + proj->animate->RandomAnimate( "idle", EV_StopAnimating ); + + // If can hit owner clear the owner of this projectile in a second + + if ( proj->can_hit_owner ) + proj->PostEvent( EV_Projectile_ClearOwner, 1.0f ); + + // Notify any actor that is in way of projectile + Vector end_pos; + trace_t trace; + Event *event; + Entity *victim; + + end_pos = ( dir * 1000.0f ) + start; + trace = G_Trace( start, vec_zero, vec_zero, end_pos, owner, MASK_PROJECTILE, false, "ProjectileAttack" ); + + if ( trace.entityNum != ENTITYNUM_NONE ) + { + event = new Event( EV_ActorIncomingProjectile ); + event->AddEntity( proj ); + victim = G_GetEntity( trace.entityNum ); + victim->PostEvent( event, 0.0f ); + } + + if ( owner && proj->triggerdetonate ) + { + Event *newEvent = new Event( EV_Player_Weapon ); + newEvent->AddString( "dual" ); + newEvent->AddString( "nextSwitchTime" ); + newEvent->AddFloat( newlife ); + owner->ProcessEvent( newEvent ); + } + + return proj; +} + +float BulletAttack( + const Vector &start, + const Vector &dir, + const Vector &right, + const Vector &up, + float range, + float damage, + float knockback, + int dflags, + int meansofdeath, + const Vector &spread, + int count, + Entity *owner, + Entity *weap ) +{ + Vector end; + int i; + trace_t trace; + Entity *ent; + Player *player; + float damage_total = 0.0f; + float original_value; + float new_value; + + ent = NULL; + + if ( owner->isSubclassOf( Player ) ) + { + player = (Player *)owner; + + meansofdeath = (int)player->changetMeansOfDeath( (meansOfDeath_t)meansofdeath ); + + damage = player->getDamageDone( damage, meansofdeath, false ); + + player->shotFired(); + + if ( multiplayerManager.inMultiplayer() ) + multiplayerManager.playerFired( player ); + } + + for ( i=0; isetShader( "bloodsplat.spr" ); + decal->setOrigin( Vector( trace.endpos ) + ( Vector( trace.plane.normal ) * 0.2 ) ); + decal->setDirection( trace.plane.normal ); + decal->setOrientation( "random" ); + decal->setRadius( 8 );*/ + + if ( trace.ent ) + ent = trace.ent->entity; + + /*if ( trace.ent ) + { + ent = trace.ent->entity; + + //Let Entity know a hitscan hit has been registered + if ( ent->isSubclassOf( Actor ) ) + { + Actor *act = (Actor *)ent; + act->SetActorFlag( "incominghitscan" , true ); + } + + // See if bullet should bounce off of entity + + if ( ent->isSubclassOf( Actor ) ) + { + Actor *act = (Actor *)ent; + Vector real_dir; + Vector bounce_start; + Vector angles; + Vector left; + Vector up; + + if ( act->GetActorFlag( ACTOR_FLAG_BOUNCE_OFF ) && ( act->Immune( meansofdeath ) || act->takedamage == DAMAGE_NO ) ) + { + // Play sound + + if ( owner ) + owner->Sound( "snd_ricochet", 0 ); + + real_dir = trace.endpos - start; + + real_dir = real_dir * -1.0f; + + angles = real_dir.toAngles(); + + angles.AngleVectors( NULL, &left, &up ); + + real_dir.normalize(); + real_dir *= range; + + real_dir += ( left * G_CRandom() * 100.0f ) + ( up * G_CRandom() * 100.0f ); + + bounce_start = trace.endpos; + + trace = G_Trace( bounce_start, vec_zero, vec_zero, bounce_start + real_dir, NULL, MASK_SHOT, false, "BulletAttack2" ); + + // Add tracer + + //No tracer this time around... Too many beam weapons + + //if ( G_Random( 1.0f ) < .2f ) + //{ + // FuncBeam *beam = CreateBeam( NULL, "tracer", bounce_start, trace.endpos, 1, 1.0f, .1f, 0.f ); + // beam->PostEvent( EV_Remove, 1.0f ); + //} + + if ( trace.ent ) + { + ent = trace.ent->entity; + + if ( ent == world ) + { + // Add bullet hole + Decal *decal = new Decal; + decal->setShader( "bullethole" ); + decal->setOrigin( trace.endpos ); + decal->setDirection( trace.plane.normal ); + decal->setOrientation( "random" ); + decal->setRadius( 2.0f ); + + // Add smoke + Entity *smoke = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); + + smoke->setModel( "fx_bulletsmoke.tik" ); + smoke->setOrigin( trace.endpos ); + smoke->animate->RandomAnimate( "idle" ); + smoke->PostEvent( EV_Remove, 2.0f ); + } + } + else + { + ent = NULL; + } + } + } + } + else + { + ent = NULL; + } + */ + if ( ent && ent->takedamage ) + { + + // Override damage if we're using the GameplayManager system + // Comes from From -- + // Weapon::Shoot + // + /* + GameplayManager *gpm = GameplayManager::getTheGameplayManager(); + float bulletDamage; + + if ( weap && gpm->hasObject(weap->getArchetype()) ) + { + str attacktype = ""; + if ( owner->isSubclassOf( Player ) ) + { + Player *player = (Player*)owner; + attacktype = player->getAttackType(); + } + GameplayFormulaData fd(owner, 0, weap, attacktype); + Weapon *weapon = (Weapon*)weap; + switch ( weapon->GetCurMode() ) + { + case FIRE_MODE1: + bulletDamage = gpm->getFloatValue(weapon->getArchetype() + ".Mode1", "bulletdamage"); + if ( gpm->hasFormula("DamageMode1") ) + damage = gpm->calculate("DamageMode1", fd, bulletDamage); + break; + case FIRE_MODE2: + bulletDamage = gpm->getFloatValue(weapon->getArchetype() + ".Mode2", "bulletdamage"); + if ( gpm->hasFormula("DamageMode2") ) + damage = gpm->calculate("DamageMode2", fd, bulletDamage); + break; + } + if ( gpm->hasFormula("Knockback") ) + knockback = gpm->calculate("Knockback", fd); + } + */ + + original_value = ent->health; + ent->Damage( NULL, + owner, + damage, + trace.endpos, + dir, + trace.plane.normal, + knockback, + dflags, + meansofdeath, + trace.intersect.surface, + trace.intersect.bone, + weap); + + new_value = ent->health; + damage_total += original_value - new_value; + } + else + { + Weapon *theWeapon; + theWeapon = (Weapon*)weap; + + if ( theWeapon && theWeapon->shouldPlayMissSound() ) + { + theWeapon->SpawnSound( "snd_ricochet" , trace.endpos , -1 , 1.0 ); + } + } + + // Draw a debug trace line to show bullet fire + if ( g_showbullettrace->integer ) + G_DebugLine( start, end, 1.0f, 1.0f, 1.0f, 1.0f ); + } + + if ( damage_total > 0.0f ) + { + if ( owner && owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)owner; + player->shotHit(); + } + + return damage_total; + } + else + { + return 0; + } +} + +void ExplosionAttack( + const Vector &pos, + Entity *owner, + const str &explosionModel, + Vector dir, + Entity *ignore, + float scale ) +{ + Explosion *explosion; + Event *ev; + float damageMult = 1.0; + float damage; + int meansofdeath; + float damageDone; + + + if ( !owner ) + owner = world; + + if ( explosionModel.length() ) + { + explosion = new Explosion; + + // Create a new explosion entity and set it off + explosion->setModel( explosionModel ); + + explosion->mins = Vector( -1.0f, -1.0f, -1.0f ); + explosion->maxs = Vector( 1.0f, 1.0f, 1.0f ); + + explosion->setSolidType( SOLID_NOT ); + explosion->CancelEventsOfType( EV_ProcessInitCommands ); + + // Process the INIT commands right away + explosion->ProcessInitCommands( explosion->edict->s.modelindex ); + + explosion->owner = owner->entnum; + + explosion->edict->ownerNum = owner->entnum; + + if ( explosion->can_hit_owner ) + explosion->PostEvent( EV_Projectile_ClearOwner, 0.1f ); + + explosion->angles = dir.toAngles(); + explosion->velocity = dir * explosion->speed; + explosion->edict->s.scale = scale; + explosion->setAngles( explosion->angles ); + + //explosion->setMoveType( MOVETYPE_FLYMISSILE ); + + explosion->setMoveType( MOVETYPE_NONE ); + + explosion->edict->clipmask = MASK_PROJECTILE; + explosion->setSize( explosion->mins, explosion->maxs ); + explosion->setOrigin( pos ); + explosion->origin.copyTo( explosion->edict->s.origin2 ); + + if ( explosion->dlight_radius ) + { + G_SetConstantLight( &explosion->edict->s.constantLight, + &explosion->dlight_color[0], + &explosion->dlight_color[1], + &explosion->dlight_color[2], + &explosion->dlight_radius + ); + } + + explosion->BroadcastSound(); + explosion->animate->RandomAnimate( "idle", EV_StopAnimating ); + + damage = explosion->radius_damage * scale * damageMult; + meansofdeath = explosion->meansofdeath; + + if ( owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)owner; + + meansofdeath = (int)player->changetMeansOfDeath( (meansOfDeath_t)meansofdeath ); + + damage = player->getDamageDone( damage, explosion->meansofdeath, false ); + } + + damageDone = RadiusDamage( explosion, + owner, + damage, + ignore, + meansofdeath, + explosion->radius * scale, + explosion->knockback, + explosion->constant_damage + ); + + if ( damageDone && owner && owner->isSubclassOf( Player ) ) + { + Player *player = (Player *)owner; + player->shotHit(); + } + + if ( explosion->flash_radius ) + { + FlashPlayers( explosion->origin, + explosion->flash_r, + explosion->flash_g, + explosion->flash_b, + explosion->flash_a, + explosion->flash_minradius * scale, + explosion->flash_radius * scale, + explosion->flash_mintime, + explosion->flash_time, + explosion->flash_type + ); + } + + if ( explosion->damage_every_frame ) + { + explosion->PostEvent( EV_Explosion_DamageAgain, FRAMETIME ); + } + + // Remove explosion after the life has expired + + if ( explosion->life ) + { + if ( explosion->explosionmodel.length() > 0 ) + { + ev = new Event( EV_Projectile_Explode ); + explosion->PostEvent( ev, explosion->life ); + } + else + { + ev = new Event( EV_Remove ); + explosion->PostEvent( ev, explosion->life ); + } + } + } +} + +void StunAttack( + const Vector &pos, + Entity *attacker, + Entity *inflictor, + float radius, + float time, + Entity *ignore ) +{ + Entity *ent; + + ent = findradius( NULL, inflictor->origin, radius ); + + while( ent ) + { + if ( ( ent != ignore ) && ( ( ent->takedamage ) || ent->isSubclassOf( Actor ) ) ) + { + if ( inflictor->CanDamage( ent, attacker ) ) + { + // Fixme : Add in knockback + ent->Stun( time ); + } + } + ent = findradius( ent, inflictor->origin, radius ); + } +} + +float RadiusDamage( + Entity *inflictor, + Entity *attacker, + float damage, + Entity *ignore, + int mod, + float radius, + float knockback, + qboolean constant_damage) +{ + float points; + Entity *ent; + Vector org; + Vector dir; + float dist; + float rad; + float totalDamage; + + if ( radius ) + rad = radius; + else + rad = damage + 60.0f; + + totalDamage = 0.0f; + + ent = findradius( NULL, inflictor->origin, rad ); + + while( ent ) + { + if ( ( ent != ignore ) && ( ent->takedamage ) && ( ent->edict->solid != SOLID_NOT ) ) + { + org = ent->centroid; + dir = org - inflictor->origin; + dist = dir.length(); + + if ( dist > rad ) + { + ent = findradius( ent, inflictor->origin, rad ); + continue; + } + + if ( constant_damage ) + { + points = damage; + + } + else + { + points = damage - ( damage * ( dist / rad ) ); + + knockback -= knockback * ( dist / rad ); + + if ( points < 0.0f ) + points = 0.0f; + + if ( knockback < 0.0f ) + knockback = 0.0f; + } + + if ( ent == attacker ) + { + points *= 0.5f; + } + + if ( points > 0.0f ) + { + // Add this in for deathmatch maybe + + // Only players can use cover + //if ( !ent->isSubclassOf( Player ) || inflictor->CanDamage( ent, attacker ) ) + if ( inflictor->CanDamage( ent, attacker ) ) + { + org = ent->centroid; + dir = org - inflictor->origin; + ent->Damage( inflictor, + attacker, + points, + org, + dir, + vec_zero, + knockback, + DAMAGE_RADIUS, + mod + ); + + totalDamage += points; + } + } + } + ent = findradius( ent, inflictor->origin, rad ); + } + + return totalDamage; +} + + +void FlashPlayers( + const Vector &org, + float r, + float g, + float b, + float a, + float minradius, + float radius, + float mintime, + float time, + int type ) +{ + trace_t trace; + Vector delta; + float length; + Player *player; + gentity_t *ed; + int i; + Entity *ent; + float newa = 1; + Vector dirToExplosion; + Vector playerViewDir; + Vector playerViewAngles; + float dot; + + for( i = 0; i < game.maxclients; i++ ) + { + ed = &g_entities[ i ]; + if ( !ed->inuse || !ed->entity ) + continue; + + ent = ed->entity; + if ( !ent->isSubclassOf( Player ) ) + continue; + + player = ( Player * )ent; + + if ( !player->WithinDistance( org, radius ) ) + continue; + + trace = G_Trace( org, vec_zero, vec_zero, player->origin, player, MASK_OPAQUE, false, "FlashPlayers" ); + if ( trace.fraction != 1.0f ) + continue; + + delta = org - trace.endpos; + length = delta.length(); + + dirToExplosion = org - player->origin; + + playerViewAngles = player->GetVAngles(); + playerViewAngles.AngleVectors( &playerViewDir ); + + dot = dirToExplosion * playerViewDir; + + if ( dot < 0.0f ) + continue; + + // If alpha is specified, then modify it by the amount of distance away from the flash the player is + if ( a != -1.0f ) + { + if ( length < minradius ) + newa = a; + else if ( length < radius ) + newa = a * ( 1.0f - ( length - minradius ) / ( radius - minradius ) ); + else + newa = 0.0f; + } + + /* if ( g_gametype->integer == GT_SINGLE_PLAYER ) // Single player uses the global level variables for flashes + { + level.m_fade_alpha = newa; + level.m_fade_color[0] = r; + level.m_fade_color[1] = g; + level.m_fade_color[2] = b; + level.m_fade_time = time; + level.m_fade_time_start = time; + + if ( type == 1 ) + level.m_fade_style = additive; + else + level.m_fade_style = alphablend; + } + else + { + player->AddBlend( r,g,b,newa ); + } */ + + player->setFlash( Vector( r, g, b ), newa, mintime, time ); + } +} + diff --git a/dlls/game/weaputils.h b/dlls/game/weaputils.h new file mode 100644 index 0000000..67725f0 --- /dev/null +++ b/dlls/game/weaputils.h @@ -0,0 +1,351 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/weaputils.h $ +// $Revision:: 43 $ +// $Author:: Steven $ +// $Date:: 4/09/03 2:32p $ +// +// Copyright (C) 1998 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// + +#ifndef __WEAPUTILS_H__ +#define __WEAPUTILS_H__ + +#include "g_local.h" +#include "animate.h" +#include "beam.h" + +#define P_BOUNCE_TOUCH (1 << 0) +#define P_CHARGE_LIFE (1 << 1) +#define P_CHARGE_SPEED (1 << 2) +#define P_BOUNCE_ALL (1 << 3) // projectile bounces off of *everything* +#define P_QUIET_EXPIRE (1 << 4) // projectile expires (from time) quietly (no explosion) + +extern Event EV_Projectile_Explode; +extern Event EV_Projectile_UpdateBeam; +extern Event EV_Actor_ProjectileClose; + +//------------------------- CLASS ------------------------------ +// +// Name: Projectile +// Base Class: Entity +// +// Description: Projectile Entity +// +// Method of Use: Spawned into the world +//-------------------------------------------------------------- +class Projectile : public Entity +{ +public: + CLASS_PROTOTYPE( Projectile ); + + float fov; + int owner; + float speed; + float minspeed; + float bouncefactor; + float damage; + float knockback; + float life; + float minlife; + float dlight_radius; + float charge_fraction; + bool firstTimeOwnerControl; + + Vector prevPlayerAngles; + Vector originalPlayerAngles; + Vector dlight_color; + Vector addvelocity; + meansOfDeath_t meansofdeath; + FuncBeam *m_beam; + int projFlags; + str bouncesound; + str impactmarkshader; + str impactmarkorientation; + float impactmarkradius; + str explosionmodel; + EntityPtr target; + bool addownervelocity; + bool drunk; + bool can_hit_owner; + bool remove_when_stopped; + bool stick_on_touch; + bool ownercontrol; + bool _ownerControlLaser; + bool _ownerControlUsed; + float _controlTurnSpeed; + bool triggerdetonate; + float startTime; + float drunkAngleModifier; + + bool _scaleByCharge; + float _minScaleFromCharge; + float _maxScaleFromCharge; + bool _scaleExplosion; + bool _notifyActors; + bool _notShootable; + bool _hitsProjectiles; + + float _minOnGroundTime; + bool _onGround; + float _startOnGroundTime; + bool _heatSeek; + + bool _damagedSomething; + + Projectile(); + virtual ~Projectile(); + virtual void Archive( Archiver &arc ); + virtual void Touch( Event *ev ); + virtual void Think( void ); + virtual void Explode( Event *ev ); + virtual void DoDecal( void ); + + void SetAvelocity( Event *ev ); + void SetAddVelocity( Event *ev ); + void SetDLight( Event *ev ); + void SetLife( Event *ev ); + void SetMinLife( Event *ev ); + void SetBounceFactor( Event *ev ); + void SetChargeLife( Event *ev ); + void SetSpeed( Event *ev ); + void SetMinSpeed( Event *ev ); + void SetChargeSpeed( Event *ev ); + void SetDamage( Event *ev ); + void SetKnockback( Event *ev ); + void SetProjectileDLight( Event *ev ); + void SetMeansOfDeath( Event *ev ); + void SetQuietExpire( Event *ev ); + void SetBounceTouch( Event *ev ); + void SetBounceSound( Event *ev ); + void SetImpactMarkShader( Event *ev ); + void SetImpactMarkRadius( Event *ev ); + void SetImpactMarkOrientation( Event *ev ); + void SetExplosionModel( Event *ev ); + void UpdateBeam( Event *ev ); + void BeamCommand( Event *ev ); + void HeatSeek( Event *ev ); + void Drunk( Event *ev ); + void AddOwnerVelocity( Event *ev ); + float ResolveMinimumDistance( Entity *potential_target, float currmin ); + float AdjustAngle( float maxadjust, float currangle, float targetangle ); + void SetCanHitOwner( Event *ev ); + void ClearOwner( Event *ev ); + Entity * getOwner( void ); + void RemoveWhenStopped( Event *ev ); + void StickOnTouch( Event *ev ); + void Stopped( Event *ev ); + void SetOwnerControl( Event *ev ); + void setOwnerControlLaser( Event *ev ); + void setControlTurnSpeed( Event *ev ); + void TriggerDetonate( Event *ev ); + void AngleThink( Event *ev ); + + void setScaleByCharge( Event *ev ); + void setScaleExplosion( Event *ev ); + void setNotifyActors( Event *ev ); + + void NotifyActors(); + void setNotShootable( Event *ev ); + void setHitsProjectiles( Event *ev ); + void setMinOnGroundTime( Event *ev ); + + void destroyed( Event *ev ); + + void didDamage( void ); +}; + +inline void Projectile::Archive ( Archiver &arc ) +{ + Entity::Archive( arc ); + + arc.ArchiveFloat( &fov ); + arc.ArchiveInteger( &owner ); + arc.ArchiveFloat( &speed ); + arc.ArchiveFloat( &minspeed ); + arc.ArchiveFloat( &bouncefactor ); + arc.ArchiveFloat( &damage ); + arc.ArchiveFloat( &knockback ); + arc.ArchiveFloat( &life ); + arc.ArchiveFloat( &minlife ); + arc.ArchiveFloat( &dlight_radius ); + arc.ArchiveFloat( &charge_fraction ); + arc.ArchiveBool( &firstTimeOwnerControl ); + + arc.ArchiveVector( &prevPlayerAngles ); + arc.ArchiveVector( &originalPlayerAngles ); + arc.ArchiveVector( &dlight_color ); + arc.ArchiveVector( &addvelocity ); + ArchiveEnum( meansofdeath, meansOfDeath_t ); + arc.ArchiveObjectPointer( ( Class ** )&m_beam ); + arc.ArchiveInteger( &projFlags ); + arc.ArchiveString( &bouncesound ); + arc.ArchiveString( &impactmarkshader ); + arc.ArchiveString( &impactmarkorientation ); + arc.ArchiveFloat( &impactmarkradius ); + arc.ArchiveString( &explosionmodel ); + + arc.ArchiveSafePointer( &target ); + + arc.ArchiveBool( &addownervelocity ); + arc.ArchiveBool( &drunk ); + arc.ArchiveBool( &can_hit_owner ); + arc.ArchiveBool( &remove_when_stopped ); + + arc.ArchiveBool( &stick_on_touch ); + arc.ArchiveBool( &ownercontrol ); + arc.ArchiveBool( &_ownerControlLaser ); + arc.ArchiveBool( &_ownerControlUsed ); + arc.ArchiveFloat( &_controlTurnSpeed ); + arc.ArchiveBool( &triggerdetonate ); + + arc.ArchiveFloat( &startTime ); + arc.ArchiveFloat( &drunkAngleModifier ); + + arc.ArchiveBool( &_scaleByCharge ); + arc.ArchiveFloat( &_minScaleFromCharge ); + arc.ArchiveFloat( &_maxScaleFromCharge ); + arc.ArchiveBool( &_scaleExplosion ); + arc.ArchiveBool( &_notifyActors ); + arc.ArchiveBool( &_notShootable ); + arc.ArchiveBool( &_hitsProjectiles ); + + arc.ArchiveFloat( &_minOnGroundTime ); + arc.ArchiveBool( &_onGround ); + arc.ArchiveFloat( &_startOnGroundTime ); + arc.ArchiveBool( &_heatSeek ); + arc.ArchiveBool( &_damagedSomething ); + +} + +class Explosion : public Projectile +{ +public: + float flash_r; + float flash_g; + float flash_b; + float flash_a; + float flash_minradius; + float flash_radius; + float flash_mintime; + float flash_time; + int flash_type; + float radius_damage; + + float radius; + qboolean constant_damage; + qboolean damage_every_frame; + + CLASS_PROTOTYPE( Explosion ); + + Explosion(); + void SetRadius( Event *ev ); + void SetRadiusDamage( Event *ev ); + void SetConstantDamage( Event *ev ); + void SetDamageEveryFrame( Event *ev ); + void SetFlash( Event *ev ); + + void DamageAgain( Event *ev ); + virtual void Archive( Archiver &arc ); +}; + +inline void Explosion::Archive( Archiver &arc ) +{ + Projectile::Archive( arc ); + + arc.ArchiveFloat( &flash_r ); + arc.ArchiveFloat( &flash_g ); + arc.ArchiveFloat( &flash_b ); + arc.ArchiveFloat( &flash_a ); + arc.ArchiveFloat( &flash_minradius ); + arc.ArchiveFloat( &flash_radius ); + arc.ArchiveFloat( &flash_mintime ); + arc.ArchiveFloat( &flash_time ); + arc.ArchiveInteger( &flash_type ); + arc.ArchiveFloat( &radius_damage ); +} + +qboolean MeleeAttack +( + const Vector &pos, + const Vector &end, + float damage, + Entity *attacker, + meansOfDeath_t means_of_death, + float attack_width, + float attack_min_height, + float attack_max_height, + float knockback = 0, + qboolean hit_dead = true, + Container*victimlist=NULL, + Weapon *weapon = NULL, + bool critical = false +); + +Projectile *ProjectileAttack +( + const Vector &start, + Vector &dir, + Entity *owner, + const str &projectileModel, + float speedfraction, + float real_speed = 0 +); + +void ExplosionAttack +( + const Vector &pos, + Entity *owner, + const str &projectileModel, + Vector dir = Vector( 0.0f, 0.0f, 0.0f ), + Entity *ignore = NULL, + float scale=1.0f +); + +void StunAttack +( + const Vector &pos, + Entity *attacker, + Entity *inflictor, + float radius, + float time, + Entity *ignore +); + +float BulletAttack +( + const Vector &start, + const Vector &dir, + const Vector &right, + const Vector &up, + float range, + float damage, + float knockback, + int dflags, + int meansofdeath, + const Vector &spread, + int count, + Entity *owner, + Entity *weap = 0 +); + +float RadiusDamage +( + Entity *inflictor, + Entity *attacker, + float damage, + Entity *ignore, + int mod, + float radius = 0, + float knockback = 0, + qboolean constant_damage = false +); + +#endif // __WEAPUTILS_H__ diff --git a/dlls/game/work.cpp b/dlls/game/work.cpp new file mode 100644 index 0000000..b3a63d7 --- /dev/null +++ b/dlls/game/work.cpp @@ -0,0 +1,1063 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/work.cpp $ +// $Revision:: 6 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CorridorCombatWithRangedWeapon Implementation +// +// PARAMETERS: +// str _movementAnim -- The animation to play while moving to the cover node +// +// ANIMATIONS: +// _movementAnim : PARAMETER +//-------------------------------------------------------------------------------- + +#include "actor.h" +#include "work.hpp" + +//-------------------------------------------------------------- +// +// Class Declaration and Event Registration +// +//-------------------------------------------------------------- +CLASS_DECLARATION( Behavior, Work, NULL ) +{ + { &EV_Behavior_Args, &Work::SetArgs }, + { &EV_Behavior_AnimDone, &Work::AnimDone }, + { &EV_HelperNodeCommand, &Work::HandleNodeCommand }, + + { NULL, NULL } +}; + +//-------------------------------------------------------------- +// Name: Work() +// Class: Work() +// +// Description: Constructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +Work::Work() +{ + _gotoWorkAnimName = "walk"; + _maxDistance = 1024.0f; +} + +//-------------------------------------------------------------- +// Name: ~Work() +// Class: Work +// +// Description: Destructor +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +Work::~Work() +{ +} + +//-------------------------------------------------------------- +// Name: SetArgs() +// Class: Work +// +// Description: Sets the arguments of the behavior +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Work::SetArgs ( Event *ev) +{ + if (ev->NumArgs() == 1) + { + _gotoWorkAnimName = ev->GetString( 1 ); + } +} + + + +//-------------------------------------------------------------- +// Name: AnimDone() +// Class: Work +// +// Description: Handles an animation completion +// +// Parameters: Event *ev -- Event holding the completion notification +// +// Returns: None +//-------------------------------------------------------------- +void Work::AnimDone( Event *ev ) +{ + switch( _state ) + { + case WORK_ANIMATE_WAIT_ON_ANIM: + _animDone = true; + break; + + case WORK_ANIMATE_LIST_WAIT_ON_ANIM: + _animDone = true; + break; + } + +} + +//-------------------------------------------------------------- +// Name: HandleNodeCommand() +// Class: Work +// +// Description: Handles a command event from a helper node +// +// Parameters: Event *ev +// +// Returns: None +//-------------------------------------------------------------- +void Work::HandleNodeCommand( Event *ev ) +{ + switch( _state ) + { + case WORK_ANIMATE_WAIT_ON_SIGNAL: + _animDone = true; + break; + + case WORK_ANIMATE_LIST_WAIT_ON_SIGNAL: + _animDone = true; + break; + } +} + +//-------------------------------------------------------------- +// Name: Begin() +// Class: Work +// +// Description: Begins the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Work::Begin( Actor &self ) +{ + init( self ); +} + +//-------------------------------------------------------------- +// Name: Evaluate() +// Class: Work +// +// Description: Returns True Or False, and is run every frame +// that the behavior +// +// Parameters: Actor &self +// +// Returns: True or False +//-------------------------------------------------------------- +BehaviorReturnCode_t Work::Evaluate ( Actor &self ) +{ + BehaviorReturnCode_t stateResult; + + think(); + + switch ( _state ) + { + //--------------------------------------------------------------------- + case WORK_FIND_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateFindNode(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WORK_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WORK_MOVE_TO_NODE ); + break; + + //--------------------------------------------------------------------- + case WORK_MOVE_TO_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateMoveToNode(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WORK_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WORK_AT_NODE ); + + break; + + //--------------------------------------------------------------------- + case WORK_AT_NODE: + //--------------------------------------------------------------------- + stateResult = evaluateStateAtNode(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WORK_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WORK_SELECT_ANIM_MODE ); + + break; + + //--------------------------------------------------------------------- + case WORK_SELECT_ANIM_MODE: + //--------------------------------------------------------------------- + if ( _node->isUsingAnimList() ) + { + customAnimListEntry_t* animEntry = _node->GetCurrentAnimEntryFromList(); + if ( !animEntry ) + { + transitionToState( WORK_FAILED ); + return BEHAVIOR_EVALUATING; + } + + if ( animEntry->waitType == WAITTYPE_EVENT ) + transitionToState( WORK_ANIMATE_LIST_WAIT_ON_SIGNAL ); + + if ( animEntry->waitType == WAITTYPE_ANIM ) + transitionToState( WORK_ANIMATE_LIST_WAIT_ON_ANIM ); + + if ( animEntry->waitType == WAITTYPE_TIME ) + transitionToState( WORK_ANIMATE_LIST_WAIT_ON_TIME ); + + return BEHAVIOR_EVALUATING; + } + + if ( _node->isWaitForAnim() ) + { + transitionToState( WORK_ANIMATE_WAIT_ON_ANIM ); + return BEHAVIOR_EVALUATING; + } + + if ( _node->GetWaitTime() > 0 ) + { + transitionToState( WORK_ANIMATE_WAIT_ON_TIME ); + return BEHAVIOR_EVALUATING; + } + + // We default to constant working + transitionToState( WORK_ANIMATE_CONSTANT ); + + + break; + + //--------------------------------------------------------------------- + case WORK_ANIMATE_WAIT_ON_TIME: + //--------------------------------------------------------------------- + stateResult = evaluateStateAnimateWaitOnTime(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WORK_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WORK_SUCCESSFUL ); + break; + + //--------------------------------------------------------------------- + case WORK_ANIMATE_WAIT_ON_ANIM: + //--------------------------------------------------------------------- + stateResult = evaluateStateAnimateWaitOnAnim(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WORK_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WORK_SUCCESSFUL ); + break; + + //--------------------------------------------------------------------- + case WORK_ANIMATE_WAIT_ON_SIGNAL: + //--------------------------------------------------------------------- + stateResult = evaluateStateAnimateWaitOnSignal(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WORK_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WORK_SUCCESSFUL ); + break; + + //--------------------------------------------------------------------- + case WORK_ANIMATE_CONSTANT: + //--------------------------------------------------------------------- + stateResult = evaluateStateAnimateConstant(); + + // + // Should Never get to either of these + // + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WORK_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + transitionToState( WORK_SUCCESSFUL ); + break; + + //--------------------------------------------------------------------- + case WORK_ANIMATE_LIST_WAIT_ON_TIME: + //--------------------------------------------------------------------- + stateResult = evaluateStateAnimateListWaitOnTime(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WORK_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + if (_node->isAnimListFinished() ) + transitionToState( WORK_SUCCESSFUL ); + else + transitionToState( WORK_SELECT_ANIM_MODE ); + } + + break; + + //--------------------------------------------------------------------- + case WORK_ANIMATE_LIST_WAIT_ON_ANIM: + //--------------------------------------------------------------------- + stateResult = evaluateStateAnimateListWaitOnAnim(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WORK_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + if (_node->isAnimListFinished() ) + transitionToState( WORK_SUCCESSFUL ); + else + transitionToState( WORK_SELECT_ANIM_MODE ); + } + break; + + //--------------------------------------------------------------------- + case WORK_ANIMATE_LIST_WAIT_ON_SIGNAL: + //--------------------------------------------------------------------- + stateResult = evaluateStateAnimateListWaitOnSignal(); + if ( stateResult == BEHAVIOR_FAILED ) + transitionToState( WORK_FAILED ); + + if ( stateResult == BEHAVIOR_SUCCESS ) + { + if (_node->isAnimListFinished() ) + transitionToState( WORK_SUCCESSFUL ); + else + transitionToState( WORK_SELECT_ANIM_MODE ); + } + break; + + //--------------------------------------------------------------------- + case WORK_SUCCESSFUL: + //--------------------------------------------------------------------- + _node->RunExitThread(); + self.ignoreHelperNode.node = _node; + return BEHAVIOR_SUCCESS; + break; + + //--------------------------------------------------------------------- + case WORK_FAILED: + //--------------------------------------------------------------------- + return BEHAVIOR_FAILED; + break; + + } + + return BEHAVIOR_EVALUATING; +} + + + +//-------------------------------------------------------------- +// Name: End() +// Class: Work +// +// Description: Ends the Behavior +// +// Parameters: Actor &self +// +// Returns: None +//-------------------------------------------------------------- +void Work::End ( Actor &self ) +{ + if ( _node ) + { + _node->SetUser( NULL ); + } +} + +//-------------------------------------------------------------- +// Name: transitionToState() +// Class: Work +// +// Description: Transitions Internal State +// +// Parameters: workStates_t state +// +// Returns: None +//-------------------------------------------------------------- +void Work::transitionToState ( workStates_t state ) +{ + switch ( state ) + { + case WORK_FIND_NODE: + setupStateFindNode(); + setInternalState( state , "WORK_FIND_NODE" ); + break; + + case WORK_MOVE_TO_NODE: + setupStateMoveToNode(); + setInternalState( state , "WORK_MOVE_TO_NODE" ); + break; + + case WORK_AT_NODE: + setupStateAtNode(); + setInternalState( state , "WORK_AT_NODE" ); + break; + + case WORK_ANIMATE_WAIT_ON_TIME: + setupStateAnimateWaitOnTime(); + setInternalState( state , "WORK_ANIMATE_WAIT_ON_TIME" ); + break; + + case WORK_ANIMATE_WAIT_ON_ANIM: + setupStateAnimateWaitOnAnim(); + setInternalState( state , "WORK_ANIMATE_WAIT_ON_ANIM" ); + break; + + case WORK_ANIMATE_WAIT_ON_SIGNAL: + setupStateAnimateWaitOnSignal(); + setInternalState( state , "WORK_ANIMATE_WAIT_ON_SIGNAL" ); + break; + + case WORK_ANIMATE_CONSTANT: + setupStateAnimateConstant(); + setInternalState( state , "WORK_ANIMATE_CONSTANT" ); + break; + + case WORK_ANIMATE_LIST_WAIT_ON_TIME: + setupStateAnimateListWaitOnTime(); + setInternalState( state , "WORK_ANIMATE_LIST_WAIT_ON_TIME" ); + break; + + case WORK_ANIMATE_LIST_WAIT_ON_ANIM: + setupStateAnimateListWaitOnAnim(); + setInternalState( state , "WORK_ANIMATE_LIST_WAIT_ON_ANIM" ); + break; + + case WORK_ANIMATE_LIST_WAIT_ON_SIGNAL: + setupStateAnimateListWaitOnSignal(); + setInternalState( state , "WORK_ANIMATE_LIST_WAIT_ON_SIGNAL" ); + break; + + case WORK_SELECT_ANIM_MODE: + setInternalState( state, "WORK_SELECT_ANIM_MODE," ); + break; + + case WORK_SUCCESSFUL: + setInternalState( state , "WORK_SUCCESSFUL" ); + break; + + case WORK_FAILED: + setInternalState( state , "WORK_FAILED" ); + break; + } + +} + +//-------------------------------------------------------------- +// Name: setInternalState() +// Class: Work +// +// Description: Sets internal state of the behavior +// +// Parameters: workStates_t state +// const str &stateName +// +// Returns: None +//-------------------------------------------------------------- +void Work::setInternalState ( workStates_t state , const str &stateName ) +{ + _state = state; + SetInternalStateName( stateName ); +} + + + +//-------------------------------------------------------------- +// Name: init() +// Class: Work +// +// Description: Initializes memeber variables +// +// Parameters: Actor &self -- Actor executing this behavior +// +// Returns: None +//-------------------------------------------------------------- +void Work::init( Actor &self ) +{ + _self = &self; + _animDone = false; + _endTime = 0.0f; + _node = self.currentHelperNode.node; + transitionToState( WORK_FIND_NODE ); + +} + +//-------------------------------------------------------------- +// Name: think() +// Class: Work +// +// Description: Think function called every frame by evaluate +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Work::think() +{ + // We need to mindful of changes to our node, + // so we'll keep an eye out for them. If + // we note a change, then we'll re-evaluate + if ( _node && _node->isChanged() ) + { + _node->SetCriticalChange( false ); + } +} + +//-------------------------------------------------------------- +// Name: setupStateFindNode() +// Class: Work +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Work::setupStateFindNode() +{ + if ( !_node ) + _node = HelperNode::FindClosestHelperNode( *_self , NODETYPE_WORK , _maxDistance ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateFindNode() +// Class: Work +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t Work::evaluateStateFindNode() +{ + if ( !_node ) + return BEHAVIOR_FAILED; + + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateFindNode() +// Class: Work +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void Work::failureStateFindNode( const str& failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateMoveToNode() +// Class: Work +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Work::setupStateMoveToNode() +{ + _gotoHelperNode.SetNode( _node ); + _gotoHelperNode.SetMovementAnim ( _gotoWorkAnimName ); + _gotoHelperNode.Begin( *_self ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateMoveToNode() +// Class: Work +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t Work::evaluateStateMoveToNode() +{ + return _gotoHelperNode.Evaluate( *_self ); +} + +//-------------------------------------------------------------- +// Name: failureStateMoveToNode() +// Class: Work +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void Work::failureStateMoveToNode( const str& failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateAtNode() +// Class: Work +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Work::setupStateAtNode() +{ + _self->RunThread( _node->GetEntryThread() ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateAtNode() +// Class: Work +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t Work::evaluateStateAtNode() +{ + Vector nodeAngles = _node->angles; + + _self->movementSubsystem->setAnimDir( nodeAngles ); + _self->setAngles( nodeAngles ); + + _node->SetUser( this ); + return BEHAVIOR_SUCCESS; +} + +//-------------------------------------------------------------- +// Name: failureStateAtNode() +// Class: Work +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void Work::failureStateAtNode( const str& failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateAnimateWaitOnTime() +// Class: Work +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Work::setupStateAnimateWaitOnTime() +{ + if ( _node->isWaitRandom() ) + _endTime = level.time + G_Random(_node->GetWaitTime()); + else + _endTime = level.time + _node->GetWaitTime(); + + _self->SetAnim( _node->GetAnim() , EV_Actor_NotifyBehavior , legs ); + +} + +//-------------------------------------------------------------- +// Name: evaluateStateAnimateWaitOnTime() +// Class: Work +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t Work::evaluateStateAnimateWaitOnTime() +{ + if ( level.time >= _endTime ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAnimateWaitOnTime() +// Class: Work +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void Work::failureStateAnimateWaitOnTime( const str& failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateAnimateWaitOnAnim() +// Class: Work +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Work::setupStateAnimateWaitOnAnim() +{ + _animDone = false; + _self->SetAnim( _node->GetAnim() , EV_Actor_NotifyBehavior , legs ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateAnimateWaitOnAnim() +// Class: Work +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t Work::evaluateStateAnimateWaitOnAnim() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAnimateWaitOnAnim() +// Class: Work +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void Work::failureStateAnimateWaitOnAnim( const str& failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateAnimateWaitOnSignal() +// Class: Work +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Work::setupStateAnimateWaitOnSignal() +{ + _animDone = false; + _self->SetAnim( _node->GetAnim() , EV_Actor_NotifyBehavior , legs ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateAnimateWaitOnSignal() +// Class: Work +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t Work::evaluateStateAnimateWaitOnSignal() +{ + if ( _animDone ) + return BEHAVIOR_SUCCESS; + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAnimateWaitOnSignal() +// Class: Work +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void Work::failureStateAnimateWaitOnSignal( const str& failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateAnimateConstant() +// Class: Work +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Work::setupStateAnimateConstant() +{ + _self->SetAnim( _node->GetAnim() , EV_Actor_NotifyBehavior , legs ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateAnimateConstant() +// Class: Work +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t Work::evaluateStateAnimateConstant() +{ + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAnimateConstant() +// Class: Work +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void Work::failureStateAnimateConstant( const str& failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateAnimateListWaitOnTime() +// Class: Work +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Work::setupStateAnimateListWaitOnTime() +{ + customAnimListEntry_t* animListEntry; + animListEntry = _node->GetCurrentAnimEntryFromList(); + + if ( !animListEntry ) + { + _endTime = 0.0f; + return; + } + + _endTime = level.time + animListEntry->time; + _self->SetAnim( animListEntry->anim , EV_Actor_NotifyBehavior , legs ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateAnimateListWaitOnTime() +// Class: Work +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t Work::evaluateStateAnimateListWaitOnTime() +{ + if ( level.time >= _endTime ) + { + _node->NextAnim(); + return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAnimateListWaitOnTime() +// Class: Work +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void Work::failureStateAnimateListWaitOnTime( const str& failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateAnimateListWaitOnAnim() +// Class: Work +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Work::setupStateAnimateListWaitOnAnim() +{ + customAnimListEntry_t* animListEntry; + animListEntry = _node->GetCurrentAnimEntryFromList(); + + if ( !animListEntry ) + { + _animDone = true; + return; + } + + _animDone = false; + _self->SetAnim( animListEntry->anim , EV_Actor_NotifyBehavior , legs ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateAnimateListWaitOnAnim() +// Class: Work +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t Work::evaluateStateAnimateListWaitOnAnim() +{ + if ( _animDone ) + { + _node->NextAnim(); + return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAnimateListWaitOnAnim() +// Class: Work +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void Work::failureStateAnimateListWaitOnAnim( const str& failureReason ) +{ +} + + + +//-------------------------------------------------------------- +// Name: setupStateAnimateListWaitOnSignal() +// Class: Work +// +// Description: Sets up State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +void Work::setupStateAnimateListWaitOnSignal() +{ + customAnimListEntry_t* animListEntry; + animListEntry = _node->GetCurrentAnimEntryFromList(); + + if ( !animListEntry ) + { + _animDone = true; + return; + } + + _animDone = false; + _self->SetAnim( animListEntry->anim , EV_Actor_NotifyBehavior , legs ); +} + +//-------------------------------------------------------------- +// Name: evaluateStateAnimateListWaitOnSignal() +// Class: Work +// +// Description: Evaluates State +// +// Parameters: None +// +// Returns: None +//-------------------------------------------------------------- +BehaviorReturnCode_t Work::evaluateStateAnimateListWaitOnSignal() +{ + if ( _animDone ) + { + _node->NextAnim(); + return BEHAVIOR_SUCCESS; + } + + return BEHAVIOR_EVALUATING; +} + +//-------------------------------------------------------------- +// Name: failureStateAnimateListWaitOnSignal() +// Class: Work +// +// Description: Failure Handler for State +// +// Parameters: const str& failureReason -- Why we failed +// +// Returns: None +//-------------------------------------------------------------- +void Work::failureStateAnimateListWaitOnSignal( const str& failureReason ) +{ +} + + + diff --git a/dlls/game/work.hpp b/dlls/game/work.hpp new file mode 100644 index 0000000..c7f680f --- /dev/null +++ b/dlls/game/work.hpp @@ -0,0 +1,177 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/work.hpp $ +// $Revision:: 169 $ +// $Author:: sketcher $ +// $Date:: 4/26/02 2:22p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// CooridorCombatWithRangedWeapon Behavior Definition +// +//-------------------------------------------------------------------------------- + +//============================== +// Forward Declarations +//============================== +class Work; + +#ifndef __WORK_HPP__ +#define __WORK_HPP__ + +#include "behavior.h" +#include "behaviors_general.h" +#include "gotoHelperNode.hpp" + +//------------------------- CLASS ------------------------------ +// +// Name: Work +// Base Class: Behavior +// +// Description: Makes the Actor use Work Helper Nodes +// +// Method of Use: Statemachine or another behavior +//-------------------------------------------------------------- +class Work : public Behavior + { + //------------------------------------ + // States + //------------------------------------ + public: + typedef enum + { + WORK_FIND_NODE, + WORK_MOVE_TO_NODE, + WORK_AT_NODE, + WORK_ANIMATE_WAIT_ON_TIME, + WORK_ANIMATE_WAIT_ON_ANIM, + WORK_ANIMATE_WAIT_ON_SIGNAL, + WORK_ANIMATE_CONSTANT, + WORK_ANIMATE_LIST_WAIT_ON_TIME, + WORK_ANIMATE_LIST_WAIT_ON_ANIM, + WORK_ANIMATE_LIST_WAIT_ON_SIGNAL, + WORK_SELECT_ANIM_MODE, + WORK_SUCCESSFUL, + WORK_FAILED + } workStates_t; + + //------------------------------------ + // Parameters + //------------------------------------ + private: // Parameters + str _gotoWorkAnimName ; // anim to play to move to work node, default is "walk" + float _maxDistance; // maximum distance to look for node + + //------------------------------------- + // Internal Functionality + //------------------------------------- + protected: + void transitionToState ( workStates_t state ); + void setInternalState ( workStates_t state , const str &stateName ); + void init ( Actor &self ); + void think (); + + void setupStateFindNode (); + BehaviorReturnCode_t evaluateStateFindNode (); + void failureStateFindNode ( const str& failureReason ); + + void setupStateMoveToNode (); + BehaviorReturnCode_t evaluateStateMoveToNode (); + void failureStateMoveToNode ( const str& failureReason ); + + void setupStateAtNode (); + BehaviorReturnCode_t evaluateStateAtNode (); + void failureStateAtNode ( const str& failureReason ); + + void setupStateAnimateWaitOnTime (); + BehaviorReturnCode_t evaluateStateAnimateWaitOnTime (); + void failureStateAnimateWaitOnTime ( const str& failureReason ); + + void setupStateAnimateWaitOnAnim (); + BehaviorReturnCode_t evaluateStateAnimateWaitOnAnim (); + void failureStateAnimateWaitOnAnim ( const str& failureReason ); + + void setupStateAnimateWaitOnSignal (); + BehaviorReturnCode_t evaluateStateAnimateWaitOnSignal (); + void failureStateAnimateWaitOnSignal ( const str& failureReason ); + + void setupStateAnimateConstant (); + BehaviorReturnCode_t evaluateStateAnimateConstant (); + void failureStateAnimateConstant ( const str& failureReason ); + + void setupStateAnimateListWaitOnTime (); + BehaviorReturnCode_t evaluateStateAnimateListWaitOnTime (); + void failureStateAnimateListWaitOnTime ( const str& failureReason ); + + void setupStateAnimateListWaitOnAnim (); + BehaviorReturnCode_t evaluateStateAnimateListWaitOnAnim (); + void failureStateAnimateListWaitOnAnim ( const str& failureReason ); + + void setupStateAnimateListWaitOnSignal (); + BehaviorReturnCode_t evaluateStateAnimateListWaitOnSignal (); + void failureStateAnimateListWaitOnSignal ( const str& failureReason ); + + + //------------------------------------- + // Public Interface + //------------------------------------- + public: + CLASS_PROTOTYPE( Work ); + Work(); + ~Work(); + + void SetArgs ( Event *ev ); + void AnimDone ( Event *ev ); + void HandleNodeCommand ( Event *ev ); + + void Begin ( Actor &self ); + BehaviorReturnCode_t Evaluate ( Actor &self ); + void End ( Actor &self ); + + void SetNode ( HelperNode* node ); + virtual void Archive ( Archiver &arc ); + + private: // Component Behaviors + GotoHelperNode _gotoHelperNode; + + private: // Member Variables + HelperNodePtr _node; + unsigned int _state; + bool _animDone; + float _endTime; + Actor* _self; + + }; + +inline void Work::SetNode( HelperNode *node ) +{ + _node = node; +} + +inline void Work::Archive( Archiver &arc ) +{ + Behavior::Archive ( arc ); + + // Archive Parameters + arc.ArchiveString ( &_gotoWorkAnimName); + arc.ArchiveFloat ( &_maxDistance ); + + // Archive Components + arc.ArchiveObject ( &_gotoHelperNode ); + + + // Archive Member Vars + arc.ArchiveSafePointer ( &_node ); + arc.ArchiveUnsigned ( &_state ); + arc.ArchiveBool ( &_animDone ); + arc.ArchiveFloat ( &_endTime ); + arc.ArchiveObjectPointer ( ( Class ** )&_self ); +} + +#endif /* __WORK_HPP__ */ diff --git a/dlls/game/worldspawn.cpp b/dlls/game/worldspawn.cpp new file mode 100644 index 0000000..9d15760 --- /dev/null +++ b/dlls/game/worldspawn.cpp @@ -0,0 +1,1751 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /EF2/Code/DLLs/game/worldspawn.cpp $ +// $Revision:: 44 $ +// $Author:: Singlis $ +// $Date:: 9/26/03 2:36p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class for worldspawn objects. This should be subclassed whenever +// a DLL has new game behaviour that needs to be initialized before any other +// entities are created, or before any entity thinks each frame. Also controls +// spawning of clients. +// + +#include "_pch_cpp.h" +#include "entity.h" +#include "scriptmaster.h" +#include "worldspawn.h" +#include "gravpath.h" +#include "earthquake.h" +#include "specialfx.h" +#include "soundman.h" +#include "mp_manager.hpp" +#include "groupcoordinator.hpp" + +WorldPtr world; + +#define DEFAULT_ENTITY_FADE_DIST 3000 + +char worldPhysicsVarNames[ WORLD_PHYSICS_TOTAL_NUMBER ][ 32 ] = +{ + "maxSpeed", + "airAccelerate", + "gravity" +}; + +/*****************************************************************************/ +/*QUAKED worldspawn (0 0 0) ? CINEMATIC + +Only used for the world. + +"soundtrack" the soundtrack to use on the map +"skipthread" thread that is activated to skip this level (if cinematic) +"nextmap" map to goto when player exits +"message" text to print at user logon +"script" script to run on start of map +"watercolor" view color when underwater +"wateralpha" view alpha when underwater +"lavacolor" view alpha when in lava +"lavaalpha" view alpha when in lava +"farplane_color" color to fade to when the far clip plane is on +"farplane_cull" whether or not the far plane should cull, default is yes +"farplane_fog" whether or not to use fog with farplane +"farplane" distance from the viewer that the far clip plane is +"ambientlight" ambient lighting to be applied to all entities +"ambient" ambient lighting to be applied to all entities, use _color to specify color +"suncolor" color of the sun in the level +"sunlight" intensity of the sun in the level +"sundirection" direction of the sun in the level +"sunflare" worldspace position of the sun flare +"sunflare_inportalsky" whether or not the flare is in the portal sky +"lightmapdensity" default lightmap density to be used for all surfaces +"skyalpha" initial value of the sky's alpha, defaults to 1 + +******************************************************************************/ + +#define CINEMATIC 1 + +Event EV_World_SetSoundtrack +( + "soundtrack", + EV_SCRIPTONLY, + "s", + "MusicFile", + "Set music soundtrack for this level." +); +Event EV_World_SetSkipThread +( + "skipthread", + EV_SCRIPTONLY, + "s", + "skipThread", + "Set the thread for skipping cinematics" +); +Event EV_World_SetNextMap +( + "nextmap", + EV_SCRIPTONLY, + "s", + "nextMap", + "Set the next map to change to" +); +Event EV_World_SetMessage +( + "message", + EV_SCRIPTONLY, + "s", + "worldMessage", + "Set a message for the world" +); +Event EV_World_SetScript +( + "script", + EV_SCRIPTONLY, + "s", + "worldScript", + "Set the script for this map" +); +Event EV_World_SetWaterColor +( + "watercolor", + EV_SCRIPTONLY, + "v", + "color_water", + "Set the watercolor screen blend" +); +Event EV_World_SetWaterAlpha +( + "wateralpha", + EV_SCRIPTONLY, + "f", + "waterAlpha", + "Set the alpha of the water screen blend" +); +Event EV_World_SetSlimeColor +( + "slimecolor", + EV_SCRIPTONLY, + "v", + "color_slime", + "Set the slimecolor screen blend" +); +Event EV_World_SetSlimeAlpha +( + "slimealpha", + EV_SCRIPTONLY, + "f", + "slimeAlpha", + "Set the alpha of the slime screen blend" +); +Event EV_World_SetLavaColor +( + "lavacolor", + EV_SCRIPTONLY, + "v", + "color_lava", + "Set the color of lava screen blend" +); +Event EV_World_SetLavaAlpha +( + "lavaalpha", + EV_SCRIPTONLY, + "f", + "lavaAlpha", + "Set the alpha of lava screen blend" +); +Event EV_World_SetFarPlane_Color +( + "farplane_color", + EV_SCRIPTONLY, + "v", + "color_farplane", + "Set the color of the far clipping plane fog" +); +Event EV_World_SetFarPlane_Cull +( + "farplane_cull", + EV_SCRIPTONLY, + "b", + "farplaneCull", + "Whether or not the far clipping plane should cull things out of the world" +); +Event EV_World_SetFarPlane_Fog +( + "farplane_fog", + EV_SCRIPTONLY, + "b", + "farplaneFog", + "Whether or not to use fog with far plane" +); +Event EV_World_SetFarPlane +( + "farplane", + EV_SCRIPTONLY, + "f", + "farplaneDistance", + "Set the distance of the far clipping plane" +); +Event EV_World_SetTerrainGlobal +( + "terrainglobal", + EV_SCRIPTONLY, + "b", + "terrain_global_bool", + "Turns the terrain drawing as global on or off" +); +Event EV_World_SetTerrainGlobalMin +( + "terrainglobalmin", + EV_SCRIPTONLY, + "f", + "terrain_global_min", + "Turns the terrain drawing as global on and sets the minimum" +); +Event EV_World_SetEntityFadeDist +( + "entity_fade_dist", + EV_SCRIPTONLY, + "f", + "entity_fade_dist", + "Sets the default distance to fade out entities" +); +Event EV_World_SetLightIntensity +( + "light_intensity", + EV_SCRIPTONLY, + "sf", + "light_group_name light_intensity", + "Sets the intensity of a dynamic light" +); +Event EV_World_SetLightDefaultIntensity +( + "light_default_intensity", + EV_SCRIPTONLY, + "sf", + "light_group_name light_intensity", + "Sets the default intensity of a dynamic light. This is used only when r_dynamiclightmaps is 0.\n" + "A value below 0 specifies that this light has no default value and will always be dynamic" +); +Event EV_World_SetLightFade +( + "light_fade", + EV_SCRIPTONLY, + "sff", + "light_group_name end_light_intensity fade_time", + "Sets a up a dynamic light to fade" +); +Event EV_World_SetLightLightstyle +( + "light_lightstyle", + EV_SCRIPTONLY, + "ssB", + "light_group_name light_style stop_at_end", + "Sets the lightstyle of a dynamic light" +); +Event EV_World_GetLightIntensity +( + "getlightintensity", + EV_SCRIPTONLY, + "@is", + "light_intensity light_group_name", + "Gets the intensity of a dynamic light" +); +Event EV_World_SetWindDirection +( + "wind_direction", + EV_SCRIPTONLY, + "v", + "wind_direction", + "Sets the direction of the wind" +); +Event EV_World_SetWindIntensity +( + "wind_intensity", + EV_SCRIPTONLY, + "f", + "wind_intensity", + "Sets the intensity of the wind" +); +Event EV_World_SetWindGust +( + "wind_gust", + EV_SCRIPTONLY, + "fff", + "wind_intensity fade_time gust_time", + "Creates a gust of wind" +); +Event EV_World_SetWindFade +( + "wind_fade", + EV_SCRIPTONLY, + "ff", + "end_wind_intensity fade_time", + "Fades the wind intensity to the new value" +); +Event EV_World_SetWindMinMax +( + "wind_minmax", + EV_SCRIPTONLY, + "ffF", + "min_wind_intensity min_wind_intensity max_change_per_frame", + "Sets the min & max intensity of the wind" +); +Event EV_World_SetWeather +( + "weather", + EV_SCRIPTONLY, + "sf", + "type intensity", + "Sets the current weather" +); +Event EV_World_SetPhysicsVar +( + "physicsVar", + EV_SCRIPTONLY, + "sf", + "physicsVarName physicsVerValue", + "Sets one of the world's physics variables" +); +Event EV_World_SetTimeScale +( + "time_scale", + EV_SCRIPTONLY, + "f", + "time_scale", + "Sets the time scale for the world" +); +Event EV_World_SetAmbientLight +( + "ambientlight", + EV_SCRIPTONLY, + "b", + "ambientLight", + "Set whether or not ambient light should be used" +); +Event EV_World_SetAmbientIntensity +( + "ambient", + EV_SCRIPTONLY, + "f", + "ambientIntensity", + "Set the intensity of the ambient light" +); +Event EV_World_SetSunColor +( + "suncolor", + EV_SCRIPTONLY, + "v", + "sunColor", + "Set the color of the sun" +); +Event EV_World_SetSunLight +( + "sunlight", + EV_SCRIPTONLY, + "b", + "sunlight", + "Set whether or not there should be sunlight" +); +Event EV_World_SetSunDirection +( + "sundirection", + EV_SCRIPTONLY, + "v", + "sunlightDirection", + "Set the direction of the sunlight" +); +Event EV_World_LightmapDensity +( + "lightmapdensity", + EV_SCRIPTONLY, + "f", + "density", + "Set the default lightmap density for all world surfaces" +); +Event EV_World_SunFlare +( + "sunflare", + EV_SCRIPTONLY, + "v", + "position_of_sun", + "Set the position of the sun for the purposes of the sunflare" +); +Event EV_World_SunFlareInPortalSky +( + "sunflare_inportalsky", + EV_SCRIPTONLY, + NULL, + NULL, + "Let the renderer know that the sun is in the portal sky" +); +Event EV_World_SetSkyAlpha +( + "skyalpha", + EV_SCRIPTONLY, + "f", + "newAlphaForPortalSky", + "Set the alpha on the sky" +); +Event EV_World_SetSkyPortal +( + "skyportal", + EV_SCRIPTONLY, + "b", + "newSkyPortalState", + "Whether or not to use the sky portal at all" +); +Event EV_World_AddBroken +( + "addbroken", + EV_SCRIPTONLY, + "s", + "brokenThingName", + "Adds this thing to the broken list" +); +Event EV_World_RemoveBroken +( + "removebroken", + EV_SCRIPTONLY, + "s", + "brokenThingName", + "Removes this thing to the broken list" +); +Event EV_World_AddAvailableViewMode +( + "addAvailableViewMode", + EV_SCRIPTONLY, + "s", + "modeName", + "Adds this view mode as being available" +); +Event EV_World_RemoveAvailableViewMode +( + "removeAvailableViewMode", + EV_SCRIPTONLY, + "s", + "modeName", + "Removes this view mode as being available" +); +Event EV_World_ClearAvailableViewModes +( + "clearAvailableViewModes", + EV_SCRIPTONLY, + NULL, + NULL, + "Clears all available modes" +); +Event EV_World_CanShakeCamera +( + "canShakeCamera", + EV_SCRIPTONLY, + "b", + "canShakeCamera", + "Specifies whether or not cinematic camera's can shake from earthquakes." +); + +CLASS_DECLARATION( Entity, World, "worldspawn" ) +{ + { &EV_World_SetSoundtrack, &World::SetSoundtrack }, + { &EV_World_SetSkipThread, &World::SetSkipThread }, + { &EV_World_SetNextMap, &World::SetNextMap }, + { &EV_World_SetMessage, &World::SetMessage }, + { &EV_World_SetScript, &World::SetScript }, + { &EV_World_SetWaterColor, &World::SetWaterColor }, + { &EV_World_SetWaterAlpha, &World::SetWaterAlpha }, + { &EV_World_SetSlimeColor, &World::SetSlimeColor }, + { &EV_World_SetSlimeAlpha, &World::SetSlimeAlpha }, + { &EV_World_SetLavaColor, &World::SetLavaColor }, + { &EV_World_SetLavaAlpha, &World::SetLavaAlpha }, + { &EV_World_SetFarPlane_Color, &World::SetFarPlane_Color }, + { &EV_World_SetFarPlane_Cull, &World::SetFarPlane_Cull }, + { &EV_World_SetFarPlane_Fog, &World::SetFarPlane_Fog }, + { &EV_World_SetFarPlane, &World::SetFarPlane }, + { &EV_World_SetTerrainGlobal, &World::SetTerrainGlobal }, + { &EV_World_SetTerrainGlobalMin, &World::SetTerrainGlobalMin }, + { &EV_World_SetEntityFadeDist, &World::SetEntityFadeDist }, + { &EV_World_SetLightIntensity, &World::SetLightIntensity }, + { &EV_World_SetLightDefaultIntensity, &World::SetLightDefaultIntensity }, + { &EV_World_SetLightFade, &World::SetLightFade }, + { &EV_World_SetLightLightstyle, &World::SetLightLightstyle }, + { &EV_World_GetLightIntensity, &World::GetLightIntensity }, + { &EV_World_SetWindDirection, &World::SetWindDirection }, + { &EV_World_SetWindIntensity, &World::SetWindIntensity }, + { &EV_World_SetWindGust, &World::SetWindGust }, + { &EV_World_SetWindFade, &World::SetWindFade }, + { &EV_World_SetWindMinMax, &World::SetWindMinMax }, + { &EV_World_SetWeather, &World::SetWeather }, + { &EV_World_SetPhysicsVar, &World::setPhysicsVar }, + { &EV_World_SetTimeScale, &World::SetTimeScale }, + { &EV_World_SetSkyAlpha, &World::SetSkyAlpha }, + { &EV_World_SetSkyPortal, &World::SetSkyPortal }, + { &EV_World_SetAmbientLight, NULL }, + { &EV_World_SetAmbientIntensity, NULL }, + { &EV_World_SetSunColor, NULL }, + { &EV_World_SetSunLight, NULL }, + { &EV_World_SetSunDirection, NULL }, + { &EV_World_LightmapDensity, NULL }, + { &EV_World_SunFlare, NULL }, + { &EV_World_SunFlareInPortalSky, NULL }, + { &EV_World_AddBroken, &World::addBrokenThing }, + { &EV_World_RemoveBroken, &World::removeBrokenThing }, + { &EV_World_AddAvailableViewMode, &World::addAvailableViewMode }, + { &EV_World_RemoveAvailableViewMode, &World::removeAvailableViewMode }, + { &EV_World_ClearAvailableViewModes, &World::clearAvailableViewModes }, + { &EV_World_CanShakeCamera, &World::setCanShakeCamera }, + + { NULL, NULL } +}; + +World::World() +{ + const char *text; + str mapname; + int i; + + assert( this->entnum == ENTITYNUM_WORLD ); + + world = this; + world_dying = false; + + setMoveType( MOVETYPE_NONE ); + setSolidType( SOLID_BSP ); + + // world model is always index 1 + edict->s.modelindex = 1; + model = "*1"; + + turnThinkOn(); + + UpdateConfigStrings(); + + groupcoordinator = NULL; + + // Anything that modifies configstrings, or spawns things is ignored when loading savegames + if ( LoadingSavegame ) + { + return; + } + + // clear out the soundtrack from the last level + ChangeSoundtrack( "" ); + + // set the default farplane parameters + farplane_distance = 0; + farplane_color = Vector(0, 0, 0); + farplane_cull = true; + farplane_fog = true; + UpdateFog(); + + terrain_global = false; + terrain_global_min = MIN_WORLD_COORD; + UpdateTerrain(); + + entity_fade_dist = DEFAULT_ENTITY_FADE_DIST; + UpdateEntityFadeDist(); + + UpdateDynamicLights(); + + UpdateWeather(); + + time_scale = 1.0f; + sky_alpha = 1.0f; + sky_portal = true; + UpdateSky(); + + // + // see if this is a cinematic level + // + level.cinematic = ( spawnflags & CINEMATIC ) ? true : false; + + if ( level.cinematic ) + gi.cvar_set( "sv_cinematic", "1" ); + else + gi.cvar_set( "sv_cinematic", "0" ); + + level.nextmap = ""; + level.level_name = level.mapname; + + // Set up the mapname as the default script + mapname = "maps/"; + mapname += level.mapname; + for( i = mapname.length() - 1; i >= 0; i-- ) + { + if ( mapname[ i ] == '.' ) + { + mapname[ i ] = 0; + break; + } + } + + mapname += ".scr"; + text = &mapname[ 5 ]; + + // If there isn't a script with the same name as the map, then don't try to load script + if ( gi.FS_ReadFile( mapname.c_str(), NULL, true ) != -1 ) + { + gi.DPrintf( "Adding script: '%s'\n", text ); + + // just set the script, we will start it in G_Spawn + level.SetGameScript( mapname.c_str() ); + } + else + { + level.SetGameScript( "" ); + } + + level.consoleThread = Director.CreateThread(); + + SoundMan.Init(); + SoundMan.Load(); + + // Set the color for the blends. + level.water_color = Vector( 0.0f, 0.0f, 0.5f ); + level.water_alpha = 0.4f; + + level.slime_color = Vector( 0.2f, 0.4f, 0.2f ); + level.slime_alpha = 0.6f; + + level.lava_color = Vector( 0.5f, 0.15f, 0.0f ); + level.lava_alpha = 0.6f; + + // + // set the targetname of the world + // + SetTargetName( "world" ); + + groupcoordinator = new GroupCoordinator; + + // Initialize movement info + + for ( i = 0 ; i < WORLD_PHYSICS_TOTAL_NUMBER ; i++ ) + { + _physicsInfo[ i ] = -1.0f; + } + + _canShakeCamera = false; +} + +void World::UpdateConfigStrings( void ) +{ + // + // make some data visible to connecting client + // + gi.setConfigstring( CS_GAME_VERSION, GAME_VERSION ); + gi.setConfigstring( CS_LEVEL_START_TIME, va("%i", level.startTime ) ); + + // make some data visible to the server + gi.setConfigstring( CS_NAME, level.level_name.c_str() ); +} + +void World::UpdateFog( void ) +{ + gi.SetFarPlane( farplane_distance, farplane_cull ); + gi.setConfigstring( CS_FOGINFO, va( "%d %d %.0f %.4f %.4f %.4f", farplane_cull, farplane_fog, farplane_distance, farplane_color.x, farplane_color.y, farplane_color.z ) ); +} + +void World::UpdateTerrain( void ) +{ + //gi.SetFarPlane( farplane_distance ); + gi.setConfigstring( CS_TERRAININFO, va( "%d %.0f", terrain_global, terrain_global_min ) ); +} + +void World::UpdateEntityFadeDist( void ) +{ + gi.setConfigstring( CS_ENTITYFADEINFO, va( "%f", entity_fade_dist ) ); +} + +void World::UpdateDynamicLights( void ) +{ + int i; + + for ( i = 0 ; i < MAX_LIGHTING_GROUPS ; i++ ) + { + gi.SetDynamicLight( i, dynamic_lights[ i ].intensity ); + gi.SetDynamicLightDefault( i, dynamic_lights[ i ].defaultIntensity ); + } +} + +void World::UpdateWindDirection( void ) +{ + gi.SetWindDirection( wind.direction ); +} + +void World::UpdateWindIntensity( void ) +{ + gi.SetWindIntensity( wind.intensity ); +} + +void World::UpdateTimeScale( void ) +{ + gi.SetTimeScale( time_scale ); +} + +void World::UpdateWeather( void ) +{ + gi.SetWeatherInfo( weather.type, weather.intensity ); +} + +void World::UpdateSky( void ) +{ + gi.SetSkyPortal( sky_portal ); + gi.setConfigstring( CS_SKYINFO, va( "%.4f %d", sky_alpha, sky_portal ) ); +} + + +void World::SetSoundtrack( Event *ev ) +{ + const char *text; + + text = ev->GetString( 1 ); + ChangeSoundtrack( text ); +} + +void World::SetFarPlane( Event *ev ) +{ + farplane_distance = ev->GetFloat( 1 ); + UpdateFog(); +} + +void World::SetFarPlane_Color( Event *ev ) +{ + farplane_color = ev->GetVector( 1 ); + UpdateFog(); +} + +void World::SetFarPlane_Cull( Event *ev ) +{ + farplane_cull = ev->GetBoolean( 1 ); + UpdateFog(); +} + +void World::SetFarPlane_Fog( Event *ev ) +{ + farplane_fog = ev->GetBoolean( 1 ); + UpdateFog(); +} + +void World::SetTerrainGlobal( Event *ev ) +{ + terrain_global = ev->GetBoolean( 1 ); + terrain_global_min = MIN_WORLD_COORD; + UpdateTerrain(); +} + +void World::SetTerrainGlobalMin( Event *ev ) +{ + terrain_global = true; + terrain_global_min = ev->GetFloat( 1 ); + UpdateTerrain(); +} + +void World::SetEntityFadeDist( Event *ev ) +{ + entity_fade_dist = ev->GetFloat( 1 ); + UpdateEntityFadeDist(); +} + +void World::SetLightIntensity( Event *ev ) +{ + str light_group_name; + int light_group_num; + float light_intensity; + + // Get the light group + + light_group_name = ev->GetString( 1 ); + light_intensity = ev->GetFloat( 2 ); + + light_group_num = gi.GetLightingGroup( light_group_name ); + + // Make sure this is a valid light group + + if ( light_group_num == -1 ) + { + gi.WDPrintf( "Unknown light group %s\n", light_group_name.c_str() ); + return; + } + + // Set the light info + + dynamic_lights[ light_group_num ].intensity = light_intensity; + dynamic_lights[ light_group_num ].fade_time = 0; + dynamic_lights[ light_group_num ].lightstyle = ""; +} + +//---------------------------------------------------------------- +// Name: SetLightDefaultIntensity +// Class: World +// +// Description: Sets the default light intensity for a dynamic light +// +// Parameters: Event *ev - contains the following +// str light_group_name +// float light_intensity +// +// Returns: +//---------------------------------------------------------------- + +void World::SetLightDefaultIntensity( Event *ev ) +{ + str light_group_name; + int light_group_num; + float light_intensity; + + // Get the light group + + light_group_name = ev->GetString( 1 ); + light_intensity = ev->GetFloat( 2 ); + + light_group_num = gi.GetLightingGroup( light_group_name ); + + // Make sure this is a valid light group + + if ( light_group_num == -1 ) + { + gi.WDPrintf( "Unknown light group %s\n", light_group_name.c_str() ); + return; + } + + // Set the light info + + dynamic_lights[ light_group_num ].defaultIntensity = light_intensity; +} + +void World::SetLightFade( Event *ev ) +{ + str light_group_name; + int light_group_num; + float light_end_intensity; + float fade_time; + + // Get the light group + + light_group_name = ev->GetString( 1 ); + light_end_intensity = ev->GetFloat( 2 ); + fade_time = ev->GetFloat( 3 ); + + light_group_num = gi.GetLightingGroup( light_group_name ); + + // Make sure this is a valid light group + + if ( light_group_num == -1 ) + { + gi.WDPrintf( "Unknown light group %s\n", light_group_name.c_str() ); + return; + } + + // Set the light info + + dynamic_lights[ light_group_num ].start_intensity = dynamic_lights[ light_group_num ].intensity; + dynamic_lights[ light_group_num ].end_intensity = light_end_intensity; + + dynamic_lights[ light_group_num ].start_fade_time = level.time; + dynamic_lights[ light_group_num ].fade_time = fade_time; +} + +void World::SetLightLightstyle( Event *ev ) +{ + str light_group_name; + int light_group_num; + str lightstyle; + qboolean stop_at_end; + + // Get the light group + + light_group_name = ev->GetString( 1 ); + lightstyle = ev->GetString( 2 ); + + if ( ev->NumArgs() > 2 ) + stop_at_end = ev->GetBoolean( 3 ); + else + stop_at_end = false; + + light_group_num = gi.GetLightingGroup( light_group_name ); + + // Make sure this is a valid light group + + if ( light_group_num == -1 ) + { + gi.WDPrintf( "Unknown light group %s\n", light_group_name.c_str() ); + return; + } + + // Set the light info + + dynamic_lights[ light_group_num ].lightstyle = lightstyle; + dynamic_lights[ light_group_num ].current_lightstyle_position = -1; + dynamic_lights[ light_group_num ].stop_at_end = stop_at_end; +} + +void World::GetLightIntensity( Event *ev ) +{ + str light_group_name; + int light_group_num; + + // Get the light group + + light_group_name = ev->GetString( 1 ); + + light_group_num = gi.GetLightingGroup( light_group_name ); + + // Make sure this is a valid light group + + if ( light_group_num == -1 ) + { + gi.WDPrintf( "Unknown light group %s\n", light_group_name.c_str() ); + return; + } + + ev->ReturnFloat( dynamic_lights[ light_group_num ].intensity ); +} + +void World::SetWindDirection( Event *ev ) +{ + wind.direction = ev->GetVector( 1 ); +} + +void World::SetWindIntensity( Event *ev ) +{ + wind.intensity = ev->GetFloat( 1 ); + + wind.gust_time = 0; + wind.fade_time = 0; + wind.min_intensity = -1; + wind.max_intensity = -1; +} + +void World::SetWindGust( Event *ev ) +{ + wind.start_intensity = wind.intensity; + wind.end_intensity = ev->GetFloat( 1 ); + + wind.start_fade_time = level.time; + wind.fade_time = ev->GetFloat( 2 ); + + wind.gust_time = ev->GetFloat( 3 ); + + wind.min_intensity = -1; + wind.max_intensity = -1; +} + +void World::SetWindFade( Event *ev ) +{ + wind.start_intensity = wind.intensity; + wind.end_intensity = ev->GetFloat( 1 ); + + wind.start_fade_time = level.time; + wind.fade_time = ev->GetFloat( 2 ); + + wind.gust_time = 0; + + wind.min_intensity = -1; + wind.max_intensity = -1; +} + +void World::SetWindMinMax( Event *ev ) +{ + wind.fade_time = 0; + + wind.gust_time = 0; + + wind.min_intensity = ev->GetFloat( 1 ); + wind.max_intensity = ev->GetFloat( 2 ); + + if ( ev->NumArgs() > 2 ) + wind.max_change = ev->GetFloat( 3 ); + else + wind.max_change = ( wind.max_intensity - wind.min_intensity ) * 0.05f; +} + +void World::SetWeather( Event *ev ) +{ + str weather_name; + + weather_name = ev->GetString( 1 ); + + if ( weather_name == "rain" ) + weather.type = WEATHER_RAIN; + else if ( weather_name == "rain_plain" ) + weather.type = WEATHER_RAIN_PLAIN; + else if ( weather_name == "snow" ) + weather.type = WEATHER_SNOW; + else + weather.type = WEATHER_NONE; + + weather.intensity = ev->GetFloat( 2 ); + + UpdateWeather(); +} + +void World::SetTimeScale( Event *ev ) +{ + time_scale = ev->GetFloat( 1 ); +} + +void World::SetSkyAlpha( Event *ev ) +{ + sky_alpha = ev->GetFloat( 1 ); + UpdateSky(); +} + +void World::SetSkyPortal( Event *ev ) +{ + sky_portal = ev->GetBoolean( 1 ); + UpdateSky(); +} + + +void World::SetSkipThread( Event *ev ) +{ + str label; + + label = ev->GetString( 1 ); + if ( label.length() && label.icmp( "null" ) ) + { + skipthread = label; + } + else + { + skipthread = ""; + } +} + +void World::SetNextMap( Event *ev ) +{ + level.nextmap = ev->GetString( 1 ); +} + +void World::SetMessage( Event *ev ) +{ + const char *text; + + text = ev->GetString( 1 ); + level.level_name = text; + gi.setConfigstring( CS_NAME, text ); +} + +void World::SetScript( Event *ev ) +{ + str text; + str scriptname; + + text = ev->GetString( 1 ); + + gi.DPrintf( "Adding script: '%s'\n", text.c_str() ); + scriptname = "maps/" + text; + + // just set the script, we will start it in G_Spawn + level.SetGameScript( scriptname.c_str() ); +} + +void World::SetWaterColor( Event *ev ) +{ + level.water_color = ev->GetVector( 1 ); +} + +void World::SetWaterAlpha( Event *ev ) +{ + level.water_alpha = ev->GetFloat( 1 ); +} + +void World::SetSlimeColor( Event *ev ) +{ + level.slime_color = ev->GetVector( 1 ); +} + +void World::SetSlimeAlpha( Event *ev ) +{ + level.slime_alpha = ev->GetFloat( 1 ); +} + +void World::SetLavaColor( Event *ev ) +{ + level.lava_color = ev->GetVector( 1 ); +} + +void World::SetLavaAlpha( Event *ev ) +{ + level.lava_alpha = ev->GetFloat( 1 ); +} + +TargetList * World::GetTargetList( const str &targetname, qboolean createnew ) +{ + TargetList * targetlist; + int i; + + for( i = 1; i <= targetList.NumObjects(); i++ ) + { + targetlist = targetList.ObjectAt( i ); + if ( targetname == targetlist->targetname) + { + return targetlist; + } + } + + if ( !createnew ) + { + return NULL; + } + + targetlist = new TargetList( targetname ); + targetlist->index = targetList.AddObject( targetlist ); + + return targetlist; +} + +void World::AddTargetEntity( const str &targetname, Entity * ent ) +{ + TargetList * targetlist; + + targetlist = GetTargetList( targetname ); + targetlist->AddEntity( ent ); +} + +void World::RemoveTargetEntity( const str &targetname, Entity * ent ) +{ + TargetList * targetlist; + + if ( world_dying ) + return; + + targetlist = GetTargetList( targetname ); + targetlist->RemoveEntity( ent ); +} + +Entity * World::GetNextEntity( const str &targetname, Entity * ent ) +{ + TargetList * targetlist; + + targetlist = GetTargetList( targetname ); + return targetlist->GetNextEntity( ent ); +} + +World::~World() +{ + world_dying = true; + FreeTargetList(); + + if ( groupcoordinator ) + delete groupcoordinator; + + groupcoordinator = NULL; + + freeAllBrokenThings(); +} + +void World::FreeTargetList( void ) +{ + int i; + int num; + + num = targetList.NumObjects(); + for( i = 1; i <= num; i++ ) + { + delete targetList.ObjectAt( i ); + } + + targetList.FreeObjectList(); +} + +void World::Think( void ) +{ + int i; + DynamicLightInfo *dlight; + + // Do dynamic light stuff + + for ( i = 0 ; i < MAX_LIGHTING_GROUPS ; i++ ) + { + dlight = &dynamic_lights[ i ]; + + if ( dlight->fade_time ) + { + if ( level.time > ( dlight->start_fade_time + dlight->fade_time ) ) + { + dlight->intensity = dlight->end_intensity; + dlight->fade_time = 0; + } + else + { + dlight->intensity = dlight->start_intensity + ( ( dlight->end_intensity - dlight->start_intensity ) * + ( ( level.time - dlight->start_fade_time ) / dlight->fade_time ) ); + } + } + else if ( dlight->lightstyle.length() ) + { + dlight->current_lightstyle_position++; + + if ( dlight->current_lightstyle_position >= dlight->lightstyle.length() ) + { + if ( dlight->stop_at_end ) + { + dlight->lightstyle = ""; + dlight->current_lightstyle_position = -1; + } + else + { + dlight->current_lightstyle_position = 0; + } + } + + if ( dlight->current_lightstyle_position >= 0 ) + dlight->intensity = ( dlight->lightstyle[ dlight->current_lightstyle_position ] - 'a' ) / 25.0f; + } + } + + UpdateDynamicLights(); + + // Do wind stuff + + if ( wind.gust_time ) + { + if ( level.time < ( wind.start_fade_time + wind.fade_time ) ) + { + wind.intensity = wind.start_intensity + ( ( wind.end_intensity - wind.start_intensity ) * + ( ( level.time - wind.start_fade_time ) / wind.fade_time ) ); + } + else if ( level.time < ( wind.start_fade_time + wind.fade_time + wind.gust_time ) ) + { + wind.intensity = wind.end_intensity; + } + else + { + wind.intensity = wind.end_intensity + ( ( wind.start_intensity - wind.end_intensity ) * + ( ( level.time - wind.start_fade_time - wind.fade_time - wind.gust_time ) / wind.fade_time ) ); + + if ( level.time > ( wind.start_fade_time + wind.fade_time + wind.gust_time + wind.fade_time ) ) + { + wind.intensity = wind.start_intensity; + wind.fade_time = 0; + wind.gust_time = 0; + } + } + } + else if ( wind.fade_time ) + { + if ( level.time > ( wind.start_fade_time + wind.fade_time ) ) + { + wind.intensity = wind.end_intensity; + wind.fade_time = 0; + } + else + { + wind.intensity = wind.start_intensity + ( ( wind.end_intensity - wind.start_intensity ) * + ( ( level.time - wind.start_fade_time ) / wind.fade_time ) ); + } + } + else if ( ( wind.min_intensity > 0.0f ) && ( wind.max_intensity > 0.0f ) ) + { + wind.intensity += G_CRandom( wind.max_change ); + + if ( wind.intensity > wind.max_intensity ) + wind.intensity = wind.max_intensity; + + if ( wind.intensity < wind.min_intensity ) + wind.intensity = wind.min_intensity; + } + + UpdateWindDirection(); + UpdateWindIntensity(); + UpdateTimeScale(); + + // Do gravity stuff + + float gravity; + + // Try to use the world's specified gravity + + gravity = getPhysicsVar( WORLD_PHYSICS_GRAVITY ); + + // If not set, use the normal cvar for the gravity + + if ( gravity == -1 ) + { + gravity = sv_gravity->value; + } + + // If gravity has changed change the cvar + + if ( sv_currentGravity->value != gravity ) + { + gi.cvar_set( "sv_currentGravity", va( "%f", gravity ) ); + } +} + +//---------------------------------------------------------------- +// Name: findBrokenThing +// Class: World +// +// Description: Tries to find the specified broken thing in the list +// +// Parameters: const char *name - name of the potential broken thing +// +// Returns: int - index of the broken thing if found, 0 if not found +//---------------------------------------------------------------- + +int World::findBrokenThing( const char *name ) +{ + int i; + str *brokenThingName; + + for( i = 1 ; i <= _brokenThings.NumObjects() ; i++ ) + { + brokenThingName = &_brokenThings.ObjectAt( i ); + + if ( stricmp( brokenThingName->c_str(), name ) == 0 ) + return i; + } + + return 0; +} + +//---------------------------------------------------------------- +// Name: addBrokenThing +// Class: World +// +// Description: Adds a broken thing to the list +// +// Parameters: Event *ev - contains the name of the broken thing +// +// Returns: none +//---------------------------------------------------------------- + +void World::addBrokenThing( Event *ev ) +{ + addBrokenThing( ev->GetString( 1 ) ); +} + +//---------------------------------------------------------------- +// Name: removeBrokenThing +// Class: World +// +// Description: Removes a broken thing from the list +// +// Parameters: Event *ev - contains the name of the broken thing +// +// Returns: none +//---------------------------------------------------------------- + +void World::removeBrokenThing( Event *ev ) +{ + removeBrokenThing( ev->GetString( 1 ) ); +} + +//---------------------------------------------------------------- +// Name: addBrokenThing +// Class: World +// +// Description: Adds a broken thing to the list +// +// Parameters: const char *name - name of the broken thing +// +// Returns: none +//---------------------------------------------------------------- + +void World::addBrokenThing( const char *name ) +{ + if ( !findBrokenThing( name ) ) + { + _brokenThings.AddObject( name ); + } +} + +//---------------------------------------------------------------- +// Name: removeBrokenThing +// Class: World +// +// Description: Removes a broken thing from the list +// +// Parameters: const char *name - name of the broken thing +// +// Returns: none +//---------------------------------------------------------------- + +void World::removeBrokenThing( const char *name ) +{ + int index; + + index = findBrokenThing( name ); + + if ( index ) + { + _brokenThings.RemoveObjectAt( index ); + } +} + +//---------------------------------------------------------------- +// Name: freeAllBrokenThings +// Class: World +// +// Description: Frees the broken thing list +// +// Parameters: none +// +// Returns: none +//---------------------------------------------------------------- + +void World::freeAllBrokenThings( void ) +{ + _brokenThings.FreeObjectList(); +} + +//---------------------------------------------------------------- +// Name: addAvailableViewMode +// Class: World +// +// Description: Adds a mode to the list of view modes that are available +// +// Parameters: Event *ev - contains name of the mode +// +// Returns: none +//---------------------------------------------------------------- + +void World::addAvailableViewMode( Event *ev ) +{ + _availableViewModes.AddUniqueObject( ev->GetString( 1 ) ); +} + +//---------------------------------------------------------------- +// Name: removeAvailableViewMode +// Class: World +// +// Description: Removes the named mode from the list of view modes that are available +// +// Parameters: Event *ev - contains name of the mode +// +// Returns: none +//---------------------------------------------------------------- + +void World::removeAvailableViewMode( Event *ev ) +{ + if ( _availableViewModes.ObjectInList( ev->GetString( 1 ) ) ) + { + _availableViewModes.RemoveObject( ev->GetString( 1 ) ); + } +} + +//---------------------------------------------------------------- +// Name: clearAvailableViewModes +// Class: World +// +// Description: Clears all of the available view modes +// +// Parameters: Event *ev - not used +// +// Returns: none +//---------------------------------------------------------------- + +void World::clearAvailableViewModes( Event *ev ) +{ + _availableViewModes.ClearObjectList(); +} + +//---------------------------------------------------------------- +// Name: isAnyViewModeAvailable +// Class: World +// +// Description: Checks to see if any view modes are available +// +// Parameters: none +// +// Returns: bool - whether or not any view modes are available +//---------------------------------------------------------------- + +bool World::isAnyViewModeAvailable( void ) +{ + if ( _availableViewModes.NumObjects() ) + return true; + else + return false; +} + +//---------------------------------------------------------------- +// Name: isViewModeAvailable +// Class: World +// +// Description: Checks to see if the specified view mode is available +// +// Parameters: none +// +// Returns: bool - whether or not the specified view mode is available +//---------------------------------------------------------------- + +bool World::isViewModeAvailable( const char *name ) +{ + if ( _availableViewModes.IndexOfObject( name ) ) + return true; + else + return false; +} + +int World::worldPhysicsVarNameToIndex( const char *varName ) + { + int i; + + for ( i = 0 ; i < WORLD_PHYSICS_TOTAL_NUMBER ; i++ ) + { + if ( stricmp( varName, worldPhysicsVarNames[ i ] ) == 0 ) + return i; + } + + gi.DPrintf( "Unknown physics variable %s\n", varName ); + return -1; + } + +void World::setPhysicsVar( Event *ev ) +{ + setPhysicsVar( ev->GetString( 1 ), ev->GetFloat( 2 ) ); +} + +void World::setPhysicsVar( const char *varName, float varValue ) +{ + int index; + + index = worldPhysicsVarNameToIndex( varName ); + + if ( index != -1 ) + { + _physicsInfo[ index ] = varValue; + } +} + +float World::getPhysicsVar( int index ) +{ + return _physicsInfo[ index ]; +} + +float World::getPhysicsVar( const char *varName ) +{ + int index; + + index = worldPhysicsVarNameToIndex( varName ); + + if ( index != -1 ) + { + return getPhysicsVar( index ); + } + else + { + return -1.0f; + } +} + + +void World::Archive( Archiver &arc ) +{ + int i; + int num; + TargetList *tempTargetList; + + Entity::Archive( arc ); + + if ( arc.Loading() ) + { + FreeTargetList(); + } + + if ( arc.Saving() ) + num = targetList.NumObjects(); + + arc.ArchiveInteger( &num ); + + for ( i = 1; i <= num; i++ ) + { + if ( arc.Saving() ) + { + tempTargetList = targetList.ObjectAt( i ); + } + else + { + tempTargetList = new TargetList; + targetList.AddObject( tempTargetList ); + } + + arc.ArchiveObject( ( Class * )tempTargetList ); + } + + _brokenThings.Archive( arc ); + + _availableViewModes.Archive( arc ); + + arc.ArchiveBoolean( &world_dying ); + + arc.ArchiveString( &skipthread ); + arc.ArchiveFloat( &farplane_distance ); + arc.ArchiveVector( &farplane_color ); + arc.ArchiveBoolean( &farplane_cull ); + arc.ArchiveBoolean( &farplane_fog ); + arc.ArchiveBoolean( &terrain_global ); + arc.ArchiveFloat( &terrain_global_min ); + arc.ArchiveFloat( &entity_fade_dist ); + + for( i = 0 ; i < MAX_LIGHTING_GROUPS ; i++ ) + dynamic_lights[ i ].Archive( arc ); + + wind.Archive( arc ); + weather.Archive( arc ); + + arc.ArchiveFloat( &time_scale ); + + arc.ArchiveFloat( &sky_alpha ); + arc.ArchiveBoolean( &sky_portal ); + + for ( i = 0 ; i < WORLD_PHYSICS_TOTAL_NUMBER ; i++ ) + { + arc.ArchiveFloat( &_physicsInfo[ i ] ); + } + + arc.ArchiveBool( &_canShakeCamera ); + + if ( arc.Loading() ) + { + UpdateConfigStrings(); + UpdateFog(); + UpdateTerrain(); + UpdateSky(); + UpdateDynamicLights(); + UpdateWindDirection(); + UpdateWindIntensity(); + UpdateWeather(); + UpdateTimeScale(); + } + + // Archive groupcoordinator (not part of world but this is a good place) + + if ( arc.Loading() ) + { + if ( groupcoordinator ) + delete groupcoordinator; + + groupcoordinator = new GroupCoordinator; + } + + groupcoordinator->Archive( arc ); +} + +void World::setCanShakeCamera( Event *ev ) +{ + _canShakeCamera = ev->GetBoolean( 1 ); +} + +bool World::canShakeCamera( void ) +{ + return _canShakeCamera; +} + +//---------------------------------------------------------------- +// Name: isThingBroken +// Class: World +// +// Description: Checks to see if the specified thing is broken +// +// Parameters: const char *name - potential broken thing +// +// Returns: none +//---------------------------------------------------------------- + +bool World::isThingBroken( const char *name ) +{ + if ( findBrokenThing( name ) ) + return true; + else + return false; +} + +DynamicLightInfo::DynamicLightInfo() +{ + defaultIntensity = 0.5f; + + intensity = 1.0f; + fade_time = 0.0f; + + start_intensity = 0.0f; + end_intensity = 0.0f; + + start_fade_time = 0.0f; + + current_lightstyle_position = 0; + stop_at_end = false; +} + +WindInfo::WindInfo() +{ + intensity = 0.0f; + fade_time = 0.0f; + gust_time = 0.0f; + start_fade_time = 0.0f; + max_change = 0.0f; + + min_intensity = -1.0f; + max_intensity = -1.0f; + + start_intensity = 0.0f; + end_intensity = 0.0f; + direction = Vector( 1, 1, 0 ); +} + +WeatherInfo::WeatherInfo() +{ + type = WEATHER_NONE; + intensity = 0; +} + +// +// List stuff for targets +// + +CLASS_DECLARATION( Class, TargetList, NULL ) +{ + { NULL, NULL } +}; + +TargetList::TargetList() +{ +} + +TargetList::TargetList( const str &tname ) +{ + targetname = tname; +} + +TargetList::~TargetList() +{ +} + +void TargetList::AddEntity( Entity * ent ) +{ + if ( !list.ObjectInList( ent ) ) + { + list.AddObject( ent ); + } +} + +void TargetList::RemoveEntity( Entity * ent ) +{ + if ( list.ObjectInList( ent ) ) + { + list.RemoveObject( ent ); + } +} + +Entity * TargetList::GetNextEntity( Entity * ent ) +{ + int index; + + index = 0; + if ( ent ) + index = list.IndexOfObject( ent ); + index++; + if ( index > list.NumObjects() ) + return NULL; + else + return list.ObjectAt( index ); +} diff --git a/dlls/game/worldspawn.h b/dlls/game/worldspawn.h new file mode 100644 index 0000000..3754fe2 --- /dev/null +++ b/dlls/game/worldspawn.h @@ -0,0 +1,313 @@ +//----------------------------------------------------------------------------- +// +// $Logfile:: /Code/DLLs/game/worldspawn.h $ +// $Revision:: 28 $ +// $Author:: Steven $ +// $Date:: 3/02/03 4:13p $ +// +// Copyright (C) 2002 by Ritual Entertainment, Inc. +// All rights reserved. +// +// This source is may not be distributed and/or modified without +// expressly written permission by Ritual Entertainment, Inc. +// +// +// DESCRIPTION: +// Base class for worldspawn objects. This should be subclassed whenever +// a DLL has new game behaviour that needs to be initialized before any other +// entities are created, or before any entity thinks each frame. Also controls +// spawning of clients. +// + +class TargetList; +class DynamicLightInfo; +class WindInfo; +class World; + +#ifndef __WORLDSPAWN_H__ +#define __WORLDSPAWN_H__ + +#include "entity.h" +#include + +typedef enum +{ + WORLD_PHYSICS_MAXSPEED, + WORLD_PHYSICS_AIRACCELERATE, + WORLD_PHYSICS_GRAVITY, + + WORLD_PHYSICS_TOTAL_NUMBER +} WorldPhysicsVarTypes; + +class TargetList : public Class +{ +public: + CLASS_PROTOTYPE( TargetList ); + + Container list; + str targetname; + int index; + + TargetList(); + TargetList( const str &tname ); + ~TargetList(); + + void AddEntity( Entity * ent ); + void RemoveEntity( Entity * ent ); + Entity *GetNextEntity( Entity * ent ); + int GetNumTargets() { return list.NumObjects() ; } + + virtual void Archive( Archiver &arc ); +}; + +class DynamicLightInfo +{ +public: + float defaultIntensity; + + float intensity; + + float start_intensity; + float end_intensity; + + float start_fade_time; + float fade_time; + + str lightstyle; + int current_lightstyle_position; + qboolean stop_at_end; + + DynamicLightInfo(); + virtual void Archive( Archiver &arc ); +}; + +inline void DynamicLightInfo::Archive( Archiver &arc ) +{ + arc.ArchiveFloat( &defaultIntensity ); + + arc.ArchiveFloat( &intensity ); + arc.ArchiveFloat( &start_intensity ); + arc.ArchiveFloat( &end_intensity ); + + arc.ArchiveFloat( &start_fade_time ); + arc.ArchiveFloat( &fade_time ); + + arc.ArchiveString( &lightstyle ); + arc.ArchiveInteger( ¤t_lightstyle_position ); + arc.ArchiveBoolean( &stop_at_end ); +} + +class WindInfo +{ +public: + Vector direction; + float intensity; + + float start_intensity; + float end_intensity; + + float min_intensity; + float max_intensity; + + float start_fade_time; + float fade_time; + + float gust_time; + + float max_change; + + WindInfo(); + virtual void Archive( Archiver &arc ); +}; + +inline void WindInfo::Archive( Archiver &arc ) +{ + arc.ArchiveVector( &direction ); + arc.ArchiveFloat( &intensity ); + arc.ArchiveFloat( &start_intensity ); + arc.ArchiveFloat( &end_intensity ); + + arc.ArchiveFloat( &min_intensity ); + arc.ArchiveFloat( &max_intensity ); + + arc.ArchiveFloat( &start_fade_time ); + arc.ArchiveFloat( &fade_time ); + + arc.ArchiveFloat( &gust_time ); + + arc.ArchiveFloat( &max_change ); +} + +class WeatherInfo +{ +public: + weather_t type; + int intensity; + + WeatherInfo(); + virtual void Archive( Archiver &arc ); +}; + +inline void WeatherInfo::Archive( Archiver &arc ) +{ + ArchiveEnum( type, weather_t ); + arc.ArchiveInteger( &intensity ); +} + +class World : public Entity +{ +private: + Container targetList; + Container _brokenThings; + Container _availableViewModes; + qboolean world_dying; + +public: + CLASS_PROTOTYPE( World ); + + str skipthread; + float farplane_distance; + Vector farplane_color; + qboolean farplane_cull; + qboolean farplane_fog; + + qboolean terrain_global; + float terrain_global_min; + + float entity_fade_dist; + + DynamicLightInfo dynamic_lights[ MAX_LIGHTING_GROUPS ]; + + WindInfo wind; + + WeatherInfo weather; + float time_scale; + + float sky_alpha; + qboolean sky_portal; + + // Movement info + float _physicsInfo[ WORLD_PHYSICS_TOTAL_NUMBER ]; + + bool _canShakeCamera; + + World(); + ~World(); + + void FreeTargetList( void ); + //TargetList *GetTargetList( const str &targetname ); + TargetList *GetTargetList( const str &targetname, qboolean createnew = true ); + TargetList *GetTargetList( int index ); + void AddTargetEntity( const str &targetname, Entity * ent ); + void RemoveTargetEntity( const str &targetname, Entity * ent ); + Entity *GetNextEntity( const str &targetname, Entity * ent ); + void SetSoundtrack( Event *ev ); + void SetSkipThread( Event *ev ); + void SetNextMap( Event *ev ); + void SetMessage( Event *ev ); + void SetScript( Event *ev ); + void SetWaterColor( Event *ev ); + void SetWaterAlpha( Event *ev ); + void SetSlimeColor( Event *ev ); + void SetSlimeAlpha( Event *ev ); + void SetLavaColor( Event *ev ); + void SetLavaAlpha( Event *ev ); + void SetFarPlane_Color( Event *ev ); + void SetFarPlane_Cull( Event *ev ); + void SetFarPlane_Fog( Event *ev ); + void SetFarPlane( Event *ev ); + void SetTerrainGlobal( Event *ev ); + void SetTerrainGlobalMin( Event *ev ); + void SetEntityFadeDist( Event *ev ); + void SetSkyAlpha( Event *ev ); + void SetSkyPortal( Event *ev ); + void UpdateConfigStrings( void ); + void UpdateFog( void ); + void UpdateTerrain( void ); + void UpdateEntityFadeDist( void ); + void UpdateDynamicLights( void ); + void UpdateWindDirection( void ); + void UpdateWindIntensity( void ); + void UpdateWeather( void ); + void UpdateTimeScale( void ); + void UpdateSky( void ); + + void SetLightIntensity( Event *ev ); + void SetLightDefaultIntensity( Event *ev ); + void SetLightFade( Event *ev ); + void SetLightLightstyle( Event *ev ); + void GetLightIntensity( Event *ev ); + + void SetWindDensity( Event *ev ); + void SetWindDirection( Event *ev ); + void SetWindIntensity( Event *ev ); + void SetWindGust( Event *ev ); + void SetWindFade( Event *ev ); + void SetWindMinMax( Event *ev ); + void SetWeather( Event *ev ); + + int worldPhysicsVarNameToIndex( const char *varName ); + void setPhysicsVar( Event *ev ); + void setPhysicsVar( const char *varName, float varValue ); + float getPhysicsVar( const char *varName ); + float getPhysicsVar( int index ); + + void SetTimeScale( Event *ev ); + + int findBrokenThing( const char *name ); + void addBrokenThing( const char *name ); + void removeBrokenThing( const char *name ); + bool isThingBroken( const char *name ); + void addBrokenThing( Event *ev ); + void removeBrokenThing( Event *ev ); + void freeAllBrokenThings( void ); + + void addAvailableViewMode( Event *ev ); + void removeAvailableViewMode( Event *ev ); + void clearAvailableViewModes( Event *ev ); + bool isAnyViewModeAvailable( void ); + bool isViewModeAvailable( const char *name ); + + void setCanShakeCamera( Event *ev ); + bool canShakeCamera( void ); + + void Think( void ); + + virtual void Archive( Archiver &arc ); +}; + +inline void TargetList::Archive( Archiver &arc ) +{ + int num, i; + + Class::Archive( arc ); + + if ( arc.Saving() ) + num = list.NumObjects(); + + arc.ArchiveInteger( &num ); + + if ( arc.Loading() ) + { + list.ClearObjectList(); + list.Resize( num ); + } + + for ( i = 1; i <= num; i++ ) + { + arc.ArchiveObjectPointer( ( Class ** )list.AddressOfObjectAt( i ) ); + } + + arc.ArchiveString( &targetname ); + arc.ArchiveInteger( &index ); +} + +inline TargetList *World::GetTargetList( int index ) +{ + return targetList.ObjectAt( index ); +} + +typedef SafePtr WorldPtr; +extern WorldPtr world; + +#endif /* worldspawn.h */ diff --git a/licenseAgreement.txt b/licenseAgreement.txt new file mode 100644 index 0000000..c7ed284 --- /dev/null +++ b/licenseAgreement.txt @@ -0,0 +1,51 @@ + LIMITED USE SOFTWARE LICENSE AGREEMENT + +This Limited Use Software License Agreement (this "Agreement") is a legal agreement between you, the end-user, and Ritual Entertainment, Inc. ("Ritual"). BY CONTINUING THE INSTALLATION OF THIS SOFTWARE (THE "SOFTWARE"), BY DOWNLOADING, LOADING OR RUNNING THE SOFTWARE, OR BY PLACING OR COPYING THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE, COMPUTER RAM OR OTHER STORAGE, YOU ARE AGREEING TO BE BOUND BY THE TERMS OF THIS AGREEMENT. + +1. Grant of License. Subject to the terms and provisions of this Agreement, Ritual grants to you the non-exclusive and limited right to use the Software only for the uses permitted in section 3. hereinbelow. The term "Software" includes all elements of the Software. You are not receiving any ownership or proprietary right, title or interest in or to the Software or the copyrights, trademarks, or other rights related thereto. For purposes of the first sentence of this section, "use" means loading the Software into RAM and/or onto computer hard drive, as well as installation of the Software on a hard disk or other storage device and means the uses permitted in section 3. hereinbelow. You agree that the Software will not be shipped, transferred or exported into any country in violation of the U.S. Export Administration Act (or any other law governing such matters) by you or anyone at your direction and that you will not utilize and will not authorize anyone to utilize, in any other manner, the Software in violation of any applicable law. The Software shall not be downloaded or otherwise exported or re-exported into (or to a national or resident of) any country to which the U.S. has embargoed goods or to anyone or into any country who/which are prohibited, by applicable law, from receiving such property. + +2. Prohibitions. You, whether directly or indirectly, shall not do any of the following acts: + +a. rent the Software; + +b. sell the Software; + +c. lease or lend the Software; + +d. distribute the Software (except as permitted by section 3. hereinbelow); + +e. in any other manner and through any medium whatsoever commercially exploit the Software or use the Software for any commercial purpose; + +f. disassemble, reverse engineer, decompile, modify (except as permitted by Section 3. hereinbelow) or alter the Software; + +g. translate the Software; + +h. reproduce or copy the Software (except as permitted by section 3. hereinbelow); + +i. publicly display the Software; + +j. prepare or develop derivative works based upon the Software; + +k. remove or alter any legal notices or other markings or legends, such as trademark and copyright notices, affixed on or within the Software; or + +l. remove, alter, modify, disable or reduce any of the anti-piracy measures or components contained in the Star Trek Elite Force II game, including, without limitation, the CD key system and the CD check. + +3. Permitted Uses. + +a. So long as this Agreement accompanies each copy you make of the Software, and so long as you fully comply, at all times, with this Agreement, Ritual grants to you the non-exclusive and limited right to distribute copies of the Software free of charge for non-commercial purposes by electronic means only and the non-exclusive and limited right to use the Software to create your own modifications (the "New Creations") for operation only with the full version of the software game Star Trek Elite Force II; provided, however, you shall not make any New Creations unless and until you have agreed to be bound by the terms of this Agreement and of the LIMITED USE SOFTWARE LICENSE AGREEMENT which accompanies the full version of Star Trek Elite Force II. Other than the electronic copies permitted above, you may make only the following copies of the Software: (i) you may copy the Software onto your computer hard drive; (ii) you may copy the Software from your computer hard drive into your computer RAM; and (iii) you may make one (1) "back-up" or archival copy of the Software on one (1) hard disk. You shall not use, copy or distribute the Software in any infringing manner or in any manner which violates any law or third party right and you shall not distribute the Software together with any material which infringes against any third party right or which is libelous, defamatory, obscene, false, misleading, or otherwise illegal or unlawful. Ritual reserves all rights not granted in this Agreement, including, without limitation, all rights to Ritual's trademarks. You shall not commercially distribute the Software. + +b. You shall not create any New Creations which infringe against any third party right or which are libelous, defamatory, obscene, false, misleading or otherwise illegal or unlawful. You agree that the New Creations will not be shipped, transferred or exported into any country in violation of the U.S. Export Administration Act (or any other law governing such matters) by you or anyone at your direction and that you will not utilize and will not authorize anyone to utilize, in any other manner, the New Creations in violation of any applicable law. The New Creations shall not be downloaded or otherwise exported or re-exported into (or to a national or resident of) any country to which the U.S. has embargoed goods or to anyone or into any country who/which are prohibited, by applicable law, from receiving such property. You shall not rent, sell, lease, lend, offer on a pay-per-play basis or otherwise commercially exploit or commercially distribute the New Creations. You are only permitted to distribute, without any cost or charge, the New Creations to other end-users so long as such distribution is not infringing against any third party right and is not otherwise illegal or unlawful. As noted below, in the event you commercially distribute or commercial exploit the New Creations or commit any other breach of this Agreement, your licenses, and this Agreement, shall automatically terminate, without notice. + +4. Intellectual Property Rights. The Software and all copyrights, trademarks and all other conceivable intellectual property rights related to the Software are owned by Ritual and are protected by United States copyright laws, international treaty provisions and all applicable law, such as the Lanham Act. You must treat the Software like any other copyrighted material, as required by 17 U.S.C. §101 et seq. and other applicable law. You agree to use your best efforts to see that any user of the Software licensed hereunder complies with this Agreement. You agree that you are receiving a copy of the Software by license only and not by sale and that the "first sale" doctrine of 17 U.S.C. §109 does not apply to your receipt or use of the Software. + +5. NO WARRANTIES. RITUAL DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, WITH RESPECT TO THE SOFTWARE. RITUAL DOES NOT WARRANT THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED OR ERROR FREE OR THAT THE SOFTWARE WILL MEET YOUR SPECIFIC REQUIREMENTS. ADDITIONAL STATEMENTS, WHETHER ORAL OR WRITTEN, DO NOT CONSTITUTE WARRANTIES BY RITUAL AND SHOULD NOT BE RELIED UPON. THIS SECTION 5. SHALL SURVIVE CANCELLATION OR TERMINATION OF THIS AGREEMENT. + + 6. Governing Law, Venue, Indemnity and Liability Limitation. This Agreement shall be construed in accordance with and governed by the applicable laws of the State of Texas and applicable United States federal law. Copyright and other proprietary matters will be governed by United States laws and international treaties. Exclusive venue for all litigation regarding this Agreement shall be in Dallas County, Texas and you agree to submit to the jurisdiction of the courts in Dallas, Texas for any such litigation. You agree to indemnify, defend and hold harmless Ritual and Ritual's officers, employees, directors, agents, licensees (excluding you), successors and assigns from and against all losses, lawsuits, damages, causes of action and claims relating to and/or arising from: (i) your breach of this Agreement; and/or (ii) your distribution or other use of the Software; and/or (iii) your distribution or other use of the New Creations. You agree that your unauthorized use of the Software, or any part thereof, may immediately and irreparably damage Ritual such that Ritual could not be adequately compensated solely by a monetary award and that at Ritual's option Ritual shall be entitled to an injunctive order, in addition to all other available remedies including a monetary award, appropriately restraining and/or prohibiting such unauthorized use without the necessity of Ritual posting bond or other security. IN ANY CASE, RITUAL AND RITUAL'S OFFICERS, EMPLOYEES, DIRECTORS, AGENTS, LICENSEES, SUBLICENSEES, SUCCESSORS AND ASSIGNS SHALL NOT BE LIABLE FOR LOSS OF DATA, LOSS OF PROFITS, LOST SAVINGS, SPECIAL, INCIDENTAL, CONSEQUENTIAL, INDIRECT, PUNITIVE OR OTHER SIMILAR DAMAGES ARISING FROM ANY ALLEGED CLAIM FOR BREACH OF WARRANTY, BREACH OF CONTRACT, NEGLIGENCE, STRICT PRODUCT LIABILITY, OR OTHER LEGAL THEORY EVEN IF RITUAL OR ITS AGENTS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES OR EVEN IF SUCH DAMAGES ARE FORESEEABLE, OR LIABLE FOR ANY CLAIM BY ANY OTHER PARTY. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so the above limitation or exclusion may not apply to you. This Section 6. shall survive cancellation or termination of this Agreement. + +7. U.S. Government Restricted Rights. To the extent applicable, the United States Government shall only have those rights to use the Software as expressly stated and expressly limited and restricted in this Agreement, as provided in 48 C.F.R. §§ 227.7201 through 227.7204, inclusive. + +8. General Provisions. Neither this Agreement nor any part or portion hereof shall be assigned or sublicensed by you. Ritual may assign its rights under this Agreement in Ritual's sole discretion. Should any provision of this Agreement be held to be void, invalid, unenforceable or illegal by a court of competent jurisdiction, the validity and enforceability of the other provisions shall not be affected thereby. If any provision is determined to be unenforceable by a court of competent jurisdiction, you agree to a modification of such provision to provide for enforcement of the provision's intent, to the extent permitted by applicable law. Failure of Ritual to enforce any provision of this Agreement shall not constitute or be construed as a waiver of such provision or of the right to enforce such provision. Immediately upon your failure to comply with, or immediately upon your breach of, any term or provision of this Agreement, THIS AGREEMENT AND YOUR LICENSE SHALL AUTOMATICALLY TERMINATE, WITHOUT NOTICE, AND RITUAL MAY PURSUE ALL RELIEF AND REMEDIES AGAINST YOU WHICH ARE AVAILABLE UNDER APPLICABLE LAW AND/OR THIS AGREEMENT. In the event this Agreement is terminated, you shall have no right to use the Software, in any manner, and you shall immediately destroy all copies of the Software in your possession, custody or control. + +YOU ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT, YOU UNDERSTAND THIS AGREEMENT, AND UNDERSTAND THAT BY CONTINUING THE INSTALLATION OF THE SOFTWARE, BY DOWNLOADING, LOADING OR RUNNING THE SOFTWARE, OR BY PLACING OR COPYING THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE, COMPUTER RAM OR OTHER STORAGE, YOU AGREE TO BE BOUND BY THE TERMS AND CONDITIONS OF THIS AGREEMENT. YOU FURTHER AGREE THAT, EXCEPT FOR WRITTEN SEPARATE AGREEMENTS BETWEEN RITUAL AND YOU, THIS AGREEMENT IS A COMPLETE AND EXCLUSIVE STATEMENT OF THE RIGHTS AND LIABILITIES OF THE PARTIES HERETO REGARDING THE SUBJECT MATTER HEREOF. THIS AGREEMENT SUPERSEDES ALL PRIOR ORAL AGREEMENTS OR UNDERSTANDINGS AND ANY OTHER COMMUNICATIONS BETWEEN RITUAL AND YOU RELATING TO THE SUBJECT MATTER OF THIS AGREEMENT. + + October 15, 2003 3:37 p.m. diff --git a/linux/Makefile b/linux/Makefile new file mode 100644 index 0000000..e65af21 --- /dev/null +++ b/linux/Makefile @@ -0,0 +1,843 @@ +# +# Quake3 Unix Makefile +# +# Currently build for the following: +# Linux i386 (full client) +# Linux Alpha (dedicated server only) +# SGI IRIX (full client) +# +# Nov '98 by Zoid +# +# GNU Make required +# + +PLATFORM=$(shell uname|tr A-Z a-z) +PLATFORM_RELEASE=$(shell uname -r) + +### +### These paths are where you probably want to change things +### + +# Where we are building to, libMesaVoodooGL.so.3.3 should be here, etc. +# the demo pk3 file should be here in demoq3/pak0.pk3 or baseq3/pak0.pk3 +#BDIR=/home/zoid/Quake3 +BDIR=./ + +# Where we are building from (where the source code should be!) +#MOUNT_DIR=/projects/Quake3/q3code +MOUNT_DIR=../.. + +# Custom lcc location, used in vm builds +LCC=/home/zoid/Quake3/lcc + +# Custom q3asm location, used in vm builds +Q3ASM=/home/zoid/Quake3/q3asm + +# Build name + +#BUILD_NAME=linuxquake3 +BUILD_NAME=linuxef2ded + +############################################################################# +## +## You shouldn't have to touch anything below here +## +############################################################################# + +BASEQ3_DIR=$(BDIR)/baseq3 + +BD=debug$(ARCH)$(GLIBC) +BR=release$(ARCH)$(GLIBC) +CDIR=$(MOUNT_DIR)/Executable/client +SDIR=$(MOUNT_DIR)/Executable/server +RDIR=$(MOUNT_DIR)/Libs/renderer +CMDIR=$(MOUNT_DIR)/Shared/qcommon +UDIR=$(MOUNT_DIR)/Executable/unix +GDIR=$(MOUNT_DIR)/DLLs/game +CGDIR=$(MOUNT_DIR)/DLLs/cgame +BAIDIR=$(MOUNT_DIR)/botai +BLIBDIR=$(MOUNT_DIR)/Executable/botlib +NDIR=$(MOUNT_DIR)/Executable/null +UIDIR=$(MOUNT_DIR)/ui +JPDIR=$(MOUNT_DIR)/jpeg-6 +DLLINC = $(MOUNT_DIR)/DLLs +SHAREINC = $(MOUNT_DIR)/Shared +EXECINC = $(MOUNT_DIR)/Executable +LIBINC = $(MOUNT_DIR)/Libs + + +#used for linux i386 builds +MESADIR=/usr/local/src/Mesa + +VERSION=1.16j +RPM_RELEASE=1 + +############################################################################# +# SETUP AND BUILD -- LINUX +############################################################################# + +ifeq ($(PLATFORM),linux) + +ifneq (,$(findstring libc6,$(shell if [ -e /lib/libc.so.6* ];then echo libc6;fi))) +GLIBC=-glibc +else +GLIBC= +endif #libc6 test + + +ifneq (,$(findstring alpha,$(shell uname -m))) +ARCH=axp +RPMARCH=alpha +VENDOR=dec +else #default to i386 +ARCH=i386 +RPMARCH=i386 +VENDOR=unknown +endif #alpha test + +BASE_CFLAGS=-Dstricmp=strcasecmp -Dstrcmpi=strcasecmp -Dstrnicmp=strncasecmp -pipe +#BASE_CFLAGS=-Dstricmp=strcasecmp -I$(MESADIR)/include -I/usr/X11R6/include -I/usr/include/glide -pipe + +DEBUG_CFLAGS=$(BASE_CFLAGS) -g -Wno-deprecated +ifeq ($(ARCH),axp) +#CC=gcc +CC=g++ +GC=gcc +RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O6 -ffast-math -funroll-loops -fomit-frame-pointer -fexpensive-optimizations +else +CC=g++ +GC=gcc +#NEWGCC=/usr/local/gcc-2.95/bin/gcc +#CC=$(shell if [ -f $(NEWGCC) ]; then echo $(NEWGCC); else echo g++; fi ) +RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O6 -march=pentium -fomit-frame-pointer -pipe -ffast-math -falign-loops=2 -falign-jumps=2 -falign-functions=2 -fno-strict-aliasing -fstrength-reduce -Wno-deprecated + +endif + +LIBEXT=a + +SHLIBEXT=so +SHLIBCFLAGS= -fPIC +SHLIBLDFLAGS=-shared + +ARFLAGS=ar rv +RANLIB=ranlib + +THREAD_LDFLAGS=-lpthread +LDFLAGS=-ldl -lm +GLLDFLAGS=-L/usr/X11R6/lib -L$(MESADIR)/lib -lX11 -lXext -lXxf86dga -lXxf86vm + +#ifeq ($(ARCH),axp) +TARGETS=\ + $(B)/ef2game$(ARCH).$(SHLIBEXT) \ + $(B)/$(PLATFORM)ef2ded + +#else +#TARGETS=\ +# $(B)/$(PLATFORM)quake3 \ +# $(B)/$(PLATFORM)quake3-smp \ +# $(B)/$(PLATFORM)q3ded \ +# $(B)/vm/qagame.qvm \ +# $(B)/vm/cgame.qvm \ +# $(B)/vm/ui.qvm + +# $(B)/cgame$(ARCH).$(SHLIBEXT) \ +# $(B)/qagame$(ARCH).$(SHLIBEXT) \ +# $(B)/ui$(ARCH).$(SHLIBEXT) \ +# +#endif + +else # ifeq Linux + +############################################################################# +# SETUP AND BUILD -- IRIX +############################################################################# + +ifeq ($(PLATFORM),irix) + +ARCH=mips #default to MIPS +VENDOR=sgi +GLIBC= #libc is irrelevant + +CC=cc +BASE_CFLAGS=-Dstricmp=strcasecmp -Dstrnicmp=strncasecmp -Xcpluscomm -woff 1185 -mips3 \ + -nostdinc -I. -I$(ROOT)/usr/include +RELEASE_CFLAGS=$(BASE_CFLAGS) -O3 +DEBUG_CFLAGS=$(BASE_CFLAGS) -g + +LIBEXT=a + +SHLIBEXT=so +SHLIBCFLAGS= +SHLIBLDFLAGS=-shared + +ARFLAGS=ar rv +RANLIB=ranlib + +LDFLAGS=-ldl -lm +GLLDFLAGS=-L/usr/X11/lib -lGL -lX11 -lXext -lm + +TARGETS=$(B)/sgiquake3 \ + $(B)/q3ded + +else # ifeq IRIX + +############################################################################# +# SETUP AND BUILD -- GENERIC +############################################################################# + +CC=cc +#CC=gcc +BASE_CFLAGS=-Dstricmp=strcasecmp -Dstrnicmp=strncasecmp +DEBUG_CFLAGS=$(BASE_CFLAGS) -g +RELEASE_CFLAGS=$(BASE_CFLAGS) -DNDEBUG -O + +LIBEXT=a + +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +ARFLAGS=ar rv +RANLIB=ranlib + +LDFLAGS=-ldl -lm + +TARGETS=\ + $(B)/$(PLATFORM)ef2ded + +endif #Linux +endif #IRIX + +DO_CC=$(CC) $(CFLAGS) -o $@ -c $< +DO_SMP_CC=$(CC) $(CFLAGS) -DSMP -o $@ -c $< +DO_BOT_CC=$(GC) $(CFLAGS) -DBOTLIB -DAASINTERN -DMISSIONPACK -DENABLE_ALTROUTING $(SHLIBCFLAGS) -I$(DLLINC) -I$(SHAREINC) -I$(EXECINC) -I$(CMDIR) -I$(LIBINC) -o $@ -c $< +DO_DEBUG_CC=$(CC) $(DEBUG_CFLAGS) -o $@ -c $< +DO_SHLIB_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -I$(DLLINC) -I$(SHAREINC) -I$(EXECINC) -I$(CMDIR) -I$(LIBINC) -o $@ -c $< +DO_GAME_SHLIB_CC=$(CC) -DGAME_DLL -DDEDICATED -DMISSIONPACK -DLINUX $(CFLAGS) $(SHLIBCFLAGS) -I$(DLLINC) -I$(SHAREINC) -I$(EXECINC) -I$(CMDIR) -I$(LIBINC) -o $@ -c $< +DO_GAME_SHLIB_GC=$(GC) -DGAME_DLL -DDEDICATED -DMISSIONPACK -DLINUX $(CFLAGS) $(SHLIBCFLAGS) -I$(DLLINC) -I$(SHAREINC) -I$(EXECINC) -I$(CMDIR) -I$(LIBINC) -o $@ -c $< +DO_SHLIB_DEBUG_CC=$(CC) $(DEBUG_CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< + +DO_AS=$(CC) $(CFLAGS) -DELF -x assembler-with-cpp -o $@ -c $< + +DO_DED_CC=$(CC) -DDEDICATED -DEF2 -DAASINTERN -DAAS_SAMPLE_DEBUG -DDEBUG_AI_MOVE -DDEBUG_AI_WEAP -DDEBUG_AI_GOAL -DALTROUTE_DEBUG -DROUTING_DEBUG -DREACH_DEBUG -DAAS_MOVE_DEBUG -DMISSIONPACK -DENABLE_ALTROUTING -DLINUX -DBOTLIB -DC_ONLY $(CFLAGS) -I$(DLLINC) -I$(SHAREINC) -I$(EXECINC) -I$(CMDIR) -I$(LIBINC) -c -o $@ $< +DO_DED_GC=$(GC) -DDEDICATED -DEF2 -DAASINTERN -DAAS_SAMPLE_DEBUG -DDEBUG_AI_MOVE -DDEBUG_AI_WEAP -DDEBUG_AI_GOAL -DALTROUTE_DEBUG -DROUTING_DEBUG -DREACH_DEBUG -DAAS_MOVE_DEBUG -DMISSIONPACK -DENABLE_ALTROUTING -DLINUX -DBOTLIB -DC_ONLY $(CFLAGS) -I$(DLLINC) -I$(SHAREINC) -I$(EXECINC) -I$(CMDIR) -I$(LIBINC) -c -o $@ $< +#DO_DED_CC=$(CC) -DDEDICATED -DC_ONLY $(CFLAGS) -o $@ -c $< + +DO_LCC=$(LCC) -S -Wf-target=bytecode -Wf-g -I$(CGDIR) -I$(GDIR) -I$(BAIDIR) -I$(BLIBDIR) -I$(UIDIR) -o $@ -c $< + +#### DEFAULT TARGET +# default:build_release + +#build_debug: +# $(MAKE) targets B=$(BD) CFLAGS="$(DEBUG_CFLAGS)" + +build_release: + $(MAKE) targets B=$(BR) CFLAGS="$(RELEASE_CFLAGS)" +build_debug: + $(MAKE) targets B=$(BD) CFLAGS="$(DEBUG_CFLAGS)" + + +#Build both debug and release builds +#all:build_debug build_release +all:build_debug + +targets:makedirs $(TARGETS) + +makedirs: + @if [ ! -d $(B) ]; then mkdir $(B);fi; \ + if [ ! -d $(B)/ded ];then mkdir $(B)/ded;fi; \ + if [ ! -d $(B)/game ]; then mkdir $(B)/game;fi + + + +############################################################################# +# DEDICATED SERVER +############################################################################# + +Q3DOBJ = \ + $(B)/ded/sv_bot.o \ + $(B)/ded/sv_client.o \ + $(B)/ded/sv_ccmds.o \ + $(B)/ded/sv_game.o \ + $(B)/ded/sv_init.o \ + $(B)/ded/sv_main.o \ + $(B)/ded/sv_netchan.o \ + $(B)/ded/sv_snapshot.o \ + $(B)/ded/sv_world.o \ + \ + $(B)/ded/alias.o \ + $(B)/ded/cm_load.o \ + $(B)/ded/cm_patch.o \ + $(B)/ded/cm_polylib.o \ + $(B)/ded/cm_terrain.o \ + $(B)/ded/cm_test.o \ + $(B)/ded/cm_trace.o \ + $(B)/ded/cmd.o \ + $(B)/ded/common.o \ + $(B)/ded/crc.o \ + $(B)/ded/cvar.o \ + $(B)/ded/files.o \ + $(B)/ded/gameplaydatabase.o \ + $(B)/ded/gameplayformulamanager.o \ + $(B)/ded/gameplaymanager.o \ + $(B)/ded/huffman.o \ + $(B)/ded/levelManager.o \ + $(B)/ded/lip.o \ + $(B)/ded/msg.o \ + $(B)/ded/net_chan.o \ + $(B)/ded/output.o \ + \ + $(B)/ded/q_math.o \ + $(B)/ded/q_mathsys.o \ + $(B)/ded/q_shared.o \ + $(B)/ded/memory.o \ + \ + $(B)/ded/str.o \ + $(B)/ded/stringresource.o \ + $(B)/ded/listener.o \ + $(B)/ded/sv_snd.o \ + $(B)/ded/viewmode.o \ + $(B)/ded/class.o \ + \ + $(B)/ded/tiki_main.o \ + $(B)/ded/tiki_script.o \ + $(B)/ded/unzip.o \ + \ + $(B)/ded/be_aas_bspq3.o \ + $(B)/ded/be_aas_cluster.o \ + $(B)/ded/be_aas_debug.o \ + $(B)/ded/be_aas_entity.o \ + $(B)/ded/be_aas_file.o \ + $(B)/ded/be_aas_main.o \ + $(B)/ded/be_aas_move.o \ + $(B)/ded/be_aas_optimize.o \ + $(B)/ded/be_aas_reach.o \ + $(B)/ded/be_aas_route.o \ + $(B)/ded/be_aas_routealt.o \ + $(B)/ded/be_aas_sample.o \ + $(B)/ded/be_ai_char.o \ + $(B)/ded/be_ai_chat.o \ + $(B)/ded/be_ai_gen.o \ + $(B)/ded/be_ai_goal.o \ + $(B)/ded/be_ai_move.o \ + $(B)/ded/be_ai_weap.o \ + $(B)/ded/be_ai_weight.o \ + $(B)/ded/be_ea.o \ + $(B)/ded/be_interface.o \ + $(B)/ded/l_crc.o \ + $(B)/ded/l_libvar.o \ + $(B)/ded/l_log.o \ + $(B)/ded/l_memory.o \ + $(B)/ded/l_precomp.o \ + $(B)/ded/l_script.o \ + $(B)/ded/l_struct.o \ + \ + $(B)/ded/unix_main.o \ + $(B)/ded/unix_net.o \ + $(B)/ded/unix_shared.o \ + \ + $(B)/ded/md4.o \ + $(B)/ded/null_client.o \ + $(B)/ded/null_cl.o \ + $(B)/ded/null_input.o \ + $(B)/ded/null_snddma.o + +$(B)/$(PLATFORM)ef2ded : $(Q3DOBJ) + $(CC) $(CFLAGS) -o $@ $(Q3DOBJ) $(LDFLAGS) + +$(B)/ded/sv_bot.o : $(BLIBDIR)/sv_bot.c; $(DO_DED_GC) +$(B)/ded/sv_client.o : $(SDIR)/sv_client.c; $(DO_DED_GC) +$(B)/ded/sv_ccmds.o : $(SDIR)/sv_ccmds.c; $(DO_DED_GC) +$(B)/ded/sv_game.o : $(SDIR)/sv_game.c; $(DO_DED_GC) +$(B)/ded/sv_init.o : $(SDIR)/sv_init.c; $(DO_DED_GC) +$(B)/ded/sv_main.o : $(SDIR)/sv_main.c; $(DO_DED_GC) +$(B)/ded/sv_netchan.o : $(SDIR)/sv_net_chan.c; $(DO_DED_GC) +$(B)/ded/sv_snapshot.o : $(SDIR)/sv_snapshot.c; $(DO_DED_GC) +$(B)/ded/sv_snd.o : $(SDIR)/sv_snd.c; $(DO_DED_GC) +$(B)/ded/sv_world.o : $(SDIR)/sv_world.c; $(DO_DED_GC) +$(B)/ded/alias.o : $(CMDIR)/alias.c; $(DO_DED_GC) +$(B)/ded/class.o : $(GDIR)/class.cpp; $(DO_DED_CC) +$(B)/ded/cm_load.o : $(CMDIR)/cm_load.c; $(DO_DED_GC) +$(B)/ded/cm_polylib.o : $(CMDIR)/cm_polylib.c; $(DO_DED_GC) +$(B)/ded/cm_terrain.o : $(CMDIR)/cm_terrain.c; $(DO_DED_GC) +$(B)/ded/cm_test.o : $(CMDIR)/cm_test.c; $(DO_DED_GC) +$(B)/ded/cm_trace.o : $(CMDIR)/cm_trace.c; $(DO_DED_GC) +$(B)/ded/cm_patch.o : $(CMDIR)/cm_patch.c; $(DO_DED_GC) +$(B)/ded/cmd.o : $(CMDIR)/cmd.c; $(DO_DED_GC) +$(B)/ded/common.o : $(CMDIR)/common.c; $(DO_DED_GC) +$(B)/ded/crc.o : $(CMDIR)/crc.c; $(DO_DED_GC) +$(B)/ded/cvar.o : $(CMDIR)/cvar.c; $(DO_DED_GC) +$(B)/ded/files.o : $(CMDIR)/files.c; $(DO_DED_GC) +$(B)/ded/md4.o : $(CMDIR)/md4.c; $(DO_DED_GC) +$(B)/ded/gameplaydatabase.o : $(CMDIR)/gameplaydatabase.cpp; $(DO_DED_CC) +$(B)/ded/gameplayformulamanager.o : $(CMDIR)/gameplayformulamanager.cpp; $(DO_DED_CC) +$(B)/ded/gameplaymanager.o : $(CMDIR)/gameplaymanager.cpp; $(DO_DED_CC) +$(B)/ded/huffman.o : $(CMDIR)/huffman.c; $(DO_DED_GC) +$(B)/ded/levelManager.o : $(CMDIR)/levelManager.cpp; $(DO_DED_CC) +$(B)/ded/listener.o : $(GDIR)/listener.cpp; $(DO_DED_CC) +$(B)/ded/lip.o : $(CMDIR)/lip.c; $(DO_DED_GC) + +$(B)/ded/msg.o : $(CMDIR)/msg.c; $(DO_DED_GC) +$(B)/ded/net_chan.o : $(CMDIR)/net_chan.c; $(DO_DED_GC) +$(B)/ded/output.o : $(CMDIR)/output.cpp; $(DO_DED_CC) +$(B)/ded/q_shared.o : $(GDIR)/q_shared.c; $(DO_DED_GC) +$(B)/ded/memory.o : $(CMDIR)/memory.c; $(DO_DED_GC) +$(B)/ded/q_math.o : $(GDIR)/q_math.c; $(DO_DED_GC) +$(B)/ded/q_mathsys.o : $(GDIR)/q_mathsys.c; $(DO_DED_GC) + +$(B)/ded/str.o : $(GDIR)/str.cpp; $(DO_DED_CC) +$(B)/ded/stringresource.o : $(CMDIR)/stringresource.cpp; $(DO_DED_CC) + +$(B)/ded/tiki_main.o : $(CMDIR)/tiki_main.cpp; $(DO_DED_CC) +$(B)/ded/tiki_script.o : $(CMDIR)/tiki_script.cpp; $(DO_DED_CC) +$(B)/ded/viewmode.o : $(CMDIR)/viewmode.cpp; $(DO_DED_CC) + +$(B)/ded/be_aas_bspq3.o : $(BLIBDIR)/be_aas_bspq3.c; $(DO_BOT_CC) +$(B)/ded/be_aas_cluster.o : $(BLIBDIR)/be_aas_cluster.c; $(DO_BOT_CC) +$(B)/ded/be_aas_debug.o : $(BLIBDIR)/be_aas_debug.c; $(DO_BOT_CC) +$(B)/ded/be_aas_entity.o : $(BLIBDIR)/be_aas_entity.c; $(DO_BOT_CC) +$(B)/ded/be_aas_file.o : $(BLIBDIR)/be_aas_file.c; $(DO_BOT_CC) +$(B)/ded/be_aas_main.o : $(BLIBDIR)/be_aas_main.c; $(DO_BOT_CC) +$(B)/ded/be_aas_move.o : $(BLIBDIR)/be_aas_move.c; $(DO_BOT_CC) +$(B)/ded/be_aas_optimize.o : $(BLIBDIR)/be_aas_optimize.c; $(DO_BOT_CC) +$(B)/ded/be_aas_reach.o : $(BLIBDIR)/be_aas_reach.c; $(DO_BOT_CC) +$(B)/ded/be_aas_route.o : $(BLIBDIR)/be_aas_route.c; $(DO_BOT_CC) +$(B)/ded/be_aas_routealt.o : $(BLIBDIR)/be_aas_routealt.c; $(DO_BOT_CC) +$(B)/ded/be_aas_sample.o : $(BLIBDIR)/be_aas_sample.c; $(DO_BOT_CC) +$(B)/ded/be_ai_char.o : $(BLIBDIR)/be_ai_char.c; $(DO_BOT_CC) +$(B)/ded/be_ai_chat.o : $(BLIBDIR)/be_ai_chat.c; $(DO_BOT_CC) +$(B)/ded/be_ai_gen.o : $(BLIBDIR)/be_ai_gen.c; $(DO_BOT_CC) +$(B)/ded/be_ai_goal.o : $(BLIBDIR)/be_ai_goal.c; $(DO_BOT_CC) +$(B)/ded/be_ai_move.o : $(BLIBDIR)/be_ai_move.c; $(DO_BOT_CC) +$(B)/ded/be_ai_weap.o : $(BLIBDIR)/be_ai_weap.c; $(DO_BOT_CC) +$(B)/ded/be_ai_weight.o : $(BLIBDIR)/be_ai_weight.c; $(DO_BOT_CC) +$(B)/ded/be_ea.o : $(BLIBDIR)/be_ea.c; $(DO_BOT_CC) +$(B)/ded/be_interface.o : $(BLIBDIR)/be_interface.c; $(DO_BOT_CC) +$(B)/ded/l_crc.o : $(BLIBDIR)/l_crc.c; $(DO_BOT_CC) +$(B)/ded/l_libvar.o : $(BLIBDIR)/l_libvar.c; $(DO_BOT_CC) +$(B)/ded/l_log.o : $(BLIBDIR)/l_log.c; $(DO_BOT_CC) +$(B)/ded/l_memory.o : $(BLIBDIR)/l_memory.c; $(DO_BOT_CC) +$(B)/ded/l_precomp.o : $(BLIBDIR)/l_precomp.c; $(DO_BOT_CC) +$(B)/ded/l_script.o : $(BLIBDIR)/l_script.c; $(DO_BOT_CC) +$(B)/ded/l_struct.o : $(BLIBDIR)/l_struct.c; $(DO_BOT_CC) + +$(B)/ded/unix_main.o : $(UDIR)/unix_main.c; $(DO_DED_GC) +$(B)/ded/unix_net.o : $(UDIR)/unix_net.c; $(DO_DED_GC) +$(B)/ded/unix_shared.o : $(UDIR)/unix_shared.c; $(DO_DED_GC) +$(B)/ded/null_client.o : $(NDIR)/null_client.c; $(DO_DED_GC) +$(B)/ded/null_cl.o : $(NDIR)/null_cl.cpp; $(DO_DED_CC) +$(B)/ded/null_input.o : $(NDIR)/null_input.c; $(DO_DED_GC) +$(B)/ded/null_snddma.o : $(NDIR)/null_snddma.c; $(DO_DED_GC) +$(B)/ded/unzip.o : $(CMDIR)/unzip.c; $(DO_DED_GC) +#$(B)/ded/vm.o : $(CMDIR)/vm.c; $(DO_DED_CC) +#$(B)/ded/vm_interpreted.o : $(CMDIR)/vm_interpreted.c; $(DO_DED_CC) +#$(B)/ded/vm_x86.o : $(CMDIR)/vm_x86.c $(DO_DED_CC) + +############################################################################# +# GAME +############################################################################# + +GOBJ =\ + $(B)/game/changePosture.o \ + $(B)/game/closeInOnEnemy.o \ + $(B)/game/closeInOnEnemyWhileFiringWeapon.o \ + $(B)/game/closeInOnPlayer.o \ + $(B)/game/corridorCombatWithRangedWeapon.o \ + $(B)/game/coverCombatWithRangedWeapon.o \ + $(B)/game/doAttack.o \ + $(B)/game/generalCombatWithMeleeWeapon.o \ + $(B)/game/generalCombatWithRangedWeapon.o \ + $(B)/game/gotoCurrentHelperNode.o \ + $(B)/game/gotoHelperNode.o \ + $(B)/game/gotoHelperNodeEX.o \ + $(B)/game/gotoHelperNodeNearestEnemy.o \ + $(B)/game/healGroupMember.o \ + $(B)/game/holdPosition.o \ + $(B)/game/MoveRandomDirection.o \ + $(B)/game/patrol.o \ + $(B)/game/PlayAnim.o \ + $(B)/game/rangedCombatWithWeapon.o \ + $(B)/game/rotateToEntity.o \ + $(B)/game/selectBestWeapon.o \ + $(B)/game/snipeEnemy.o \ + $(B)/game/stationaryFireCombat.o \ + $(B)/game/stationaryFireCombatEX.o \ + $(B)/game/suppressionFireCombat.o \ + $(B)/game/talk.o \ + $(B)/game/teleportToEntity.o \ + $(B)/game/teleportToPosition.o \ + $(B)/game/torsoAimAndFireWeapon.o \ + $(B)/game/useAlarm.o \ + $(B)/game/watchEntity.o \ + $(B)/game/watchEntityEX.o \ + $(B)/game/work.o \ + \ + $(B)/game/mp_awardsystem.o \ + $(B)/game/mp_manager.o \ + $(B)/game/mp_modeBase.o \ + $(B)/game/mp_modeCtf.o \ + $(B)/game/mp_modeDm.o \ + $(B)/game/mp_modeTeamBase.o \ + $(B)/game/mp_modeTeamDm.o \ + $(B)/game/mp_modifiers.o \ + $(B)/game/mp_team.o \ + \ + $(B)/game/ai_chat.o \ + $(B)/game/ai_cmd.o \ + $(B)/game/ai_dmnet.o \ + $(B)/game/ai_dmq3.o \ + $(B)/game/ai_main.o \ + $(B)/game/ai_team.o \ + $(B)/game/ai_vcmd.o \ + $(B)/game/g_bot.o \ + \ + $(B)/game/actor.o \ + $(B)/game/actor_combatsubsystem.o \ + $(B)/game/actor_enemymanager.o \ + $(B)/game/actor_headwatcher.o \ + $(B)/game/actor_locomotion.o \ + $(B)/game/actor_posturecontroller.o \ + $(B)/game/actor_sensoryperception.o \ + $(B)/game/actorgamecomponents.o \ + $(B)/game/actorstrategies.o \ + $(B)/game/actorutil.o \ + $(B)/game/ammo.o \ + $(B)/game/animate.o \ + $(B)/game/archive.o \ + $(B)/game/armor.o \ + $(B)/game/beam.o \ + $(B)/game/behavior.o \ + $(B)/game/behaviors_general.o \ + $(B)/game/behaviors_specific.o \ + $(B)/game/bg_misc.o \ + $(B)/game/bg_pmove.o \ + $(B)/game/body.o \ + $(B)/game/bspline.o \ + $(B)/game/camera.o \ + $(B)/game/CameraPath.o \ + $(B)/game/characterstate.o \ + $(B)/game/CinematicArmature.o \ + $(B)/game/class.o \ + $(B)/game/compiler.o \ + $(B)/game/DamageModification.o \ + $(B)/game/debuglines.o \ + $(B)/game/decals.o \ + $(B)/game/dispenser.o \ + $(B)/game/doors.o \ + $(B)/game/earthquake.o \ + $(B)/game/entity.o \ + $(B)/game/equipment.o \ + $(B)/game/explosion.o \ + $(B)/game/FollowPath.o \ + $(B)/game/FollowPathToEntity.o \ + $(B)/game/FollowPathToPoint.o \ + $(B)/game/g_main.o \ + $(B)/game/g_phys.o \ + $(B)/game/g_spawn.o \ + $(B)/game/g_utils.o \ + $(B)/game/game.o \ + $(B)/game/gamecmds.o \ + $(B)/game/gamecvars.o \ + $(B)/game/gameplaydatabase.o \ + $(B)/game/gameplayformulamanager.o \ + $(B)/game/gameplaymanager.o \ + $(B)/game/gamescript.o \ + $(B)/game/gibs.o \ + $(B)/game/globalcmd.o \ + $(B)/game/GoDirectlyToPoint.o \ + $(B)/game/goo.o \ + $(B)/game/gravpath.o \ + $(B)/game/groupcoordinator.o \ + $(B)/game/health.o \ + $(B)/game/helper_node.o \ + $(B)/game/interpreter.o \ + $(B)/game/inventoryitem.o \ + $(B)/game/ipfilter.o \ + $(B)/game/item.o \ + $(B)/game/level.o \ + $(B)/game/lexer.o \ + $(B)/game/light.o \ + $(B)/game/listener.o \ + $(B)/game/misc.o \ + $(B)/game/mover.o \ + $(B)/game/nature.o \ + $(B)/game/navigate.o \ + $(B)/game/object.o \ + $(B)/game/output.o \ + $(B)/game/path.o \ + $(B)/game/player.o \ + $(B)/game/player_combat.o \ + $(B)/game/player_util.o \ + $(B)/game/playerheuristics.o \ + $(B)/game/PlayerStart.o \ + $(B)/game/portal.o \ + $(B)/game/powerups.o \ + $(B)/game/program.o \ + $(B)/game/puzzleobject.o \ + $(B)/game/q_math.o \ + $(B)/game/q_mathsys.o \ + $(B)/game/q_shared.o \ + $(B)/game/RageAI.o \ + $(B)/game/script.o \ + $(B)/game/scriptmaster.o \ + $(B)/game/scriptslave.o \ + $(B)/game/scriptvariable.o \ + $(B)/game/sentient.o \ + $(B)/game/shrapnelbomb.o \ + $(B)/game/soundman.o \ + $(B)/game/spawners.o \ + $(B)/game/specialfx.o \ + $(B)/game/stationaryvehicle.o \ + $(B)/game/steering.o \ + $(B)/game/str.o \ + $(B)/game/teammateroster.o \ + $(B)/game/trigger.o \ + $(B)/game/UseData.o \ + $(B)/game/vehicle.o \ + $(B)/game/viewthing.o \ + $(B)/game/waypoints.o \ + $(B)/game/weapon.o \ + $(B)/game/WeaponDualWield.o \ + $(B)/game/weaputils.o \ + $(B)/game/worldspawn.o + + + +$(B)/ef2game$(ARCH).$(SHLIBEXT) : $(GOBJ) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(GOBJ) + +$(B)/game/changePosture.o : $(GDIR)/changePosture.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/closeInOnEnemy.o : $(GDIR)/closeInOnEnemy.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/closeInOnEnemyWhileFiringWeapon.o : $(GDIR)/closeInOnEnemyWhileFiringWeapon.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/closeInOnPlayer.o : $(GDIR)/closeInOnPlayer.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/corridorCombatWithRangedWeapon.o : $(GDIR)/corridorCombatWithRangedWeapon.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/coverCombatWithRangedWeapon.o : $(GDIR)/coverCombatWithRangedWeapon.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/doAttack.o : $(GDIR)/doAttack.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/generalCombatWithMeleeWeapon.o : $(GDIR)/generalCombatWithMeleeWeapon.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/generalCombatWithRangedWeapon.o : $(GDIR)/generalCombatWithRangedWeapon.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gotoCurrentHelperNode.o : $(GDIR)/gotoCurrentHelperNode.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gotoHelperNode.o : $(GDIR)/gotoHelperNode.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gotoHelperNodeEX.o : $(GDIR)/gotoHelperNodeEX.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gotoHelperNodeNearestEnemy.o : $(GDIR)/gotoHelperNodeNearestEnemy.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/healGroupMember.o : $(GDIR)/healGroupMember.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/holdPosition.o : $(GDIR)/holdPosition.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/MoveRandomDirection.o : $(GDIR)/MoveRandomDirection.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/patrol.o : $(GDIR)/patrol.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/PlayAnim.o : $(GDIR)/PlayAnim.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/rangedCombatWithWeapon.o : $(GDIR)/rangedCombatWithWeapon.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/rotateToEntity.o : $(GDIR)/rotateToEntity.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/selectBestWeapon.o : $(GDIR)/selectBestWeapon.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/snipeEnemy.o : $(GDIR)/snipeEnemy.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/stationaryFireCombat.o : $(GDIR)/stationaryFireCombat.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/stationaryFireCombatEX.o : $(GDIR)/stationaryFireCombatEX.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/suppressionFireCombat.o : $(GDIR)/suppressionFireCombat.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/talk.o : $(GDIR)/talk.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/teleportToEntity.o : $(GDIR)/teleportToEntity.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/teleportToPosition.o : $(GDIR)/teleportToPosition.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/torsoAimAndFireWeapon.o : $(GDIR)/torsoAimAndFireWeapon.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/useAlarm.o : $(GDIR)/useAlarm.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/watchEntity.o : $(GDIR)/watchEntity.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/watchEntityEX.o : $(GDIR)/watchEntityEX.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/work.o : $(GDIR)/work.cpp; $(DO_GAME_SHLIB_CC) + +$(B)/game/mp_awardsystem.o : $(GDIR)/mp_awardsystem.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/mp_manager.o : $(GDIR)/mp_manager.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/mp_modeBase.o : $(GDIR)/mp_modeBase.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/mp_modeCtf.o : $(GDIR)/mp_modeCtf.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/mp_modeDm.o : $(GDIR)/mp_modeDm.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/mp_modeTeamBase.o : $(GDIR)/mp_modeTeamBase.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/mp_modeTeamDm.o : $(GDIR)/mp_modeTeamDm.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/mp_modifiers.o : $(GDIR)/mp_modifiers.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/mp_team.o : $(GDIR)/mp_team.cpp; $(DO_GAME_SHLIB_CC) + +$(B)/game/ai_chat.o : $(GDIR)/ai_chat.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/ai_cmd.o : $(GDIR)/ai_cmd.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/ai_dmnet.o : $(GDIR)/ai_dmnet.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/ai_dmq3.o : $(GDIR)/ai_dmq3.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/ai_main.o : $(GDIR)/ai_main.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/ai_team.o : $(GDIR)/ai_team.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/ai_vcmd.o : $(GDIR)/ai_vcmd.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/g_bot.o : $(GDIR)/g_bot.cpp; $(DO_GAME_SHLIB_CC) + +$(B)/game/actor.o : $(GDIR)/actor.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/actor_combatsubsystem.o : $(GDIR)/actor_combatsubsystem.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/actor_enemymanager.o : $(GDIR)/actor_enemymanager.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/actor_headwatcher.o : $(GDIR)/actor_headwatcher.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/actor_locomotion.o : $(GDIR)/actor_locomotion.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/actor_posturecontroller.o : $(GDIR)/actor_posturecontroller.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/actor_sensoryperception.o : $(GDIR)/actor_sensoryperception.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/actorgamecomponents.o : $(GDIR)/actorgamecomponents.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/actorstrategies.o : $(GDIR)/actorstrategies.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/actorutil.o : $(GDIR)/actorutil.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/ammo.o : $(GDIR)/ammo.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/animate.o : $(GDIR)/animate.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/archive.o : $(GDIR)/archive.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/armor.o : $(GDIR)/armor.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/beam.o : $(GDIR)/beam.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/behavior.o : $(GDIR)/behavior.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/behaviors_general.o : $(GDIR)/behaviors_general.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/behaviors_specific.o : $(GDIR)/behaviors_specific.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/bg_misc.o : $(GDIR)/bg_misc.c; $(DO_GAME_SHLIB_GC) +$(B)/game/bg_pmove.o : $(GDIR)/bg_pmove.c; $(DO_GAME_SHLIB_GC) +$(B)/game/body.o : $(GDIR)/body.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/bspline.o : $(GDIR)/bspline.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/camera.o : $(GDIR)/camera.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/CameraPath.o : $(GDIR)/CameraPath.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/characterstate.o : $(GDIR)/characterstate.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/CinematicArmature.o : $(GDIR)/CinematicArmature.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/class.o : $(GDIR)/class.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/compiler.o : $(GDIR)/compiler.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/DamageModification.o : $(GDIR)/DamageModification.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/debuglines.o : $(GDIR)/debuglines.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/decals.o : $(GDIR)/decals.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/dispenser.o : $(GDIR)/dispenser.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/doors.o : $(GDIR)/doors.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/earthquake.o : $(GDIR)/earthquake.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/entity.o : $(GDIR)/entity.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/equipment.o : $(GDIR)/equipment.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/explosion.o : $(GDIR)/explosion.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/FollowPath.o : $(GDIR)/FollowPath.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/FollowPathToEntity.o : $(GDIR)/FollowPathToEntity.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/FollowPathToPoint.o : $(GDIR)/FollowPathToPoint.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/g_main.o : $(GDIR)/g_main.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/g_phys.o : $(GDIR)/g_phys.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/g_spawn.o : $(GDIR)/g_spawn.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/g_utils.o : $(GDIR)/g_utils.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/game.o : $(GDIR)/game.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gamecmds.o : $(GDIR)/gamecmds.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gamecvars.o : $(GDIR)/gamecvars.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gameplaydatabase.o : $(CMDIR)/gameplaydatabase.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gameplayformulamanager.o : $(CMDIR)/gameplayformulamanager.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gameplaymanager.o : $(CMDIR)/gameplaymanager.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gamescript.o : $(GDIR)/gamescript.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gibs.o : $(GDIR)/gibs.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/globalcmd.o : $(GDIR)/globalcmd.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/GoDirectlyToPoint.o : $(GDIR)/GoDirectlyToPoint.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/goo.o : $(GDIR)/goo.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/gravpath.o : $(GDIR)/gravpath.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/groupcoordinator.o : $(GDIR)/groupcoordinator.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/health.o : $(GDIR)/health.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/helper_node.o : $(GDIR)/helper_node.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/interpreter.o : $(GDIR)/interpreter.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/inventoryitem.o : $(GDIR)/inventoryitem.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/ipfilter.o : $(GDIR)/ipfilter.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/item.o : $(GDIR)/item.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/level.o : $(GDIR)/level.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/lexer.o : $(GDIR)/lexer.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/light.o : $(GDIR)/light.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/listener.o : $(GDIR)/listener.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/misc.o : $(GDIR)/misc.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/mover.o : $(GDIR)/mover.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/nature.o : $(GDIR)/nature.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/navigate.o : $(GDIR)/navigate.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/object.o : $(GDIR)/object.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/output.o : $(CMDIR)/output.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/path.o : $(GDIR)/path.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/player.o : $(GDIR)/player.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/player_combat.o : $(GDIR)/player_combat.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/player_util.o : $(GDIR)/player_util.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/playerheuristics.o : $(GDIR)/playerheuristics.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/PlayerStart.o : $(GDIR)/PlayerStart.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/portal.o : $(GDIR)/portal.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/powerups.o : $(GDIR)/powerups.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/program.o : $(GDIR)/program.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/puzzleobject.o : $(GDIR)/puzzleobject.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/q_math.o : $(GDIR)/q_math.c; $(DO_GAME_SHLIB_GC) +$(B)/game/q_mathsys.o : $(GDIR)/q_mathsys.c; $(DO_GAME_SHLIB_GC) +$(B)/game/q_shared.o : $(GDIR)/q_shared.c; $(DO_GAME_SHLIB_GC) +$(B)/game/RageAI.o : $(GDIR)/RageAI.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/script.o : $(GDIR)/script.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/scriptmaster.o : $(GDIR)/scriptmaster.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/scriptslave.o : $(GDIR)/scriptslave.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/scriptvariable.o : $(GDIR)/scriptvariable.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/sentient.o : $(GDIR)/sentient.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/shrapnelbomb.o : $(GDIR)/shrapnelbomb.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/soundman.o : $(GDIR)/soundman.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/spawners.o : $(GDIR)/spawners.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/specialfx.o : $(GDIR)/specialfx.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/stationaryvehicle.o : $(GDIR)/stationaryvehicle.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/steering.o : $(GDIR)/steering.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/str.o : $(GDIR)/str.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/teammateroster.o : $(GDIR)/teammateroster.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/trigger.o : $(GDIR)/trigger.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/UseData.o : $(GDIR)/UseData.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/vehicle.o : $(GDIR)/vehicle.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/viewthing.o : $(GDIR)/viewthing.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/waypoints.o : $(GDIR)/waypoints.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/weapon.o : $(GDIR)/weapon.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/WeaponDualWield.o : $(GDIR)/WeaponDualWield.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/weaputils.o : $(GDIR)/weaputils.cpp; $(DO_GAME_SHLIB_CC) +$(B)/game/worldspawn.o : $(GDIR)/worldspawn.cpp; $(DO_GAME_SHLIB_CC) + +############################################################################# +# RPM +############################################################################# + +TMPDIR=/var/tmp +TARDIR=$(TMPDIR)/$(BUILD_NAME) +TARFILE = $(BUILD_NAME)-$(VERSION)-$(RPM_RELEASE).$(ARCH).tar + +tar: + if [ ! -d archives ];then mkdir archives;chmod 755 archives;fi + $(MAKE) copyfiles COPYDIR=$(TARDIR) + cd $(TARDIR); tar cvf $(TARFILE) * && gzip -9 $(TARFILE) + mv $(TARDIR)/$(TARFILE).gz archives/. + rm -rf $(TARDIR) + +# Make RPMs. You need to be root to make this work +RPMROOT=/usr/src/redhat +RPM = rpm +RPMFLAGS = -bb +INSTALLDIR = /usr/local/games/$(BUILD_NAME) +RPMDIR = $(TMPDIR)/$(BUILD_NAME)-$(VERSION) +DESTDIR= $(RPMDIR)/$(INSTALLDIR) + +rpm: $(BUILD_NAME).spec + touch $(RPMROOT)/SOURCES/$(BUILD_NAME)-$(VERSION).tar.gz + if [ ! -d archives ];then mkdir archives;fi + $(MAKE) copyfiles COPYDIR=$(DESTDIR) + cp $(UDIR)/quake3.gif $(RPMROOT)/SOURCES/. + cp $(BUILD_NAME).spec $(RPMROOT)/SPECS/. + cd $(RPMROOT)/SPECS; $(RPM) $(RPMFLAGS) $(BUILD_NAME).spec + rm -rf $(RPMDIR) + mv $(RPMROOT)/RPMS/$(RPMARCH)/$(BUILD_NAME)-$(VERSION)-$(RPM_RELEASE).$(RPMARCH).rpm archives/$(BUILD_NAME)-$(VERSION)-$(RPM_RELEASE).$(RPMARCH).rpm + +copyfiles: + -mkdirhier $(COPYDIR) + cp $(BR)/linuxquake3 $(COPYDIR)/quake3.x86 + strip $(COPYDIR)/quake3.x86 + chmod 755 $(COPYDIR)/quake3.x86 + cp $(BR)/linuxq3ded $(COPYDIR)/q3ded + strip $(COPYDIR)/q3ded + chmod 755 $(COPYDIR)/q3ded + cp $(BDIR)/libMesaVoodooGL.so.3.2 $(COPYDIR)/. + chmod 755 $(COPYDIR)/libMesaVoodooGL.so.3.2 + ( cd $(COPYDIR); ln -s libMesaVoodooGL.so.3.2 libMesaVoodooGL.so ) + cp $(BDIR)/Quake_III_Arena_FAQ.html $(COPYDIR)/. + chmod 644 $(COPYDIR)/Quake_III_Arena_FAQ.html + mkdir $(COPYDIR)/baseq3 + cp $(BASEQ3_DIR)/pak2.pk3 $(COPYDIR)/baseq3/. + chmod 644 $(COPYDIR)/baseq3/pak2.pk3 + +$(BUILD_NAME).spec : $(UDIR)/$(BUILD_NAME).spec.sh Makefile + sh $< $(VERSION) $(RPM_RELEASE) $(ARCH) $(INSTALLDIR) > $@ + +############################################################################# +# MISC +############################################################################# +clean:clean-dedicated + +clean-dedicated: + rm -rf $(BR)/ded/* + rm -rf $(BR)/game/* + rm -rf $(BD)/ded/* + rm -rf $(BD)/game/* + +#clean:clean-debug clean-release + +#clean-debug: +# $(MAKE) clean2 B=$(BD) CFLAGS="$(DEBUG_CFLAGS)" + +#clean-release: +# $(MAKE) clean2 B=$(BR) CFLAGS="$(DEBUG_CFLAGS)" + diff --git a/linux/make_debug.sh b/linux/make_debug.sh new file mode 100644 index 0000000..0cd6adc --- /dev/null +++ b/linux/make_debug.sh @@ -0,0 +1,3 @@ +make build_debug +cp debugi386-glibc/linuxef2ded ../../../GAME/ +cp debugi386-glibc/ef2gamei386.so ../../../GAME/ diff --git a/linux/make_release.sh b/linux/make_release.sh new file mode 100644 index 0000000..defa54c --- /dev/null +++ b/linux/make_release.sh @@ -0,0 +1,3 @@ +make build_release +cp releasei386-glibc/linuxef2ded ../../../GAME/ +cp releasei386-glibc/ef2gamei386.so ../../../GAME/ diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..89bfc5e --- /dev/null +++ b/readme.txt @@ -0,0 +1,68 @@ + + + +This is the game source for Star Trek Elite Force II. The package contains all of the +server side game code for singleplayer and multi-player and it includes all of the +changes up to and including the 1.10 patch. + +How To Use +---------- +When compiled it will create the file gamex86.dll which will be placed in the +.\executable\Debug directory or the .\executable\release directory depending on +if you are creating a debug or a release build of the dll. + +You can either replace the old gamex86.dll from the full game (although you +should back it up) or you can change which game dll the executable uses +by specifing which one to use at the command line. + +ex. +EF2 +set gamedll c:\projects\EF2\executable\debug\gamex86.dll + +NOTE: the code has only been tested under Visual Studio 6.0. + + +Quick layout of the code +------------------------ + +multiplayer code: + +mp_manager - manages all of the multiplayer code + +mp_modeBase - the base class for all of the modes (dm, team, ctf, etc.) +mp_modeTeamBase - code that is shared between for all team related modes + +mp_modeDM - all deathmatch related code +mp_modeTeamDM - all team deathmatch specific code +mp_modeCtf - all CTF related code + +mp_modifiers - all of the code for the various modifiers (action hero, disintegration, etc.) + + +main class hierarchy: + +class - base class, allows safe pointers + listener - adds event handling + entity - basic entity class + sentient - anything that thinks on its own + actor - AI creatures + player - the player(s) of course + trigger - a host of different kind of entities that are turned on in some manner (touch, damaged, etc) to do a specific task + item - your basic item + weapon - weapons that can be used by sentients (players or actors) + powerups - special items that can be picked up, are automatically used, and usually last a specific amount of time + +This hierarchy only shows a small portion of the code base but it is a fairly good start at understanding +it. + + +Restrictions +------------ +This code and the dll that it will build will only work with a full copy of Star Trek Elite Force II. + +All code and contents included in this package are +Copyright (C) 1998 by Ritual Entertainment, Inc. +All rights reserved. + +This entire package is supplied as is. Ritual and Activision will not provide any support. + +See the file licenseAgreement.txt in this package for the full license agreement.